From 709d6607d9b50f1c423f7019a70c43c24e34fb04 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 9 Apr 2025 19:06:53 +0300 Subject: [PATCH] first commit --- README.md | 58 ++++++++++++++- pg_probackup_exporter.py | 151 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 pg_probackup_exporter.py diff --git a/README.md b/README.md index 6e40763..b0e3a12 100644 --- a/README.md +++ b/README.md @@ -1 +1,57 @@ -# pg_probackup_exporter \ No newline at end of file +# pg_probackup_exporter + + +Postgres pg_probackup prometheus metrics exporter. +This is a tool for monitoring postgresql backups. +It exports pg_probackup metrics as a web service in prometheus format. + +**Installation**
+1) download pg_probackup_exporter.py and put to your folder. E.g. _/usr/local/bin_ +2) install python flask library. For ubuntu: _sudo apt install python3-flask_ +3) set environment variables and run the program with the following command: _python3 /usr/local/bin/pg_probackup_exporter.py_ + +**You need to set 3 environment vars:**
+ 1) path to your pg_probackup executable e.g. + PG_PROBACKUP_COMMAND = '/usr/bin/pg_probackup-17' + 2) web service port, default 9899 + PG_PROBACKUP_EXPORTER_PORT = '9899' + 3) path to backup folder e.g. + PG_PROBACKUP_PATH = '/mnt/backup' + +Metrics are available under _http://your_host:port/metrics_
+ +**Example service file for ubuntu linux:**
+_sudo nano /etc/systemd/system/pg_probackup_exporter.service_
+
+[Service]
+Type=simple
+User=postgres
+Group=postgres
+Environment=PG_PROBACKUP_COMMAND='/usr/bin/pg_probackup-17'
+Environment=PG_PROBACKUP_PATH='/mnt/backup'
+
+ExecStart=/usr/bin/python3 /usr/local/bin/pg_probackup_exporter.py
+KillMode=control-group
+TimeoutSec=5
+Restart=on-failure
+RestartSec=10
+
+[Install]
+WantedBy=multi-user.target
+ + +**Example for prometheus config** +scrape_configs:
+ - job_name: pg_probackup
+ scrape_interval: 30s
+ scrape_timeout: 30s
+ scheme: http
+ static_configs:
+ - targets: ["10.8.200.138:9899"]
+ labels:
+ "service": "postgres"
+ "instance": "pg_master"
+ "host": "10.8.200.138"
+ "Business_Critical": "Medium"
+ "Team": "Postgres"
+ diff --git a/pg_probackup_exporter.py b/pg_probackup_exporter.py new file mode 100644 index 0000000..47d1fc7 --- /dev/null +++ b/pg_probackup_exporter.py @@ -0,0 +1,151 @@ +#!/usr/bin/python3 + +# Author: Pavel Sobolev + + +""" +pg_probackup prometheus metrics exporter. +This is a tool for monitoring postgres backups. +It exports pg_probackup metrics as a web service in prometheus format. + +To use this program, you need to set 3 environment vars: + 1) path to your pg_probackup executable e.g. + PG_PROBACKUP_COMMAND = '/usr/bin/pg_probackup-17' + 2) web service port, default 9899 + PG_PROBACKUP_EXPORTER_PORT = '9899' + 3) path to backup folder e.g. + PG_PROBACKUP_PATH = '/mnt/backup' + +Metrics are available under http://your_host:port/metrics + +""" + + +from flask import Flask +from flask import Response +from datetime import datetime + +import os +import json +from json import JSONDecodeError +import sys + +cmd = os.environ.get('PG_PROBACKUP_COMMAND') + +backup_path = os.environ.get('PG_PROBACKUP_PATH') + +if cmd is None: + print('ERROR: Need PG_PROBACKUP_COMMAND environment varible') + sys.exit() + +if backup_path is None: + print('ERROR: Need PG_PROBACKUP_PATH environment varible') + sys.exit() + +port = os.environ.get('PG_PROBACKUP_EXPORTER_PORT','9899') + +fields = {'id':'id', + 'status':'status', + 'wal':'wal', + 'backup-mode':'backup_mode', + 'compress-alg':'compress_alg', + 'start-time':'start_time', + 'end-time':'end_time', + 'retention-redundancy':'retention_redundancy', + 'retention-window':'retention_window' + } + +figures = {'count':'count', + 'error':'error', + 'data-bytes':'data_bytes', + 'wal-bytes':'wal_bytes', + 'uncompressed-bytes':'uncompressed_bytes'} + + +app = Flask(__name__) + +@app.route('/') +def root_folder(): + return 'pg_probackup metrics' + +@app.route('/metrics') +def metrics_folder(): + + # call pg_probackup_command + show_cmd = f"""{cmd} show --backup-path="{backup_path}" --format=json""" + cmd_result = os.popen(show_cmd).read() + + try: + data = json.loads(cmd_result) + except JSONDecodeError: + print(f'ERROR: {show_cmd}\n {cmd_result}') + + res_figures = {} + for fig in figures: + res_figures[fig] = f"# HELP {figures[fig]}\n" + res_figures[fig] += f"# TYPE postgres_backup_{figures[fig]} gauge\n" + + for d in data: + n = 0 + for b in d['backups']: + start_time = datetime.strptime(b['start-time'][0:19],'%Y-%m-%d %H:%M:%S') + status = b['status'] + instance = d['instance'] + + config_cmd = f"""{cmd} show-config --backup-path="{backup_path}" --format=json --instance={instance}""" + cmd_result = os.popen(config_cmd).read() + try: + config_data = json.loads(cmd_result) + except JSONDecodeError: + print(f'ERROR: {config_cmd}\n {cmd_result}') + + # append config data to backup data + b = b | config_data + + duration_minutes = 0 + + + + if 'recovery-time' in b: + recovery_time = datetime.strptime(b['recovery-time'][0:19],'%Y-%m-%d %H:%M:%S') + duration_minutes = round((recovery_time - start_time).seconds/60,2) + + for fig in figures: + res_figures[fig] += "postgres_backup_" + figures[fig] + "{service_id=\"" + instance + "\"" + + # add all characteristic fields to figure data + for fld in fields: + if fld in b: + res_figures[fig] += "," + fields[fld] + "=\"" + b[fld] + "\"" + + # add backup index + res_figures[fig] += ",backup_no=\"" + ('00' + str(n))[-3:] + "\"" + + # add backup duration + res_figures[fig] += ",duration_minutes=\"" + str(duration_minutes) + "\"" + + if fig == 'count': + res_figures[fig] += "} 1\n" + elif fig == 'error': + if status=='ERROR': + res_figures[fig] += "} 1\n" + else: + res_figures[fig] += "} 0\n" + else: + if fig in b: + res_figures[fig] += "} " + str(b[fig]) + "\n" + else: + res_figures[fig] += "} 0\n" + n+=1 + + # add all figure data to result + res = "" + for fig in figures: + res += res_figures[fig] + + return Response(res, mimetype='text/plain') + + +if __name__ == "__main__": + + app.run(host='0.0.0.0', port=port)