@cryptotaxi247 / netdata-1 / commits / eeeec4269

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: