37 Commits

Author SHA1 Message Date
7cb0f7ad40 Fix typo in activity query 2025-07-05 01:17:35 -04:00
83fa12ec54 Correct types for slot LSN lag metrics 2025-07-04 02:46:25 -04:00
98b74d9aed Add sequence metrics to Zabbix template 2025-07-04 02:45:43 -04:00
45953848e2 Remove some type casts from working around Decimal types 2025-07-03 10:16:20 -04:00
6116f4f885 Fix missing json default 2025-07-03 02:08:45 -04:00
24d1214855 Teach json how to serialize decimals 2025-07-03 01:47:06 -04:00
3c39d8aa97 Remove CIDR prefix from replication id 2025-07-03 01:13:58 -04:00
ebb084aa9d Add initial query for sequence usage 2025-07-01 02:29:56 -04:00
86d5e8917b Merge branch 'dev/io_stats' into develop 2025-07-01 01:30:21 -04:00
be84e2b89a Report some I/O metrics in bytes 2025-06-30 02:00:06 -04:00
fd0a9e1230 Add backend I/O discoovery items to template 2025-06-29 02:08:19 -04:00
b934a30124 Add pg_stat_io query
* Add a query for pg_stat_io metrics grouped by backend type

* Switch some unsupported values from 0 to NULL to better reflect their
  nonexistence
2025-06-28 00:30:03 -04:00
ea397ef889 Improve docker-related query test failure avoidance 2025-06-18 17:39:16 -04:00
def6bab7f4 Add a pause after deploying test db containers as a temporary kludge 2025-06-18 03:30:27 -04:00
ecb616f6d9 Allow retry in query tests 2025-06-18 03:23:43 -04:00
b7f731c6ac Fix and reformat several queries 2025-06-18 03:03:34 -04:00
4fba81dc2c Add initial bgwriter and activity metrics queries 2025-06-17 02:40:52 -04:00
9225e745b0 Handle division by zero possibility
* When no I/O activity has been reported in pg_statio_all_tables for a
  database, the queries resulted in a division by zero. Cange this to a
  NULL instead.  Still a problem for Zabbix, but maybe a little cleaner.
2025-06-17 01:16:57 -04:00
b981a9ad36 Add slot lag metrics 2025-06-16 00:56:14 -04:00
c0185d4b86 Remove extra quotes in replication slot query 2025-06-15 02:20:45 -04:00
d34dfc5bf7 Fix replication monitoring queries 2025-06-15 02:19:10 -04:00
7c395a80fe Add metrics for tracking cache hit ratios 2025-06-13 01:16:24 -04:00
8fe81e3ab3 Fix global variable placement 2025-06-04 12:49:35 -04:00
cfe01eb63e Fix typo in latest version Zabbix item 2025-06-04 01:44:25 -04:00
55b9b16deb Install to /usr/bin
* Standardize all install methods to use /usr/bin rather than some of
  them using /usr/local/bin

* Make the init script executable in install-openrc
2025-06-04 01:16:59 -04:00
6a22597f1f Fix ID age query and template 2025-06-04 00:47:51 -04:00
295d1d6310 Fix typo in install-openrc 2025-06-03 19:38:59 -04:00
8b804f3912 Add requests as a dependency, split install target
* Add requests as a dependency on all OSs

* Split the install target into a common target and separate
  install-systemd and install-openrc targets to support installing on
  Alpine
2025-06-03 19:34:58 -04:00
bc9e039cb3 Fix Gentoo package prefix, update ebuild 2025-06-03 12:46:09 -04:00
54cf117591 Fix package contents for Gentoo
* Use git to identify only the version controlled files when creating
  the Gentoo tarball.
2025-06-03 12:06:34 -04:00
8e32e01c20 Merge branch 'dev/pg-version-lag' 2025-06-03 02:00:48 -04:00
af3bbf7515 Bump version to 1.0.3 2025-06-03 02:00:26 -04:00
180fa31d14 Add latest version metric
* Add a special handler for latest version info

* Add initial latest version items/triggers to the Zabbix template
2025-06-03 01:58:44 -04:00
39a6a9d23e Add replication slot monitoring to Zabbix template 2025-06-03 01:42:50 -04:00
f716569aa7 Add more tests for the version check code 2025-06-02 12:39:34 -04:00
22ae634a87 Format code with black 2025-06-02 03:41:15 -04:00
375bf6a982 Restructure latest version code for testing
* Split the latest version check code into functions for unit testing

* Add initial unit tests for latest version check
2025-06-02 03:39:52 -04:00
13 changed files with 1667 additions and 146 deletions

View File

@@ -3,7 +3,7 @@ Version: 1.0
Section: utils Section: utils
Priority: optional Priority: optional
Architecture: all Architecture: all
Depends: logrotate, python3 (>= 3.6), python3-psycopg2, python3-yaml, systemd Depends: logrotate, python3 (>= 3.6), python3-psycopg2, python3-requests, python3-yaml, systemd
Maintainer: James Campbell <james@commandprompt.com> Maintainer: James Campbell <james@commandprompt.com>
Homepage: https://www.commandprompt.com Homepage: https://www.commandprompt.com
Description: A bridge to sit between monitoring tools and PostgreSQL Description: A bridge to sit between monitoring tools and PostgreSQL

74
GENTOO/pgmon-1.0.3.ebuild Normal file
View File

@@ -0,0 +1,74 @@
# Copyright 2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
EAPI=8
PYTHON_COMPAT=( python3_{6..13} )
inherit python-r1 systemd
DESCRIPTION="PostgreSQL monitoring bridge"
HOMEPAGE="None"
LICENSE="BSD"
SLOT="0"
KEYWORDS="amd64"
SRC_URI="https://code2.shh-dot-com.org/james/${PN}/releases/download/v${PV}/${P}.tar.bz2"
IUSE="-systemd"
DEPEND="
${PYTHON_DEPS}
dev-python/psycopg:2
dev-python/pyyaml
dev-python/requests
app-admin/logrotate
"
RDEPEND="${DEPEND}"
BDEPEND=""
#RESTRICT="fetch"
#S="${WORKDIR}/${PN}"
#pkg_nofetch() {
# einfo "Please download"
# einfo " - ${P}.tar.bz2"
# einfo "from ${HOMEPAGE} and place it in your DISTDIR directory."
# einfo "The file should be owned by portage:portage."
#}
src_compile() {
true
}
src_install() {
# Install init script
if ! use systemd ; then
newinitd "openrc/pgmon.initd" pgmon
newconfd "openrc/pgmon.confd" pgmon
fi
# Install systemd unit
if use systemd ; then
systemd_dounit "systemd/pgmon.service"
fi
# Install script
exeinto /usr/bin
newexe "src/pgmon.py" pgmon
# Install default config
diropts -o root -g root -m 0755
insinto /etc/pgmon
doins "sample-config/pgmon.yml"
doins "sample-config/pgmon-metrics.yml"
# Install logrotate config
insinto /etc/logrotate.d
newins "logrotate/pgmon.logrotate" pgmon
# Install man page
doman manpages/pgmon.1
}

