Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
Boundary PostgreSQL Plugin
--------------------------
**Awaiting Certification**
# Boundary PostgreSQL Plugin

Extracts metrics from a PostgreSQL database instance.

### Prerequisites
## Prerequisites

### Supported OS

| OS | Linux | Windows | SmartOS | OS X |
|:----------|:-----:|:-------:|:-------:|:----:|
| Supported | v | v | v | v |

#### Boundary Meter Versions V4.0 or later

- To install new meter go to Settings->Installation or [see instructons|https://help.boundary.com/hc/en-us/sections/200634331-Installation].
- To upgrade the meter to the latest version - [see instructons|https://help.boundary.com/hc/en-us/articles/201573102-Upgrading-the-Boundary-Meter].

#### For Boundary Meter less than V4.0

| Runtime | node.js | Python | Java |
|:---------|:-------:|:------:|:----:|
Expand All @@ -20,6 +26,7 @@ Extracts metrics from a PostgreSQL database instance.

### Plugin Setup

#### For Boundary Meter Versions Less than V4.0
#### Installation of the PsycoPG2 Library Using `pip`

1. Install [pip](http://pip.readthedocs.org/en/latest/installing.html) if not already installed
Expand All @@ -35,6 +42,9 @@ Extracts metrics from a PostgreSQL database instance.
```

### Plugin Configuration Fields

#### For All Versions

|Field Name|Description |
|:-------|:-------------------------------------------------|
|host |database host name or IP |
Expand All @@ -46,6 +56,8 @@ Extracts metrics from a PostgreSQL database instance.

### Metrics Collected

#### For All Versions

|Metric Name |Description |
|:----------------------------------------------|:----------------------------------------------|
|PostgreSQL - Locks Exclusive |PostgreSQL - Locks Exclusive |
Expand Down
85 changes: 85 additions & 0 deletions init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
local boundary = require('boundary')
local pginfo = require('pginfo')
local table = require('table')
local string = require('string')
local timer = require('timer')

-- Default params
local connections = {
[0] = {
host = "localhost",
port = 5432,
user = "postgres",
password = "123qwe",
database = "postgres",
source = "",
pollInterval = 5000
}}

-- Fetching params
if (boundary.param ~= nil) then
connections = boundary.param or connections
end

print("_bevent:Boundary LUA Postgres plugin up : version 1.0|t:info|tags:lua,plugin")

local function getStats(conobj, pgcon, source)
conobj:get_bg_writer_stats(pgcon, function(writer_stats)
conobj:get_lock_stats_mode(pgcon, function(db_locks)
conobj:get_database_stats(pgcon, function(db_stats)

--lock stats
p(string.format("POSTGRESQL_EXCLUSIVE_LOCKS %s %s", db_locks['all']['Exclusive'], source))
p(string.format("POSTGRESQL_ROW_EXCLUSIVE_LOCKS %s %s", db_locks['all']['RowExclusive'], source))
p(string.format("POSTGRESQL_SHARE_ROW_EXCLUSIVE_LOCKS %s %s", db_locks['all']['ShareRowExclusive'], source))
p(string.format("POSTGRESQL_SHARE_UPDATE_EXCLUSIVE_LOCKS %s %s", db_locks['all']['ShareUpdateExclusive'], source))
p(string.format("POSTGRESQL_SHARE_LOCKS %s %s", db_locks['all']['Share'], source))
p(string.format("POSTGRESQL_ACCESS_SHARE_LOCKS %s %s", db_locks['all']['AccessShare'], source))

--checkpoint/bgwriter stats
p(string.format("POSTGRESQL_CHECKPOINT_WRITE_TIME %s %s", writer_stats['checkpoint_write_time'], source))
p(string.format("POSTGRESQL_CHECKPOINTS_TIMED %s %s", writer_stats['checkpoints_timed'], source))
p(string.format("POSTGRESQL_BUFFERS_ALLOCATED %s %s", writer_stats['buffers_alloc'], source))
p(string.format("POSTGRESQL_BUFFERS_CLEAN %s %s", writer_stats['buffers_clean'], source))
p(string.format("POSTGRESQL_BUFFERS_BACKEND_FSYNC %s %s", writer_stats['buffers_backend_fsync'], source))
p(string.format("POSTGRESQL_CHECKPOINT_SYNCHRONIZATION_TIME %s %s", writer_stats['checkpoint_sync_time'], source))
p(string.format("POSTGRESQL_CHECKPOINTS_REQUESTED %s %s", writer_stats['checkpoints_req'], source))
p(string.format("POSTGRESQL_BUFFERS_BACKEND %s %s", writer_stats['buffers_backend'], source))
p(string.format("POSTGRESQL_MAXIMUM_WRITTEN_CLEAN %s %s", writer_stats['maxwritten_clean'], source))
p(string.format("POSTGRESQL_BUFFERS_CHECKPOINT %s %s", writer_stats['buffers_checkpoint'], source))

--Global DB Stats
p(string.format("POSTGRESQL_BLOCKS_READ %s %s", db_stats['totals']['blks_read'], source))
p(string.format("POSTGRESQL_DISK_SIZE %s %s", db_stats['totals']['disk_size'], source))
p(string.format("POSTGRESQL_TRANSACTIONS_COMMITTED %s %s", db_stats['totals']['xact_commit'], source))
p(string.format("POSTGRESQL_TUPLES_DELETED %s %s", db_stats['totals']['tup_deleted'], source))
p(string.format("POSTGRESQL_TRANSACTIONS_ROLLEDBACK %s %s", db_stats['totals']['xact_rollback'], source))
p(string.format("POSTGRESQL_BLOCKS_HIT %s %s", db_stats['totals']['blks_hit'], source))
p(string.format("POSTGRESQL_TUPLES_RETURNED %s %s", db_stats['totals']['tup_returned'], source))
p(string.format("POSTGRESQL_TUPLES_FETCHED %s %s", db_stats['totals']['tup_fetched'], source))
p(string.format("POSTGRESQL_TUPLES_UPDATED %s %s", db_stats['totals']['tup_updated'], source))
p(string.format("POSTGRESQL_TUPLES_INSERTED %s %s", db_stats['totals']['tup_inserted'], source))
p(string.format("POSTGRESQL_TUPLES_FETCHED %s %s", db_stats['totals']['tup_fetched'], source))

end)
end)
end)
end

local function poll(connections)
if table.getn(connections) > 0 then
local query = connections[1]
local dbcon = pginfo:new(query.host, query.port, query.user, query.password, query.database, query.source)
table.remove(connections, 1)

dbcon:establish(function(connection)
dbcon:get_databases(connection, function(dbs)
getStats(connection, query.source)
timer.setInterval(query.pollInterval, getStats, dbcon, connection, query.source)
poll(connections)
end)
end)
end
end

poll(connections)
220 changes: 220 additions & 0 deletions modules/pginfo.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
--------------------------------------------------------------------------
-- Module to extract Postgres Process Information for Boundary Lua Postgres Plugin
--
-- Author: Yegor Dia
-- Email: yegordia at gmail.com
--
--------------------------------------------------------------------------

local object = require('core').Object
local ffi = require("ffi")

--[[ Check os for binding library path
]]
if ffi.os == "Windows" then
_G.POSTGRESQL_LIBRARY_PATH = ".\\windows-bindings\\lib\\x64\\pq"
end
postgresLuvit = require('postgresLuvit')


local function callIfNotNil(callback, ...)
if callback ~= nil then
callback(...)
end
end

local function createDictWithKeys(keys, default_val)
local dict = {}
local default = nil

if default_val ~= nil then
default = default_val
end

for i, name in ipairs(keys) do
dict[name] = default
end
return dict
end

local function createDictWithKeysAndValues(keys, values)
local dict = {}

for i, name in ipairs(keys) do
dict[name] = values[i]
end
return dict
end

local function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end

local PgInfo = object:extend()

--[[ Initialize PgInfo with connection parameters
]]
function PgInfo:initialize(host, port, user, pwd, database, source)
self.connection_string = string.format("host=%s port=%s dbname=%s user=%s password=%s", host, port, database, user, pwd)
--p(string.format("connection string: %s", self.connection_string))
self.lock_modes = {'AccessExclusive', 'Exclusive', 'ShareRowExclusive', 'Share', 'ShareUpdateExclusive', 'RowExclusive', 'RowShare', 'AccessShare'}
self.connection = nil
return self
end

--[[ Establishing method required to be used before every query
]]
function PgInfo:establish(queries_callback)
self.connection = postgresLuvit:new(self.connection_string, function(err)
assert(not err, err)
callIfNotNil(queries_callback, self.connection)
end)
end

--[[ Test function
]]
function PgInfo:test(connection, callback)
connection:sendQuery("DROP TABLE IF EXISTS test",function(err, res)
assert(not err, err)

connection:sendQuery("CREATE TABLE test (id bigserial primary key, content text, addedAt timestamp)",function(err, res)
assert(not err, err)

connection:sendQuery("INSERT INTO test (content, addedAt) VALUES (" ..
connection:escape([["); DROP TABLE test; --]]) ..
", now() )",function(err, res)
assert(not err, err)

connection:sendQuery("SELECT * FROM test",function(err, res)
assert(not err, err)
callIfNotNil(callback)
end)
end)
end)
end)
end

--[[ Get list of databases
]]
function PgInfo:get_databases(connection, callback)
connection:sendQuery("SELECT datname FROM pg_database;",function(err, res)
assert(not err, err)
local dbs = res

callIfNotNil(callback, dbs)
end)
end


--[[ Returns the number of active lock discriminated by lock mode
]]
function PgInfo:get_lock_stats_mode(connection, callback)
connection:sendQuery("SELECT TRIM(mode, 'Lock'), granted, COUNT(*) FROM pg_locks " .. "GROUP BY TRIM(mode, 'Lock'), granted;", function(err, res)
assert(not err, err)
info_dict = { wait = createDictWithKeys(self.lock_modes, 0), all = createDictWithKeys(self.lock_modes, 0)}

for i, value in ipairs(res) do
local mode = value[1]
local granted = value[2] == "t"
local count = value[3]

info_dict['all'][mode] = info_dict['all'][mode] + count
if not granted then
info_dict['wait'][mode] = info_dict['wait'][mode] + count
end
end

callIfNotNil(callback, info_dict)
end)
end


--[[ Global Background Writer and Checkpoint Activity stats
]]
function PgInfo:get_bg_writer_stats(connection, callback)
connection:sendQuery("SELECT * FROM pg_stat_bgwriter;", function(err, res)
assert(not err, err)
info_dict = {}

for i=1, table.getn(res[0]) do
local name = res[0][i]
local result = res[1][i]
info_dict[name] = result
end

callIfNotNil(callback, info_dict)
end)
end

--[[ Returns database block read, transaction and tuple stats for each
database
]]
function PgInfo:get_database_stats(connection, callback)
local headers = {'datname', 'numbackends', 'xact_commit', 'xact_rollback',
'blks_read', 'blks_hit', 'tup_returned', 'tup_fetched',
'tup_inserted', 'tup_updated', 'tup_deleted', 'disk_size'}
local query_headers = deepcopy(headers)
table.remove(query_headers, table.getn(query_headers))

local query_string = string.format("SELECT %s, pg_database_size(datname) FROM pg_stat_database;", table.concat(query_headers, ", "))
connection:sendQuery(query_string, function(err, res)
assert(not err, err)
info_dict = {}
info_dict['databases'] = self:create_stats_dict(headers, res)
info_dict['totals'] = self:create_totals_dict(headers, res)

callIfNotNil(callback, info_dict)
end)
end


--[[ Utility method that returns database stats as a nested dictionary
]]
function PgInfo:create_stats_dict(headers, rows)
local db_stats = {}
table.remove(headers, 1)
for index, row in ipairs(rows) do
local key = row[1]
table.remove(row, 1)
local stats = createDictWithKeysAndValues(headers, row)
db_stats[key] = stats
end
return db_stats
end


--[[ Utility method that returns totals for database statistics
]]
function PgInfo:create_totals_dict(headers, rows)
local totals_dict = {}
local totals = {}
for key,row in ipairs(rows) do
local key = row[1]

for i=1, table.getn(row) do
if totals[i] ~= nil then
totals[i] = totals[i] + row[i]
else
totals[i] = row[i]
end
end
end

for index, header in ipairs(headers) do
totals_dict[header] = totals[index]
end
return totals_dict
end

return PgInfo
Loading