summaryrefslogtreecommitdiffstats
path: root/python.d/postgres.chart.py
diff options
context:
space:
mode:
Diffstat (limited to 'python.d/postgres.chart.py')
-rw-r--r--python.d/postgres.chart.py344
1 files changed, 305 insertions, 39 deletions
diff --git a/python.d/postgres.chart.py b/python.d/postgres.chart.py
index ef69a9c77..0522b1938 100644
--- a/python.d/postgres.chart.py
+++ b/python.d/postgres.chart.py
@@ -32,6 +32,8 @@ METRICS = dict(
'tup_updated',
'tup_deleted',
'conflicts',
+ 'temp_files',
+ 'temp_bytes',
'size'],
BACKENDS=['backends_active',
'backends_idle'],
@@ -39,11 +41,21 @@ METRICS = dict(
'index_size'],
TABLE_STATS=['table_size',
'table_count'],
+ WAL=['written_wal',
+ 'recycled_wal',
+ 'total_wal'],
+ WAL_WRITES=['wal_writes'],
ARCHIVE=['ready_count',
'done_count',
'file_count'],
- BGWRITER=['writer_scheduled',
- 'writer_requested'],
+ BGWRITER=['checkpoint_scheduled',
+ 'checkpoint_requested',
+ 'buffers_checkpoint',
+ 'buffers_clean',
+ 'maxwritten_clean',
+ 'buffers_backend',
+ 'buffers_alloc',
+ 'buffers_backend_fsync'],
LOCKS=['ExclusiveLock',
'RowShareLock',
'SIReadLock',
@@ -52,17 +64,44 @@ METRICS = dict(
'AccessShareLock',
'ShareRowExclusiveLock',
'ShareLock',
- 'RowExclusiveLock']
+ 'RowExclusiveLock'],
+ AUTOVACUUM=['analyze',
+ 'vacuum_analyze',
+ 'vacuum',
+ 'vacuum_freeze',
+ 'brin_summarize'],
+ STANDBY_DELTA=['sent_delta',
+ 'write_delta',
+ 'flush_delta',
+ 'replay_delta'],
+ REPSLOT_FILES=['replslot_wal_keep',
+ 'replslot_files']
+
)
QUERIES = dict(
+ WAL="""
+SELECT
+ count(*) as total_wal,
+ count(*) FILTER (WHERE type = 'recycled') AS recycled_wal,
+ count(*) FILTER (WHERE type = 'written') AS written_wal
+FROM
+ (SELECT wal.name,
+ pg_{0}file_name(CASE pg_is_in_recovery() WHEN true THEN NULL ELSE pg_current_{0}_{1}() END ),
+ CASE WHEN wal.name > pg_{0}file_name(CASE pg_is_in_recovery() WHEN true THEN NULL ELSE pg_current_{0}_{1}() END ) THEN 'recycled'
+ ELSE 'written'
+ END AS type
+ FROM pg_catalog.pg_ls_dir('pg_{0}') AS wal(name)
+ WHERE name ~ '^[0-9A-F]{{24}}$'
+ ORDER BY (pg_stat_file('pg_{0}/'||name)).modification, wal.name DESC) sub;
+""",
ARCHIVE="""
SELECT
CAST(COUNT(*) AS INT) AS file_count,
CAST(COALESCE(SUM(CAST(archive_file ~ $r$\.ready$$r$ as INT)), 0) AS INT) AS ready_count,
CAST(COALESCE(SUM(CAST(archive_file ~ $r$\.done$$r$ AS INT)), 0) AS INT) AS done_count
FROM
- pg_catalog.pg_ls_dir('{0}/archive_status') AS archive_files (archive_file);
+ pg_catalog.pg_ls_dir('pg_{0}/archive_status') AS archive_files (archive_file);
""",
BACKENDS="""
SELECT
@@ -86,28 +125,37 @@ WHERE relkind = 'i';""",
DATABASE="""
SELECT
datname AS database_name,
- sum(numbackends) AS connections,
- sum(xact_commit) AS xact_commit,
- sum(xact_rollback) AS xact_rollback,
- sum(blks_read) AS blks_read,
- sum(blks_hit) AS blks_hit,
- sum(tup_returned) AS tup_returned,
- sum(tup_fetched) AS tup_fetched,
- sum(tup_inserted) AS tup_inserted,
- sum(tup_updated) AS tup_updated,
- sum(tup_deleted) AS tup_deleted,
- sum(conflicts) AS conflicts,
- pg_database_size(datname) AS size
+ numbackends AS connections,
+ xact_commit AS xact_commit,
+ xact_rollback AS xact_rollback,
+ blks_read AS blks_read,
+ blks_hit AS blks_hit,
+ tup_returned AS tup_returned,
+ tup_fetched AS tup_fetched,
+ tup_inserted AS tup_inserted,
+ tup_updated AS tup_updated,
+ tup_deleted AS tup_deleted,
+ conflicts AS conflicts,
+ pg_database_size(datname) AS size,
+ temp_files AS temp_files,
+ temp_bytes AS temp_bytes
FROM pg_stat_database
WHERE datname IN %(databases)s
-GROUP BY datname;
+;
""",
BGWRITER="""
SELECT
- checkpoints_timed AS writer_scheduled,
- checkpoints_req AS writer_requested
-FROM pg_stat_bgwriter;""",
- LOCKS="""
+ checkpoints_timed AS checkpoint_scheduled,
+ checkpoints_req AS checkpoint_requested,
+ buffers_checkpoint * current_setting('block_size')::numeric buffers_checkpoint,
+ buffers_clean * current_setting('block_size')::numeric buffers_clean,
+ maxwritten_clean,
+ buffers_backend * current_setting('block_size')::numeric buffers_backend,
+ buffers_alloc * current_setting('block_size')::numeric buffers_alloc,
+ buffers_backend_fsync
+FROM pg_stat_bgwriter;
+""",
+ LOCKS="""
SELECT
pg_database.datname as database_name,
mode,
@@ -123,12 +171,69 @@ FROM pg_stat_database
WHERE has_database_privilege((SELECT current_user), datname, 'connect')
AND NOT datname ~* '^template\d+';
""",
+ FIND_STANDBY="""
+SELECT application_name
+FROM pg_stat_replication
+WHERE application_name IS NOT NULL
+GROUP BY application_name;
+""",
+ FIND_REPLICATION_SLOT="""
+SELECT slot_name
+FROM pg_replication_slots;
+""",
+ STANDBY_DELTA="""
+SELECT application_name,
+ pg_{0}_{1}_diff(CASE pg_is_in_recovery() WHEN true THEN pg_last_{0}_receive_{1}() ELSE pg_current_{0}_{1}() END , sent_{1}) AS sent_delta,
+ pg_{0}_{1}_diff(CASE pg_is_in_recovery() WHEN true THEN pg_last_{0}_receive_{1}() ELSE pg_current_{0}_{1}() END , write_{1}) AS write_delta,
+ pg_{0}_{1}_diff(CASE pg_is_in_recovery() WHEN true THEN pg_last_{0}_receive_{1}() ELSE pg_current_{0}_{1}() END , flush_{1}) AS flush_delta,
+ pg_{0}_{1}_diff(CASE pg_is_in_recovery() WHEN true THEN pg_last_{0}_receive_{1}() ELSE pg_current_{0}_{1}() END , replay_{1}) AS replay_delta
+FROM pg_stat_replication
+WHERE application_name IS NOT NULL;
+""",
+ REPSLOT_FILES="""
+WITH wal_size AS (
+ SELECT current_setting('wal_block_size')::INT * setting::INT AS val
+ FROM pg_settings
+ WHERE name = 'wal_segment_size'
+)
+SELECT slot_name, slot_type, replslot_wal_keep, count(slot_file) AS replslot_files
+FROM (
+ SELECT slot.slot_name, CASE WHEN slot_file <> 'state' THEN 1 END AS slot_file , slot_type,
+ COALESCE (floor((pg_wal_lsn_diff (pg_current_wal_lsn (),
+ slot.restart_lsn) - (pg_walfile_name_offset (restart_lsn)).file_offset) / (s.val)),
+ 0) AS replslot_wal_keep
+ FROM pg_replication_slots slot
+ LEFT JOIN (
+ SELECT slot2.slot_name,
+ pg_ls_dir('pg_replslot/' || slot2.slot_name) AS slot_file
+ FROM pg_replication_slots slot2
+ ) files (slot_name, slot_file)
+ ON slot.slot_name = files.slot_name
+ CROSS JOIN wal_size s) AS d
+GROUP BY slot_name, slot_type, replslot_wal_keep;
+""",
IF_SUPERUSER="""
SELECT current_setting('is_superuser') = 'on' AS is_superuser;
- """,
+""",
DETECT_SERVER_VERSION="""
SHOW server_version_num;
- """
+""",
+ AUTOVACUUM="""
+SELECT
+ count(*) FILTER (WHERE query LIKE 'autovacuum: ANALYZE%%') AS analyze,
+ count(*) FILTER (WHERE query LIKE 'autovacuum: VACUUM ANALYZE%%') AS vacuum_analyze,
+ count(*) FILTER (WHERE query LIKE 'autovacuum: VACUUM%%'
+ AND query NOT LIKE 'autovacuum: VACUUM ANALYZE%%'
+ AND query NOT LIKE '%%to prevent wraparound%%') AS vacuum,
+ count(*) FILTER (WHERE query LIKE '%%to prevent wraparound%%') AS vacuum_freeze,
+ count(*) FILTER (WHERE query LIKE 'autovacuum: BRIN summarize%%') AS brin_summarize
+FROM pg_stat_activity
+WHERE query NOT LIKE '%%pg_stat_activity%%';
+""",
+ DIFF_LSN="""
+SELECT pg_{0}_{1}_diff(CASE pg_is_in_recovery() WHEN true THEN pg_last_{0}_receive_{1}() ELSE pg_current_{0}_{1}() END, '0/0') as wal_writes ;
+"""
+
)
@@ -138,8 +243,11 @@ QUERY_STATS = {
QUERIES['LOCKS']: METRICS['LOCKS']
}
-ORDER = ['db_stat_transactions', 'db_stat_tuple_read', 'db_stat_tuple_returned', 'db_stat_tuple_write', 'database_size',
- 'backend_process', 'index_count', 'index_size', 'table_count', 'table_size', 'wal', 'background_writer']
+ORDER = ['db_stat_temp_files', 'db_stat_temp_bytes', 'db_stat_blks', 'db_stat_tuple_returned', 'db_stat_tuple_write',
+ 'db_stat_transactions','db_stat_connections', 'database_size', 'backend_process', 'index_count', 'index_size',
+ 'table_count', 'table_size', 'wal', 'wal_writes', 'archive_wal', 'checkpointer', 'stat_bgwriter_alloc', 'stat_bgwriter_checkpoint',
+ 'stat_bgwriter_backend', 'stat_bgwriter_backend_fsync' , 'stat_bgwriter_bgwriter', 'stat_bgwriter_maxwritten',
+ 'replication_slot', 'standby_delta', 'autovacuum']
CHARTS = {
'db_stat_transactions': {
@@ -155,8 +263,8 @@ CHARTS = {
'lines': [
['connections', 'connections', 'absolute']
]},
- 'db_stat_tuple_read': {
- 'options': [None, 'Tuple reads from db', 'reads/s', 'db statistics', 'postgres.db_stat_tuple_read', 'line'],
+ 'db_stat_blks': {
+ 'options': [None, 'Disk blocks reads from db', 'reads/s', 'db statistics', 'postgres.db_stat_blks', 'line'],
'lines': [
['blks_read', 'disk', 'incremental'],
['blks_hit', 'cache', 'incremental']
@@ -176,6 +284,16 @@ CHARTS = {
['tup_deleted', 'deleted', 'incremental'],
['conflicts', 'conflicts', 'incremental']
]},
+ 'db_stat_temp_bytes': {
+ 'options': [None, 'Temp files written to disk', 'KB/s', 'db statistics', 'postgres.db_stat_temp_bytes', 'line'],
+ 'lines': [
+ ['temp_bytes', 'size', 'incremental', 1, 1024]
+ ]},
+ 'db_stat_temp_files': {
+ 'options': [None, 'Temp files written to disk', 'files', 'db statistics', 'postgres.db_stat_temp_files', 'line'],
+ 'lines': [
+ ['temp_files', 'files', 'incremental']
+ ]},
'database_size': {
'options': [None, 'Database size', 'MB', 'database size', 'postgres.db_size', 'stacked'],
'lines': [
@@ -208,17 +326,82 @@ CHARTS = {
['table_size', 'size', 'absolute', 1, 1024 * 1024]
]},
'wal': {
- 'options': [None, 'Write-Ahead Logging Statistics', 'files/s', 'write ahead log', 'postgres.wal', 'line'],
+ 'options': [None, 'Write-Ahead Logs', 'files', 'wal', 'postgres.wal', 'line'],
+ 'lines': [
+ ['written_wal', 'written', 'absolute'],
+ ['recycled_wal', 'recycled', 'absolute'],
+ ['total_wal', 'total', 'absolute']
+ ]},
+ 'wal_writes': {
+ 'options': [None, 'Write-Ahead Logs', 'kilobytes/s', 'wal_writes', 'postgres.wal_writes', 'line'],
+ 'lines': [
+ ['wal_writes', 'writes', 'incremental', 1, 1024]
+ ]},
+ 'archive_wal': {
+ 'options': [None, 'Archive Write-Ahead Logs', 'files/s', 'archive wal', 'postgres.archive_wal', 'line'],
'lines': [
['file_count', 'total', 'incremental'],
['ready_count', 'ready', 'incremental'],
['done_count', 'done', 'incremental']
]},
- 'background_writer': {
- 'options': [None, 'Checkpoints', 'writes/s', 'background writer', 'postgres.background_writer', 'line'],
+ 'checkpointer': {
+ 'options': [None, 'Checkpoints', 'writes', 'checkpointer', 'postgres.checkpointer', 'line'],
+ 'lines': [
+ ['checkpoint_scheduled', 'scheduled', 'incremental'],
+ ['checkpoint_requested', 'requested', 'incremental']
+ ]},
+ 'stat_bgwriter_alloc': {
+ 'options': [None, 'Buffers allocated', 'kilobytes/s', 'bgwriter', 'postgres.stat_bgwriter_alloc', 'line'],
+ 'lines': [
+ ['buffers_alloc', 'alloc', 'incremental', 1, 1024]
+ ]},
+ 'stat_bgwriter_checkpoint': {
+ 'options': [None, 'Buffers written during checkpoints', 'kilobytes/s', 'bgwriter', 'postgres.stat_bgwriter_checkpoint', 'line'],
+ 'lines': [
+ ['buffers_checkpoint', 'checkpoint', 'incremental', 1, 1024]
+ ]},
+ 'stat_bgwriter_backend': {
+ 'options': [None, 'Buffers written directly by a backend', 'kilobytes/s', 'bgwriter', 'postgres.stat_bgwriter_backend', 'line'],
+ 'lines': [
+ ['buffers_backend', 'backend', 'incremental', 1, 1024]
+ ]},
+ 'stat_bgwriter_backend_fsync': {
+ 'options': [None, 'Fsync by backend', 'times', 'bgwriter', 'postgres.stat_bgwriter_backend_fsync', 'line'],
+ 'lines': [
+ ['buffers_backend_fsync', 'backend fsync', 'incremental']
+ ]},
+ 'stat_bgwriter_bgwriter': {
+ 'options': [None, 'Buffers written by the background writer', 'kilobytes/s', 'bgwriter', 'postgres.bgwriter_bgwriter', 'line'],
+ 'lines': [
+ ['buffers_clean', 'clean', 'incremental', 1, 1024]
+ ]},
+ 'stat_bgwriter_maxwritten': {
+ 'options': [None, 'Too many buffers written', 'times', 'bgwriter', 'postgres.stat_bgwriter_maxwritten', 'line'],
+ 'lines': [
+ ['maxwritten_clean', 'maxwritten', 'incremental']
+ ]},
+ 'autovacuum': {
+ 'options': [None, 'Autovacuum workers', 'workers', 'autovacuum', 'postgres.autovacuum', 'line'],
'lines': [
- ['writer_scheduled', 'scheduled', 'incremental'],
- ['writer_requested', 'requested', 'incremental']
+ ['analyze', 'analyze', 'absolute'],
+ ['vacuum', 'vacuum', 'absolute'],
+ ['vacuum_analyze', 'vacuum analyze', 'absolute'],
+ ['vacuum_freeze', 'vacuum freeze', 'absolute'],
+ ['brin_summarize', 'brin summarize', 'absolute']
+ ]},
+ 'standby_delta': {
+ 'options': [None, 'Standby delta', 'kilobytes', 'replication delta', 'postgres.standby_delta', 'line'],
+ 'lines': [
+ ['sent_delta', 'sent delta', 'absolute', 1, 1024],
+ ['write_delta', 'write delta', 'absolute', 1, 1024],
+ ['flush_delta', 'flush delta', 'absolute', 1, 1024],
+ ['replay_delta', 'replay delta', 'absolute', 1, 1024]
+ ]},
+ 'replication_slot': {
+ 'options': [None, 'Replication slot files', 'files', 'replication slot', 'postgres.replication_slot', 'line'],
+ 'lines': [
+ ['replslot_wal_keep', 'wal keeped', 'absolute'],
+ ['replslot_files', 'pg_replslot files', 'absolute']
]}
}
@@ -237,6 +420,8 @@ class Service(SimpleService):
self.data = dict()
self.locks_zeroed = dict()
self.databases = list()
+ self.secondaries = list()
+ self.replication_slots = list()
self.queries = QUERY_STATS.copy()
def _connect(self):
@@ -270,7 +455,10 @@ class Service(SimpleService):
cursor = self.connection.cursor()
self.databases = discover_databases_(cursor, QUERIES['FIND_DATABASES'])
is_superuser = check_if_superuser_(cursor, QUERIES['IF_SUPERUSER'])
+ self.secondaries = discover_secondaries_(cursor, QUERIES['FIND_STANDBY'])
self.server_version = detect_server_version(cursor, QUERIES['DETECT_SERVER_VERSION'])
+ if self.server_version >= 94000:
+ self.replication_slots = discover_replication_slots_(cursor, QUERIES['FIND_REPLICATION_SLOT'])
cursor.close()
if self.database_poll and isinstance(self.database_poll, str):
@@ -286,29 +474,51 @@ class Service(SimpleService):
return False
def add_additional_queries_(self, is_superuser):
+
+ if self.server_version >= 100000:
+ wal = 'wal'
+ lsn = 'lsn'
+ else:
+ wal = 'xlog'
+ lsn = 'location'
+ self.queries[QUERIES['BGWRITER']] = METRICS['BGWRITER']
+ self.queries[QUERIES['DIFF_LSN'].format(wal,lsn)] = METRICS['WAL_WRITES']
+ self.queries[QUERIES['STANDBY_DELTA'].format(wal,lsn)] = METRICS['STANDBY_DELTA']
+
if self.index_stats:
self.queries[QUERIES['INDEX_STATS']] = METRICS['INDEX_STATS']
if self.table_stats:
self.queries[QUERIES['TABLE_STATS']] = METRICS['TABLE_STATS']
if is_superuser:
- self.queries[QUERIES['BGWRITER']] = METRICS['BGWRITER']
+ self.queries[QUERIES['ARCHIVE'].format(wal)] = METRICS['ARCHIVE']
+ if self.server_version >= 90400:
+ self.queries[QUERIES['WAL'].format(wal,lsn)] = METRICS['WAL']
if self.server_version >= 100000:
- wal_dir_name = 'pg_wal'
- else:
- wal_dir_name = 'pg_xlog'
- self.queries[QUERIES['ARCHIVE'].format(wal_dir_name)] = METRICS['ARCHIVE']
+ self.queries[QUERIES['REPSLOT_FILES']] = METRICS['REPSLOT_FILES']
+ if self.server_version >= 90400:
+ self.queries[QUERIES['AUTOVACUUM']] = METRICS['AUTOVACUUM']
def create_dynamic_charts_(self):
for database_name in self.databases[::-1]:
self.definitions['database_size']['lines'].append([database_name + '_size',
database_name, 'absolute', 1, 1024 * 1024])
- for chart_name in [name for name in CHARTS if name.startswith('db_stat')]:
+ for chart_name in [name for name in self.order if name.startswith('db_stat')]:
add_database_stat_chart_(order=self.order, definitions=self.definitions,
name=chart_name, database_name=database_name)
add_database_lock_chart_(order=self.order, definitions=self.definitions, database_name=database_name)
+ for application_name in self.secondaries[::-1]:
+ add_replication_delta_chart_(order=self.order, definitions=self.definitions,
+ name='standby_delta', application_name=application_name)
+
+ for slot_name in self.replication_slots[::-1]:
+ add_replication_slot_chart_(order=self.order, definitions=self.definitions,
+ name='replication_slot', slot_name=slot_name)
+
+
+
def _get_data(self):
result, error = self._connect()
if result:
@@ -332,7 +542,14 @@ class Service(SimpleService):
cursor.execute(query, dict(databases=tuple(self.databases)))
for row in cursor:
for metric in metrics:
- dimension_id = '_'.join([row['database_name'], metric]) if 'database_name' in row else metric
+ if 'database_name' in row:
+ dimension_id = '_'.join([row['database_name'], metric])
+ elif 'application_name' in row:
+ dimension_id = '_'.join([row['application_name'], metric])
+ elif 'slot_name' in row:
+ dimension_id = '_'.join([row['slot_name'], metric])
+ else:
+ dimension_id = metric
if metric in row:
self.data[dimension_id] = int(row[metric])
elif 'locks_count' in row:
@@ -347,6 +564,21 @@ def discover_databases_(cursor, query):
result.append(db)
return result
+def discover_secondaries_(cursor, query):
+ cursor.execute(query)
+ result = list()
+ for sc in [standby[0] for standby in cursor]:
+ if sc not in result:
+ result.append(sc)
+ return result
+
+def discover_replication_slots_(cursor, query):
+ cursor.execute(query)
+ result = list()
+ for slot in [replication_slot[0] for replication_slot in cursor]:
+ if slot not in result:
+ result.append(slot)
+ return result
def check_if_superuser_(cursor, query):
cursor.execute(query)
@@ -398,3 +630,37 @@ def add_database_stat_chart_(order, definitions, name, database_name):
definitions[chart_name] = {
'options': [name, title + ': ' + database_name, units, 'db ' + database_name, context, chart_type],
'lines': create_lines(database_name, chart_template['lines'])}
+
+def add_replication_delta_chart_(order, definitions, name, application_name):
+ def create_lines(standby, lines):
+ result = list()
+ for line in lines:
+ new_line = ['_'.join([standby, line[0]])] + line[1:]
+ result.append(new_line)
+ return result
+
+ chart_template = CHARTS[name]
+ chart_name = '_'.join([application_name, name])
+ position = order.index('database_size')
+ order.insert(position, chart_name)
+ name, title, units, family, context, chart_type = chart_template['options']
+ definitions[chart_name] = {
+ 'options': [name, title + ': ' + application_name, units, 'replication delta', context, chart_type],
+ 'lines': create_lines(application_name, chart_template['lines'])}
+
+def add_replication_slot_chart_(order, definitions, name, slot_name):
+ def create_lines(slot, lines):
+ result = list()
+ for line in lines:
+ new_line = ['_'.join([slot, line[0]])] + line[1:]
+ result.append(new_line)
+ return result
+
+ chart_template = CHARTS[name]
+ chart_name = '_'.join([slot_name, name])
+ position = order.index('database_size')
+ order.insert(position, chart_name)
+ name, title, units, family, context, chart_type = chart_template['options']
+ definitions[chart_name] = {
+ 'options': [name, title + ': ' + slot_name, units, 'replication slot files', context, chart_type],
+ 'lines': create_lines(slot_name, chart_template['lines'])}