Show locks per database.
Facetoe committed
Oct 23, 2016 at 17:44 UTC
31e68fa954f8c42063b8cf5909b2bcdff4c5a06d
1 file changed
+99
-63
python.d/postgres.chart.py
+99
-63
@@ -2,6 +2,7 @@
2
# Description: example netdata python.d module
3
# Authors: facetoe, dangtranhoang
4
5
+import re
6
from copy import deepcopy
7
8
import psycopg2
@@ -90,7 +91,16 @@ WHERE
91
schemaname <> 'pg_catalog';
92
"""
93
BGWRITER = 'SELECT * FROM pg_stat_bgwriter;'
93
-LOCKS = 'SELECT mode, count(mode) AS count FROM pg_locks GROUP BY mode ORDER BY mode;'
94
+DATABASE_LOCKS = """
95
+SELECT
96
+ pg_database.datname as database_name,
97
+ mode,
98
+ count(mode) AS count
99
+FROM pg_locks
100
+ INNER JOIN pg_database ON pg_database.oid = pg_locks.database
101
+GROUP BY datname, mode
102
+ORDER BY datname, mode;
103
+"""
104
REPLICATION = """
105
SELECT
106
client_hostname,
@@ -109,18 +119,29 @@ FROM (
119
) AS s;
120
"""
121
112
-LOCK_MAP = {'AccessExclusiveLock': 'lock_access_exclusive',
113
- 'AccessShareLock': 'lock_access_share',
114
- 'ExclusiveLock': 'lock_exclusive',
115
- 'RowExclusiveLock': 'lock_row_exclusive',
116
- 'RowShareLock': 'lock_row_share',
117
- 'ShareUpdateExclusiveLock': 'lock_update_exclusive_lock',
118
- 'ShareLock': 'lock_share',
119
- 'ShareRowExclusiveLock': 'lock_share_row_exclusive',
120
- 'SIReadLock': 'lock_si_read'}
122
+# LOCK_MAP = {'AccessExclusiveLock': 'lock_access_exclusive',
123
+# 'AccessShareLock': 'lock_access_share',
124
+# 'ExclusiveLock': 'lock_exclusive',
125
+# 'RowExclusiveLock': 'lock_row_exclusive',
126
+# 'RowShareLock': 'lock_row_share',
127
+# 'ShareUpdateExclusiveLock': 'lock_update_exclusive_lock',
128
+# 'ShareLock': 'lock_share',
129
+# 'ShareRowExclusiveLock': 'lock_share_row_exclusive',
130
+# 'SIReadLock': 'lock_si_read'}
131
+LOCK_TYPES = [
132
+ 'ExclusiveLock',
133
+ 'RowShareLock',
134
+ 'SIReadLock',
135
+ 'ShareUpdateExclusiveLock',
136
+ 'AccessExclusiveLock',
137
+ 'AccessShareLock',
138
+ 'ShareRowExclusiveLock',
139
+ 'ShareLock',
140
+ 'RowExclusiveLock'
141
+]
142
143
ORDER = ['db_stat_transactions', 'db_stat_tuple_read', 'db_stat_tuple_returned', 'db_stat_tuple_write',
123
- 'backend_process', 'index_count', 'index_size', 'table_count', 'table_size', 'locks', 'wal', 'operations_heap',
144
+ 'backend_process', 'index_count', 'index_size', 'table_count', 'table_size', 'wal', 'operations_heap',
145
'operations_index', 'operations_toast', 'operations_toast_index', 'background_writer']
146
147
CHARTS = {
@@ -176,19 +197,6 @@ CHARTS = {
197
'lines': [
198
['table_size', 'Size', 'absolute', 1, 1024 * 1024]
199
]},
179
- 'locks': {
180
- 'options': [None, 'Table size', 'Count', 'Locks', 'postgres.locks', 'line'],
181
- 'lines': [
182
- ['lock_access_exclusive', 'Access Exclusive', 'absolute'],
183
- ['lock_access_share', 'Access Share', 'absolute'],
184
- ['lock_exclusive', 'Exclusive', 'absolute'],
185
- ['lock_row_exclusive', 'Row Exclusive', 'absolute'],
186
- ['lock_row_share', 'Row Share', 'absolute'],
187
- ['lock_update_exclusive_lock', 'Update Exclusive Lock', 'absolute'],
188
- ['lock_share', 'Share', 'absolute'],
189
- ['lock_share_row_exclusive', 'Share Row Exclusive', 'absolute'],
190
- ['lock_si_read', 'SI Read', 'absolute']
191
- ]},
200
'wal': {
201
'options': [None, 'WAL stats', 'Files', 'WAL', 'postgres.wal', 'line'],
202
'lines': [
@@ -238,6 +246,7 @@ class Service(SimpleService):
246
self.connection = None
247
self.data = {}
248
self.old_data = {}
249
+ self.databases = set()
250
251
def connect(self):
252
params = dict(user='postgres',
@@ -253,6 +262,7 @@ class Service(SimpleService):
262
def check(self):
263
try:
264
self.connect()
265
+ self.discover_databases()
266
self._create_definitions()
267
return True
268
except Exception as e:
@@ -260,42 +270,67 @@ class Service(SimpleService):
270
return False
271
272
def _create_definitions(self):
273
+ for database_name in self.databases:
274
+ self.databases.add(database_name)
275
+ for chart_template_name in list(CHARTS):
276
+ if chart_template_name.startswith('db_stat'):
277
+ self._add_database_stat_chart(chart_template_name, database_name)
278
+ self._add_database_lock_chart(database_name)
279
+
280
+ def discover_databases(self):
281
cursor = self.connection.cursor()
282
cursor.execute("""
283
SELECT datname
284
FROM pg_stat_database
285
WHERE NOT datname ~* '^template\d+'
286
""")
269
-
270
- for row in cursor:
271
- database_name = row[0]
272
- for chart_template_name in list(CHARTS):
273
- if not chart_template_name.startswith('db_stat'):
274
- continue
275
-
276
- chart_template = CHARTS[chart_template_name]
277
- chart_name = "{}_{}".format(database_name, chart_template_name)
278
- if chart_name not in self.order:
279
- self.order.insert(0, chart_name)
280
- name, title, units, family, context, chart_type = chart_template['options']
281
- self.definitions[chart_name] = {
282
- 'options': [
283
- name,
284
- database_name + title,
285
- units,
286
- database_name + family,
287
- database_name + context,
288
- chart_type
289
- ]
290
- }
291
-
292
- self.definitions[chart_name]['lines'] = []
293
- for line in deepcopy(chart_template['lines']):
294
- line[0] = "{}_{}".format(database_name, line[0])
295
- self.definitions[chart_name]['lines'].append(line)
296
-
287
+ self.databases = set(r[0] for r in cursor)
288
cursor.close()
289
290
+ def _add_database_stat_chart(self, chart_template_name, database_name):
291
+ chart_template = CHARTS[chart_template_name]
292
+ chart_name = "{}_{}".format(database_name, chart_template_name)
293
+ if chart_name not in self.order:
294
+ self.order.insert(0, chart_name)
295
+ name, title, units, family, context, chart_type = chart_template['options']
296
+ self.definitions[chart_name] = {
297
+ 'options': [
298
+ name,
299
+ database_name + title,
300
+ units,
301
+ database_name + family,
302
+ database_name + context,
303
+ chart_type
304
+ ]
305
+ }
306
+
307
+ self.definitions[chart_name]['lines'] = []
308
+ for line in deepcopy(chart_template['lines']):
309
+ line[0] = "{}_{}".format(database_name, line[0])
310
+ self.definitions[chart_name]['lines'].append(line)
311
+
312
+ def _add_database_lock_chart(self, database_name):
313
+ chart_name = "{}_locks".format(database_name)
314
+ if chart_name not in self.order:
315
+ self.order.insert(0, chart_name)
316
+ self.definitions[chart_name] = dict(
317
+ options=
318
+ [
319
+ None,
320
+ database_name + ' locks',
321
+ 'Count',
322
+ database_name + ' database statistics',
323
+ database_name + '.locks',
324
+ 'line'
325
+ ],
326
+ lines=[]
327
+ )
328
+
329
+ for lock_type in LOCK_TYPES:
330
+ lock_id = "{}_{}".format(database_name, lock_type.lower())
331
+ label = re.sub("([a-z])([A-Z])", "\g<1> \g<2>", lock_type)
332
+ self.definitions[chart_name]['lines'].append([lock_id, label, 'absolute'])
333
+
334
def _get_data(self):
335
self.connect()
336
@@ -357,17 +392,18 @@ class Service(SimpleService):
392
self.data['table_size'] = int(temp.get('size_relations', 0))
393
394
def add_lock_stats(self, cursor):
360
- cursor.execute(LOCKS)
361
- temp = cursor.fetchall()
362
- for key in LOCK_MAP:
363
- found = False
364
- for row in temp:
365
- if row['mode'] == key:
366
- found = True
367
- self.data[LOCK_MAP[key]] = int(row['count'])
368
-
369
- if not found:
370
- self.data[LOCK_MAP[key]] = 0
395
+ cursor.execute(DATABASE_LOCKS)
396
+ # First zero out all current lock values.
397
+ for database_name in self.databases:
398
+ for lock_type in LOCK_TYPES:
399
+ lock_id = "{}_{}".format(database_name, lock_type.lower())
400
+ self.data[lock_id] = 0
401
+
402
+ # Now populate those that have current locks
403
+ for row in cursor:
404
+ database_name, lock_type, lock_count = row
405
+ lock_id = "{}_{}".format(database_name, lock_type.lower())
406
+ self.data[lock_id] = lock_count
407
408
def add_wal_stats(self, cursor):
409
cursor.execute(ARCHIVE)