View File

@@ -23,7 +23,7 @@ SUPPORTED := ubuntu-20.04 \
# These targets are the main ones to use for most things. # These targets are the main ones to use for most things.
## ##
.PHONY: all clean tgz test query-tests install .PHONY: all clean tgz test query-tests install-common install-openrc install-systemd
# Build all packages # Build all packages
@@ -34,7 +34,7 @@ all: $(foreach distro_release, $(SUPPORTED), package-$(distro_release))
.PHONY: package-gentoo .PHONY: package-gentoo
package-gentoo: package-gentoo:
mkdir -p $(BUILD_DIR)/gentoo mkdir -p $(BUILD_DIR)/gentoo
tar --transform "s,^\.,$(PACKAGE_NAME)-$(VERSION)," -acjf $(BUILD_DIR)/gentoo/$(PACKAGE_NAME)-$(VERSION).tar.bz2 --exclude $(BUILD_DIR) . tar --transform "s,^,$(PACKAGE_NAME)-$(VERSION)/," -acjf $(BUILD_DIR)/gentoo/$(PACKAGE_NAME)-$(VERSION).tar.bz2 --exclude .gitignore $(shell git ls-tree --full-tree --name-only -r HEAD)
# Create a deb package # Create a deb package
@@ -54,7 +54,7 @@ package-%:
tgz: tgz:
rm -rf $(BUILD_DIR)/tgz/root rm -rf $(BUILD_DIR)/tgz/root
mkdir -p $(BUILD_DIR)/tgz/root mkdir -p $(BUILD_DIR)/tgz/root
$(MAKE) install DESTDIR=$(BUILD_DIR)/tgz/root $(MAKE) install-openrc DESTDIR=$(BUILD_DIR)/tgz/root
tar -cz -f $(BUILD_DIR)/tgz/$(PACKAGE_NAME)-$(VERSION).tgz -C $(BUILD_DIR)/tgz/root . tar -cz -f $(BUILD_DIR)/tgz/$(PACKAGE_NAME)-$(VERSION).tgz -C $(BUILD_DIR)/tgz/root .
# Clean up the build directory # Clean up the build directory
@@ -69,18 +69,17 @@ test:
query-tests: query-tests:
cd tests ; ./run-tests.sh cd tests ; ./run-tests.sh
# Install the script at the specified base directory # Install the script at the specified base directory (common components)
install: install-common:
# Set up directories # Set up directories
mkdir -p $(DESTDIR)/etc/$(PACKAGE_NAME) mkdir -p $(DESTDIR)/etc/$(PACKAGE_NAME)
mkdir -p ${DESTDIR}/etc/logrotate.d mkdir -p ${DESTDIR}/etc/logrotate.d
mkdir -p $(DESTDIR)/lib/systemd/system mkdir -p $(DESTDIR)/usr/bin
mkdir -p $(DESTDIR)/usr/local/bin
mkdir -p $(DESTDIR)/usr/share/man/man1 mkdir -p $(DESTDIR)/usr/share/man/man1
# Install script # Install script
cp $(SCRIPT) $(DESTDIR)/usr/local/bin/$(PACKAGE_NAME) cp $(SCRIPT) $(DESTDIR)/usr/bin/$(PACKAGE_NAME)
chmod 755 $(DESTDIR)/usr/local/bin/$(PACKAGE_NAME) chmod 755 $(DESTDIR)/usr/bin/$(PACKAGE_NAME)
# Install manpage # Install manpage
cp manpages/* $(DESTDIR)/usr/share/man/man1/ cp manpages/* $(DESTDIR)/usr/share/man/man1/
@@ -89,12 +88,36 @@ install:
# Install sample config # Install sample config
cp sample-config/* $(DESTDIR)/etc/$(PACKAGE_NAME)/ cp sample-config/* $(DESTDIR)/etc/$(PACKAGE_NAME)/
# Install systemd unit files
cp systemd/* $(DESTDIR)/lib/systemd/system/
# Install logrotate config # Install logrotate config
cp logrotate/${PACKAGE_NAME}.logrotate ${DESTDIR}/etc/logrotate.d/${PACKAGE_NAME} cp logrotate/${PACKAGE_NAME}.logrotate ${DESTDIR}/etc/logrotate.d/${PACKAGE_NAME}
# Install for systemd
install-systemd:
# Install the common stuff
$(MAKE) install-common
# Set up directories
mkdir -p $(DESTDIR)/lib/systemd/system
# Install systemd unit files
cp systemd/* $(DESTDIR)/lib/systemd/system/
# Install for open-rc
install-openrc:
# Install the common stuff
$(MAKE) install-common
# Set up directories
mkdir -p $(DESTDIR)/etc/init.d
mkdir -p $(DESTDIR)/etc/conf.d
# Install init script
cp openrc/pgmon.initd $(DESTDIR)/etc/init.d/pgmon
chmod 755 $(DESTDIR)/etc/init.d/pgmon
# Install init script config file
cp openrc/pgmon.confd $(DESTDIR)/etc/conf.d/pgmon
# Run all of the install tests # Run all of the install tests
.PHONY: install-tests debian-%-install-test rockylinux-%-install-test ubuntu-%-install-test gentoo-install-test .PHONY: install-tests debian-%-install-test rockylinux-%-install-test ubuntu-%-install-test gentoo-install-test
@@ -167,7 +190,7 @@ package-image-%:
# Debian package creation # Debian package creation
actually-package-debian-%: actually-package-debian-%:
$(MAKE) install DESTDIR=/output/debian-$* $(MAKE) install-systemd DESTDIR=/output/debian-$*
cp -r --preserve=mode DEBIAN /output/debian-$*/ cp -r --preserve=mode DEBIAN /output/debian-$*/
dpkg-deb -Zgzip --build /output/debian-$* "/output/$(PACKAGE_NAME)-$(VERSION)-debian-$*.deb" dpkg-deb -Zgzip --build /output/debian-$* "/output/$(PACKAGE_NAME)-$(VERSION)-debian-$*.deb"
@@ -182,7 +205,7 @@ actually-package-rockylinux-%:
# Ubuntu package creation # Ubuntu package creation
actually-package-ubuntu-%: actually-package-ubuntu-%:
$(MAKE) install DESTDIR=/output/ubuntu-$* $(MAKE) install-systemd DESTDIR=/output/ubuntu-$*
cp -r --preserve=mode DEBIAN /output/ubuntu-$*/ cp -r --preserve=mode DEBIAN /output/ubuntu-$*/
dpkg-deb -Zgzip --build /output/ubuntu-$* "/output/$(PACKAGE_NAME)-$(VERSION)-ubuntu-$*.deb" dpkg-deb -Zgzip --build /output/ubuntu-$* "/output/$(PACKAGE_NAME)-$(VERSION)-ubuntu-$*.deb"

View File

@@ -7,7 +7,7 @@ License: MIT
URL: https://www.commandprompt.com URL: https://www.commandprompt.com
BuildArch: noarch BuildArch: noarch
Requires: logrotate, python, python-psycopg2, PyYAML, systemd Requires: logrotate, python, python-psycopg2, PyYAML, python-requests, systemd
%description %description
A bridge to sit between monitoring tools and PostgreSQL A bridge to sit between monitoring tools and PostgreSQL
@@ -19,7 +19,7 @@ A bridge to sit between monitoring tools and PostgreSQL
# Do nothing since we have nothing to build # Do nothing since we have nothing to build
%install %install
make -C /src install DESTDIR=%{buildroot} make -C /src install-systemd DESTDIR=%{buildroot}
%files %files
/etc/logrotate.d/pgmon /etc/logrotate.d/pgmon
@@ -28,7 +28,7 @@ make -C /src install DESTDIR=%{buildroot}
/etc/pgmon/pgmon-service.conf /etc/pgmon/pgmon-service.conf
/lib/systemd/system/pgmon.service /lib/systemd/system/pgmon.service
/lib/systemd/system/pgmon@.service /lib/systemd/system/pgmon@.service
/usr/local/bin/pgmon /usr/bin/pgmon
/usr/share/man/man1/pgmon.1.gz /usr/share/man/man1/pgmon.1.gz
%post %post

View File

@@ -7,7 +7,7 @@ License: MIT
URL: https://www.commandprompt.com URL: https://www.commandprompt.com
BuildArch: noarch BuildArch: noarch
Requires: logrotate, python3, python3-psycopg2, python3-pyyaml, systemd Requires: logrotate, python3, python3-psycopg2, python3-pyyaml, python3-requests, systemd
%description %description
A bridge to sit between monitoring tools and PostgreSQL A bridge to sit between monitoring tools and PostgreSQL
@@ -19,7 +19,7 @@ A bridge to sit between monitoring tools and PostgreSQL
# Do nothing since we have nothing to build # Do nothing since we have nothing to build
%install %install
make -C /src install DESTDIR=%{buildroot} make -C /src install-systemd DESTDIR=%{buildroot}
%files %files
/etc/logrotate.d/pgmon /etc/logrotate.d/pgmon
@@ -28,7 +28,7 @@ make -C /src install DESTDIR=%{buildroot}
/etc/pgmon/pgmon-service.conf /etc/pgmon/pgmon-service.conf
/lib/systemd/system/pgmon.service /lib/systemd/system/pgmon.service
/lib/systemd/system/pgmon@.service /lib/systemd/system/pgmon@.service
/usr/local/bin/pgmon /usr/bin/pgmon
/usr/share/man/man1/pgmon.1.gz /usr/share/man/man1/pgmon.1.gz
%post %post

View File

@@ -1,63 +1,307 @@
metrics: metrics:
##
# Discovery metrics # Discovery metrics
##
discover_dbs: discover_dbs:
type: set type: set
query: query:
0: SELECT datname AS dbname FROM pg_database 0: >
SELECT datname AS dbname
FROM pg_database
# Note: If the user lacks sufficient privileges, these fields will be NULL.
# The WHERE clause is intended to prevent Zabbix from discovering a
# connection it cannot monitor. Ideally this would generate an error
# instead.
discover_rep: discover_rep:
type: set type: set
query: query:
0: SELECT client_addr || '_' || regexp_replace(application_name, '[ ,]', '_', 'g') AS repid, client_addr, state FROM pg_stat_replication 0: >
SELECT host(client_addr) || '_' || regexp_replace(application_name, '[ ,]', '_', 'g') AS repid,
client_addr,
state
FROM pg_stat_replication
WHERE state IS NOT NULL
discover_slots: discover_slots:
type: set type: set
query: query:
90400: SELECT slot_name, plugin, slot_type, database, false as temporary, active FROM pg_replication_slots 90400: >
100000: SELECT slot_name, plugin, slot_type, database, temporary, active FROM pg_replication_slots SELECT slot_name,
plugin,
slot_type,
database,
false as temporary,
active
FROM pg_replication_slots
100000: >
SELECT slot_name,
plugin,
slot_type,
database,
temporary,
active
FROM pg_replication_slots
##
# cluster-wide metrics # cluster-wide metrics
##
version: version:
type: value type: value
query: query:
0: SHOW server_version_num 0: SHOW server_version_num
max_frozen_age: max_frozen_age:
type: row type: row
query: query:
0: SELECT max(age(datfrozenxid)), max(mxid_age(datminmxid)) FROM pg_database 0: >
SELECT max(age(datfrozenxid)) AS xid_age,
NULL AS mxid_age
FROM pg_database
90600: >
SELECT max(age(datfrozenxid)) AS xid_age,
max(mxid_age(datminmxid)) AS mxid_age
FROM pg_database
bgwriter:
type: row
query:
0: >
SELECT checkpoints_timed,
checkpoints_req,
checkpoint_write_time,
checkpoint_sync_time,
buffers_checkpoint,
buffers_clean,
maxwritten_clean,
buffers_backend,
buffers_backend_fsync,
buffers_alloc
FROM pg_stat_bgwriter
170000: >
SELECT cp.num_timed AS checkpoints_timed,
cp.num_requested AS checkpoints_req,
cp.write_time AS checkpoint_write_time,
cp.sync_time AS checkpoint_sync_time,
cp.buffers_written AS buffers_checkpoint,
bg.buffers_clean AS buffers_clean,
bg.maxwritten_clean AS maxwritten_clean,
NULL AS buffers_backend,
NULL AS buffers_backend_fsync,
bg.buffers_alloc AS buffers_alloc
FROM pg_stat_bgwriter bg
CROSS JOIN pg_stat_checkpointer cp
io_per_backend:
type: set
query:
160000: >
SELECT backend_type,
COALESCE(SUM(reads * op_bytes), 0) AS reads,
COALESCE(SUM(read_time), 0) AS read_time,
COALESCE(SUM(writes * op_bytes), 0) AS writes,
COALESCE(SUM(write_time), 0) AS write_time,
COALESCE(SUM(writebacks * op_bytes), 0) AS writebacks,
COALESCE(SUM(writeback_time), 0) AS writeback_time,
COALESCE(SUM(extends * op_bytes), 0) AS extends,
COALESCE(SUM(extend_time), 0) AS extend_time,
COALESCE(SUM(op_bytes), 0) AS op_bytes,
COALESCE(SUM(hits), 0) AS hits,
COALESCE(SUM(evictions), 0) AS evictions,
COALESCE(SUM(reuses), 0) AS reuses,
COALESCE(SUM(fsyncs), 0) AS fsyncs,
COALESCE(SUM(fsync_time), 0) AS fsync_time
FROM pg_stat_io
GROUP BY backend_type
##
# Per-database metrics # Per-database metrics
##
db_stats: db_stats:
type: row type: row
query: query:
0: SELECT numbackends, xact_commit, xact_rollback, blks_read, blks_hit, tup_returned, tup_fetched, tup_inserted, tup_updated, tup_deleted, conflicts, temp_files, temp_bytes, deadlocks, blk_read_time, blk_write_time, extract('epoch' from stats_reset)::float FROM pg_stat_database WHERE datname = %(dbname)s 0: >
140000: SELECT numbackends, xact_commit, xact_rollback, blks_read, blks_hit, tup_returned, tup_fetched, tup_inserted, tup_updated, tup_deleted, conflicts, temp_files, temp_bytes, deadlocks, COALESCE(checksum_failures, 0) AS checksum_failures, blk_read_time, blk_write_time, session_time, active_time, idle_in_transaction_time, sessions, sessions_abandoned, sessions_fatal, sessions_killed, extract('epoch' from stats_reset)::float FROM pg_stat_database WHERE datname = %(dbname)s SELECT numbackends,
xact_commit,
xact_rollback,
blks_read,
blks_hit,
tup_returned,
tup_fetched,
tup_inserted,
tup_updated,
tup_deleted,
conflicts,
temp_files,
temp_bytes,
deadlocks,
NULL AS checksum_failures,
blk_read_time,
blk_write_time,
NULL AS session_time,
NULL AS active_time,
NULL AS idle_in_transaction_time,
NULL AS sessions,
NULL AS sessions_abandoned,
NULL AS sessions_fatal,
NULL AS sessions_killed,
extract('epoch' from stats_reset) AS stats_reset
FROM pg_stat_database WHERE datname = %(dbname)s
140000: >
SELECT numbackends,
xact_commit,
xact_rollback,
blks_read,
blks_hit,
tup_returned,
tup_fetched,
tup_inserted,
tup_updated,
tup_deleted,
conflicts,
temp_files,
temp_bytes,
deadlocks,
COALESCE(checksum_failures, 0) AS checksum_failures,
blk_read_time,
blk_write_time,
session_time,
active_time,
idle_in_transaction_time,
sessions,
sessions_abandoned,
sessions_fatal,
sessions_killed,
extract('epoch' from stats_reset) AS stats_reset
FROM pg_stat_database WHERE datname = %(dbname)s
test_args: test_args:
dbname: postgres dbname: postgres
hit_ratios:
type: row
query:
0: >
SELECT sum(heap_blks_read)::float / NULLIF(sum(heap_blks_read + heap_blks_hit), 0) AS avg_heap_hit_ratio,
sum(idx_blks_hit)::float / NULLIF(sum(idx_blks_read + idx_blks_hit), 0) AS avg_idx_hit_ratio,
sum(toast_blks_hit)::float / NULLIF(sum(toast_blks_read + toast_blks_hit), 0) AS avg_toast_hit_ratio,
sum(tidx_blks_hit)::float / NULLIF(sum(tidx_blks_read + tidx_blks_hit), 0) AS avg_tidx_hit_ratio
FROM pg_statio_all_tables
test_args:
dbname: postgres
activity:
type: set
query:
0: >
SELECT state,
count(*) AS backend_count,
COALESCE(EXTRACT(EPOCH FROM max(now() - state_change)), 0) AS max_state_time
FROM pg_stat_activity
WHERE datname = %(dbname)s
GROUP BY state
test_args:
dbname: postgres
sequence_usage:
type: value
query:
# 9.2 lacks lateral joins, the pg_sequence_last_value function, and the pg_sequences view
# 0: >
# SELECT COALESCE(MAX(pg_sequence_last_value(c.oid)::float / (pg_sequence_parameters(oid)).maximum_value), 0) AS max_usage
# FROM pg_class c
# WHERE c.relkind = 'S'
# 9.3 - 9.6 lacks the pg_sequence_last_value function, and pg_sequences view
# 90300: >
# SELECT COALESCE(MAX(pg_sequence_last_value(c.oid)::float / s.maximum_value), 0) AS max_usage
# FROM pg_class c
# CROSS JOIN LATERAL pg_sequence_parameters(c.oid) AS s
# WHERE c.relkind = 'S'
100000: SELECT COALESCE(MAX(last_value::float / max_value), 0) AS max_usage FROM pg_sequences;
test_args:
dbname: postgres
sequence_visibility:
type: row
query:
100000: >
SELECT COUNT(*) FILTER (WHERE has_sequence_privilege(c.oid, 'SELECT,USAGE')) AS visible_sequences,
COUNT(*) AS total_sequences
FROM pg_class AS c
WHERE relkind = 'S';
##
# Per-replication metrics
##
rep_stats:
type: row
query:
90400: >
SELECT pid, usename,
EXTRACT(EPOCH FROM backend_start) AS backend_start,
state,
pg_xlog_location_diff(pg_current_xlog_location(), sent_location) AS sent_lsn,
pg_xlog_location_diff(pg_current_xlog_location(), write_location) AS write_lsn,
pg_xlog_location_diff(pg_current_xlog_location(), flush_location) AS flush_lsn,
pg_xlog_location_diff(pg_current_xlog_location(), replay_location) AS replay_lsn,
NULL AS write_lag,
NULL AS flush_lag,
NULL AS replay_lag,
sync_state
FROM pg_stat_replication
WHERE host(client_addr) || '_' || regexp_replace(application_name, '[ ,]', '_', 'g') = %(repid)s
100000: >
SELECT pid, usename,
EXTRACT(EPOCH FROM backend_start) AS backend_start,
state,
pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn) AS sent_lsn,
pg_wal_lsn_diff(pg_current_wal_lsn(), write_lsn) AS write_lsn,
pg_wal_lsn_diff(pg_current_wal_lsn(), flush_lsn) AS flush_lsn,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_lsn,
COALESCE(EXTRACT(EPOCH FROM write_lag), 0) AS write_lag,
COALESCE(EXTRACT(EPOCH FROM flush_lag), 0) AS flush_lag,
COALESCE(EXTRACT(EPOCH FROM replay_lag), 0) AS replay_lag,
sync_state
FROM pg_stat_replication
WHERE host(client_addr) || '_' || regexp_replace(application_name, '[ ,]', '_', 'g') = %(repid)s
test_args:
repid: 127.0.0.1_test_rep
##
# Per-slot metrics
##
slot_stats:
type: row
query:
90400: >
SELECT NULL as active_pid,
xmin,
pg_xlog_location_diff(pg_current_xlog_location(), restart_lsn) AS restart_bytes,
NULL AS confirmed_flush_bytes
FROM pg_replication_slots WHERE slot_name = %(slot)s
90600: >
SELECT active_pid,
xmin,
pg_xlog_location_diff(pg_current_xlog_location(), restart_lsn) AS restart_bytes,
pg_xlog_location_diff(pg_current_xlog_location(), confirmed_flush_lsn) AS confirmed_flush_bytes
FROM pg_replication_slots WHERE slot_name = %(slot)s
100000: >
SELECT active_pid,
xmin,
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS restart_bytes,
pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) AS confirmed_flush_bytes
FROM pg_replication_slots WHERE slot_name = %(slot)s
test_args:
slot: test_slot
##
# Debugging # Debugging
##
ntables: ntables:
type: value type: value
query: query:
0: SELECT count(*) AS ntables FROM pg_stat_user_tables 0: SELECT count(*) AS ntables FROM pg_stat_user_tables
# Per-replication metrics
rep_stats:
type: row
query:
90400: SELECT * FROM pg_stat_replication WHERE client_addr || '_' || regexp_replace(application_name, '[ ,]', '_', 'g') = '{repid}'
test_args:
repid: 127.0.0.1_test_rep
# Debugging
sleep:
type: value
query:
0: SELECT now(), pg_sleep(5);
# Per-slot metrics
slot_stats:
type: row
query:
90400: SELECT active_pid, xmin, pg_xlog_location_diff(pg_current_xlog_location(), restart_lsn) AS restart_bytes, pg_xlog_location_diff(pg_current_xlog_location(), confirmed_flush_lsn) AS confirmed_flush_bytes FROM pg_replication_slots WHERE slot_name = '{slot}'
100000: SELECT active_pid, xmin, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS restart_bytes, pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) AS confirmed_flush_bytes FROM pg_replication_slots WHERE slot_name = '{slot}'
test_args:
slot: test_slot

View File

@@ -27,7 +27,9 @@ from urllib.parse import urlparse, parse_qs
import requests import requests
import re import re
VERSION = "1.0.2" from decimal import Decimal
VERSION = "1.0.3"
# Configuration # Configuration
config = {} config = {}
@@ -391,6 +393,16 @@ def get_query(metric, version):
raise MetricVersionError("Missing metric query for PostgreSQL {}".format(version)) raise MetricVersionError("Missing metric query for PostgreSQL {}".format(version))
def json_encode_special(obj):
"""
Encoder function to handle types the standard JSON package doesn't know what
to do with
"""
if isinstance(obj, Decimal):
return float(obj)
raise TypeError(f'Cannot serialize object of {type(obj)}')
def run_query_no_retry(pool, return_type, query, args): def run_query_no_retry(pool, return_type, query, args):
""" """
Run the query with no explicit retry code Run the query with no explicit retry code
@@ -408,13 +420,13 @@ def run_query_no_retry(pool, return_type, query, args):
elif return_type == "row": elif return_type == "row":
if len(res) == 0: if len(res) == 0:
return "[]" return "[]"
return json.dumps(res[0]) return json.dumps(res[0], default=json_encode_special)
elif return_type == "column": elif return_type == "column":
if len(res) == 0: if len(res) == 0:
return "[]" return "[]"
return json.dumps([list(r.values())[0] for r in res]) return json.dumps([list(r.values())[0] for r in res], default=json_encode_special)
elif return_type == "set": elif return_type == "set":
return json.dumps(res) return json.dumps(res, default=json_encode_special)
except: except:
dbname = pool.name dbname = pool.name
if dbname in unhappy_cooldown: if dbname in unhappy_cooldown:
@@ -497,13 +509,91 @@ def get_cluster_version():
return cluster_version return cluster_version
def version_num_to_release(version_num):
"""
Extract the revease from a version_num.
In other words, this converts things like:
90603 => 9.6
130010 => 13
"""
if version_num // 10000 < 10:
return version_num // 10000 + (version_num % 10000 // 100 / 10)
else:
return version_num // 10000
def parse_version_rss(raw_rss, release):
"""
Parse the raw RSS from the versions.rss feed to extract the latest version of
PostgreSQL that's availabe for the cluster being monitored.
This sets these global variables:
latest_version
release_supported
It is expected that the caller already holds the latest_version_lock lock.
params:
raw_rss: The raw rss text from versions.rss
release: The PostgreSQL release we care about (ex: 9.2, 14)
"""
global latest_version
global release_supported
# Regular expressions for parsing the RSS document
version_line = re.compile(
r".*?([0-9][0-9.]+) is the latest release in the {} series.*".format(release)
)
unsupported_line = re.compile(r"^This version is unsupported")
# Loop through the RSS until we find the current release
release_found = False
for line in raw_rss.splitlines():
m = version_line.match(line)
if m:
# Note that we found the version we were looking for
release_found = True
# Convert the version to version_num format
version = m.group(1)
parts = list(map(int, version.split(".")))
if parts[0] < 10:
latest_version = int(
"{}{:02}{:02}".format(parts[0], parts[1], parts[2])
)
else:
latest_version = int("{}00{:02}".format(parts[0], parts[1]))
elif release_found:
# The next line after the version tells if the version is supported
if unsupported_line.match(line):
release_supported = False
else:
release_supported = True
break
# Make sure we actually found it
if not release_found:
raise LatestVersionCheckError("Current release ({}) not found".format(release))
log.info(
"Got latest PostgreSQL version: {} supported={}".format(
latest_version, release_supported
)
)
log.debug(
"Next latest PostgreSQL version check will be after: {}".format(
latest_version_next_check
)
)
def get_latest_version(): def get_latest_version():
""" """
Get the latest supported version of the major PostgreSQL release running on the server being monitored. Get the latest supported version of the major PostgreSQL release running on the server being monitored.
""" """
global latest_version
global latest_version_next_check global latest_version_next_check
global release_supported
# If we don't know the latest version or it's past the recheck time, get the # If we don't know the latest version or it's past the recheck time, get the
# version from the PostgreSQL RSS feed. Only one thread needs to do this, so # version from the PostgreSQL RSS feed. Only one thread needs to do this, so
@@ -513,6 +603,10 @@ def get_latest_version():
or latest_version_next_check is None or latest_version_next_check is None
or latest_version_next_check < datetime.now() or latest_version_next_check < datetime.now()
): ):
# Note: we get the cluster version here before grabbing the latest_version_lock
# lock so it's not held while trying to talk with the DB.
release = version_num_to_release(get_cluster_version())
with latest_version_lock: with latest_version_lock:
# Only check if nobody already got the version before us # Only check if nobody already got the version before us
if ( if (
@@ -521,74 +615,17 @@ def get_latest_version():
or latest_version_next_check < datetime.now() or latest_version_next_check < datetime.now()
): ):
log.info("Checking latest PostgreSQL version") log.info("Checking latest PostgreSQL version")
cluster_version = get_cluster_version()
latest_version_next_check = datetime.now() + timedelta( latest_version_next_check = datetime.now() + timedelta(
seconds=int(config["latest_version_check_period"]) seconds=int(config["latest_version_check_period"])
) )
# Extract the release
# 90603 => 9.6
# 130010 => 13
if cluster_version // 10000 < 10:
release = cluster_version // 10000 + (
cluster_version % 10000 // 100 / 10
)
else:
release = cluster_version // 10000
# Grab the RSS feed # Grab the RSS feed
raw_rss = requests.get("https://www.postgresql.org/versions.rss") raw_rss = requests.get("https://www.postgresql.org/versions.rss")
if raw_rss.status != 200: if raw_rss.status_code != 200:
raise LatestVersionCheckError("code={}".format(r.status)) raise LatestVersionCheckError("code={}".format(r.status_code))
# Regular expressions for parsing the RSS document # Parse the RSS body and set global variables
version_line = re.compile( parse_version_rss(raw_rss.text, release)
r"([0-9][0-9.]+) is the latest release in the ([0-9][0-9.]+) series"
)
unsupported_line = re.compile(r"^This version is unsupported")
# Loop through the RSS until we find the current release
release_found = False
for line in raw_rss.text.lines():
m = version_line.match(line)
if m:
if release == int(m.group(2)):
release_found = True
version = float(m.group(1))
if version < 10:
parts = list(map(int, version.split(".")))
latest_version = int(
"{}{:02}{:02}".format(parts[0], parts[1], parts[2])
)
else:
parts = list(map(int, version.split(".")))
latest_version = int(
"{}00{:02}".format(parts[0], parts[1])
)
release_found = True
elif release_found:
if unsupported.match(line):
release_supported = False
else:
release_supported = True
break
# Make sure we actually found it
if not release_found:
raise LatestVersionCheckError(
"Current release ({}) not found".format(release)
)
log.info(
"Got latest PostgreSQL version: {} supported={}".format(
latest_version, release_supported
)
)
log.debug(
"Next latest PostgreSQL version check will be after: {}".format(
latest_version_next_check
)
)
return latest_version return latest_version
@@ -630,8 +667,25 @@ def test_queries():
for name, metric in config["metrics"].items(): for name, metric in config["metrics"].items():
# If the metric has arguments to use while testing, grab those # If the metric has arguments to use while testing, grab those
args = metric.get("test_args", {}) args = metric.get("test_args", {})
# Run the query without the ability to retry. print("Testing {} [{}]".format(name, ", ".join(["{}={}".format(key, value) for key, value in args.items()])))
res = sample_metric(dbname, name, args, retry=False) # When testing against a docker container, we may end up connecting
# before the service is truly up (it restarts during the initialization
# phase). To cope with this, we'll allow a few connection failures.
tries = 5
while True:
# Run the query without the ability to retry
try:
res = sample_metric(dbname, name, args, retry=False)
break
except MetricVersionError:
res = "Unsupported for this version"
break
except psycopg2.OperationalError as e:
print("Error encountered, {} tries left: {}".format(tries, e))
if tries <= 0:
raise
time.sleep(1)
tries -= 1
# Compare the result to the provided sample results # Compare the result to the provided sample results
# TODO # TODO
print("{} -> {}".format(name, res)) print("{} -> {}".format(name, res))
@@ -674,6 +728,22 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
if metric_name == "agent_version": if metric_name == "agent_version":
self._reply(200, VERSION) self._reply(200, VERSION)
return return
elif metric_name == "latest_version_info":
try:
get_latest_version()
self._reply(
200,
json.dumps(
{
"latest": latest_version,
"supported": 1 if release_supported else 0,
}
),
)
except LatestVersionCheckError as e:
log.error("Failed to retrieve latest version information: {}".format(e))
self._reply(503, "Failed to retrieve latest version info")
return
# Note: parse_qs returns the values as a list. Since we always expect # Note: parse_qs returns the values as a list. Since we always expect
# single values, just grab the first from each. # single values, just grab the first from each.

