Support dynamically discovering and charting databases.
Facetoe committed
Oct 23, 2016 at 14:40 UTC
eeeec4269337c0e657d1593f66106555e1dccf4c
1 file changed
+87
-40
python.d/postgres.chart.py
+87
-40
@@ -2,13 +2,13 @@
2
# Description: example netdata python.d module
3
# Author: Pawel Krupa (paulfantom)
4
5
+from copy import deepcopy
6
+
7
import psycopg2
6
-from base import SimpleService
7
-from psycopg2.extras import DictCursor
8
from psycopg2 import extensions
9
-from psycopg2 import extras
9
+from psycopg2.extras import DictCursor
10
11
-NAME = "test"
11
+from base import SimpleService
12
13
# default module values
14
update_every = 1
@@ -59,17 +59,20 @@ SELECT count(1) as indexes FROM pg_class WHERE relkind = 'i';
59
60
DATABASE = """
61
SELECT
62
- sum(xact_commit) AS xact_commit,
63
- sum(xact_rollback) AS xact_rollback,
64
- sum(blks_read) AS blks_read,
65
- sum(blks_hit) AS blks_hit,
66
- sum(tup_returned) AS tup_returned,
67
- sum(tup_fetched) AS tup_fetched,
68
- sum(tup_inserted) AS tup_inserted,
69
- sum(tup_updated) AS tup_updated,
70
- sum(tup_deleted) AS tup_deleted,
71
- sum(conflicts) AS conflicts
72
-FROM pg_stat_database;
62
+ datname AS database_name,
63
+ sum(xact_commit) AS xact_commit,
64
+ sum(xact_rollback) AS xact_rollback,
65
+ sum(blks_read) AS blks_read,
66
+ sum(blks_hit) AS blks_hit,
67
+ sum(tup_returned) AS tup_returned,
68
+ sum(tup_fetched) AS tup_fetched,
69
+ sum(tup_inserted) AS tup_inserted,
70
+ sum(tup_updated) AS tup_updated,
71
+ sum(tup_deleted) AS tup_deleted,
72
+ sum(conflicts) AS conflicts
73
+FROM pg_stat_database
74
+WHERE NOT datname ~* '^template\d+'
75
+GROUP BY database_name;
76
"""
77
78
STATIO = """
@@ -117,29 +120,31 @@ LOCK_MAP = {'AccessExclusiveLock': 'lock_access_exclusive',
120
'ShareRowExclusiveLock': 'lock_share_row_exclusive',
121
'SIReadLock': 'lock_si_read'}
122
120
-ORDER = ['db_stat_transactions', 'db_stat_tuple_read', 'db_stat_tuple_returned', 'db_stat_tuple_write', 'backend_process', 'index_count', 'index_size', 'table_count', 'table_size', 'locks', 'wal', 'operations_heap', 'operations_index', 'operations_toast', 'operations_toast_index', 'background_writer']
123
+ORDER = ['db_stat_transactions', 'db_stat_tuple_read', 'db_stat_tuple_returned', 'db_stat_tuple_write',
124
+ 'backend_process', 'index_count', 'index_size', 'table_count', 'table_size', 'locks', 'wal', 'operations_heap',
125
+ 'operations_index', 'operations_toast', 'operations_toast_index', 'background_writer']
126
127
CHARTS = {
128
'db_stat_transactions': {
124
- 'options': [None, 'Transactions', 'Count', 'Database Stat', 'postgres.db_stat_transactions', 'line'],
129
+ 'options': [None, ' Transactions', 'Count', ' database statistics', '.db_stat_transactions', 'line'],
130
'lines': [
131
['db_stat_xact_commit', 'Committed', 'absolute'],
132
['db_stat_xact_rollback', 'Rolled Back', 'absolute']
133
]},
134
'db_stat_tuple_read': {
130
- 'options': [None, 'Tuple read', 'Count', 'Database Stat', 'postgres.db_stat_tuple_read', 'line'],
135
+ 'options': [None, ' Tuple read', 'Count', ' database statistics', '.db_stat_tuple_read', 'line'],
136
'lines': [
137
['db_stat_blks_read', 'Disk', 'absolute'],
138
['db_stat_blks_hit', 'Cache', 'absolute']
139
]},
140
'db_stat_tuple_returned': {
136
- 'options': [None, 'Tuple returned', 'Count', 'Database Stat', 'postgres.db_stat_tuple_returned', 'line'],
141
+ 'options': [None, ' Tuple returned', 'Count', ' database statistics', '.db_stat_tuple_returned', 'line'],
142
'lines': [
143
['db_stat_tup_returned', 'Sequential', 'absolute'],
144
['db_stat_tup_fetched', 'Bitmap', 'absolute']
145
]},
146
'db_stat_tuple_write': {
142
- 'options': [None, 'Tuple write', 'Count', 'Database Stat', 'postgres.db_stat_tuple_write', 'line'],
147
+ 'options': [None, ' Tuple write', 'Count', ' database statistics', '.db_stat_tuple_write', 'line'],
148
'lines': [
149
['db_stat_tup_inserted', 'Inserted', 'absolute'],
150
['db_stat_tup_updated', 'Updated', 'absolute'],
@@ -160,7 +165,7 @@ CHARTS = {
165
'index_size': {
166
'options': [None, 'Index size', 'MB', 'Index', 'postgres.index_size', 'line'],
167
'lines': [
163
- ['index_size', 'Size', 'absolute', 1, 1024*1024]
168
+ ['index_size', 'Size', 'absolute', 1, 1024 * 1024]
169
]},
170
'table_count': {
171
'options': [None, 'Total table', 'Count', 'Table', 'postgres.table_count', 'line'],
@@ -170,7 +175,7 @@ CHARTS = {
175
'table_size': {
176
'options': [None, 'Table size', 'MB', 'Table', 'postgres.table_size', 'line'],
177
'lines': [
173
- ['table_size', 'Size', 'absolute', 1, 1024*1024]
178
+ ['table_size', 'Size', 'absolute', 1, 1024 * 1024]
179
]},
180
'locks': {
181
'options': [None, 'Table size', 'Count', 'Locks', 'postgres.locks', 'line'],
@@ -224,6 +229,7 @@ CHARTS = {
229
]}
230
}
231
232
+
233
class Service(SimpleService):
234
def __init__(self, configuration=None, name=None):
235
super(self.__class__, self).__init__(configuration=configuration, name=name)
@@ -243,15 +249,54 @@ class Service(SimpleService):
249
params.update(self.configuration)
250
self.connection = psycopg2.connect(**params)
251
self.connection.set_isolation_level(extensions.ISOLATION_LEVEL_AUTOCOMMIT)
252
+ self.connection.set_session(readonly=True)
253
254
def check(self):
255
try:
256
self.connect()
257
+ self._create_definitions()
258
return True
259
except Exception as e:
260
self.error(e)
261
return False
262
263
+ def _create_definitions(self):
264
+ cursor = self.connection.cursor()
265
+ cursor.execute("""
266
+ SELECT datname
267
+ FROM pg_stat_database
268
+ WHERE NOT datname ~* '^template\d+'
269
+ """)
270
+
271
+ for row in cursor:
272
+ database_name = row[0]
273
+ for chart_template_name in list(CHARTS):
274
+ if not chart_template_name.startswith('db_stat'):
275
+ continue
276
+
277
+ chart_template = CHARTS[chart_template_name]
278
+ chart_name = "{}_{}".format(database_name, chart_template_name)
279
+ if chart_name not in self.order:
280
+ self.order.insert(0, chart_name)
281
+ name, title, units, family, context, chart_type = chart_template['options']
282
+ self.definitions[chart_name] = {
283
+ 'options': [
284
+ name,
285
+ database_name + title,
286
+ units,
287
+ database_name + family,
288
+ database_name + context,
289
+ chart_type
290
+ ]
291
+ }
292
+
293
+ self.definitions[chart_name]['lines'] = []
294
+ for line in deepcopy(chart_template['lines']):
295
+ line[0] = "{}_{}".format(database_name, line[0])
296
+ self.definitions[chart_name]['lines'].append(line)
297
+
298
+ cursor.close()
299
+
300
def _get_data(self):
301
self.connect()
302
@@ -261,7 +306,7 @@ class Service(SimpleService):
306
cursor.close()
307
self.connection.close()
308
264
- return self.data;
309
+ return self.data
310
311
def add_stats(self, cursor):
312
self.add_database_stats(cursor)
@@ -272,26 +317,26 @@ class Service(SimpleService):
317
self.add_statio_stats(cursor)
318
self.add_bgwriter_stats(cursor)
319
275
- #self.add_replication_stats(cursor)
320
+ # self.add_replication_stats(cursor)
321
322
# add_wal_metrics needs superuser to get directory listings
278
- #if self.config.get('superuser', True):
279
- #self.add_wal_stats(cursor)
323
+ # if self.config.get('superuser', True):
324
+ # self.add_wal_stats(cursor)
325
326
def add_database_stats(self, cursor):
327
cursor.execute(DATABASE)
283
- temp = cursor.fetchone()
284
-
285
- self.add_derive_value('db_stat_xact_commit', int(temp.get('xact_commit', 0)))
286
- self.add_derive_value('db_stat_xact_rollback', int(temp.get('xact_rollback', 0)))
287
- self.add_derive_value('db_stat_blks_read', int(temp.get('blks_read', 0)))
288
- self.add_derive_value('db_stat_blks_hit', int(temp.get('blks_hit', 0)))
289
- self.add_derive_value('db_stat_tup_returned', int(temp.get('tup_returned', 0)))
290
- self.add_derive_value('db_stat_tup_fetched', int(temp.get('tup_fetched', 0)))
291
- self.add_derive_value('db_stat_tup_inserted', int(temp.get('tup_inserted', 0)))
292
- self.add_derive_value('db_stat_tup_updated', int(temp.get('tup_updated', 0)))
293
- self.add_derive_value('db_stat_tup_deleted', int(temp.get('tup_deleted', 0)))
294
- self.add_derive_value('db_stat_conflicts', int(temp.get('conflicts', 0)))
328
+ for row in cursor:
329
+ database_name = row.get('database_name')
330
+ self.add_derive_value('db_stat_xact_commit', prefix=database_name, value=int(row.get('xact_commit', 0)))
331
+ self.add_derive_value('db_stat_xact_rollback', prefix=database_name, value=int(row.get('xact_rollback', 0)))
332
+ self.add_derive_value('db_stat_blks_read', prefix=database_name, value=int(row.get('blks_read', 0)))
333
+ self.add_derive_value('db_stat_blks_hit', prefix=database_name, value=int(row.get('blks_hit', 0)))
334
+ self.add_derive_value('db_stat_tup_returned', prefix=database_name, value=int(row.get('tup_returned', 0)))
335
+ self.add_derive_value('db_stat_tup_fetched', prefix=database_name, value=int(row.get('tup_fetched', 0)))
336
+ self.add_derive_value('db_stat_tup_inserted', prefix=database_name, value=int(row.get('tup_inserted', 0)))
337
+ self.add_derive_value('db_stat_tup_updated', prefix=database_name, value=int(row.get('tup_updated', 0)))
338
+ self.add_derive_value('db_stat_tup_deleted', prefix=database_name, value=int(row.get('tup_deleted', 0)))
339
+ self.add_derive_value('db_stat_conflicts', prefix=database_name, value=int(row.get('conflicts', 0)))
340
341
def add_backend_stats(self, cursor):
342
cursor.execute(BACKENDS)
@@ -329,7 +374,7 @@ class Service(SimpleService):
374
self.data[LOCK_MAP[key]] = int(row['count'])
375
376
if not found:
332
- self.data[LOCK_MAP[key]] = 0
377
+ self.data[LOCK_MAP[key]] = 0
378
379
def add_wal_stats(self, cursor):
380
cursor.execute(ARCHIVE)
@@ -357,7 +402,9 @@ class Service(SimpleService):
402
self.add_derive_value('background_writer_scheduled', temp.get('checkpoints_timed', 0))
403
self.add_derive_value('background_writer_requested', temp.get('checkpoints_requests', 0))
404
360
- def add_derive_value(self, key, value):
405
+ def add_derive_value(self, key, value, prefix=None):
406
+ if prefix:
407
+ key = "{}_{}".format(prefix, key)
408
if key not in self.old_data.keys():
409
self.data[key] = 0
410
else: