1 # -*- coding: utf-8 -*-
2 # Description: example netdata python.d module
3 # Author: Pawel Krupa (paulfantom)
6 from base import SimpleService
7 from psycopg2.extras import DictCursor
8 from psycopg2 import extensions
9 from psycopg2 import extras
13 # default module values
18 # Default Config options.
23 # 'host': 'localhost',
29 CAST(COUNT(*) AS INT) AS file_count,
30 CAST(COALESCE(SUM(CAST(archive_file ~ $r$\.ready$$r$ as INT)), 0) AS INT) AS ready_count,
31 CAST(COALESCE(SUM(CAST(archive_file ~ $r$\.done$$r$ AS INT)), 0) AS INT) AS done_count
33 pg_catalog.pg_ls_dir('pg_xlog/archive_status') AS archive_files (archive_file);
38 count(*) - (SELECT count(*) FROM pg_stat_activity WHERE state = 'idle') AS backends_active,
39 (SELECT count(*) FROM pg_stat_activity WHERE state = 'idle' ) AS backends_idle
44 TABLE_SIZE_ON_DISK = """
45 SELECT ((sum(relpages)* 8) * 1024) AS size_relations FROM pg_class WHERE relkind IN ('r', 't');
49 SELECT count(1) as relations FROM pg_class WHERE relkind IN ('r', 't');
52 INDEX_SIZE_ON_DISK = """
53 SELECT ((sum(relpages)* 8) * 1024) AS size_indexes FROM pg_class WHERE relkind = 'i';
57 SELECT count(1) as indexes FROM pg_class WHERE relkind = 'i';
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;
77 sum(heap_blks_read) AS heap_blocks_read,
78 sum(heap_blks_hit) AS heap_blocks_hit,
79 sum(idx_blks_read) AS index_blocks_read,
80 sum(idx_blks_hit) AS index_blocks_hit,
81 sum(toast_blks_read) AS toast_blocks_read,
82 sum(toast_blks_hit) AS toast_blocks_hit,
83 sum(tidx_blks_read) AS toastindex_blocks_read,
84 sum(tidx_blks_hit) AS toastindex_blocks_hit
88 schemaname <> 'pg_catalog';
90 BGWRITER = 'SELECT * FROM pg_stat_bgwriter;'
91 LOCKS = 'SELECT mode, count(mode) AS count FROM pg_locks GROUP BY mode ORDER BY mode;'
98 replay_offset - (sent_xlog - replay_xlog) * 255 * 16 ^ 6 ) AS byte_lag
101 client_addr, client_hostname, state,
102 ('x' || lpad(split_part(sent_location, '/', 1), 8, '0'))::bit(32)::bigint AS sent_xlog,
103 ('x' || lpad(split_part(replay_location, '/', 1), 8, '0'))::bit(32)::bigint AS replay_xlog,
104 ('x' || lpad(split_part(sent_location, '/', 2), 8, '0'))::bit(32)::bigint AS sent_offset,
105 ('x' || lpad(split_part(replay_location, '/', 2), 8, '0'))::bit(32)::bigint AS replay_offset
106 FROM pg_stat_replication
110 LOCK_MAP = {'AccessExclusiveLock': 'lock_access_exclusive',
111 'AccessShareLock': 'lock_access_share',
112 'ExclusiveLock': 'lock_exclusive',
113 'RowExclusiveLock': 'lock_row_exclusive',
114 'RowShareLock': 'lock_row_share',
115 'ShareUpdateExclusiveLock': 'lock_update_exclusive_lock',
116 'ShareLock': 'lock_share',
117 'ShareRowExclusiveLock': 'lock_share_row_exclusive',
118 'SIReadLock': 'lock_si_read'}
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 'db_stat_transactions': {
124 'options': [None, 'Transactions', 'Count', 'Database Stat', 'postgres.db_stat_transactions', 'line'],
126 ['db_stat_xact_commit', 'Committed', 'absolute'],
127 ['db_stat_xact_rollback', 'Rolled Back', 'absolute']
129 'db_stat_tuple_read': {
130 'options': [None, 'Tuple read', 'Count', 'Database Stat', 'postgres.db_stat_tuple_read', 'line'],
132 ['db_stat_blks_read', 'Disk', 'absolute'],
133 ['db_stat_blks_hit', 'Cache', 'absolute']
135 'db_stat_tuple_returned': {
136 'options': [None, 'Tuple returned', 'Count', 'Database Stat', 'postgres.db_stat_tuple_returned', 'line'],
138 ['db_stat_tup_returned', 'Sequential', 'absolute'],
139 ['db_stat_tup_fetched', 'Bitmap', 'absolute']
141 'db_stat_tuple_write': {
142 'options': [None, 'Tuple write', 'Count', 'Database Stat', 'postgres.db_stat_tuple_write', 'line'],
144 ['db_stat_tup_inserted', 'Inserted', 'absolute'],
145 ['db_stat_tup_updated', 'Updated', 'absolute'],
146 ['db_stat_tup_deleted', 'Deleted', 'absolute'],
147 ['db_stat_conflicts', 'Conflicts', 'absolute']
150 'options': [None, 'Backend processes', 'Count', 'Backend processes', 'postgres.backend_process', 'line'],
152 ['backend_process_active', 'Active', 'absolute'],
153 ['backend_process_idle', 'Idle', 'absolute']
156 'options': [None, 'Total index', 'Count', 'Index', 'postgres.index_count', 'line'],
158 ['index_count', 'Total index', 'absolute']
161 'options': [None, 'Index size', 'MB', 'Index', 'postgres.index_size', 'line'],
163 ['index_size', 'Size', 'absolute', 1, 1024*1024]
166 'options': [None, 'Total table', 'Count', 'Table', 'postgres.table_count', 'line'],
168 ['table_count', 'Total table', 'absolute']
171 'options': [None, 'Table size', 'MB', 'Table', 'postgres.table_size', 'line'],
173 ['table_size', 'Size', 'absolute', 1, 1024*1024]
176 'options': [None, 'Table size', 'Count', 'Locks', 'postgres.locks', 'line'],
178 ['lock_access_exclusive', 'Access Exclusive', 'absolute'],
179 ['lock_access_share', 'Access Share', 'absolute'],
180 ['lock_exclusive', 'Exclusive', 'absolute'],
181 ['lock_row_exclusive', 'Row Exclusive', 'absolute'],
182 ['lock_row_share', 'Row Share', 'absolute'],
183 ['lock_update_exclusive_lock', 'Update Exclusive Lock', 'absolute'],
184 ['lock_share', 'Share', 'absolute'],
185 ['lock_share_row_exclusive', 'Share Row Exclusive', 'absolute'],
186 ['lock_si_read', 'SI Read', 'absolute']
189 'options': [None, 'WAL stats', 'Files', 'WAL', 'postgres.wal', 'line'],
191 ['wal_total', 'Total', 'absolute'],
192 ['wal_ready', 'Ready', 'absolute'],
193 ['wal_done', 'Done', 'absolute']
196 'options': [None, 'Heap', 'iops', 'IO Operations', 'postgres.operations_heap', 'line'],
198 ['operations_heap_blocks_read', 'Read', 'absolute'],
199 ['operations_heap_blocks_hit', 'Hit', 'absolute']
201 'operations_index': {
202 'options': [None, 'Index', 'iops', 'IO Operations', 'postgres.operations_index', 'line'],
204 ['operations_index_blocks_read', 'Read', 'absolute'],
205 ['operations_index_blocks_hit', 'Hit', 'absolute']
207 'operations_toast': {
208 'options': [None, 'Toast', 'iops', 'IO Operations', 'postgres.operations_toast', 'line'],
210 ['operations_toast_blocks_read', 'Read', 'absolute'],
211 ['operations_toast_blocks_hit', 'Hit', 'absolute']
213 'operations_toast_index': {
214 'options': [None, 'Toast index', 'iops', 'IO Operations', 'postgres.operations_toast_index', 'line'],
216 ['operations_toastindex_blocks_read', 'Read', 'absolute'],
217 ['operations_toastindex_blocks_hit', 'Hit', 'absolute']
219 'background_writer': {
220 'options': [None, 'Checkpoints', 'Count', 'Background Writer', 'postgres.background_writer', 'line'],
222 ['background_writer_scheduled', 'Scheduled', 'absolute'],
223 ['background_writer_requested', 'Requested', 'absolute']
227 class Service(SimpleService):
228 def __init__(self, configuration=None, name=None):
229 super(self.__class__, self).__init__(configuration=configuration, name=name)
231 self.definitions = CHARTS
232 self.configuration = configuration
233 self.connection = None
238 params = dict(user='postgres',
243 params.update(self.configuration)
244 self.connection = psycopg2.connect(**params)
245 self.connection.set_isolation_level(extensions.ISOLATION_LEVEL_AUTOCOMMIT)
251 except Exception as e:
258 cursor = self.connection.cursor(cursor_factory=DictCursor)
259 self.add_stats(cursor)
262 self.connection.close()
266 def add_stats(self, cursor):
267 self.add_database_stats(cursor)
268 self.add_backend_stats(cursor)
269 self.add_index_stats(cursor)
270 self.add_table_stats(cursor)
271 self.add_lock_stats(cursor)
272 self.add_statio_stats(cursor)
273 self.add_bgwriter_stats(cursor)
275 #self.add_replication_stats(cursor)
277 # add_wal_metrics needs superuser to get directory listings
278 #if self.config.get('superuser', True):
279 #self.add_wal_stats(cursor)
281 def add_database_stats(self, cursor):
282 cursor.execute(DATABASE)
283 temp = cursor.fetchone()
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)))
296 def add_backend_stats(self, cursor):
297 cursor.execute(BACKENDS)
298 temp = cursor.fetchone()
300 self.data['backend_process_active'] = int(temp.get('backends_active', 0))
301 self.data['backend_process_idle'] = int(temp.get('backends_idle', 0))
303 def add_index_stats(self, cursor):
304 cursor.execute(INDEX_COUNT)
305 temp = cursor.fetchone()
306 self.data['index_count'] = int(temp.get('indexes', 0))
308 cursor.execute(INDEX_SIZE_ON_DISK)
309 temp = cursor.fetchone()
310 self.data['index_size'] = int(temp.get('size_indexes', 0))
312 def add_table_stats(self, cursor):
313 cursor.execute(TABLE_COUNT)
314 temp = cursor.fetchone()
315 self.data['table_count'] = int(temp.get('relations', 0))
317 cursor.execute(TABLE_SIZE_ON_DISK)
318 temp = cursor.fetchone()
319 self.data['table_size'] = int(temp.get('size_relations', 0))
321 def add_lock_stats(self, cursor):
322 cursor.execute(LOCKS)
323 temp = cursor.fetchall()
327 if row['mode'] == key:
329 self.data[LOCK_MAP[key]] = int(row['count'])
332 self.data[LOCK_MAP[key]] = 0
334 def add_wal_stats(self, cursor):
335 cursor.execute(ARCHIVE)
336 temp = cursor.fetchone()
337 self.add_derive_value('wal_total', int(temp.get('file_count', 0)))
338 self.add_derive_value('wal_ready', int(temp.get('ready_count', 0)))
339 self.add_derive_value('wal_done', int(temp.get('done_count', 0)))
341 def add_statio_stats(self, cursor):
342 cursor.execute(STATIO)
343 temp = cursor.fetchone()
344 self.add_derive_value('operations_heap_blocks_read', int(temp.get('heap_blocks_read', 0)))
345 self.add_derive_value('operations_heap_blocks_hit', int(temp.get('heap_blocks_hit', 0)))
346 self.add_derive_value('operations_index_blocks_read', int(temp.get('index_blocks_read', 0)))
347 self.add_derive_value('operations_index_blocks_hit', int(temp.get('index_blocks_hit', 0)))
348 self.add_derive_value('operations_toast_blocks_read', int(temp.get('toast_blocks_read', 0)))
349 self.add_derive_value('operations_toast_blocks_hit', int(temp.get('toast_blocks_hit', 0)))
350 self.add_derive_value('operations_toastindex_blocks_read', int(temp.get('toastindex_blocks_read', 0)))
351 self.add_derive_value('operations_toastindex_blocks_hit', int(temp.get('toastindex_blocks_hit', 0)))
353 def add_bgwriter_stats(self, cursor):
354 cursor.execute(BGWRITER)
355 temp = cursor.fetchone()
357 self.add_derive_value('background_writer_scheduled', temp.get('checkpoints_timed', 0))
358 self.add_derive_value('background_writer_requested', temp.get('checkpoints_requests', 0))
360 def add_derive_value(self, key, value):
361 if key not in self.old_data.keys():
364 self.data[key] = value - self.old_data[key]
366 self.old_data[key] = value
370 def add_replication_stats(self, cursor):
371 cursor.execute(REPLICATION)
372 temp = cursor.fetchall()
374 self.add_gauge_value('Replication/%s' % row.get('client_addr', 'Unknown'),
376 int(row.get('byte_lag', 0)))