View File

@@ -5,11 +5,103 @@ import tempfile
import logging import logging
from decimal import Decimal
import json
import pgmon import pgmon
# Silence most logging output # Silence most logging output
logging.disable(logging.CRITICAL) logging.disable(logging.CRITICAL)
versions_rss = """
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>PostgreSQL latest versions</title><link>https://www.postgresql.org/</link><description>PostgreSQL latest versions</description><atom:link href="https://www.postgresql.org/versions.rss" rel="self"/><language>en-us</language><lastBuildDate>Thu, 08 May 2025 00:00:00 +0000</lastBuildDate><item><title>17.5
</title><link>https://www.postgresql.org/docs/17/release-17-5.html</link><description>17.5 is the latest release in the 17 series.
</description><pubDate>Thu, 08 May 2025 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/17/release-17-5.html</guid></item><item><title>16.9
</title><link>https://www.postgresql.org/docs/16/release-16-9.html</link><description>16.9 is the latest release in the 16 series.
</description><pubDate>Thu, 08 May 2025 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/16/release-16-9.html</guid></item><item><title>15.13
</title><link>https://www.postgresql.org/docs/15/release-15-13.html</link><description>15.13 is the latest release in the 15 series.
</description><pubDate>Thu, 08 May 2025 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/15/release-15-13.html</guid></item><item><title>14.18
</title><link>https://www.postgresql.org/docs/14/release-14-18.html</link><description>14.18 is the latest release in the 14 series.
</description><pubDate>Thu, 08 May 2025 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/14/release-14-18.html</guid></item><item><title>13.21
</title><link>https://www.postgresql.org/docs/13/release-13-21.html</link><description>13.21 is the latest release in the 13 series.
</description><pubDate>Thu, 08 May 2025 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/13/release-13-21.html</guid></item><item><title>12.22
</title><link>https://www.postgresql.org/docs/12/release-12-22.html</link><description>12.22 is the latest release in the 12 series.
This version is unsupported!
</description><pubDate>Thu, 21 Nov 2024 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/12/release-12-22.html</guid></item><item><title>11.22
</title><link>https://www.postgresql.org/docs/11/release-11-22.html</link><description>11.22 is the latest release in the 11 series.
This version is unsupported!
</description><pubDate>Thu, 09 Nov 2023 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/11/release-11-22.html</guid></item><item><title>10.23
</title><link>https://www.postgresql.org/docs/10/release-10-23.html</link><description>10.23 is the latest release in the 10 series.
This version is unsupported!
</description><pubDate>Thu, 10 Nov 2022 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/10/release-10-23.html</guid></item><item><title>9.6.24
</title><link>https://www.postgresql.org/docs/9.6/release-9-6-24.html</link><description>9.6.24 is the latest release in the 9.6 series.
This version is unsupported!
</description><pubDate>Thu, 11 Nov 2021 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/9.6/release-9-6-24.html</guid></item><item><title>9.5.25
</title><link>https://www.postgresql.org/docs/9.5/release-9-5-25.html</link><description>9.5.25 is the latest release in the 9.5 series.
This version is unsupported!
</description><pubDate>Thu, 11 Feb 2021 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/9.5/release-9-5-25.html</guid></item><item><title>9.4.26
</title><link>https://www.postgresql.org/docs/9.4/release-9-4-26.html</link><description>9.4.26 is the latest release in the 9.4 series.
This version is unsupported!
</description><pubDate>Thu, 13 Feb 2020 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/9.4/release-9-4-26.html</guid></item><item><title>9.3.25
</title><link>https://www.postgresql.org/docs/9.3/release-9-3-25.html</link><description>9.3.25 is the latest release in the 9.3 series.
This version is unsupported!
</description><pubDate>Thu, 08 Nov 2018 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/9.3/release-9-3-25.html</guid></item><item><title>9.2.24
</title><link>https://www.postgresql.org/docs/9.2/release-9-2-24.html</link><description>9.2.24 is the latest release in the 9.2 series.
This version is unsupported!
</description><pubDate>Thu, 09 Nov 2017 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/9.2/release-9-2-24.html</guid></item><item><title>9.1.24
</title><link>https://www.postgresql.org/docs/9.1/release-9-1-24.html</link><description>9.1.24 is the latest release in the 9.1 series.
This version is unsupported!
</description><pubDate>Thu, 27 Oct 2016 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/9.1/release-9-1-24.html</guid></item><item><title>9.0.23
</title><link>https://www.postgresql.org/docs/9.0/release-9-0-23.html</link><description>9.0.23 is the latest release in the 9.0 series.
This version is unsupported!
</description><pubDate>Thu, 08 Oct 2015 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/9.0/release-9-0-23.html</guid></item><item><title>8.4.22
</title><link>https://www.postgresql.org/docs/8.4/release-8-4-22.html</link><description>8.4.22 is the latest release in the 8.4 series.
This version is unsupported!
</description><pubDate>Thu, 24 Jul 2014 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/8.4/release-8-4-22.html</guid></item><item><title>8.3.23
</title><link>https://www.postgresql.org/docs/8.3/release-8-3-23.html</link><description>8.3.23 is the latest release in the 8.3 series.
This version is unsupported!
</description><pubDate>Thu, 07 Feb 2013 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/8.3/release-8-3-23.html</guid></item><item><title>8.2.23
</title><link>https://www.postgresql.org/docs/8.2/release-8-2-23.html</link><description>8.2.23 is the latest release in the 8.2 series.
This version is unsupported!
</description><pubDate>Mon, 05 Dec 2011 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/8.2/release-8-2-23.html</guid></item><item><title>8.1.23
</title><link>https://www.postgresql.org/docs/8.1/release.html</link><description>8.1.23 is the latest release in the 8.1 series.
This version is unsupported!
</description><pubDate>Thu, 16 Dec 2010 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/8.1/release.html</guid></item><item><title>8.0.26
</title><link>https://www.postgresql.org/docs/8.0/release.html</link><description>8.0.26 is the latest release in the 8.0 series.
This version is unsupported!
</description><pubDate>Mon, 04 Oct 2010 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/8.0/release.html</guid></item><item><title>7.4.30
</title><link>https://www.postgresql.org/docs/7.4/release.html</link><description>7.4.30 is the latest release in the 7.4 series.
This version is unsupported!
</description><pubDate>Mon, 04 Oct 2010 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/7.4/release.html</guid></item><item><title>7.3.21
</title><link>https://www.postgresql.org/docs/7.3/release.html</link><description>7.3.21 is the latest release in the 7.3 series.
This version is unsupported!
</description><pubDate>Mon, 07 Jan 2008 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/7.3/release.html</guid></item><item><title>7.2.8
</title><link>https://www.postgresql.org/docs/7.2/release.html</link><description>7.2.8 is the latest release in the 7.2 series.
This version is unsupported!
</description><pubDate>Mon, 09 May 2005 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/7.2/release.html</guid></item><item><title>7.1.3
</title><link>https://www.postgresql.org/docs/7.1/release.html</link><description>7.1.3 is the latest release in the 7.1 series.
This version is unsupported!
</description><pubDate>Fri, 17 Aug 2001 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/7.1/release.html</guid></item><item><title>7.0.3
</title><link>https://www.postgresql.org/docs/7.0/release.htm</link><description>7.0.3 is the latest release in the 7.0 series.
This version is unsupported!
</description><pubDate>Sun, 12 Nov 2000 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/7.0/release.htm</guid></item><item><title>6.5.3
</title><link>https://www.postgresql.org/docs/6.5/release.htm</link><description>6.5.3 is the latest release in the 6.5 series.
This version is unsupported!
</description><pubDate>Thu, 04 Nov 1999 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/6.5/release.htm</guid></item><item><title>6.4.2
</title><link>https://www.postgresql.org/docs/6.4/release.htm</link><description>6.4.2 is the latest release in the 6.4 series.
This version is unsupported!
</description><pubDate>Sun, 03 Jan 1999 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/6.4/release.htm</guid></item><item><title>6.3.2
</title><link>https://www.postgresql.org/docs/6.3/c2701.htm</link><description>6.3.2 is the latest release in the 6.3 series.
This version is unsupported!
</description><pubDate>Mon, 23 Feb 1998 00:00:00 +0000</pubDate><guid>https://www.postgresql.org/docs/6.3/c2701.htm</guid></item></channel></rss>
"""
class TestPgmonMethods(unittest.TestCase): class TestPgmonMethods(unittest.TestCase):
## ##
@@ -172,13 +264,16 @@ class TestPgmonMethods(unittest.TestCase):
def test_get_query__versions(self): def test_get_query__versions(self):
metric = {"type": "value", "query": {0: "DEFAULT", 110000: "NEW"}} metric = {"type": "value", "query": {0: "DEFAULT", 110000: "NEW"}}
# Test getting the default version of a query with no lower bound and a newer version # Test getting the default version of a query with no lower bound and a newer
# version
self.assertEqual(pgmon.get_query(metric, 100000), "DEFAULT") self.assertEqual(pgmon.get_query(metric, 100000), "DEFAULT")
# Test getting the newer version of a query with no lower bound and a newer version for the newer version # Test getting the newer version of a query with no lower bound and a newer
# version for the newer version
self.assertEqual(pgmon.get_query(metric, 110000), "NEW") self.assertEqual(pgmon.get_query(metric, 110000), "NEW")
# Test getting the newer version of a query with no lower bound and a newer version for an even newer version # Test getting the newer version of a query with no lower bound and a newer
# version for an even newer version
self.assertEqual(pgmon.get_query(metric, 160000), "NEW") self.assertEqual(pgmon.get_query(metric, 160000), "NEW")
# Test getting a version in bwtween two other versions # Test getting a version in bwtween two other versions
@@ -272,7 +367,8 @@ metrics:
def test_read_config__include(self): def test_read_config__include(self):
pgmon.config = {} pgmon.config = {}
# Test reading a config that includes other files (absolute and relative paths, multiple levels) # Test reading a config that includes other files (absolute and relative paths,
# multiple levels)
with tempfile.TemporaryDirectory() as tmpdirname: with tempfile.TemporaryDirectory() as tmpdirname:
with open(f"{tmpdirname}/config.yml", "w") as f: with open(f"{tmpdirname}/config.yml", "w") as f:
f.write( f.write(
@@ -661,3 +757,55 @@ metrics:
) )
self.assertEqual(pgmon.config["dbuser"], "postgres") self.assertEqual(pgmon.config["dbuser"], "postgres")
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1") self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
def test_version_num_to_release__valid(self):
self.assertEqual(pgmon.version_num_to_release(90602), 9.6)
self.assertEqual(pgmon.version_num_to_release(130002), 13)
def test_parse_version_rss__simple(self):
pgmon.parse_version_rss(versions_rss, 13)
self.assertEqual(pgmon.latest_version, 130021)
self.assertTrue(pgmon.release_supported)
pgmon.parse_version_rss(versions_rss, 9.6)
self.assertEqual(pgmon.latest_version, 90624)
self.assertFalse(pgmon.release_supported)
def test_parse_version_rss__missing(self):
# Test asking about versions that don't exist
self.assertRaises(
pgmon.LatestVersionCheckError, pgmon.parse_version_rss, versions_rss, 9.7
)
self.assertRaises(
pgmon.LatestVersionCheckError, pgmon.parse_version_rss, versions_rss, 99
)
def test_get_latest_version(self):
# Define a cluster version here so the test doesn't need a database
pgmon.cluster_version_next_check = datetime.now() + timedelta(hours=1)
pgmon.cluster_version = 90623
# Set up a default config
pgmon.update_deep(pgmon.config, pgmon.default_config)
# Make sure we can pull the RSS file (we assume the 9.6 series won't be getting
# any more updates)
self.assertEqual(pgmon.get_latest_version(), 90624)
def test_json_encode_special(self):
# Confirm that we're getting the right type
self.assertFalse(isinstance(Decimal('0.5'), float))
self.assertTrue(isinstance(pgmon.json_encode_special(Decimal('0.5')), float))
# Make sure we get sane values
self.assertEqual(pgmon.json_encode_special(Decimal('0.5')), 0.5)
self.assertEqual(pgmon.json_encode_special(Decimal('12')), 12.0)
# Make sure we can still fail for other types
self.assertRaises(
TypeError, pgmon.json_encode_special, object
)
# Make sure we can actually serialize a Decimal
self.assertEqual(json.dumps(Decimal('2.5'), default=pgmon.json_encode_special), '2.5')

