From 1fc8b43b366df1b234ea41029d98a4710a2ff691 Mon Sep 17 00:00:00 2001 From: Feike Steenbergen Date: Wed, 24 Aug 2016 09:28:58 +0200 Subject: [PATCH 1/4] Return replication information on the api To enable better monitoring, it is useful to have replication statistics. Addresses issue #261 --- patroni/api.py | 10 ++++++++-- tests/test_postgresql.py | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/patroni/api.py b/patroni/api.py index 6f3a6ae9..be57c52b 100644 --- a/patroni/api.py +++ b/patroni/api.py @@ -368,7 +368,11 @@ class RestApiHandler(BaseHTTPRequestHandler): def get_postgresql_status(self, retry=False): try: - row = self.query("""SELECT to_char(pg_postmaster_start_time(), 'YYYY-MM-DD HH24:MI:SS.MS TZ'), + row = self.query("""WITH replication_info AS ( + SELECT application_name, client_addr, state, sync_state, sync_priority + FROM pg_stat_replication + ) + SELECT to_char(pg_postmaster_start_time(), 'YYYY-MM-DD HH24:MI:SS.MS TZ'), pg_is_in_recovery(), CASE WHEN pg_is_in_recovery() THEN 0 @@ -377,12 +381,14 @@ class RestApiHandler(BaseHTTPRequestHandler): pg_xlog_location_diff(pg_last_xlog_receive_location(), '0/0')::bigint, pg_xlog_location_diff(pg_last_xlog_replay_location(), '0/0')::bigint, to_char(pg_last_xact_replay_timestamp(), 'YYYY-MM-DD HH24:MI:SS.MS TZ'), - pg_is_in_recovery() AND pg_is_xlog_replay_paused()""", retry=retry)[0] + pg_is_in_recovery() AND pg_is_xlog_replay_paused(), + (SELECT json_agg(row_to_json(ri)) FROM replication_info ri)""", retry=retry)[0] return { 'state': self.server.patroni.postgresql.state, 'postmaster_start_time': row[0], 'role': 'replica' if row[1] else 'master', 'server_version': self.server.patroni.postgresql.server_version, + 'replication': row[7], 'xlog': ({ 'received_location': row[3], 'replayed_location': row[4], diff --git a/tests/test_postgresql.py b/tests/test_postgresql.py index 2ab29124..f6aaa869 100644 --- a/tests/test_postgresql.py +++ b/tests/test_postgresql.py @@ -33,7 +33,8 @@ class MockCursor(object): elif sql == 'SELECT pg_is_in_recovery()': self.results = [(False, )] elif sql.startswith('SELECT to_char(pg_postmaster_start_time'): - self.results = [('', True, '', '', '', '', False)] + replication_info = '[{"application_name":"walreceiver","client_addr":"1.2.3.4","state":"streaming","sync_state":"async","sync_priority":0}]' + self.results = [('', True, '', '', '', '', False, replication_info)] elif sql.startswith('SELECT name, setting'): self.results = [('wal_segment_size', '2048', '8kB', 'integer', 'internal'), ('search_path', 'public', None, 'string', 'user'), From a573983753e494e6a114808a5561897f1ddf46c1 Mon Sep 17 00:00:00 2001 From: Feike Steenbergen Date: Wed, 24 Aug 2016 11:54:40 +0200 Subject: [PATCH 2/4] Include usename in replication information Also only return the key if any replication information is known --- patroni/api.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/patroni/api.py b/patroni/api.py index be57c52b..1d3aa966 100644 --- a/patroni/api.py +++ b/patroni/api.py @@ -369,7 +369,7 @@ class RestApiHandler(BaseHTTPRequestHandler): def get_postgresql_status(self, retry=False): try: row = self.query("""WITH replication_info AS ( - SELECT application_name, client_addr, state, sync_state, sync_priority + SELECT usename, application_name, client_addr, state, sync_state, sync_priority FROM pg_stat_replication ) SELECT to_char(pg_postmaster_start_time(), 'YYYY-MM-DD HH24:MI:SS.MS TZ'), @@ -383,20 +383,24 @@ class RestApiHandler(BaseHTTPRequestHandler): to_char(pg_last_xact_replay_timestamp(), 'YYYY-MM-DD HH24:MI:SS.MS TZ'), pg_is_in_recovery() AND pg_is_xlog_replay_paused(), (SELECT json_agg(row_to_json(ri)) FROM replication_info ri)""", retry=retry)[0] - return { + result = { 'state': self.server.patroni.postgresql.state, 'postmaster_start_time': row[0], 'role': 'replica' if row[1] else 'master', 'server_version': self.server.patroni.postgresql.server_version, - 'replication': row[7], - 'xlog': ({ + 'replication': row[7]} + if result['role'] == 'replica': + result['xlog'] = { 'received_location': row[3], 'replayed_location': row[4], 'replayed_timestamp': row[5], - 'paused': row[6]} if row[1] else { - 'location': row[2] - }) - } + 'paused': row[6]} + else: + result['xlog'] = {'location': row[2]} + if not result['replication']: + del result['replication'] + + return result except (psycopg2.Error, RetryFailedError, PostgresConnectionException): state = self.server.patroni.postgresql.state if state == 'running': From a09f905a78f9ba4545a960a01ba3d915627dd192 Mon Sep 17 00:00:00 2001 From: Feike Steenbergen Date: Wed, 24 Aug 2016 12:28:31 +0200 Subject: [PATCH 3/4] Only add replication info if it is found --- patroni/api.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/patroni/api.py b/patroni/api.py index 1d3aa966..8293c051 100644 --- a/patroni/api.py +++ b/patroni/api.py @@ -387,8 +387,7 @@ class RestApiHandler(BaseHTTPRequestHandler): 'state': self.server.patroni.postgresql.state, 'postmaster_start_time': row[0], 'role': 'replica' if row[1] else 'master', - 'server_version': self.server.patroni.postgresql.server_version, - 'replication': row[7]} + 'server_version': self.server.patroni.postgresql.server_version} if result['role'] == 'replica': result['xlog'] = { 'received_location': row[3], @@ -397,8 +396,8 @@ class RestApiHandler(BaseHTTPRequestHandler): 'paused': row[6]} else: result['xlog'] = {'location': row[2]} - if not result['replication']: - del result['replication'] + if row[7]: + result['replication'] = row[7] return result except (psycopg2.Error, RetryFailedError, PostgresConnectionException): From 74166e996c7abe1ac5ffc786c3de1928e6fe728b Mon Sep 17 00:00:00 2001 From: Alexander Kukushkin Date: Thu, 25 Aug 2016 10:09:32 +0200 Subject: [PATCH 4/4] Fix tests and formatting --- patroni/api.py | 14 ++++++++------ tests/test_postgresql.py | 5 +++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/patroni/api.py b/patroni/api.py index 8293c051..dc737a4f 100644 --- a/patroni/api.py +++ b/patroni/api.py @@ -383,19 +383,21 @@ class RestApiHandler(BaseHTTPRequestHandler): to_char(pg_last_xact_replay_timestamp(), 'YYYY-MM-DD HH24:MI:SS.MS TZ'), pg_is_in_recovery() AND pg_is_xlog_replay_paused(), (SELECT json_agg(row_to_json(ri)) FROM replication_info ri)""", retry=retry)[0] + result = { 'state': self.server.patroni.postgresql.state, 'postmaster_start_time': row[0], 'role': 'replica' if row[1] else 'master', - 'server_version': self.server.patroni.postgresql.server_version} - if result['role'] == 'replica': - result['xlog'] = { + 'server_version': self.server.patroni.postgresql.server_version, + 'xlog': ({ 'received_location': row[3], 'replayed_location': row[4], 'replayed_timestamp': row[5], - 'paused': row[6]} - else: - result['xlog'] = {'location': row[2]} + 'paused': row[6]} if row[1] else { + 'location': row[2] + }) + } + if row[7]: result['replication'] = row[7] diff --git a/tests/test_postgresql.py b/tests/test_postgresql.py index f6aaa869..0d931d54 100644 --- a/tests/test_postgresql.py +++ b/tests/test_postgresql.py @@ -32,8 +32,9 @@ class MockCursor(object): self.results = [(0,)] elif sql == 'SELECT pg_is_in_recovery()': self.results = [(False, )] - elif sql.startswith('SELECT to_char(pg_postmaster_start_time'): - replication_info = '[{"application_name":"walreceiver","client_addr":"1.2.3.4","state":"streaming","sync_state":"async","sync_priority":0}]' + elif sql.startswith('WITH replication_info AS ('): + replication_info = '[{"application_name":"walreceiver","client_addr":"1.2.3.4",' +\ + '"state":"streaming","sync_state":"async","sync_priority":0}]' self.results = [('', True, '', '', '', '', False, replication_info)] elif sql.startswith('SELECT name, setting'): self.results = [('wal_segment_size', '2048', '8kB', 'integer', 'internal'),