@vladonv/pm2-postgres 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # pm2-postgres
2
+ PostgreSQL module for Keymetrics
3
+
4
+ ![pm2-postgres screenshot](https://raw.githubusercontent.com/pm2-hive/pm2-postgres/master/pm2-postgres.jpg)
5
+
6
+ ## Description
7
+
8
+ PM2 module to monitor key PostgreSQL server metrics:
9
+
10
+ * Tables / Indexes Count
11
+ * Backends Active / Idle
12
+ * Exclusive / Access Share Locks
13
+ * Total Tables Size
14
+ * Transactions Committed / Rollback
15
+ * Tuples Fetched / Updated / Inserted / Deleted
16
+
17
+ ## Requirements
18
+
19
+ This module requires a PostgreSQL install (v9.3+, including SCRAM-SHA-256 auth on PostgreSQL 10-17).
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ $ npm install pm2 -g
25
+
26
+ $ pm2 install pm2-postgres
27
+ ```
28
+
29
+ ## Config
30
+
31
+ The default connection details are :
32
+ "hostname": "localhost"
33
+ "port": 5432
34
+ "username": "guest"
35
+ "password": "guest"
36
+ "database": "postgres"
37
+
38
+ To modify the config values you can use the commands:
39
+ ```bash
40
+ $ pm2 set pm2-postgres:hostname localhost
41
+ $ pm2 set pm2-postgres:port 5432
42
+ $ pm2 set pm2-postgres:username guest
43
+ $ pm2 set pm2-postgres:password guest
44
+ $ pm2 set pm2-postgres:database postgres
45
+ ```
46
+
47
+ ## Uninstall
48
+
49
+ ```bash
50
+ $ pm2 uninstall pm2-postgres
51
+ ```
52
+
53
+ # License
54
+
55
+ MIT
package/app.js ADDED
@@ -0,0 +1,67 @@
1
+ var fs = require('fs');
2
+ var path = require('path');
3
+ var pmx = require('pmx');
4
+ var pgClientFactory = require('./lib/clientFactory.js');
5
+ var pgStats = require('./lib/stats.js');
6
+ var pgActions = require('./lib/actions.js');
7
+
8
+ // Debian/Ubuntu's postgresql-common names pidfiles "<version>-main.pid"
9
+ // (e.g. "17-main.pid", "9.6-main.pid"). Scanning the directory instead of
10
+ // hardcoding a version list keeps this working for future major versions.
11
+ function detectPostgresPidPaths() {
12
+ var pgRunDir = '/var/run/postgresql';
13
+
14
+ try {
15
+ return fs.readdirSync(pgRunDir)
16
+ .filter(function (name) { return /^\d+(\.\d+)?-main\.pid$/.test(name); })
17
+ .sort(function (a, b) { return parseFloat(b) - parseFloat(a); })
18
+ .map(function (name) { return path.join(pgRunDir, name); });
19
+ } catch (e) {
20
+ return [];
21
+ }
22
+ }
23
+
24
+ pmx.initModule({
25
+
26
+ pid: pmx.resolvePidPaths(detectPostgresPidPaths()),
27
+
28
+ // Options related to the display style on Keymetrics
29
+ widget: {
30
+
31
+ // Logo displayed
32
+ logo: 'http://www.inquidia.com/sites/default/files/postgresql_logo%5B1%5D.png',
33
+
34
+ // Module colors
35
+ // 0 = main element
36
+ // 1 = secondary
37
+ // 2 = main border
38
+ // 3 = secondary border
39
+ theme: ['#60798c', '#326892', '#ffffff', '#807C7C'],
40
+
41
+ // Section to show / hide
42
+ el: {
43
+ probes: true,
44
+ actions: true
45
+ },
46
+
47
+ // Main block to show / hide
48
+ block: {
49
+ actions: true,
50
+ issues: true,
51
+ meta: true,
52
+
53
+ // Custom metrics to put in BIG
54
+ main_probes: ['Tables', 'Indexes','Total Tables Size','Backends Active','Exclusive Locks']
55
+ }
56
+
57
+ }
58
+
59
+ }, function (err, conf) {
60
+ var pgClient = pgClientFactory.build(conf);
61
+
62
+ // Init metrics refresh loop
63
+ pgStats.init(pgClient);
64
+
65
+ // Init actions
66
+ pgActions.init(pgClient);
67
+ });
package/lib/actions.js ADDED
@@ -0,0 +1,36 @@
1
+ var pmx = require('pmx');
2
+
3
+ function initActions(pgClient) {
4
+
5
+ // List DBs
6
+ pmx.action('List DBs', function (reply) {
7
+ var queryString = "SELECT datname FROM pg_database WHERE datistemplate = false;"
8
+ pgClient.query(queryString, function (err, results) {
9
+ if (err) {
10
+ return reply(err);
11
+ }
12
+
13
+ reply(results.rows.map(function (row) { return row.datname; }));
14
+ })
15
+ });
16
+
17
+ // Show Settings
18
+ pmx.action('Show Settings', function (reply) {
19
+ var queryString = "SHOW ALL;";
20
+ pgClient.query(queryString, function (err, results) {
21
+ if (err) {
22
+ return reply(err);
23
+ }
24
+
25
+ reply(results.rows);
26
+ })
27
+ });
28
+
29
+
30
+ }
31
+
32
+ function init(pgClient) {
33
+ initActions(pgClient);
34
+ }
35
+
36
+ module.exports.init = init;
@@ -0,0 +1,30 @@
1
+ var pg = require('pg');
2
+ var pmx = require('pmx');
3
+
4
+ function build(conf) {
5
+ var pgClient = {};
6
+
7
+ var connectionString = "postgres://" + conf.username + ":" + conf.password + "@" + conf.hostname + ":" + conf.port + "/" + conf.database;
8
+
9
+ var pool = new pg.Pool({ connectionString: connectionString });
10
+
11
+ pool.on('error', function (err) {
12
+ // Errors on idle clients (e.g. connection dropped) must not crash the process.
13
+ pmx.notify("Postgres pool error: " + err);
14
+ });
15
+
16
+ pgClient.query = function (queryString, cb) {
17
+ pool.query(queryString, function (err, result) {
18
+ if (err) {
19
+ pmx.notify("Couldn't query postgres: " + err);
20
+ return cb(err);
21
+ }
22
+
23
+ return cb(null, result);
24
+ });
25
+ };
26
+
27
+ return pgClient;
28
+ }
29
+
30
+ module.exports.build = build;
@@ -0,0 +1,19 @@
1
+ var pmx = require('pmx');
2
+
3
+ module.exports = function refreshBackendMetrics(metrics, pgClient) {
4
+ var queryString = "SELECT count(*) - ( SELECT count(*) FROM pg_stat_activity WHERE"
5
+ + " state = 'idle' ) AS backends_active, ( SELECT count(*) FROM"
6
+ + " pg_stat_activity WHERE state = 'idle' ) AS backends_idle"
7
+ + " FROM pg_stat_activity;";
8
+ pgClient.query(queryString, function (err, results) {
9
+ if (err) {
10
+ return pmx.notify("Backend Query Error: " + err);
11
+ }
12
+
13
+ // Backends Active
14
+ metrics.backendsActive.set(results.rows[0].backends_active);
15
+
16
+ // Backends Idle
17
+ metrics.backendsIdle.set(results.rows[0].backends_idle);
18
+ });
19
+ };
@@ -0,0 +1,13 @@
1
+ var pmx = require('pmx');
2
+
3
+ module.exports = function refreshIndexCount(metrics, pgClient) {
4
+ var queryString = "SELECT count(1) as indexes FROM pg_class WHERE relkind = 'i';";
5
+ pgClient.query(queryString, function (err, results) {
6
+ if (err) {
7
+ return pmx.notify("Index Query Error: " + err);
8
+ }
9
+
10
+ // # of Indexes
11
+ metrics.indexCount.set(results.rows[0].indexes);
12
+ });
13
+ };
@@ -0,0 +1,21 @@
1
+ var pmx = require('pmx');
2
+
3
+ function findLockCount(rows, mode) {
4
+ var row = rows.find(function (row) { return row.mode === mode; });
5
+ return row ? row.count : 'N/A';
6
+ }
7
+
8
+ module.exports = function refreshLockCount(metrics, pgClient) {
9
+ var queryString = "SELECT mode, count(mode) AS count FROM pg_locks GROUP BY mode ORDER BY mode;";
10
+ pgClient.query(queryString, function (err, results) {
11
+ if (err) {
12
+ return pmx.notify("Lock Query Error: " + err);
13
+ }
14
+
15
+ // # of Access Share Locks
16
+ metrics.accessShareLockCount.set(findLockCount(results.rows, 'AccessShareLock'));
17
+
18
+ // # of Exclusive Locks
19
+ metrics.exclusiveLockCount.set(findLockCount(results.rows, 'ExclusiveLock'));
20
+ });
21
+ };
@@ -0,0 +1,13 @@
1
+ var pmx = require('pmx');
2
+
3
+ module.exports = function refreshTableCount(metrics, pgClient) {
4
+ var queryString = "SELECT count(1) as relations FROM pg_class WHERE relkind IN ('r', 't');";
5
+ pgClient.query(queryString, function (err, results) {
6
+ if (err) {
7
+ return pmx.notify("Table Query Error: " + err);
8
+ }
9
+
10
+ // # of Tables
11
+ metrics.tableCount.set(results.rows[0].relations);
12
+ });
13
+ };
@@ -0,0 +1,14 @@
1
+ var pmx = require('pmx');
2
+ var humanize = require('humanize');
3
+
4
+ module.exports = function refreshTablesSize(metrics, pgClient) {
5
+ var queryString = "SELECT ((sum(relpages)* 8) * 1024) AS size_relations FROM pg_class WHERE relkind IN ('r', 't');";
6
+ pgClient.query(queryString, function (err, results) {
7
+ if (err) {
8
+ return pmx.notify("Table Size Query Error: " + err);
9
+ }
10
+
11
+ // Total Tables Size
12
+ metrics.tablesSize.set(humanize.filesize(results.rows[0].size_relations));
13
+ });
14
+ };
@@ -0,0 +1,33 @@
1
+ var pmx = require('pmx');
2
+
3
+ module.exports = function refreshTransactionMetrics(metrics, pgClient) {
4
+ var queryString = "SELECT sum(xact_commit) AS transactions_committed,"
5
+ + " sum(xact_rollback) AS transactions_rollback, sum(blks_read) AS blocks_read,"
6
+ + " sum(blks_hit) AS blocks_hit, sum(tup_returned) AS tuples_returned,"
7
+ + " sum(tup_fetched) AS tuples_fetched, sum(tup_inserted) AS tuples_inserted,"
8
+ + " sum(tup_updated) AS tuples_updated, sum(tup_deleted) AS tuples_deleted"
9
+ + " FROM pg_stat_database;";
10
+ pgClient.query(queryString, function (err, results) {
11
+ if (err) {
12
+ return pmx.notify("Transaction Query Error: " + err);
13
+ }
14
+
15
+ // Transactions Committed
16
+ metrics.transactionsCommitted.set(results.rows[0].transactions_committed);
17
+
18
+ // Transactions Rollback
19
+ metrics.transactionsRollback.set(results.rows[0].transactions_rollback);
20
+
21
+ // Tuples Fetched
22
+ metrics.tuplesFetched.set(results.rows[0].tuples_fetched);
23
+
24
+ // Tuples Inserted
25
+ metrics.tuplesInserted.set(results.rows[0].tuples_inserted);
26
+
27
+ // Tuples Updated
28
+ metrics.tuplesUpdated.set(results.rows[0].tuples_updated);
29
+
30
+ // Tuples Deleted
31
+ metrics.tuplesDeleted.set(results.rows[0].tuples_deleted);
32
+ });
33
+ };
@@ -0,0 +1,17 @@
1
+ var pmx = require('pmx');
2
+
3
+ module.exports = function refreshVersion(metrics, pgClient) {
4
+ var queryString = "SELECT version();";
5
+ pgClient.query(queryString, function (err, results) {
6
+ if (err) {
7
+ return pmx.notify("Version Query Error: " + err);
8
+ }
9
+
10
+ // # of Indexes
11
+ var fullVersion = results.rows[0].version;
12
+ var match = fullVersion.match(/^PostgreSQL (\d+(?:\.\d+)*)/);
13
+ if (match) {
14
+ metrics.version.set(match[1]);
15
+ }
16
+ });
17
+ };
package/lib/stats.js ADDED
@@ -0,0 +1,103 @@
1
+ var pmx = require('pmx');
2
+
3
+ var refreshTableCount = require('./stats/refreshTableCount');
4
+ var refreshIndexCount = require('./stats/refreshIndexCount');
5
+ var refreshTransactionMetrics = require('./stats/refreshTransactionMetrics');
6
+ var refreshTablesSize = require('./stats/refreshTablesSize');
7
+ var refreshBackendMetrics = require('./stats/refreshBackendMetrics');
8
+ var refreshLockCount = require('./stats/refreshLockCount');
9
+ var refreshVersion = require('./stats/refreshVersion');
10
+
11
+ var metrics = {};
12
+ var REFRESH_RATE = 10000; // ms
13
+ var probe = pmx.probe();
14
+
15
+ // Init metrics with default values
16
+ function initMetrics() {
17
+ metrics.version = probe.metric({
18
+ name: 'PostgreSQL Version',
19
+ value: 'N/A'
20
+ });
21
+ metrics.tableCount = probe.metric({
22
+ name: 'Tables',
23
+ value: 'N/A'
24
+ });
25
+ metrics.indexCount = probe.metric({
26
+ name: 'Indexes',
27
+ value: 'N/A'
28
+ });
29
+ metrics.transactionsCommitted = probe.metric({
30
+ name: 'Transactions Committed',
31
+ value: 'N/A'
32
+ });
33
+ metrics.transactionsRollback = probe.metric({
34
+ name: 'Transactions Rollback',
35
+ value: 'N/A'
36
+ });
37
+ metrics.tuplesFetched = probe.metric({
38
+ name: 'Tuples Fetched',
39
+ value: 'N/A'
40
+ });
41
+ metrics.tuplesInserted = probe.metric({
42
+ name: 'Tuples Inserted',
43
+ value: 'N/A'
44
+ });
45
+ metrics.tuplesUpdated = probe.metric({
46
+ name: 'Tuples Updated',
47
+ value: 'N/A'
48
+ });
49
+ metrics.tuplesDeleted = probe.metric({
50
+ name: 'Tuples Deleted',
51
+ value: 'N/A'
52
+ });
53
+ metrics.tablesSize = probe.metric({
54
+ name: 'Total Tables Size',
55
+ value: 'N/A'
56
+ });
57
+ metrics.backendsActive = probe.metric({
58
+ name: 'Backends Active',
59
+ value: 'N/A'
60
+ });
61
+ metrics.backendsIdle = probe.metric({
62
+ name: 'Backends Idle',
63
+ value: 'N/A',
64
+ alert: {
65
+ mode: 'threshold-avg',
66
+ value: 8,
67
+ msg: 'Too many Idle Backends',
68
+ cmp: ">"
69
+ }
70
+ });
71
+ metrics.accessShareLockCount = probe.metric({
72
+ name: 'Access Share Locks',
73
+ value: 'N/A'
74
+ });
75
+ metrics.exclusiveLockCount = probe.metric({
76
+ name: 'Exclusive Locks',
77
+ value: 'N/A',
78
+ alert: {
79
+ mode: 'threshold-avg',
80
+ value: 500,
81
+ msg: 'Too many Exclusive Locks',
82
+ cmp: ">"
83
+ }
84
+ });
85
+ }
86
+
87
+ // Refresh metrics
88
+ function refreshMetrics(pgClient) {
89
+ refreshTableCount(metrics, pgClient);
90
+ refreshIndexCount(metrics, pgClient);
91
+ refreshTransactionMetrics(metrics, pgClient);
92
+ refreshTablesSize(metrics, pgClient);
93
+ refreshBackendMetrics(metrics, pgClient);
94
+ refreshLockCount(metrics, pgClient);
95
+ }
96
+
97
+ function init(pgClient) {
98
+ initMetrics();
99
+ setInterval(refreshMetrics.bind(this, pgClient), REFRESH_RATE);
100
+ refreshVersion(metrics,pgClient);
101
+ }
102
+
103
+ module.exports.init = init;
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@vladonv/pm2-postgres",
3
+ "version": "0.1.1",
4
+ "description": "PM2 PostgreSQL Module (fork of pm2-hive/pm2-postgres, updated for PostgreSQL 10-18 / SCRAM auth support)",
5
+ "main": "app.js",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "dependencies": {
10
+ "humanize": "0.0.9",
11
+ "pg": "^8.23.0",
12
+ "pmx": ">=0.5.5"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/vladonv/pm2-postgres.git"
17
+ },
18
+ "config": {
19
+ "hostname": "localhost",
20
+ "port": 5432,
21
+ "username": "guest",
22
+ "password": "guest",
23
+ "database": "postgres"
24
+ },
25
+ "apps": [
26
+ {
27
+ "merge_logs": true,
28
+ "max_memory_restart": "200M",
29
+ "script": "app.js"
30
+ }
31
+ ],
32
+ "author": "Keymetrics Inc. (original), vladonv (fork maintainer)",
33
+ "license": "MIT",
34
+ "devDependencies": {}
35
+ }
Binary file