View File

@@ -7,7 +7,7 @@ After=network.target
[Service] [Service]
EnvironmentFile=/etc/pgmon/%i-service.conf EnvironmentFile=/etc/pgmon/%i-service.conf
User=${SERVICE_USER:-postgres} User=${SERVICE_USER:-postgres}
ExecStart=/usr/local/bin/pgmon -c /etc/pgmon/%i.yml ExecStart=/usr/bin/pgmon -c /etc/pgmon/%i.yml
ExecReload=kill -HUP $MAINPID ExecReload=kill -HUP $MAINPID
Restart=on-failure Restart=on-failure
Type=exec Type=exec

View File

@@ -2,6 +2,7 @@ FROM alpine:3.21
RUN apk update && \ RUN apk update && \
apk add py3-psycopg2 \ apk add py3-psycopg2 \
py3-requests \
py3-yaml \ py3-yaml \
tini tini

View File

@@ -23,7 +23,7 @@ services:
test: [ "CMD-SHELL", "pg_controldata /var/lib/postgresql/data/ | grep -q 'in production'" ] test: [ "CMD-SHELL", "pg_controldata /var/lib/postgresql/data/ | grep -q 'in production'" ]
interval: 5s interval: 5s
timeout: 2s timeout: 2s
retries: 20 retries: 40
command: > command: >
postgres -c ssl=on postgres -c ssl=on
-c ssl_cert_file='/etc/ssl/certs/ssl-cert-snakeoil.pem' -c ssl_cert_file='/etc/ssl/certs/ssl-cert-snakeoil.pem'

View File

@@ -6,12 +6,15 @@ versions=( $@ )
# If we weren't given any versions, test them all # If we weren't given any versions, test them all
if [ ${#versions[@]} -eq 0 ] if [ ${#versions[@]} -eq 0 ]
then then
versions=( 9.2 9.6 10 11 12 13 14 15 16 17 ) versions=( 9.2 9.4 9.6 10 11 12 13 14 15 16 17 )
fi fi
# Image tags to use # Image tags to use
declare -A images=() declare -A images=()
images["9.2"]='9.2' images["9.2"]='9.2'
images["9.3"]='9.3'
images["9.4"]='9.4'
images["9.5"]='9.5'
images["9.6"]='9.6-bullseye' images["9.6"]='9.6-bullseye'
images["10"]='10-bullseye' images["10"]='10-bullseye'
images["11"]='11-bookworm' images["11"]='11-bookworm'

File diff suppressed because it is too large Load Diff