Compare commits
20 Commits
v1.0.0
...
f716569aa7
| Author | SHA1 | Date | |
|---|---|---|---|
|
f716569aa7
|
|||
|
22ae634a87
|
|||
|
375bf6a982
|
|||
|
487386a7cc
|
|||
|
15097dcba4
|
|||
|
1d642d41b2
|
|||
|
80304f40d1
|
|||
|
c0e1531083
|
|||
|
529bef9679
|
|||
|
8928bba337
|
|||
|
c872fc6b90
|
|||
|
030afafc20
|
|||
|
2dfc336288
|
|||
|
27e1c517bc
|
|||
|
e6166d1fe3
|
|||
|
bffabd9c8f
|
|||
|
98ac25743b
|
|||
|
7fc23961b0
|
|||
|
8ace133c23
|
|||
|
2afeb827ed
|
@@ -1,52 +0,0 @@
|
|||||||
# Copyright 2024 Gentoo Authors
|
|
||||||
# Distributed under the terms of the GNU General Public License v2
|
|
||||||
|
|
||||||
EAPI=8
|
|
||||||
|
|
||||||
PYTHON_COMPAT=( python3_{6..12} )
|
|
||||||
|
|
||||||
inherit git-r3 python-r1
|
|
||||||
|
|
||||||
DESCRIPTION="PostgreSQL monitoring bridge"
|
|
||||||
HOMEPAGE="None"
|
|
||||||
|
|
||||||
LICENSE="BSD"
|
|
||||||
SLOT="0"
|
|
||||||
KEYWORDS="amd64"
|
|
||||||
|
|
||||||
EGIT_REPO_URI="https://code2.shh-dot-com.org/james/pgmon.git"
|
|
||||||
#EGIT_COMMIT=""
|
|
||||||
|
|
||||||
DEPEND="
|
|
||||||
${PYTHON_DEPS}
|
|
||||||
dev-python/psycopg:3
|
|
||||||
dev-python/pyyaml
|
|
||||||
acct-user/zabbix
|
|
||||||
acct-group/zabbix
|
|
||||||
agent? ( net-analyzer/zabbix[agent] )
|
|
||||||
agent2? ( net-analyzer/zabbix[agent2] )
|
|
||||||
app-admin/logrotate
|
|
||||||
"
|
|
||||||
RDEPEND="${DEPEND}"
|
|
||||||
BDEPEND=""
|
|
||||||
|
|
||||||
src_install() {
|
|
||||||
default
|
|
||||||
|
|
||||||
# Install init script
|
|
||||||
newinitd "${FILESDIR}/pgmon.openrc" pgmon
|
|
||||||
|
|
||||||
# Install script
|
|
||||||
exeinto /usr/bin
|
|
||||||
newexe "${S}/pgmon.py" pgmon
|
|
||||||
|
|
||||||
# Install default config
|
|
||||||
diropts -o root -g zabbix -m 0755
|
|
||||||
insinto /etc/pgmon
|
|
||||||
doins "${FILESDIR}/pgmon.yml"
|
|
||||||
doins "${S}/pgmon-metrics.yml"
|
|
||||||
|
|
||||||
# Install logrotate config
|
|
||||||
insinto /etc/logrotate.d
|
|
||||||
newins "${FILESDIR}/pgmon.logrotate" pgmon
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ EAPI=8
|
|||||||
|
|
||||||
PYTHON_COMPAT=( python3_{6..13} )
|
PYTHON_COMPAT=( python3_{6..13} )
|
||||||
|
|
||||||
inherit git-r3 python-r1
|
inherit python-r1
|
||||||
|
|
||||||
DESCRIPTION="PostgreSQL monitoring bridge"
|
DESCRIPTION="PostgreSQL monitoring bridge"
|
||||||
HOMEPAGE="None"
|
HOMEPAGE="None"
|
||||||
@@ -14,7 +14,9 @@ LICENSE="BSD"
|
|||||||
SLOT="0"
|
SLOT="0"
|
||||||
KEYWORDS="amd64"
|
KEYWORDS="amd64"
|
||||||
|
|
||||||
SRC_URI="https://code2.shh-dot-com.org/james/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz"
|
SRC_URI="https://code2.shh-dot-com.org/james/${PN}/archive/v${PV}.tar.bz2 -> ${P}.tar.bz2"
|
||||||
|
|
||||||
|
IUSE="-systemd"
|
||||||
|
|
||||||
DEPEND="
|
DEPEND="
|
||||||
${PYTHON_DEPS}
|
${PYTHON_DEPS}
|
||||||
@@ -25,21 +27,36 @@ DEPEND="
|
|||||||
RDEPEND="${DEPEND}"
|
RDEPEND="${DEPEND}"
|
||||||
BDEPEND=""
|
BDEPEND=""
|
||||||
|
|
||||||
S="${WORKDIR}/${PN}"
|
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() {
|
src_install() {
|
||||||
default
|
|
||||||
|
|
||||||
# Install init script
|
# Install init script
|
||||||
|
if ! use systemd ; then
|
||||||
newinitd "openrc/pgmon.initd" pgmon
|
newinitd "openrc/pgmon.initd" pgmon
|
||||||
newconfd "openrc/pgmon.confd" pgmon
|
newconfd "openrc/pgmon.confd" pgmon
|
||||||
|
fi
|
||||||
|
|
||||||
# Install systemd unit
|
# Install systemd unit
|
||||||
|
if use systemd ; then
|
||||||
systemd_dounit "systemd/pgmon.service"
|
systemd_dounit "systemd/pgmon.service"
|
||||||
|
fi
|
||||||
|
|
||||||
# Install script
|
# Install script
|
||||||
exeinto /usr/bin
|
exeinto /usr/bin
|
||||||
newexe "pgmon.py" pgmon
|
newexe "src/pgmon.py" pgmon
|
||||||
|
|
||||||
# Install default config
|
# Install default config
|
||||||
diropts -o root -g root -m 0755
|
diropts -o root -g root -m 0755
|
||||||
73
GENTOO/pgmon-1.0.2.ebuild
Normal file
73
GENTOO/pgmon-1.0.2.ebuild
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
DESCRIPTION="PostgreSQL monitoring bridge"
|
||||||
|
HOMEPAGE="None"
|
||||||
|
|
||||||
|
LICENSE="BSD"
|
||||||
|
SLOT="0"
|
||||||
|
KEYWORDS="amd64"
|
||||||
|
|
||||||
|
SRC_URI="https://code2.shh-dot-com.org/james/${PN}/archive/v${PV}.tar.bz2 -> ${P}.tar.bz2"
|
||||||
|
|
||||||
|
IUSE="-systemd"
|
||||||
|
|
||||||
|
DEPEND="
|
||||||
|
${PYTHON_DEPS}
|
||||||
|
dev-python/psycopg:2
|
||||||
|
dev-python/pyyaml
|
||||||
|
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
|
||||||
|
}
|
||||||
30
Makefile
30
Makefile
@@ -1,9 +1,10 @@
|
|||||||
# Package details
|
# Package details
|
||||||
PACKAGE_NAME := pgmon
|
PACKAGE_NAME := pgmon
|
||||||
VERSION := 1.0
|
|
||||||
|
|
||||||
SCRIPT := src/$(PACKAGE_NAME).py
|
SCRIPT := src/$(PACKAGE_NAME).py
|
||||||
|
|
||||||
|
VERSION := $(shell grep -m 1 '^VERSION = ' "$(SCRIPT)" | sed -ne 's/.*"\(.*\)".*/\1/p')
|
||||||
|
|
||||||
# Where packages are built
|
# Where packages are built
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build
|
||||||
|
|
||||||
@@ -15,19 +16,26 @@ SUPPORTED := ubuntu-20.04 \
|
|||||||
debian-11 \
|
debian-11 \
|
||||||
rockylinux-8 \
|
rockylinux-8 \
|
||||||
rockylinux-9 \
|
rockylinux-9 \
|
||||||
oraclelinux-7
|
oraclelinux-7 \
|
||||||
|
gentoo
|
||||||
|
|
||||||
##
|
##
|
||||||
# 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 install
|
.PHONY: all clean tgz test query-tests install
|
||||||
|
|
||||||
|
|
||||||
# Build all packages
|
# Build all packages
|
||||||
.PHONY: package-all
|
.PHONY: package-all
|
||||||
all: $(foreach distro_release, $(SUPPORTED), package-$(distro_release))
|
all: $(foreach distro_release, $(SUPPORTED), package-$(distro_release))
|
||||||
|
|
||||||
|
# Gentoo package (tar.gz) creation
|
||||||
|
.PHONY: package-gentoo
|
||||||
|
package-gentoo:
|
||||||
|
mkdir -p $(BUILD_DIR)/gentoo
|
||||||
|
tar --transform "s,^\.,$(PACKAGE_NAME)-$(VERSION)," -acjf $(BUILD_DIR)/gentoo/$(PACKAGE_NAME)-$(VERSION).tar.bz2 --exclude $(BUILD_DIR) .
|
||||||
|
|
||||||
|
|
||||||
# Create a deb package
|
# Create a deb package
|
||||||
.PHONY: package-%
|
.PHONY: package-%
|
||||||
@@ -42,7 +50,6 @@ package-%:
|
|||||||
--user $(shell id -u):$(shell id -g) \
|
--user $(shell id -u):$(shell id -g) \
|
||||||
"$(DISTRO)-packager:$(RELEASE)"
|
"$(DISTRO)-packager:$(RELEASE)"
|
||||||
|
|
||||||
|
|
||||||
# Create a tarball
|
# Create a tarball
|
||||||
tgz:
|
tgz:
|
||||||
rm -rf $(BUILD_DIR)/tgz/root
|
rm -rf $(BUILD_DIR)/tgz/root
|
||||||
@@ -58,6 +65,10 @@ clean:
|
|||||||
test:
|
test:
|
||||||
cd src ; python3 -m unittest
|
cd src ; python3 -m unittest
|
||||||
|
|
||||||
|
# Run query tests
|
||||||
|
query-tests:
|
||||||
|
cd tests ; ./run-tests.sh
|
||||||
|
|
||||||
# Install the script at the specified base directory
|
# Install the script at the specified base directory
|
||||||
install:
|
install:
|
||||||
# Set up directories
|
# Set up directories
|
||||||
@@ -86,7 +97,7 @@ install:
|
|||||||
|
|
||||||
|
|
||||||
# Run all of the install tests
|
# Run all of the install tests
|
||||||
.PHONY: install-tests debian-%-install-test rockylinux-%-install-test ubuntu-%-install-test
|
.PHONY: install-tests debian-%-install-test rockylinux-%-install-test ubuntu-%-install-test gentoo-install-test
|
||||||
install-tests: $(foreach distro_release, $(SUPPORTED), $(distro_release)-install-test)
|
install-tests: $(foreach distro_release, $(SUPPORTED), $(distro_release)-install-test)
|
||||||
|
|
||||||
|
|
||||||
@@ -118,6 +129,11 @@ oraclelinux-%-install-test:
|
|||||||
oraclelinux:7 \
|
oraclelinux:7 \
|
||||||
bash -c 'yum makecache && yum install -y /output/$(PACKAGE_NAME)-$(VERSION)-1.el7.noarch.rpm'
|
bash -c 'yum makecache && yum install -y /output/$(PACKAGE_NAME)-$(VERSION)-1.el7.noarch.rpm'
|
||||||
|
|
||||||
|
# Run a Gentoo install test
|
||||||
|
gentoo-install-test:
|
||||||
|
# May impliment this in the future, but would require additional headaches to set up a repo
|
||||||
|
true
|
||||||
|
|
||||||
##
|
##
|
||||||
# Container targets
|
# Container targets
|
||||||
#
|
#
|
||||||
@@ -158,7 +174,7 @@ actually-package-debian-%:
|
|||||||
# RedHat package creation
|
# RedHat package creation
|
||||||
actually-package-rockylinux-%:
|
actually-package-rockylinux-%:
|
||||||
mkdir -p /output/rockylinux-$*/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
mkdir -p /output/rockylinux-$*/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
||||||
cp RPM/$(PACKAGE_NAME).spec /output/rockylinux-$*/SPECS/
|
sed -e "s/@@VERSION@@/$(VERSION)/g" RPM/$(PACKAGE_NAME).spec > /output/rockylinux-$*/SPECS/$(PACKAGE_NAME).spec
|
||||||
rpmbuild --define '_topdir /output/rockylinux-$*' \
|
rpmbuild --define '_topdir /output/rockylinux-$*' \
|
||||||
--define 'version $(VERSION)' \
|
--define 'version $(VERSION)' \
|
||||||
-bb /output/rockylinux-$*/SPECS/$(PACKAGE_NAME).spec
|
-bb /output/rockylinux-$*/SPECS/$(PACKAGE_NAME).spec
|
||||||
@@ -173,7 +189,7 @@ actually-package-ubuntu-%:
|
|||||||
# OracleLinux package creation
|
# OracleLinux package creation
|
||||||
actually-package-oraclelinux-%:
|
actually-package-oraclelinux-%:
|
||||||
mkdir -p /output/oraclelinux-$*/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
mkdir -p /output/oraclelinux-$*/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
||||||
cp RPM/$(PACKAGE_NAME)-el7.spec /output/oraclelinux-$*/SPECS/$(PACKAGE_NAME).spec
|
sed -e "s/@@VERSION@@/$(VERSION)/g" RPM/$(PACKAGE_NAME)-el7.spec > /output/oraclelinux-$*/SPECS/$(PACKAGE_NAME).spec
|
||||||
rpmbuild --define '_topdir /output/oraclelinux-$*' \
|
rpmbuild --define '_topdir /output/oraclelinux-$*' \
|
||||||
--define 'version $(VERSION)' \
|
--define 'version $(VERSION)' \
|
||||||
-bb /output/oraclelinux-$*/SPECS/$(PACKAGE_NAME).spec
|
-bb /output/oraclelinux-$*/SPECS/$(PACKAGE_NAME).spec
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Name: pgmon
|
Name: pgmon
|
||||||
Version: 1.0
|
Version: @@VERSION@@
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: A bridge to sit between monitoring tools and PostgreSQL
|
Summary: A bridge to sit between monitoring tools and PostgreSQL
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Name: pgmon
|
Name: pgmon
|
||||||
Version: 1.0
|
Version: @@VERSION@@
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: A bridge to sit between monitoring tools and PostgreSQL
|
Summary: A bridge to sit between monitoring tools and PostgreSQL
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,14 @@ PGMON_USER="${PGMON_USER:-postgres}"
|
|||||||
PGMON_GROUP="${PGMON_GROUP:-$PGMON_USER}"
|
PGMON_GROUP="${PGMON_GROUP:-$PGMON_USER}"
|
||||||
CONFIG_FILE="/etc/pgmon/${agent_name}.yml"
|
CONFIG_FILE="/etc/pgmon/${agent_name}.yml"
|
||||||
|
|
||||||
|
output_log=/var/log/pgmon/${SVCNAME}.log
|
||||||
|
error_log=/var/log/pgmon/${SVCNAME}.err
|
||||||
|
|
||||||
|
start_pre() {
|
||||||
|
checkpath -f -m 0644 -o "${PGMON_USER}:${PGMON_GROUP}" "${output_log}" "${error_log}"
|
||||||
|
}
|
||||||
|
|
||||||
command="/usr/bin/pgmon"
|
command="/usr/bin/pgmon"
|
||||||
command_args="'$CONFIG_FILE'"
|
command_args="-c '$CONFIG_FILE'"
|
||||||
command_background="true"
|
command_background="true"
|
||||||
command_user="${PGMON_USER}:${PGMON_GROUP}"
|
command_user="${PGMON_USER}:${PGMON_GROUP}"
|
||||||
|
|||||||
4
requirements-dev.yml
Normal file
4
requirements-dev.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
-r requirements.txt
|
||||||
|
testcontainers[postgresql]
|
||||||
|
pytest
|
||||||
|
black
|
||||||
@@ -8,6 +8,11 @@ metrics:
|
|||||||
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 client_addr || '_' || regexp_replace(application_name, '[ ,]', '_', 'g') AS repid, client_addr, state FROM pg_stat_replication
|
||||||
|
discover_slots:
|
||||||
|
type: set
|
||||||
|
query:
|
||||||
|
90400: 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:
|
||||||
@@ -15,9 +20,9 @@ metrics:
|
|||||||
query:
|
query:
|
||||||
0: SHOW server_version_num
|
0: SHOW server_version_num
|
||||||
max_frozen_age:
|
max_frozen_age:
|
||||||
type: value
|
type: row
|
||||||
query:
|
query:
|
||||||
0: SELECT max(age(datfrozenxid)) FROM pg_database
|
0: SELECT max(age(datfrozenxid)), max(mxid_age(datminmxid)) FROM pg_database
|
||||||
|
|
||||||
# Per-database metrics
|
# Per-database metrics
|
||||||
db_stats:
|
db_stats:
|
||||||
@@ -25,6 +30,8 @@ metrics:
|
|||||||
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: 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
|
||||||
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
|
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
|
||||||
|
test_args:
|
||||||
|
dbname: postgres
|
||||||
|
|
||||||
# Debugging
|
# Debugging
|
||||||
ntables:
|
ntables:
|
||||||
@@ -36,10 +43,21 @@ metrics:
|
|||||||
rep_stats:
|
rep_stats:
|
||||||
type: row
|
type: row
|
||||||
query:
|
query:
|
||||||
0: SELECT * FROM pg_stat_database WHERE client_addr || '_' || regexp_replace(application_name, '[ ,]', '_', 'g') = '{repid}'
|
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
|
# Debugging
|
||||||
sleep:
|
sleep:
|
||||||
type: value
|
type: value
|
||||||
query:
|
query:
|
||||||
0: SELECT now(), pg_sleep(5);
|
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
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
# The address the agent binds to
|
||||||
|
#address: 127.0.0.1
|
||||||
|
|
||||||
|
# The port the agent listens on for requests
|
||||||
|
#port: 5400
|
||||||
|
|
||||||
# Min PostgreSQL connection pool size (per database)
|
# Min PostgreSQL connection pool size (per database)
|
||||||
#min_pool_size: 0
|
#min_pool_size: 0
|
||||||
|
|
||||||
@@ -23,6 +29,9 @@
|
|||||||
# Default database to connect to when none is specified for a metric
|
# Default database to connect to when none is specified for a metric
|
||||||
#dbname: 'postgres'
|
#dbname: 'postgres'
|
||||||
|
|
||||||
|
# SSL connection mode
|
||||||
|
#ssl_mode: require
|
||||||
|
|
||||||
# Timeout for getting a connection slot from a pool
|
# Timeout for getting a connection slot from a pool
|
||||||
#pool_slot_timeout: 5
|
#pool_slot_timeout: 5
|
||||||
|
|
||||||
|
|||||||
524
src/pgmon.py
524
src/pgmon.py
@@ -4,6 +4,7 @@ import yaml
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
@@ -11,7 +12,7 @@ import logging
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
from psycopg2.extras import DictCursor
|
from psycopg2.extras import RealDictCursor
|
||||||
from psycopg2.pool import ThreadedConnectionPool
|
from psycopg2.pool import ThreadedConnectionPool
|
||||||
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
@@ -23,7 +24,10 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
|
|||||||
from http.server import ThreadingHTTPServer
|
from http.server import ThreadingHTTPServer
|
||||||
from urllib.parse import urlparse, parse_qs
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
|
||||||
VERSION = '0.1.0'
|
import requests
|
||||||
|
import re
|
||||||
|
|
||||||
|
VERSION = "1.0.2"
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
config = {}
|
config = {}
|
||||||
@@ -42,6 +46,12 @@ cluster_version = None
|
|||||||
cluster_version_next_check = None
|
cluster_version_next_check = None
|
||||||
cluster_version_lock = Lock()
|
cluster_version_lock = Lock()
|
||||||
|
|
||||||
|
# PostgreSQL latest version information
|
||||||
|
latest_version = None
|
||||||
|
latest_version_next_check = None
|
||||||
|
latest_version_lock = Lock()
|
||||||
|
release_supported = None
|
||||||
|
|
||||||
# Running state (used to gracefully shut down)
|
# Running state (used to gracefully shut down)
|
||||||
running = True
|
running = True
|
||||||
|
|
||||||
@@ -53,64 +63,79 @@ config_file = None
|
|||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s: %(funcName)s() line %(lineno)d: %(message)s')
|
formatter = logging.Formatter(
|
||||||
|
"%(asctime)s - %(levelname)s - %(filename)s: %(funcName)s() line %(lineno)d: %(message)s"
|
||||||
|
)
|
||||||
console_log_handler = logging.StreamHandler()
|
console_log_handler = logging.StreamHandler()
|
||||||
console_log_handler.setFormatter(formatter)
|
console_log_handler.setFormatter(formatter)
|
||||||
log.addHandler(console_log_handler)
|
log.addHandler(console_log_handler)
|
||||||
|
|
||||||
|
|
||||||
# Error types
|
# Error types
|
||||||
class ConfigError(Exception):
|
class ConfigError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DisconnectedError(Exception):
|
class DisconnectedError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UnhappyDBError(Exception):
|
class UnhappyDBError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownMetricError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MetricVersionError(Exception):
|
class MetricVersionError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LatestVersionCheckError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Default config settings
|
# Default config settings
|
||||||
default_config = {
|
default_config = {
|
||||||
|
# The address the agent binds to
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
# The port the agent listens on for requests
|
||||||
|
"port": 5400,
|
||||||
# Min PostgreSQL connection pool size (per database)
|
# Min PostgreSQL connection pool size (per database)
|
||||||
'min_pool_size': 0,
|
"min_pool_size": 0,
|
||||||
|
|
||||||
# Max PostgreSQL connection pool size (per database)
|
# Max PostgreSQL connection pool size (per database)
|
||||||
'max_pool_size': 4,
|
"max_pool_size": 4,
|
||||||
|
|
||||||
# How long a connection can sit idle in the pool before it's removed (seconds)
|
# How long a connection can sit idle in the pool before it's removed (seconds)
|
||||||
'max_idle_time': 30,
|
"max_idle_time": 30,
|
||||||
|
|
||||||
# Log level for stderr logging
|
# Log level for stderr logging
|
||||||
'log_level': 'error',
|
"log_level": "error",
|
||||||
|
|
||||||
# Database user to connect as
|
# Database user to connect as
|
||||||
'dbuser': 'postgres',
|
"dbuser": "postgres",
|
||||||
|
|
||||||
# Database host
|
# Database host
|
||||||
'dbhost': '/var/run/postgresql',
|
"dbhost": "/var/run/postgresql",
|
||||||
|
|
||||||
# Database port
|
# Database port
|
||||||
'dbport': 5432,
|
"dbport": 5432,
|
||||||
|
|
||||||
# Default database to connect to when none is specified for a metric
|
# Default database to connect to when none is specified for a metric
|
||||||
'dbname': 'postgres',
|
"dbname": "postgres",
|
||||||
|
# SSL connection mode
|
||||||
|
"ssl_mode": "require",
|
||||||
# Timeout for getting a connection slot from a pool
|
# Timeout for getting a connection slot from a pool
|
||||||
'pool_slot_timeout': 5,
|
"pool_slot_timeout": 5,
|
||||||
|
|
||||||
# PostgreSQL connection timeout (seconds)
|
# PostgreSQL connection timeout (seconds)
|
||||||
# Note: It can actually be double this because of retries
|
# Note: It can actually be double this because of retries
|
||||||
'connect_timeout': 5,
|
"connect_timeout": 5,
|
||||||
|
|
||||||
# Time to wait before trying to reconnect again after a reconnect failure (seconds)
|
# Time to wait before trying to reconnect again after a reconnect failure (seconds)
|
||||||
'reconnect_cooldown': 30,
|
"reconnect_cooldown": 30,
|
||||||
|
|
||||||
# How often to check the version of PostgreSQL (seconds)
|
# How often to check the version of PostgreSQL (seconds)
|
||||||
'version_check_period': 300,
|
"version_check_period": 300,
|
||||||
|
# How often to check the latest supported version of PostgreSQL (seconds)
|
||||||
|
"latest_version_check_period": 86400,
|
||||||
# Metrics
|
# Metrics
|
||||||
'metrics': {}
|
"metrics": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def update_deep(d1, d2):
|
def update_deep(d1, d2):
|
||||||
"""
|
"""
|
||||||
Recursively update a dict, adding keys to dictionaries and appending to
|
Recursively update a dict, adding keys to dictionaries and appending to
|
||||||
@@ -124,23 +149,32 @@ def update_deep(d1, d2):
|
|||||||
The new d1
|
The new d1
|
||||||
"""
|
"""
|
||||||
if not isinstance(d1, dict) or not isinstance(d2, dict):
|
if not isinstance(d1, dict) or not isinstance(d2, dict):
|
||||||
raise TypeError('Both arguments to update_deep need to be dictionaries')
|
raise TypeError("Both arguments to update_deep need to be dictionaries")
|
||||||
|
|
||||||
for k, v2 in d2.items():
|
for k, v2 in d2.items():
|
||||||
if isinstance(v2, dict):
|
if isinstance(v2, dict):
|
||||||
v1 = d1.get(k, {})
|
v1 = d1.get(k, {})
|
||||||
if not isinstance(v1, dict):
|
if not isinstance(v1, dict):
|
||||||
raise TypeError('Type mismatch between dictionaries: {} is not a dict'.format(type(v1).__name__))
|
raise TypeError(
|
||||||
|
"Type mismatch between dictionaries: {} is not a dict".format(
|
||||||
|
type(v1).__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
d1[k] = update_deep(v1, v2)
|
d1[k] = update_deep(v1, v2)
|
||||||
elif isinstance(v2, list):
|
elif isinstance(v2, list):
|
||||||
v1 = d1.get(k, [])
|
v1 = d1.get(k, [])
|
||||||
if not isinstance(v1, list):
|
if not isinstance(v1, list):
|
||||||
raise TypeError('Type mismatch between dictionaries: {} is not a list'.format(type(v1).__name__))
|
raise TypeError(
|
||||||
|
"Type mismatch between dictionaries: {} is not a list".format(
|
||||||
|
type(v1).__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
d1[k] = v1 + v2
|
d1[k] = v1 + v2
|
||||||
else:
|
else:
|
||||||
d1[k] = v2
|
d1[k] = v2
|
||||||
return d1
|
return d1
|
||||||
|
|
||||||
|
|
||||||
def read_config(path, included=False):
|
def read_config(path, included=False):
|
||||||
"""
|
"""
|
||||||
Read a config file.
|
Read a config file.
|
||||||
@@ -151,7 +185,7 @@ def read_config(path, included = False):
|
|||||||
"""
|
"""
|
||||||
# Read config file
|
# Read config file
|
||||||
log.info("Reading log file: {}".format(path))
|
log.info("Reading log file: {}".format(path))
|
||||||
with open(path, 'r') as f:
|
with open(path, "r") as f:
|
||||||
try:
|
try:
|
||||||
cfg = yaml.safe_load(f)
|
cfg = yaml.safe_load(f)
|
||||||
except yaml.parser.ParserError as e:
|
except yaml.parser.ParserError as e:
|
||||||
@@ -161,41 +195,52 @@ def read_config(path, included = False):
|
|||||||
config_base = os.path.dirname(path)
|
config_base = os.path.dirname(path)
|
||||||
|
|
||||||
# Read any external queries and validate metric definitions
|
# Read any external queries and validate metric definitions
|
||||||
for name, metric in cfg.get('metrics', {}).items():
|
for name, metric in cfg.get("metrics", {}).items():
|
||||||
# Validate return types
|
# Validate return types
|
||||||
try:
|
try:
|
||||||
if metric['type'] not in ['value', 'row', 'column', 'set']:
|
if metric["type"] not in ["value", "row", "column", "set"]:
|
||||||
raise ConfigError("Invalid return type: {} for metric {} in {}".format(metric['type'], name, path))
|
raise ConfigError(
|
||||||
|
"Invalid return type: {} for metric {} in {}".format(
|
||||||
|
metric["type"], name, path
|
||||||
|
)
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ConfigError("No type specified for metric {} in {}".format(name, path))
|
raise ConfigError(
|
||||||
|
"No type specified for metric {} in {}".format(name, path)
|
||||||
|
)
|
||||||
|
|
||||||
# Ensure queries exist
|
# Ensure queries exist
|
||||||
query_dict = metric.get('query', {})
|
query_dict = metric.get("query", {})
|
||||||
if type(query_dict) is not dict:
|
if type(query_dict) is not dict:
|
||||||
raise ConfigError("Query definition should be a dictionary, got: {} for metric {} in {}".format(query_dict, name, path))
|
raise ConfigError(
|
||||||
|
"Query definition should be a dictionary, got: {} for metric {} in {}".format(
|
||||||
|
query_dict, name, path
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if len(query_dict) == 0:
|
if len(query_dict) == 0:
|
||||||
raise ConfigError("Missing queries for metric {} in {}".format(name, path))
|
raise ConfigError("Missing queries for metric {} in {}".format(name, path))
|
||||||
|
|
||||||
# Read external sql files and validate version keys
|
# Read external sql files and validate version keys
|
||||||
for vers, query in metric['query'].items():
|
for vers, query in metric["query"].items():
|
||||||
try:
|
try:
|
||||||
int(vers)
|
int(vers)
|
||||||
except:
|
except:
|
||||||
raise ConfigError("Invalid version: {} for metric {} in {}".format(vers, name, path))
|
raise ConfigError(
|
||||||
|
"Invalid version: {} for metric {} in {}".format(vers, name, path)
|
||||||
|
)
|
||||||
|
|
||||||
if query.startswith('file:'):
|
if query.startswith("file:"):
|
||||||
query_path = query[5:]
|
query_path = query[5:]
|
||||||
if not query_path.startswith('/'):
|
if not query_path.startswith("/"):
|
||||||
query_path = os.path.join(config_base, query_path)
|
query_path = os.path.join(config_base, query_path)
|
||||||
with open(query_path, 'r') as f:
|
with open(query_path, "r") as f:
|
||||||
metric['query'][vers] = f.read()
|
metric["query"][vers] = f.read()
|
||||||
|
|
||||||
|
|
||||||
# Read any included config files
|
# Read any included config files
|
||||||
for inc in cfg.get('include', []):
|
for inc in cfg.get("include", []):
|
||||||
# Prefix relative paths with the directory from the current config
|
# Prefix relative paths with the directory from the current config
|
||||||
if not inc.startswith('/'):
|
if not inc.startswith("/"):
|
||||||
inc = os.path.join(config_base, inc)
|
inc = os.path.join(config_base, inc)
|
||||||
update_deep(cfg, read_config(inc, included=True))
|
update_deep(cfg, read_config(inc, included=True))
|
||||||
|
|
||||||
@@ -209,19 +254,26 @@ def read_config(path, included = False):
|
|||||||
update_deep(new_config, cfg)
|
update_deep(new_config, cfg)
|
||||||
|
|
||||||
# Minor sanity checks
|
# Minor sanity checks
|
||||||
if len(new_config['metrics']) == 0:
|
if len(new_config["metrics"]) == 0:
|
||||||
log.error("No metrics are defined")
|
log.error("No metrics are defined")
|
||||||
raise ConfigError("No metrics defined")
|
raise ConfigError("No metrics defined")
|
||||||
|
|
||||||
# Validate the new log level before changing the config
|
# Validate the new log level before changing the config
|
||||||
if new_config['log_level'].upper() not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
|
if new_config["log_level"].upper() not in [
|
||||||
raise ConfigError("Invalid log level: {}".format(new_config['log_level']))
|
"DEBUG",
|
||||||
|
"INFO",
|
||||||
|
"WARNING",
|
||||||
|
"ERROR",
|
||||||
|
"CRITICAL",
|
||||||
|
]:
|
||||||
|
raise ConfigError("Invalid log level: {}".format(new_config["log_level"]))
|
||||||
|
|
||||||
global config
|
global config
|
||||||
config = new_config
|
config = new_config
|
||||||
|
|
||||||
# Apply changes to log level
|
# Apply changes to log level
|
||||||
log.setLevel(logging.getLevelName(config['log_level'].upper()))
|
log.setLevel(logging.getLevelName(config["log_level"].upper()))
|
||||||
|
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
"""
|
"""
|
||||||
@@ -245,10 +297,11 @@ def signal_handler(sig, frame):
|
|||||||
log.warning("Received config reload signal")
|
log.warning("Received config reload signal")
|
||||||
read_config(config_file)
|
read_config(config_file)
|
||||||
|
|
||||||
|
|
||||||
class ConnectionPool(ThreadedConnectionPool):
|
class ConnectionPool(ThreadedConnectionPool):
|
||||||
def __init__(self, dbname, minconn, maxconn, *args, **kwargs):
|
def __init__(self, dbname, minconn, maxconn, *args, **kwargs):
|
||||||
# Make sure dbname isn't different in the kwargs
|
# Make sure dbname isn't different in the kwargs
|
||||||
kwargs['dbname'] = dbname
|
kwargs["dbname"] = dbname
|
||||||
|
|
||||||
super().__init__(minconn, maxconn, *args, **kwargs)
|
super().__init__(minconn, maxconn, *args, **kwargs)
|
||||||
self.name = dbname
|
self.name = dbname
|
||||||
@@ -270,7 +323,10 @@ class ConnectionPool(ThreadedConnectionPool):
|
|||||||
except psycopg2.pool.PoolError:
|
except psycopg2.pool.PoolError:
|
||||||
# If we failed to get the connection slot, wait a bit and try again
|
# If we failed to get the connection slot, wait a bit and try again
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
raise TimeoutError("Timed out waiting for an available connection to {}".format(self.name))
|
raise TimeoutError(
|
||||||
|
"Timed out waiting for an available connection to {}".format(self.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_pool(dbname):
|
def get_pool(dbname):
|
||||||
"""
|
"""
|
||||||
@@ -288,26 +344,32 @@ def get_pool(dbname):
|
|||||||
# lock
|
# lock
|
||||||
if dbname not in connections:
|
if dbname not in connections:
|
||||||
log.info("Creating connection pool for: {}".format(dbname))
|
log.info("Creating connection pool for: {}".format(dbname))
|
||||||
|
# Actually create the connection pool
|
||||||
connections[dbname] = ConnectionPool(
|
connections[dbname] = ConnectionPool(
|
||||||
dbname,
|
dbname,
|
||||||
int(config['min_pool_size']),
|
int(config["min_pool_size"]),
|
||||||
int(config['max_pool_size']),
|
int(config["max_pool_size"]),
|
||||||
application_name='pgmon',
|
application_name="pgmon",
|
||||||
host=config['dbhost'],
|
host=config["dbhost"],
|
||||||
port=config['dbport'],
|
port=config["dbport"],
|
||||||
user=config['dbuser'],
|
user=config["dbuser"],
|
||||||
connect_timeout=float(config['connect_timeout']),
|
connect_timeout=int(config["connect_timeout"]),
|
||||||
sslmode='require')
|
sslmode=config["ssl_mode"],
|
||||||
|
)
|
||||||
# Clear the unhappy indicator if present
|
# Clear the unhappy indicator if present
|
||||||
unhappy_cooldown.pop(dbname, None)
|
unhappy_cooldown.pop(dbname, None)
|
||||||
return connections[dbname]
|
return connections[dbname]
|
||||||
|
|
||||||
|
|
||||||
def handle_connect_failure(pool):
|
def handle_connect_failure(pool):
|
||||||
"""
|
"""
|
||||||
Mark the database as being unhappy so we can leave it alone for a while
|
Mark the database as being unhappy so we can leave it alone for a while
|
||||||
"""
|
"""
|
||||||
dbname = pool.name
|
dbname = pool.name
|
||||||
unhappy_cooldown[dbname] = datetime.now() + timedelta(seconds=int(config['reconnect_cooldown']))
|
unhappy_cooldown[dbname] = datetime.now() + timedelta(
|
||||||
|
seconds=int(config["reconnect_cooldown"])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_query(metric, version):
|
def get_query(metric, version):
|
||||||
"""
|
"""
|
||||||
@@ -318,42 +380,51 @@ def get_query(metric, version):
|
|||||||
version: The PostgreSQL version number, as given by server_version_num
|
version: The PostgreSQL version number, as given by server_version_num
|
||||||
"""
|
"""
|
||||||
# Select the correct query
|
# Select the correct query
|
||||||
for v in reversed(sorted(metric['query'].keys())):
|
for v in reversed(sorted(metric["query"].keys())):
|
||||||
if version >= v:
|
if version >= v:
|
||||||
if len(metric['query'][v].strip()) == 0:
|
if len(metric["query"][v].strip()) == 0:
|
||||||
raise MetricVersionError("Metric no longer applies to PostgreSQL {}".format(version))
|
raise MetricVersionError(
|
||||||
return metric['query'][v]
|
"Metric no longer applies to PostgreSQL {}".format(version)
|
||||||
|
)
|
||||||
|
return metric["query"][v]
|
||||||
|
|
||||||
raise MetricVersionError('Missing metric query for PostgreSQL {}'.format(version))
|
raise MetricVersionError("Missing metric query for PostgreSQL {}".format(version))
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
"""
|
"""
|
||||||
with pool.connection(float(config['connect_timeout'])) as conn:
|
with pool.connection(float(config["connect_timeout"])) as conn:
|
||||||
try:
|
try:
|
||||||
with conn.cursor(cursor_factory=DictCursor) as curs:
|
with conn.cursor(cursor_factory=RealDictCursor) as curs:
|
||||||
curs.execute(query, args)
|
curs.execute(query, args)
|
||||||
res = curs.fetchall()
|
res = curs.fetchall()
|
||||||
|
|
||||||
if return_type == 'value':
|
if return_type == "value":
|
||||||
|
if len(res) == 0:
|
||||||
|
return ""
|
||||||
return str(list(res[0].values())[0])
|
return str(list(res[0].values())[0])
|
||||||
elif return_type == 'row':
|
elif return_type == "row":
|
||||||
|
if len(res) == 0:
|
||||||
|
return "[]"
|
||||||
return json.dumps(res[0])
|
return json.dumps(res[0])
|
||||||
elif return_type == 'column':
|
elif return_type == "column":
|
||||||
|
if len(res) == 0:
|
||||||
|
return "[]"
|
||||||
return json.dumps([list(r.values())[0] for r in res])
|
return json.dumps([list(r.values())[0] for r in res])
|
||||||
elif return_type == 'set':
|
elif return_type == "set":
|
||||||
return json.dumps(res)
|
return json.dumps(res)
|
||||||
except:
|
except:
|
||||||
dbname = pool.name
|
dbname = pool.name
|
||||||
if dbname in unhappy_cooldown:
|
if dbname in unhappy_cooldown:
|
||||||
raise UnhappyDBError()
|
raise UnhappyDBError()
|
||||||
elif conn.broken:
|
elif conn.closed != 0:
|
||||||
raise DisconnectedError()
|
raise DisconnectedError()
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def run_query(pool, return_type, query, args):
|
def run_query(pool, return_type, query, args):
|
||||||
"""
|
"""
|
||||||
Run the query, and if we find upon the first attempt that the connection
|
Run the query, and if we find upon the first attempt that the connection
|
||||||
@@ -384,6 +455,7 @@ def run_query(pool, return_type, query, args):
|
|||||||
handle_connect_failure(pool)
|
handle_connect_failure(pool)
|
||||||
raise UnhappyDBError()
|
raise UnhappyDBError()
|
||||||
|
|
||||||
|
|
||||||
def get_cluster_version():
|
def get_cluster_version():
|
||||||
"""
|
"""
|
||||||
Get the PostgreSQL version if we don't already know it, or if it's been
|
Get the PostgreSQL version if we don't already know it, or if it's been
|
||||||
@@ -395,26 +467,211 @@ def get_cluster_version():
|
|||||||
# If we don't know the version or it's past the recheck time, get the
|
# If we don't know the version or it's past the recheck time, get the
|
||||||
# version from the database. Only one thread needs to do this, so they all
|
# version from the database. Only one thread needs to do this, so they all
|
||||||
# try to grab the lock, and then make sure nobody else beat them to it.
|
# try to grab the lock, and then make sure nobody else beat them to it.
|
||||||
if cluster_version is None or cluster_version_next_check is None or cluster_version_next_check < datetime.now():
|
if (
|
||||||
|
cluster_version is None
|
||||||
|
or cluster_version_next_check is None
|
||||||
|
or cluster_version_next_check < datetime.now()
|
||||||
|
):
|
||||||
with cluster_version_lock:
|
with cluster_version_lock:
|
||||||
# Only check if nobody already got the version before us
|
# Only check if nobody already got the version before us
|
||||||
if cluster_version is None or cluster_version_next_check is None or cluster_version_next_check < datetime.now():
|
if (
|
||||||
log.info('Checking PostgreSQL cluster version')
|
cluster_version is None
|
||||||
pool = get_pool(config['dbname'])
|
or cluster_version_next_check is None
|
||||||
cluster_version = int(run_query(pool, 'value', 'SHOW server_version_num', None))
|
or cluster_version_next_check < datetime.now()
|
||||||
cluster_version_next_check = datetime.now() + timedelta(seconds=int(config['version_check_period']))
|
):
|
||||||
|
log.info("Checking PostgreSQL cluster version")
|
||||||
|
pool = get_pool(config["dbname"])
|
||||||
|
cluster_version = int(
|
||||||
|
run_query(pool, "value", "SHOW server_version_num", None)
|
||||||
|
)
|
||||||
|
cluster_version_next_check = datetime.now() + timedelta(
|
||||||
|
seconds=int(config["version_check_period"])
|
||||||
|
)
|
||||||
log.info("Got PostgreSQL cluster version: {}".format(cluster_version))
|
log.info("Got PostgreSQL cluster version: {}".format(cluster_version))
|
||||||
log.debug("Next PostgreSQL cluster version check will be after: {}".format(cluster_version_next_check))
|
log.debug(
|
||||||
|
"Next PostgreSQL cluster version check will be after: {}".format(
|
||||||
|
cluster_version_next_check
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
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
|
||||||
|
latest_version_next_check
|
||||||
|
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 latest_version_next_check
|
||||||
|
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():
|
||||||
|
"""
|
||||||
|
Get the latest supported version of the major PostgreSQL release running on the server being monitored.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# they all try to grab the lock, and then make sure nobody else beat them to it.
|
||||||
|
if (
|
||||||
|
latest_version is None
|
||||||
|
or latest_version_next_check is None
|
||||||
|
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:
|
||||||
|
# Only check if nobody already got the version before us
|
||||||
|
if (
|
||||||
|
latest_version is None
|
||||||
|
or latest_version_next_check is None
|
||||||
|
or latest_version_next_check < datetime.now()
|
||||||
|
):
|
||||||
|
log.info("Checking latest PostgreSQL version")
|
||||||
|
latest_version_next_check = datetime.now() + timedelta(
|
||||||
|
seconds=int(config["latest_version_check_period"])
|
||||||
|
)
|
||||||
|
|
||||||
|
# Grab the RSS feed
|
||||||
|
raw_rss = requests.get("https://www.postgresql.org/versions.rss")
|
||||||
|
if raw_rss.status_code != 200:
|
||||||
|
raise LatestVersionCheckError("code={}".format(r.status_code))
|
||||||
|
|
||||||
|
# Parse the RSS body and set global variables
|
||||||
|
parse_version_rss(raw_rss.text, release)
|
||||||
|
|
||||||
|
return latest_version
|
||||||
|
|
||||||
|
|
||||||
|
def sample_metric(dbname, metric_name, args, retry=True):
|
||||||
|
"""
|
||||||
|
Run the appropriate query for the named metric against the specified database
|
||||||
|
"""
|
||||||
|
# Get the metric definition
|
||||||
|
try:
|
||||||
|
metric = config["metrics"][metric_name]
|
||||||
|
except KeyError:
|
||||||
|
raise UnknownMetricError("Unknown metric: {}".format(metric_name))
|
||||||
|
|
||||||
|
# Get the connection pool for the database, or create one if it doesn't
|
||||||
|
# already exist.
|
||||||
|
pool = get_pool(dbname)
|
||||||
|
|
||||||
|
# Identify the PostgreSQL version
|
||||||
|
version = get_cluster_version()
|
||||||
|
|
||||||
|
# Get the query version
|
||||||
|
query = get_query(metric, version)
|
||||||
|
|
||||||
|
# Execute the quert
|
||||||
|
if retry:
|
||||||
|
return run_query(pool, metric["type"], query, args)
|
||||||
|
else:
|
||||||
|
return run_query_no_retry(pool, metric["type"], query, args)
|
||||||
|
|
||||||
|
|
||||||
|
def test_queries():
|
||||||
|
"""
|
||||||
|
Run all of the metric queries against a database and check the results
|
||||||
|
"""
|
||||||
|
# We just use the default db for tests
|
||||||
|
dbname = config["dbname"]
|
||||||
|
# Loop through all defined metrics.
|
||||||
|
for name, metric in config["metrics"].items():
|
||||||
|
# If the metric has arguments to use while testing, grab those
|
||||||
|
args = metric.get("test_args", {})
|
||||||
|
# Run the query without the ability to retry.
|
||||||
|
res = sample_metric(dbname, name, args, retry=False)
|
||||||
|
# Compare the result to the provided sample results
|
||||||
|
# TODO
|
||||||
|
print("{} -> {}".format(name, res))
|
||||||
|
# Return the number of errors
|
||||||
|
# TODO
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||||
"""
|
"""
|
||||||
This is our request handling server. It is responsible for listening for
|
This is our request handling server. It is responsible for listening for
|
||||||
requests, processing them, and responding.
|
requests, processing them, and responding.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def log_request(self, code='-', size='-'):
|
def log_request(self, code="-", size="-"):
|
||||||
"""
|
"""
|
||||||
Override to suppress standard request logging
|
Override to suppress standard request logging
|
||||||
"""
|
"""
|
||||||
@@ -436,10 +693,10 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
|||||||
"""
|
"""
|
||||||
# Parse the URL
|
# Parse the URL
|
||||||
parsed_path = urlparse(self.path)
|
parsed_path = urlparse(self.path)
|
||||||
name = parsed_path.path.strip('/')
|
metric_name = parsed_path.path.strip("/")
|
||||||
parsed_query = parse_qs(parsed_path.query)
|
parsed_query = parse_qs(parsed_path.query)
|
||||||
|
|
||||||
if name == 'agent_version':
|
if metric_name == "agent_version":
|
||||||
self._reply(200, VERSION)
|
self._reply(200, VERSION)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -447,60 +704,31 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
|||||||
# single values, just grab the first from each.
|
# single values, just grab the first from each.
|
||||||
args = {key: values[0] for key, values in parsed_query.items()}
|
args = {key: values[0] for key, values in parsed_query.items()}
|
||||||
|
|
||||||
# Get the metric definition
|
|
||||||
try:
|
|
||||||
metric = config['metrics'][name]
|
|
||||||
except KeyError:
|
|
||||||
log.error("Unknown metric: {}".format(name))
|
|
||||||
self._reply(404, 'Unknown metric')
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get the dbname. If none was provided, use the default from the
|
# Get the dbname. If none was provided, use the default from the
|
||||||
# config.
|
# config.
|
||||||
dbname = args.get('dbname', config['dbname'])
|
dbname = args.get("dbname", config["dbname"])
|
||||||
|
|
||||||
# Get the connection pool for the database, or create one if it doesn't
|
# Sample the metric
|
||||||
# already exist.
|
|
||||||
try:
|
try:
|
||||||
pool = get_pool(dbname)
|
self._reply(200, sample_metric(dbname, metric_name, args))
|
||||||
except UnhappyDBError:
|
|
||||||
log.info("Database {} is unhappy, please be patient".format(dbname))
|
|
||||||
self._reply(503, 'Database unavailable')
|
|
||||||
return
|
return
|
||||||
|
except UnknownMetricError as e:
|
||||||
# Identify the PostgreSQL version
|
log.error("Unknown metric: {}".format(metric_name))
|
||||||
try:
|
self._reply(404, "Unknown metric")
|
||||||
version = get_cluster_version()
|
return
|
||||||
except UnhappyDBError:
|
except MetricVersionError as e:
|
||||||
|
log.error(
|
||||||
|
"Failed to find a version of {} for {}".format(metric_name, version)
|
||||||
|
)
|
||||||
|
self._reply(404, "Unsupported version")
|
||||||
|
return
|
||||||
|
except UnhappyDBError as e:
|
||||||
|
log.info("Database {} is unhappy, please be patient".format(dbname))
|
||||||
|
self._reply(503, "Database unavailable")
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if dbname in unhappy_cooldown:
|
|
||||||
log.info("Database {} is unhappy, please be patient".format(dbname))
|
|
||||||
self._reply(503, 'Database unavailable')
|
|
||||||
else:
|
|
||||||
log.error("Failed to get PostgreSQL version: {}".format(e))
|
|
||||||
self._reply(500, 'Error getting DB version')
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get the query version
|
|
||||||
try:
|
|
||||||
query = get_query(metric, version)
|
|
||||||
except KeyError:
|
|
||||||
log.error("Failed to find a version of {} for {}".format(name, version))
|
|
||||||
self._reply(404, 'Unsupported version')
|
|
||||||
return
|
|
||||||
|
|
||||||
# Execute the quert
|
|
||||||
try:
|
|
||||||
self._reply(200, run_query(pool, metric['type'], query, args))
|
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
if dbname in unhappy_cooldown:
|
|
||||||
log.info("Database {} is unhappy, please be patient".format(dbname))
|
|
||||||
self._reply(503, 'Database unavailable')
|
|
||||||
else:
|
|
||||||
log.error("Error running query: {}".format(e))
|
log.error("Error running query: {}".format(e))
|
||||||
self._reply(500, "Error running query")
|
self._reply(500, "Unexpected error: {}".format(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
def _reply(self, code, content):
|
def _reply(self, code, content):
|
||||||
@@ -508,19 +736,29 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
|||||||
Send a reply to the client
|
Send a reply to the client
|
||||||
"""
|
"""
|
||||||
self.send_response(code)
|
self.send_response(code)
|
||||||
self.send_header('Content-type', 'application/json')
|
self.send_header("Content-type", "application/json")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
self.wfile.write(bytes(content, 'utf-8'))
|
self.wfile.write(bytes(content, "utf-8"))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
# Handle cli args
|
# Handle cli args
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog = 'pgmon',
|
prog="pgmon", description="A PostgreSQL monitoring agent"
|
||||||
description='A PostgreSQL monitoring agent')
|
)
|
||||||
|
|
||||||
parser.add_argument('config_file', default='pgmon.yml', nargs='?',
|
parser.add_argument(
|
||||||
help='The config file to read (default: %(default)s)')
|
"-c",
|
||||||
|
"--config_file",
|
||||||
|
default="pgmon.yml",
|
||||||
|
nargs="?",
|
||||||
|
help="The config file to read (default: %(default)s)",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-t", "--test", action="store_true", help="Run query tests and exit"
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@@ -530,8 +768,16 @@ if __name__ == '__main__':
|
|||||||
# Read the config file
|
# Read the config file
|
||||||
read_config(config_file)
|
read_config(config_file)
|
||||||
|
|
||||||
|
# Run query tests and exit if test mode is enabled
|
||||||
|
if args.test:
|
||||||
|
errors = test_queries()
|
||||||
|
if errors > 0:
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# Set up the http server to receive requests
|
# Set up the http server to receive requests
|
||||||
server_address = ('127.0.0.1', config['port'])
|
server_address = (config["address"], config["port"])
|
||||||
httpd = ThreadingHTTPServer(server_address, SimpleHTTPRequestHandler)
|
httpd = ThreadingHTTPServer(server_address, SimpleHTTPRequestHandler)
|
||||||
|
|
||||||
# Set up the signal handler
|
# Set up the signal handler
|
||||||
@@ -539,7 +785,7 @@ if __name__ == '__main__':
|
|||||||
signal.signal(signal.SIGHUP, signal_handler)
|
signal.signal(signal.SIGHUP, signal_handler)
|
||||||
|
|
||||||
# Handle requests.
|
# Handle requests.
|
||||||
log.info("Listening on port {}...".format(config['port']))
|
log.info("Listening on port {}...".format(config["port"]))
|
||||||
while running:
|
while running:
|
||||||
httpd.handle_request()
|
httpd.handle_request()
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,96 @@ 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):
|
||||||
##
|
##
|
||||||
# update_deep
|
# update_deep
|
||||||
@@ -22,103 +112,104 @@ class TestPgmonMethods(unittest.TestCase):
|
|||||||
self.assertEqual(d1, {})
|
self.assertEqual(d1, {})
|
||||||
self.assertEqual(d2, {})
|
self.assertEqual(d2, {})
|
||||||
|
|
||||||
d1 = {'a': 1}
|
d1 = {"a": 1}
|
||||||
d2 = {}
|
d2 = {}
|
||||||
pgmon.update_deep(d1, d2)
|
pgmon.update_deep(d1, d2)
|
||||||
self.assertEqual(d1, { 'a': 1 })
|
self.assertEqual(d1, {"a": 1})
|
||||||
self.assertEqual(d2, {})
|
self.assertEqual(d2, {})
|
||||||
|
|
||||||
d1 = {}
|
d1 = {}
|
||||||
d2 = {'a': 1}
|
d2 = {"a": 1}
|
||||||
pgmon.update_deep(d1, d2)
|
pgmon.update_deep(d1, d2)
|
||||||
self.assertEqual(d1, { 'a': 1 })
|
self.assertEqual(d1, {"a": 1})
|
||||||
self.assertEqual(d2, d1)
|
self.assertEqual(d2, d1)
|
||||||
|
|
||||||
def test_update_deep__scalars(self):
|
def test_update_deep__scalars(self):
|
||||||
# Test adding/updating scalar values
|
# Test adding/updating scalar values
|
||||||
d1 = {'foo': 1, 'bar': "text", 'hello': "world"}
|
d1 = {"foo": 1, "bar": "text", "hello": "world"}
|
||||||
d2 = {'foo': 2, 'baz': "blah"}
|
d2 = {"foo": 2, "baz": "blah"}
|
||||||
pgmon.update_deep(d1, d2)
|
pgmon.update_deep(d1, d2)
|
||||||
self.assertEqual(d1, {'foo': 2, 'bar': "text", 'baz': "blah", 'hello': "world"})
|
self.assertEqual(d1, {"foo": 2, "bar": "text", "baz": "blah", "hello": "world"})
|
||||||
self.assertEqual(d2, {'foo': 2, 'baz': "blah"})
|
self.assertEqual(d2, {"foo": 2, "baz": "blah"})
|
||||||
|
|
||||||
def test_update_deep__lists(self):
|
def test_update_deep__lists(self):
|
||||||
# Test adding to lists
|
# Test adding to lists
|
||||||
d1 = {'lst1': []}
|
d1 = {"lst1": []}
|
||||||
d2 = {'lst1': [1, 2]}
|
d2 = {"lst1": [1, 2]}
|
||||||
pgmon.update_deep(d1, d2)
|
pgmon.update_deep(d1, d2)
|
||||||
self.assertEqual(d1, {'lst1': [1, 2]})
|
self.assertEqual(d1, {"lst1": [1, 2]})
|
||||||
self.assertEqual(d2, d1)
|
self.assertEqual(d2, d1)
|
||||||
|
|
||||||
d1 = {'lst1': [1, 2]}
|
d1 = {"lst1": [1, 2]}
|
||||||
d2 = {'lst1': []}
|
d2 = {"lst1": []}
|
||||||
pgmon.update_deep(d1, d2)
|
pgmon.update_deep(d1, d2)
|
||||||
self.assertEqual(d1, {'lst1': [1, 2]})
|
self.assertEqual(d1, {"lst1": [1, 2]})
|
||||||
self.assertEqual(d2, {'lst1': []})
|
self.assertEqual(d2, {"lst1": []})
|
||||||
|
|
||||||
d1 = {'lst1': [1, 2, 3]}
|
d1 = {"lst1": [1, 2, 3]}
|
||||||
d2 = {'lst1': [3, 4]}
|
d2 = {"lst1": [3, 4]}
|
||||||
pgmon.update_deep(d1, d2)
|
pgmon.update_deep(d1, d2)
|
||||||
self.assertEqual(d1, {'lst1': [1, 2, 3, 3, 4]})
|
self.assertEqual(d1, {"lst1": [1, 2, 3, 3, 4]})
|
||||||
self.assertEqual(d2, {'lst1': [3, 4]})
|
self.assertEqual(d2, {"lst1": [3, 4]})
|
||||||
|
|
||||||
# Lists of objects
|
# Lists of objects
|
||||||
d1 = {'lst1': [{'id': 1}, {'id': 2}, {'id': 3}]}
|
d1 = {"lst1": [{"id": 1}, {"id": 2}, {"id": 3}]}
|
||||||
d2 = {'lst1': [{'id': 3}, {'id': 4}]}
|
d2 = {"lst1": [{"id": 3}, {"id": 4}]}
|
||||||
pgmon.update_deep(d1, d2)
|
pgmon.update_deep(d1, d2)
|
||||||
self.assertEqual(d1, {'lst1': [{'id': 1}, {'id': 2}, {'id': 3}, {'id': 3}, {'id': 4}]})
|
self.assertEqual(
|
||||||
self.assertEqual(d2, {'lst1': [{'id': 3}, {'id': 4}]})
|
d1, {"lst1": [{"id": 1}, {"id": 2}, {"id": 3}, {"id": 3}, {"id": 4}]}
|
||||||
|
)
|
||||||
|
self.assertEqual(d2, {"lst1": [{"id": 3}, {"id": 4}]})
|
||||||
|
|
||||||
# Nested lists
|
# Nested lists
|
||||||
d1 = {'obj1': {'l1': [1, 2]}}
|
d1 = {"obj1": {"l1": [1, 2]}}
|
||||||
d2 = {'obj1': {'l1': [3, 4]}}
|
d2 = {"obj1": {"l1": [3, 4]}}
|
||||||
pgmon.update_deep(d1, d2)
|
pgmon.update_deep(d1, d2)
|
||||||
self.assertEqual(d1, {'obj1': {'l1': [1, 2, 3, 4]}})
|
self.assertEqual(d1, {"obj1": {"l1": [1, 2, 3, 4]}})
|
||||||
self.assertEqual(d2, {'obj1': {'l1': [3, 4]}})
|
self.assertEqual(d2, {"obj1": {"l1": [3, 4]}})
|
||||||
|
|
||||||
def test_update_deep__dicts(self):
|
def test_update_deep__dicts(self):
|
||||||
# Test adding to lists
|
# Test adding to lists
|
||||||
d1 = {'obj1': {}}
|
d1 = {"obj1": {}}
|
||||||
d2 = {'obj1': {'a': 1, 'b': 2}}
|
d2 = {"obj1": {"a": 1, "b": 2}}
|
||||||
pgmon.update_deep(d1, d2)
|
pgmon.update_deep(d1, d2)
|
||||||
self.assertEqual(d1, {'obj1': {'a': 1, 'b': 2}})
|
self.assertEqual(d1, {"obj1": {"a": 1, "b": 2}})
|
||||||
self.assertEqual(d2, d1)
|
self.assertEqual(d2, d1)
|
||||||
|
|
||||||
d1 = {'obj1': {'a': 1, 'b': 2}}
|
d1 = {"obj1": {"a": 1, "b": 2}}
|
||||||
d2 = {'obj1': {}}
|
d2 = {"obj1": {}}
|
||||||
pgmon.update_deep(d1, d2)
|
pgmon.update_deep(d1, d2)
|
||||||
self.assertEqual(d1, {'obj1': {'a': 1, 'b': 2}})
|
self.assertEqual(d1, {"obj1": {"a": 1, "b": 2}})
|
||||||
self.assertEqual(d2, {'obj1': {}})
|
self.assertEqual(d2, {"obj1": {}})
|
||||||
|
|
||||||
d1 = {'obj1': {'a': 1, 'b': 2}}
|
d1 = {"obj1": {"a": 1, "b": 2}}
|
||||||
d2 = {'obj1': {'a': 5, 'c': 12}}
|
d2 = {"obj1": {"a": 5, "c": 12}}
|
||||||
pgmon.update_deep(d1, d2)
|
pgmon.update_deep(d1, d2)
|
||||||
self.assertEqual(d1, {'obj1': {'a': 5, 'b': 2, 'c': 12}})
|
self.assertEqual(d1, {"obj1": {"a": 5, "b": 2, "c": 12}})
|
||||||
self.assertEqual(d2, {'obj1': {'a': 5, 'c': 12}})
|
self.assertEqual(d2, {"obj1": {"a": 5, "c": 12}})
|
||||||
|
|
||||||
# Nested dicts
|
# Nested dicts
|
||||||
d1 = {'obj1': {'d1': {'a': 1, 'b': 2}}}
|
d1 = {"obj1": {"d1": {"a": 1, "b": 2}}}
|
||||||
d2 = {'obj1': {'d1': {'a': 5, 'c': 12}}}
|
d2 = {"obj1": {"d1": {"a": 5, "c": 12}}}
|
||||||
pgmon.update_deep(d1, d2)
|
pgmon.update_deep(d1, d2)
|
||||||
self.assertEqual(d1, {'obj1': {'d1': {'a': 5, 'b': 2, 'c': 12}}})
|
self.assertEqual(d1, {"obj1": {"d1": {"a": 5, "b": 2, "c": 12}}})
|
||||||
self.assertEqual(d2, {'obj1': {'d1': {'a': 5, 'c': 12}}})
|
self.assertEqual(d2, {"obj1": {"d1": {"a": 5, "c": 12}}})
|
||||||
|
|
||||||
def test_update_deep__types(self):
|
def test_update_deep__types(self):
|
||||||
# Test mismatched types
|
# Test mismatched types
|
||||||
d1 = {'foo': 5}
|
d1 = {"foo": 5}
|
||||||
d2 = None
|
d2 = None
|
||||||
self.assertRaises(TypeError, pgmon.update_deep, d1, d2)
|
self.assertRaises(TypeError, pgmon.update_deep, d1, d2)
|
||||||
|
|
||||||
d1 = None
|
d1 = None
|
||||||
d2 = {'foo': 5}
|
d2 = {"foo": 5}
|
||||||
self.assertRaises(TypeError, pgmon.update_deep, d1, d2)
|
self.assertRaises(TypeError, pgmon.update_deep, d1, d2)
|
||||||
|
|
||||||
# Nested mismatched types
|
# Nested mismatched types
|
||||||
d1 = {'foo': [1, 2]}
|
d1 = {"foo": [1, 2]}
|
||||||
d2 = {'foo': {'a': 7}}
|
d2 = {"foo": {"a": 7}}
|
||||||
self.assertRaises(TypeError, pgmon.update_deep, d1, d2)
|
self.assertRaises(TypeError, pgmon.update_deep, d1, d2)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# get_pool
|
# get_pool
|
||||||
##
|
##
|
||||||
@@ -126,20 +217,19 @@ class TestPgmonMethods(unittest.TestCase):
|
|||||||
def test_get_pool__simple(self):
|
def test_get_pool__simple(self):
|
||||||
# Just get a pool in a normal case
|
# Just get a pool in a normal case
|
||||||
pgmon.config.update(pgmon.default_config)
|
pgmon.config.update(pgmon.default_config)
|
||||||
pool = pgmon.get_pool('postgres')
|
pool = pgmon.get_pool("postgres")
|
||||||
self.assertIsNotNone(pool)
|
self.assertIsNotNone(pool)
|
||||||
|
|
||||||
def test_get_pool__unhappy(self):
|
def test_get_pool__unhappy(self):
|
||||||
# Test getting an unhappy database pool
|
# Test getting an unhappy database pool
|
||||||
pgmon.config.update(pgmon.default_config)
|
pgmon.config.update(pgmon.default_config)
|
||||||
pgmon.unhappy_cooldown['postgres'] = datetime.now() + timedelta(60)
|
pgmon.unhappy_cooldown["postgres"] = datetime.now() + timedelta(60)
|
||||||
self.assertRaises(pgmon.UnhappyDBError, pgmon.get_pool, 'postgres')
|
self.assertRaises(pgmon.UnhappyDBError, pgmon.get_pool, "postgres")
|
||||||
|
|
||||||
# Test getting a different database when there's an unhappy one
|
# Test getting a different database when there's an unhappy one
|
||||||
pool = pgmon.get_pool('template0')
|
pool = pgmon.get_pool("template0")
|
||||||
self.assertIsNotNone(pool)
|
self.assertIsNotNone(pool)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# handle_connect_failure
|
# handle_connect_failure
|
||||||
##
|
##
|
||||||
@@ -148,70 +238,47 @@ class TestPgmonMethods(unittest.TestCase):
|
|||||||
# Test adding to an empty unhappy list
|
# Test adding to an empty unhappy list
|
||||||
pgmon.config.update(pgmon.default_config)
|
pgmon.config.update(pgmon.default_config)
|
||||||
pgmon.unhappy_cooldown = {}
|
pgmon.unhappy_cooldown = {}
|
||||||
pool = pgmon.get_pool('postgres')
|
pool = pgmon.get_pool("postgres")
|
||||||
pgmon.handle_connect_failure(pool)
|
pgmon.handle_connect_failure(pool)
|
||||||
self.assertGreater(pgmon.unhappy_cooldown['postgres'], datetime.now())
|
self.assertGreater(pgmon.unhappy_cooldown["postgres"], datetime.now())
|
||||||
|
|
||||||
# Test adding another database
|
# Test adding another database
|
||||||
pool = pgmon.get_pool('template0')
|
pool = pgmon.get_pool("template0")
|
||||||
pgmon.handle_connect_failure(pool)
|
pgmon.handle_connect_failure(pool)
|
||||||
self.assertGreater(pgmon.unhappy_cooldown['postgres'], datetime.now())
|
self.assertGreater(pgmon.unhappy_cooldown["postgres"], datetime.now())
|
||||||
self.assertGreater(pgmon.unhappy_cooldown['template0'], datetime.now())
|
self.assertGreater(pgmon.unhappy_cooldown["template0"], datetime.now())
|
||||||
self.assertEqual(len(pgmon.unhappy_cooldown), 2)
|
self.assertEqual(len(pgmon.unhappy_cooldown), 2)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# get_query
|
# get_query
|
||||||
##
|
##
|
||||||
|
|
||||||
def test_get_query__basic(self):
|
def test_get_query__basic(self):
|
||||||
# Test getting a query with one version
|
# Test getting a query with one version
|
||||||
metric = {
|
metric = {"type": "value", "query": {0: "DEFAULT"}}
|
||||||
'type': 'value',
|
self.assertEqual(pgmon.get_query(metric, 100000), "DEFAULT")
|
||||||
'query': {
|
|
||||||
0: 'DEFAULT'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.assertEqual(pgmon.get_query(metric, 100000), 'DEFAULT')
|
|
||||||
|
|
||||||
def test_get_query__versions(self):
|
def test_get_query__versions(self):
|
||||||
metric = {
|
metric = {"type": "value", "query": {0: "DEFAULT", 110000: "NEW"}}
|
||||||
'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
|
||||||
self.assertEqual(pgmon.get_query(metric, 100000), 'DEFAULT')
|
# version
|
||||||
|
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
|
||||||
self.assertEqual(pgmon.get_query(metric, 110000), 'NEW')
|
# version for the newer version
|
||||||
|
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
|
||||||
self.assertEqual(pgmon.get_query(metric, 160000), 'NEW')
|
# version for an even newer version
|
||||||
|
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
|
||||||
metric = {
|
metric = {"type": "value", "query": {0: "DEFAULT", 96000: "OLD", 110000: "NEW"}}
|
||||||
'type': 'value',
|
self.assertEqual(pgmon.get_query(metric, 100000), "OLD")
|
||||||
'query': {
|
|
||||||
0: 'DEFAULT',
|
|
||||||
96000: 'OLD',
|
|
||||||
110000: 'NEW'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.assertEqual(pgmon.get_query(metric, 100000), 'OLD')
|
|
||||||
|
|
||||||
def test_get_query__missing_version(self):
|
def test_get_query__missing_version(self):
|
||||||
metric = {
|
metric = {"type": "value", "query": {96000: "OLD", 110000: "NEW", 150000: ""}}
|
||||||
'type': 'value',
|
|
||||||
'query': {
|
|
||||||
96000: 'OLD',
|
|
||||||
110000: 'NEW',
|
|
||||||
150000: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Test getting a metric that only exists for newer versions
|
# Test getting a metric that only exists for newer versions
|
||||||
self.assertRaises(pgmon.MetricVersionError, pgmon.get_query, metric, 80000)
|
self.assertRaises(pgmon.MetricVersionError, pgmon.get_query, metric, 80000)
|
||||||
@@ -219,7 +286,6 @@ class TestPgmonMethods(unittest.TestCase):
|
|||||||
# Test getting a metric that only exists for older versions
|
# Test getting a metric that only exists for older versions
|
||||||
self.assertRaises(pgmon.MetricVersionError, pgmon.get_query, metric, 160000)
|
self.assertRaises(pgmon.MetricVersionError, pgmon.get_query, metric, 160000)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# read_config
|
# read_config
|
||||||
##
|
##
|
||||||
@@ -229,27 +295,32 @@ class TestPgmonMethods(unittest.TestCase):
|
|||||||
|
|
||||||
# Test reading just a metric and using the defaults for everything else
|
# Test reading just a metric and using the defaults for everything else
|
||||||
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(
|
||||||
|
"""---
|
||||||
# This is a comment!
|
# This is a comment!
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
type: value
|
type: value
|
||||||
query:
|
query:
|
||||||
0: TEST1
|
0: TEST1
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
pgmon.read_config(f"{tmpdirname}/config.yml")
|
pgmon.read_config(f"{tmpdirname}/config.yml")
|
||||||
|
|
||||||
self.assertEqual(pgmon.config['max_pool_size'], pgmon.default_config['max_pool_size'])
|
self.assertEqual(
|
||||||
self.assertEqual(pgmon.config['dbuser'], pgmon.default_config['dbuser'])
|
pgmon.config["max_pool_size"], pgmon.default_config["max_pool_size"]
|
||||||
|
)
|
||||||
|
self.assertEqual(pgmon.config["dbuser"], pgmon.default_config["dbuser"])
|
||||||
|
|
||||||
pgmon.config = {}
|
pgmon.config = {}
|
||||||
|
|
||||||
# Test reading a basic config
|
# Test reading a basic config
|
||||||
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(
|
||||||
|
"""---
|
||||||
# This is a comment!
|
# This is a comment!
|
||||||
min_pool_size: 1
|
min_pool_size: 1
|
||||||
max_pool_size: 2
|
max_pool_size: 2
|
||||||
@@ -280,22 +351,25 @@ metrics:
|
|||||||
type: column
|
type: column
|
||||||
query:
|
query:
|
||||||
0: TEST4
|
0: TEST4
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
pgmon.read_config(f"{tmpdirname}/config.yml")
|
pgmon.read_config(f"{tmpdirname}/config.yml")
|
||||||
|
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'someone')
|
self.assertEqual(pgmon.config["dbuser"], "someone")
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['type'], 'value')
|
self.assertEqual(pgmon.config["metrics"]["test1"]["type"], "value")
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
self.assertEqual(pgmon.config['metrics']['test2']['query'][0], 'TEST2')
|
self.assertEqual(pgmon.config["metrics"]["test2"]["query"][0], "TEST2")
|
||||||
|
|
||||||
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"""---
|
f.write(
|
||||||
|
f"""---
|
||||||
# This is a comment!
|
# This is a comment!
|
||||||
min_pool_size: 1
|
min_pool_size: 1
|
||||||
max_pool_size: 2
|
max_pool_size: 2
|
||||||
@@ -308,18 +382,22 @@ version_check_period: 3600
|
|||||||
include:
|
include:
|
||||||
- dbsettings.yml
|
- dbsettings.yml
|
||||||
- {tmpdirname}/metrics.yml
|
- {tmpdirname}/metrics.yml
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
with open(f"{tmpdirname}/dbsettings.yml", 'w') as f:
|
with open(f"{tmpdirname}/dbsettings.yml", "w") as f:
|
||||||
f.write(f"""---
|
f.write(
|
||||||
|
f"""---
|
||||||
dbuser: someone
|
dbuser: someone
|
||||||
dbhost: localhost
|
dbhost: localhost
|
||||||
dbport: 5555
|
dbport: 5555
|
||||||
dbname: template0
|
dbname: template0
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
with open(f"{tmpdirname}/metrics.yml", 'w') as f:
|
with open(f"{tmpdirname}/metrics.yml", "w") as f:
|
||||||
f.write(f"""---
|
f.write(
|
||||||
|
f"""---
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
type: value
|
type: value
|
||||||
@@ -331,31 +409,35 @@ metrics:
|
|||||||
0: TEST2
|
0: TEST2
|
||||||
include:
|
include:
|
||||||
- more_metrics.yml
|
- more_metrics.yml
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
with open(f"{tmpdirname}/more_metrics.yml", 'w') as f:
|
with open(f"{tmpdirname}/more_metrics.yml", "w") as f:
|
||||||
f.write(f"""---
|
f.write(
|
||||||
|
f"""---
|
||||||
metrics:
|
metrics:
|
||||||
test3:
|
test3:
|
||||||
type: value
|
type: value
|
||||||
query:
|
query:
|
||||||
0: TEST3
|
0: TEST3
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
pgmon.read_config(f"{tmpdirname}/config.yml")
|
pgmon.read_config(f"{tmpdirname}/config.yml")
|
||||||
|
|
||||||
self.assertEqual(pgmon.config['max_idle_time'], 10)
|
self.assertEqual(pgmon.config["max_idle_time"], 10)
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'someone')
|
self.assertEqual(pgmon.config["dbuser"], "someone")
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
self.assertEqual(pgmon.config['metrics']['test2']['query'][0], 'TEST2')
|
self.assertEqual(pgmon.config["metrics"]["test2"]["query"][0], "TEST2")
|
||||||
self.assertEqual(pgmon.config['metrics']['test3']['query'][0], 'TEST3')
|
self.assertEqual(pgmon.config["metrics"]["test3"]["query"][0], "TEST3")
|
||||||
|
|
||||||
def test_read_config__reload(self):
|
def test_read_config__reload(self):
|
||||||
pgmon.config = {}
|
pgmon.config = {}
|
||||||
|
|
||||||
# Test rereading a config to update an existing config
|
# Test rereading a config to update an existing config
|
||||||
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(
|
||||||
|
"""---
|
||||||
# This is a comment!
|
# This is a comment!
|
||||||
min_pool_size: 1
|
min_pool_size: 1
|
||||||
max_pool_size: 2
|
max_pool_size: 2
|
||||||
@@ -378,15 +460,17 @@ metrics:
|
|||||||
type: value
|
type: value
|
||||||
query:
|
query:
|
||||||
0: TEST2
|
0: TEST2
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
pgmon.read_config(f"{tmpdirname}/config.yml")
|
pgmon.read_config(f"{tmpdirname}/config.yml")
|
||||||
|
|
||||||
# Just make sure the first config was read
|
# Just make sure the first config was read
|
||||||
self.assertEqual(len(pgmon.config['metrics']), 2)
|
self.assertEqual(len(pgmon.config["metrics"]), 2)
|
||||||
|
|
||||||
with open(f"{tmpdirname}/config.yml", 'w') as f:
|
with open(f"{tmpdirname}/config.yml", "w") as f:
|
||||||
f.write("""---
|
f.write(
|
||||||
|
"""---
|
||||||
# This is a comment!
|
# This is a comment!
|
||||||
min_pool_size: 7
|
min_pool_size: 7
|
||||||
metrics:
|
metrics:
|
||||||
@@ -394,34 +478,39 @@ metrics:
|
|||||||
type: value
|
type: value
|
||||||
query:
|
query:
|
||||||
0: NEW1
|
0: NEW1
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
pgmon.read_config(f"{tmpdirname}/config.yml")
|
pgmon.read_config(f"{tmpdirname}/config.yml")
|
||||||
|
|
||||||
self.assertEqual(pgmon.config['min_pool_size'], 7)
|
self.assertEqual(pgmon.config["min_pool_size"], 7)
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'NEW1')
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "NEW1")
|
||||||
self.assertEqual(len(pgmon.config['metrics']), 1)
|
self.assertEqual(len(pgmon.config["metrics"]), 1)
|
||||||
|
|
||||||
def test_read_config__query_file(self):
|
def test_read_config__query_file(self):
|
||||||
pgmon.config = {}
|
pgmon.config = {}
|
||||||
|
|
||||||
# Read a config file that reads a query from a file
|
# Read a config file that reads a query from a file
|
||||||
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(
|
||||||
|
"""---
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
type: value
|
type: value
|
||||||
query:
|
query:
|
||||||
0: file:some_query.sql
|
0: file:some_query.sql
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
with open(f"{tmpdirname}/some_query.sql", 'w') as f:
|
with open(f"{tmpdirname}/some_query.sql", "w") as f:
|
||||||
f.write("This is a query")
|
f.write("This is a query")
|
||||||
|
|
||||||
pgmon.read_config(f"{tmpdirname}/config.yml")
|
pgmon.read_config(f"{tmpdirname}/config.yml")
|
||||||
|
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'This is a query')
|
self.assertEqual(
|
||||||
|
pgmon.config["metrics"]["test1"]["query"][0], "This is a query"
|
||||||
|
)
|
||||||
|
|
||||||
def test_read_config__invalid(self):
|
def test_read_config__invalid(self):
|
||||||
pgmon.config = {}
|
pgmon.config = {}
|
||||||
@@ -429,38 +518,47 @@ metrics:
|
|||||||
# For all of these tests, we start with a valid config and also ensure that
|
# For all of these tests, we start with a valid config and also ensure that
|
||||||
# it is not modified when a new config read fails
|
# it is not modified when a new config read fails
|
||||||
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(
|
||||||
|
"""---
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
type: value
|
type: value
|
||||||
query:
|
query:
|
||||||
0: TEST1
|
0: TEST1
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
pgmon.read_config(f"{tmpdirname}/config.yml")
|
pgmon.read_config(f"{tmpdirname}/config.yml")
|
||||||
|
|
||||||
# Just make sure the config was read
|
# Just make sure the config was read
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
|
|
||||||
# Test reading a nonexistant config file
|
# Test reading a nonexistant config file
|
||||||
with tempfile.TemporaryDirectory() as tmpdirname:
|
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||||
self.assertRaises(FileNotFoundError, pgmon.read_config, f'{tmpdirname}/missing.yml')
|
self.assertRaises(
|
||||||
|
FileNotFoundError, pgmon.read_config, f"{tmpdirname}/missing.yml"
|
||||||
|
)
|
||||||
|
|
||||||
# Test reading an invalid config file
|
# Test reading an invalid config file
|
||||||
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("""[default]
|
f.write(
|
||||||
|
"""[default]
|
||||||
This looks a lot like an ini file to me
|
This looks a lot like an ini file to me
|
||||||
|
|
||||||
Or maybe a TOML?
|
Or maybe a TOML?
|
||||||
""")
|
"""
|
||||||
self.assertRaises(pgmon.ConfigError, pgmon.read_config, f'{tmpdirname}/config.yml')
|
)
|
||||||
|
self.assertRaises(
|
||||||
|
pgmon.ConfigError, pgmon.read_config, f"{tmpdirname}/config.yml"
|
||||||
|
)
|
||||||
|
|
||||||
# Test reading a config that includes an invalid file
|
# Test reading a config that includes an invalid file
|
||||||
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(
|
||||||
|
"""---
|
||||||
dbuser: evil
|
dbuser: evil
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
@@ -469,15 +567,19 @@ metrics:
|
|||||||
0: EVIL1
|
0: EVIL1
|
||||||
include:
|
include:
|
||||||
- missing_file.yml
|
- missing_file.yml
|
||||||
""")
|
"""
|
||||||
self.assertRaises(FileNotFoundError, pgmon.read_config, f'{tmpdirname}/config.yml')
|
)
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'postgres')
|
self.assertRaises(
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
FileNotFoundError, pgmon.read_config, f"{tmpdirname}/config.yml"
|
||||||
|
)
|
||||||
|
self.assertEqual(pgmon.config["dbuser"], "postgres")
|
||||||
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
|
|
||||||
# Test invalid log level
|
# Test invalid log level
|
||||||
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(
|
||||||
|
"""---
|
||||||
log_level: noisy
|
log_level: noisy
|
||||||
dbuser: evil
|
dbuser: evil
|
||||||
metrics:
|
metrics:
|
||||||
@@ -485,132 +587,205 @@ metrics:
|
|||||||
type: value
|
type: value
|
||||||
query:
|
query:
|
||||||
0: EVIL1
|
0: EVIL1
|
||||||
""")
|
"""
|
||||||
self.assertRaises(pgmon.ConfigError, pgmon.read_config, f'{tmpdirname}/config.yml')
|
)
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'postgres')
|
self.assertRaises(
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
pgmon.ConfigError, pgmon.read_config, f"{tmpdirname}/config.yml"
|
||||||
|
)
|
||||||
|
self.assertEqual(pgmon.config["dbuser"], "postgres")
|
||||||
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
|
|
||||||
# Test invalid query return type
|
# Test invalid query return type
|
||||||
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(
|
||||||
|
"""---
|
||||||
dbuser: evil
|
dbuser: evil
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
type: lots_of_data
|
type: lots_of_data
|
||||||
query:
|
query:
|
||||||
0: EVIL1
|
0: EVIL1
|
||||||
""")
|
"""
|
||||||
self.assertRaises(pgmon.ConfigError, pgmon.read_config, f'{tmpdirname}/config.yml')
|
)
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'postgres')
|
self.assertRaises(
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
pgmon.ConfigError, pgmon.read_config, f"{tmpdirname}/config.yml"
|
||||||
|
)
|
||||||
|
self.assertEqual(pgmon.config["dbuser"], "postgres")
|
||||||
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
|
|
||||||
# Test invalid query dict type
|
# Test invalid query dict type
|
||||||
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(
|
||||||
|
"""---
|
||||||
dbuser: evil
|
dbuser: evil
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
type: lots_of_data
|
type: lots_of_data
|
||||||
query: EVIL1
|
query: EVIL1
|
||||||
""")
|
"""
|
||||||
self.assertRaises(pgmon.ConfigError, pgmon.read_config, f'{tmpdirname}/config.yml')
|
)
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'postgres')
|
self.assertRaises(
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
pgmon.ConfigError, pgmon.read_config, f"{tmpdirname}/config.yml"
|
||||||
|
)
|
||||||
|
self.assertEqual(pgmon.config["dbuser"], "postgres")
|
||||||
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
|
|
||||||
# Test incomplete metric: missing type
|
# Test incomplete metric: missing type
|
||||||
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(
|
||||||
|
"""---
|
||||||
dbuser: evil
|
dbuser: evil
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
query:
|
query:
|
||||||
0: EVIL1
|
0: EVIL1
|
||||||
""")
|
"""
|
||||||
self.assertRaises(pgmon.ConfigError, pgmon.read_config, f'{tmpdirname}/config.yml')
|
)
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'postgres')
|
self.assertRaises(
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
pgmon.ConfigError, pgmon.read_config, f"{tmpdirname}/config.yml"
|
||||||
|
)
|
||||||
|
self.assertEqual(pgmon.config["dbuser"], "postgres")
|
||||||
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
|
|
||||||
# Test incomplete metric: missing queries
|
# Test incomplete metric: missing queries
|
||||||
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(
|
||||||
|
"""---
|
||||||
dbuser: evil
|
dbuser: evil
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
type: value
|
type: value
|
||||||
""")
|
"""
|
||||||
self.assertRaises(pgmon.ConfigError, pgmon.read_config, f'{tmpdirname}/config.yml')
|
)
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'postgres')
|
self.assertRaises(
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
pgmon.ConfigError, pgmon.read_config, f"{tmpdirname}/config.yml"
|
||||||
|
)
|
||||||
|
self.assertEqual(pgmon.config["dbuser"], "postgres")
|
||||||
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
|
|
||||||
# Test incomplete metric: empty queries
|
# Test incomplete metric: empty queries
|
||||||
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(
|
||||||
|
"""---
|
||||||
dbuser: evil
|
dbuser: evil
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
type: value
|
type: value
|
||||||
query: {}
|
query: {}
|
||||||
""")
|
"""
|
||||||
self.assertRaises(pgmon.ConfigError, pgmon.read_config, f'{tmpdirname}/config.yml')
|
)
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'postgres')
|
self.assertRaises(
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
pgmon.ConfigError, pgmon.read_config, f"{tmpdirname}/config.yml"
|
||||||
|
)
|
||||||
|
self.assertEqual(pgmon.config["dbuser"], "postgres")
|
||||||
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
|
|
||||||
# Test incomplete metric: query dict is None
|
# Test incomplete metric: query dict is None
|
||||||
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(
|
||||||
|
"""---
|
||||||
dbuser: evil
|
dbuser: evil
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
type: value
|
type: value
|
||||||
query:
|
query:
|
||||||
""")
|
"""
|
||||||
self.assertRaises(pgmon.ConfigError, pgmon.read_config, f'{tmpdirname}/config.yml')
|
)
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'postgres')
|
self.assertRaises(
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
pgmon.ConfigError, pgmon.read_config, f"{tmpdirname}/config.yml"
|
||||||
|
)
|
||||||
|
self.assertEqual(pgmon.config["dbuser"], "postgres")
|
||||||
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
|
|
||||||
# Test reading a config with no metrics
|
# Test reading a config with no metrics
|
||||||
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(
|
||||||
|
"""---
|
||||||
dbuser: evil
|
dbuser: evil
|
||||||
""")
|
"""
|
||||||
self.assertRaises(pgmon.ConfigError, pgmon.read_config, f'{tmpdirname}/config.yml')
|
)
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'postgres')
|
self.assertRaises(
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
pgmon.ConfigError, pgmon.read_config, f"{tmpdirname}/config.yml"
|
||||||
|
)
|
||||||
|
self.assertEqual(pgmon.config["dbuser"], "postgres")
|
||||||
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
|
|
||||||
# Test reading a query defined in a file but the file is missing
|
# Test reading a query defined in a file but the file is missing
|
||||||
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(
|
||||||
|
"""---
|
||||||
dbuser: evil
|
dbuser: evil
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
type: value
|
type: value
|
||||||
query:
|
query:
|
||||||
0: file:missing.sql
|
0: file:missing.sql
|
||||||
""")
|
"""
|
||||||
self.assertRaises(FileNotFoundError, pgmon.read_config, f'{tmpdirname}/config.yml')
|
)
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'postgres')
|
self.assertRaises(
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
FileNotFoundError, pgmon.read_config, f"{tmpdirname}/config.yml"
|
||||||
|
)
|
||||||
|
self.assertEqual(pgmon.config["dbuser"], "postgres")
|
||||||
|
self.assertEqual(pgmon.config["metrics"]["test1"]["query"][0], "TEST1")
|
||||||
|
|
||||||
# Test invalid query versions
|
# Test invalid query versions
|
||||||
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(
|
||||||
|
"""---
|
||||||
dbuser: evil
|
dbuser: evil
|
||||||
metrics:
|
metrics:
|
||||||
test1:
|
test1:
|
||||||
type: value
|
type: value
|
||||||
query:
|
query:
|
||||||
default: EVIL1
|
default: EVIL1
|
||||||
""")
|
"""
|
||||||
self.assertRaises(pgmon.ConfigError, pgmon.read_config, f'{tmpdirname}/config.yml')
|
)
|
||||||
self.assertEqual(pgmon.config['dbuser'], 'postgres')
|
self.assertRaises(
|
||||||
self.assertEqual(pgmon.config['metrics']['test1']['query'][0], 'TEST1')
|
pgmon.ConfigError, pgmon.read_config, f"{tmpdirname}/config.yml"
|
||||||
|
)
|
||||||
|
self.assertEqual(pgmon.config["dbuser"], "postgres")
|
||||||
|
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)
|
||||||
|
|||||||
@@ -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 /etc/pgmon/%i.yml
|
ExecStart=/usr/local/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
|
||||||
|
|||||||
22
tests/Dockerfile
Normal file
22
tests/Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
FROM alpine:3.21
|
||||||
|
|
||||||
|
RUN apk update && \
|
||||||
|
apk add py3-psycopg2 \
|
||||||
|
py3-yaml \
|
||||||
|
tini
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY src/pgmon.py /app/
|
||||||
|
|
||||||
|
COPY sample-config/pgmon-metrics.yml /app/
|
||||||
|
|
||||||
|
COPY tests/test-config.yml /app/
|
||||||
|
|
||||||
|
COPY --chmod=0600 --chown=postgres:postgres tests/pgpass /root/.pgpass
|
||||||
|
|
||||||
|
ENTRYPOINT ["tini", "--"]
|
||||||
|
|
||||||
|
EXPOSE 5400
|
||||||
|
|
||||||
|
CMD ["/app/pgmon.py", "-c", "/app/test-config.yml", "--test"]
|
||||||
32
tests/docker-compose.yml
Normal file
32
tests/docker-compose.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
services:
|
||||||
|
agent:
|
||||||
|
image: pgmon
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: tests/Dockerfile
|
||||||
|
ports:
|
||||||
|
- :5400
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: "postgres:${PGTAG:-17-bookworm}"
|
||||||
|
ports:
|
||||||
|
- :5432
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: secret
|
||||||
|
healthcheck:
|
||||||
|
#test: [ "CMD", "pg_isready", "-U", "postgres" ]
|
||||||
|
test: [ "CMD-SHELL", "pg_controldata /var/lib/postgresql/data/ | grep -q 'in production'" ]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 20
|
||||||
|
command: >
|
||||||
|
postgres -c ssl=on
|
||||||
|
-c ssl_cert_file='/etc/ssl/certs/ssl-cert-snakeoil.pem'
|
||||||
|
-c ssl_key_file='/etc/ssl/private/ssl-cert-snakeoil.key'
|
||||||
|
-c listen_addresses='*'
|
||||||
|
|
||||||
1
tests/pgpass
Normal file
1
tests/pgpass
Normal file
@@ -0,0 +1 @@
|
|||||||
|
db:5432:*:postgres:secret
|
||||||
62
tests/run-tests.sh
Executable file
62
tests/run-tests.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Versions to test
|
||||||
|
versions=( $@ )
|
||||||
|
|
||||||
|
# If we weren't given any versions, test them all
|
||||||
|
if [ ${#versions[@]} -eq 0 ]
|
||||||
|
then
|
||||||
|
versions=( 9.2 9.6 10 11 12 13 14 15 16 17 )
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Image tags to use
|
||||||
|
declare -A images=()
|
||||||
|
images["9.2"]='9.2'
|
||||||
|
images["9.6"]='9.6-bullseye'
|
||||||
|
images["10"]='10-bullseye'
|
||||||
|
images["11"]='11-bookworm'
|
||||||
|
images["12"]='12-bookworm'
|
||||||
|
images["13"]='13-bookworm'
|
||||||
|
images["14"]='14-bookworm'
|
||||||
|
images["15"]='15-bookworm'
|
||||||
|
images["16"]='16-bookworm'
|
||||||
|
images["17"]='17-bookworm'
|
||||||
|
|
||||||
|
declare -A results=()
|
||||||
|
|
||||||
|
# Make sure everything's down to start with
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# Make sure our agent container is up to date
|
||||||
|
docker compose build agent
|
||||||
|
|
||||||
|
for version in "${versions[@]}"
|
||||||
|
do
|
||||||
|
echo
|
||||||
|
echo "Testing: PostgreSQL ${version}"
|
||||||
|
|
||||||
|
# Specify the version we're testing against
|
||||||
|
export PGTAG="${images["$version"]}"
|
||||||
|
|
||||||
|
# Start the containers
|
||||||
|
docker compose up --exit-code-from=agent agent
|
||||||
|
rc=$?
|
||||||
|
|
||||||
|
results["$version"]=$rc
|
||||||
|
|
||||||
|
# Destroy the containers
|
||||||
|
docker compose down
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
for v in "${versions[@]}"
|
||||||
|
do
|
||||||
|
case "${results["$v"]}" in
|
||||||
|
0) msg="OK" ;;
|
||||||
|
1) msg="Query failure detected" ;;
|
||||||
|
18) msg="Docker image error: 18" ;;
|
||||||
|
*) msg="Unexpected error: ${results["$v"]}" ;;
|
||||||
|
esac
|
||||||
|
echo "$v -> $msg"
|
||||||
|
done
|
||||||
17
tests/test-config.yml
Normal file
17
tests/test-config.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
# Bind to all interfaces so we can submit requests from outside the test container
|
||||||
|
address: 0.0.0.0
|
||||||
|
|
||||||
|
# We always just connect to the db container
|
||||||
|
dbhost: db
|
||||||
|
dbport: 5432
|
||||||
|
dbuser: postgres
|
||||||
|
|
||||||
|
# The SSL cipher parameters are too old in the 9.2 container, so we allow the tests
|
||||||
|
# to be run without encryption
|
||||||
|
ssl_mode: prefer
|
||||||
|
|
||||||
|
# Pull in the standard metrics
|
||||||
|
include:
|
||||||
|
- pgmon-metrics.yml
|
||||||
@@ -13,58 +13,58 @@ zabbix_export:
|
|||||||
items:
|
items:
|
||||||
- uuid: 763920af8da84db8a9a2667d9653cb21
|
- uuid: 763920af8da84db8a9a2667d9653cb21
|
||||||
name: 'PostgreSQL Agent Version'
|
name: 'PostgreSQL Agent Version'
|
||||||
key: 'web.page.get[localhost,/agent_version,{$AGENT_PORT}]'
|
type: HTTP_AGENT
|
||||||
|
key: 'pgmon[agent_version]'
|
||||||
delay: 1h
|
delay: 1h
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: TEXT
|
value_type: TEXT
|
||||||
|
trends: '0'
|
||||||
description: 'PostgreSQL monitoring agent version number'
|
description: 'PostgreSQL monitoring agent version number'
|
||||||
preprocessing:
|
url: 'http://localhost:{$AGENT_PORT}/agent_version'
|
||||||
- type: REGEX
|
|
||||||
parameters:
|
|
||||||
- '\n\s?\n([\s\S]*)'
|
|
||||||
- \1
|
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
- uuid: 8706eccb7edc4fa394f552fc31f401a9
|
- uuid: 8706eccb7edc4fa394f552fc31f401a9
|
||||||
name: 'Max Frozen XID Age'
|
name: 'Max Frozen XID Age'
|
||||||
key: 'web.page.get[localhost,/max_frozen_age,{$AGENT_PORT}]'
|
type: HTTP_AGENT
|
||||||
|
key: 'pgmon[max_frozen_age]'
|
||||||
history: 90d
|
history: 90d
|
||||||
trends: '0'
|
|
||||||
description: 'Maximum age of any frozen XID in any database'
|
description: 'Maximum age of any frozen XID in any database'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
- type: REGEX
|
|
||||||
parameters:
|
|
||||||
- '\n\s?\n([\s\S]*)'
|
|
||||||
- \1
|
|
||||||
- type: MATCHES_REGEX
|
- type: MATCHES_REGEX
|
||||||
parameters:
|
parameters:
|
||||||
- '^[0-9]+$'
|
- '^[0-9]+$'
|
||||||
|
url: 'http://localhost:{$AGENT_PORT}/max_frozen_age'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
- uuid: ee88f5f4d2384f97946d049af5af4502
|
- uuid: ee88f5f4d2384f97946d049af5af4502
|
||||||
name: 'PostgreSQL version'
|
name: 'PostgreSQL version'
|
||||||
key: 'web.page.get[localhost,/version,{$AGENT_PORT}]'
|
type: HTTP_AGENT
|
||||||
|
key: 'pgmon[version]'
|
||||||
delay: 1h
|
delay: 1h
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'PostgreSQL Server version number'
|
description: 'PostgreSQL Server version number'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
- type: REGEX
|
|
||||||
parameters:
|
|
||||||
- '\n\s?\n([\s\S]*)'
|
|
||||||
- \1
|
|
||||||
- type: MATCHES_REGEX
|
- type: MATCHES_REGEX
|
||||||
parameters:
|
parameters:
|
||||||
- '^[0-9]+$'
|
- '^[0-9]+$'
|
||||||
|
url: 'http://localhost:{$AGENT_PORT}/version'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
discovery_rules:
|
discovery_rules:
|
||||||
- uuid: 085de335305e435dbb4439bd52e0d35d
|
- uuid: 085de335305e435dbb4439bd52e0d35d
|
||||||
name: 'Discover Databases'
|
name: 'Discover Databases'
|
||||||
key: 'web.page.get[localhost,/discover_dbs,{$AGENT_PORT}]'
|
type: HTTP_AGENT
|
||||||
|
key: pgmon_discover_dbs
|
||||||
delay: 10m
|
delay: 10m
|
||||||
|
filter:
|
||||||
|
conditions:
|
||||||
|
- macro: '{#DBNAME}'
|
||||||
|
value: ^template0$
|
||||||
|
operator: NOT_MATCHES_REGEX
|
||||||
|
formulaid: A
|
||||||
lifetime: 30d
|
lifetime: 30d
|
||||||
enabled_lifetime_type: DISABLE_NEVER
|
enabled_lifetime_type: DISABLE_NEVER
|
||||||
item_prototypes:
|
item_prototypes:
|
||||||
@@ -72,6 +72,7 @@ zabbix_export:
|
|||||||
name: 'Time spent executing statements on {#DBNAME}'
|
name: 'Time spent executing statements on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[active_time,{#DBNAME}]'
|
key: 'pgmon_db[active_time,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: FLOAT
|
value_type: FLOAT
|
||||||
units: s
|
units: s
|
||||||
@@ -84,7 +85,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- '0.001'
|
- '0.001'
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -94,6 +95,7 @@ zabbix_export:
|
|||||||
name: 'Number of backends on {#DBNAME}'
|
name: 'Number of backends on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[backends,{#DBNAME}]'
|
key: 'pgmon_db[backends,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of backends currently connected to this database, or NULL for shared objects. This is the only column in this view that returns a value reflecting current state; all other columns return the accumulated values since the last reset.'
|
description: 'Number of backends currently connected to this database, or NULL for shared objects. This is the only column in this view that returns a value reflecting current state; all other columns return the accumulated values since the last reset.'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -101,7 +103,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.numbackends
|
- $.numbackends
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -111,6 +113,7 @@ zabbix_export:
|
|||||||
name: 'Blocks hit on {#DBNAME}'
|
name: 'Blocks hit on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[blks_hit,{#DBNAME}]'
|
key: 'pgmon_db[blks_hit,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of times disk blocks were found already in the buffer cache, so that a read was not necessary (this only includes hits in the PostgreSQL buffer cache, not the operating system''s file system cache)'
|
description: 'Number of times disk blocks were found already in the buffer cache, so that a read was not necessary (this only includes hits in the PostgreSQL buffer cache, not the operating system''s file system cache)'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -118,7 +121,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.blks_hit
|
- $.blks_hit
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -128,6 +131,7 @@ zabbix_export:
|
|||||||
name: 'Blocks read on {#DBNAME}'
|
name: 'Blocks read on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[blks_read,{#DBNAME}]'
|
key: 'pgmon_db[blks_read,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of disk blocks read in this database'
|
description: 'Number of disk blocks read in this database'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -135,7 +139,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.blks_read
|
- $.blks_read
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -145,6 +149,7 @@ zabbix_export:
|
|||||||
name: 'Time spent reading blocks on {#DBNAME}'
|
name: 'Time spent reading blocks on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[blk_read_time,{#DBNAME}]'
|
key: 'pgmon_db[blk_read_time,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: FLOAT
|
value_type: FLOAT
|
||||||
units: s
|
units: s
|
||||||
@@ -157,7 +162,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- '0.001'
|
- '0.001'
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -167,6 +172,7 @@ zabbix_export:
|
|||||||
name: 'Time spent writing blocks on {#DBNAME}'
|
name: 'Time spent writing blocks on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[blk_write_time,{#DBNAME}]'
|
key: 'pgmon_db[blk_write_time,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: FLOAT
|
value_type: FLOAT
|
||||||
units: s
|
units: s
|
||||||
@@ -179,7 +185,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- '0.001'
|
- '0.001'
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -189,6 +195,7 @@ zabbix_export:
|
|||||||
name: 'Total number of checksum failures on {#DBNAME}'
|
name: 'Total number of checksum failures on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[checksum_failures,{#DBNAME}]'
|
key: 'pgmon_db[checksum_failures,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of data page checksum failures detected in this database (or on a shared object), or NULL if data checksums are not enabled.'
|
description: 'Number of data page checksum failures detected in this database (or on a shared object), or NULL if data checksums are not enabled.'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -201,7 +208,7 @@ zabbix_export:
|
|||||||
error_handler: CUSTOM_VALUE
|
error_handler: CUSTOM_VALUE
|
||||||
error_handler_params: '0'
|
error_handler_params: '0'
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -211,6 +218,7 @@ zabbix_export:
|
|||||||
name: 'Total number of conflicts on {#DBNAME}'
|
name: 'Total number of conflicts on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[conflicts,{#DBNAME}]'
|
key: 'pgmon_db[conflicts,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of queries canceled due to conflicts with recovery in this database. (Conflicts occur only on standby servers; see pg_stat_database_conflicts for details.)'
|
description: 'Number of queries canceled due to conflicts with recovery in this database. (Conflicts occur only on standby servers; see pg_stat_database_conflicts for details.)'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -218,7 +226,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.conflicts
|
- $.conflicts
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -228,6 +236,7 @@ zabbix_export:
|
|||||||
name: 'Total number of deadlocks on {#DBNAME}'
|
name: 'Total number of deadlocks on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[deadlocks,{#DBNAME}]'
|
key: 'pgmon_db[deadlocks,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of deadlocks detected in this database'
|
description: 'Number of deadlocks detected in this database'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -235,7 +244,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.deadlocks
|
- $.deadlocks
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -245,6 +254,7 @@ zabbix_export:
|
|||||||
name: 'Time spent in idle transactions on {#DBNAME}'
|
name: 'Time spent in idle transactions on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[idle_in_transaction_time,{#DBNAME}]'
|
key: 'pgmon_db[idle_in_transaction_time,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: FLOAT
|
value_type: FLOAT
|
||||||
units: s
|
units: s
|
||||||
@@ -257,7 +267,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- '0.001'
|
- '0.001'
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -267,6 +277,7 @@ zabbix_export:
|
|||||||
name: 'Total number of sessions on {#DBNAME}'
|
name: 'Total number of sessions on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[sessions,{#DBNAME}]'
|
key: 'pgmon_db[sessions,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Total number of sessions established to this database'
|
description: 'Total number of sessions established to this database'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -274,7 +285,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.sessions
|
- $.sessions
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -284,6 +295,7 @@ zabbix_export:
|
|||||||
name: 'Total number of abandoned sessions on {#DBNAME}'
|
name: 'Total number of abandoned sessions on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[sessions_abandoned,{#DBNAME}]'
|
key: 'pgmon_db[sessions_abandoned,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of database sessions to this database that were terminated because connection to the client was lost'
|
description: 'Number of database sessions to this database that were terminated because connection to the client was lost'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -291,7 +303,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.sessions_abandoned
|
- $.sessions_abandoned
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -301,6 +313,7 @@ zabbix_export:
|
|||||||
name: 'Total number of fatal sessions on {#DBNAME}'
|
name: 'Total number of fatal sessions on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[sessions_fatal,{#DBNAME}]'
|
key: 'pgmon_db[sessions_fatal,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of database sessions to this database that were terminated by fatal errors'
|
description: 'Number of database sessions to this database that were terminated by fatal errors'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -308,7 +321,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.sessions_fatal
|
- $.sessions_fatal
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -318,6 +331,7 @@ zabbix_export:
|
|||||||
name: 'Total number of terminated sessions on {#DBNAME}'
|
name: 'Total number of terminated sessions on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[sessions_killed,{#DBNAME}]'
|
key: 'pgmon_db[sessions_killed,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of database sessions to this database that were terminated by operator intervention'
|
description: 'Number of database sessions to this database that were terminated by operator intervention'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -325,7 +339,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.sessions_killed
|
- $.sessions_killed
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -335,6 +349,7 @@ zabbix_export:
|
|||||||
name: 'Total temp file size on {#DBNAME}'
|
name: 'Total temp file size on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[temp_bytes,{#DBNAME}]'
|
key: 'pgmon_db[temp_bytes,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
units: b
|
units: b
|
||||||
description: 'Total amount of data written to temporary files by queries in this database. All temporary files are counted, regardless of why the temporary file was created, and regardless of the log_temp_files setting.'
|
description: 'Total amount of data written to temporary files by queries in this database. All temporary files are counted, regardless of why the temporary file was created, and regardless of the log_temp_files setting.'
|
||||||
@@ -343,7 +358,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.temp_bytes
|
- $.temp_bytes
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -353,6 +368,7 @@ zabbix_export:
|
|||||||
name: 'Total number of temp files on {#DBNAME}'
|
name: 'Total number of temp files on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[temp_files,{#DBNAME}]'
|
key: 'pgmon_db[temp_files,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of temporary files created by queries in this database. All temporary files are counted, regardless of why the temporary file was created (e.g., sorting or hashing), and regardless of the log_temp_files setting.'
|
description: 'Number of temporary files created by queries in this database. All temporary files are counted, regardless of why the temporary file was created (e.g., sorting or hashing), and regardless of the log_temp_files setting.'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -360,7 +376,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.temp_files
|
- $.temp_files
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -370,6 +386,7 @@ zabbix_export:
|
|||||||
name: 'Tuples deleted on {#DBNAME}'
|
name: 'Tuples deleted on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[tup_deleted,{#DBNAME}]'
|
key: 'pgmon_db[tup_deleted,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of rows deleted by queries in this database'
|
description: 'Number of rows deleted by queries in this database'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -377,7 +394,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.tup_deleted
|
- $.tup_deleted
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -387,6 +404,7 @@ zabbix_export:
|
|||||||
name: 'Tuples fetched by index scans on {#DBNAME}'
|
name: 'Tuples fetched by index scans on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[tup_fetched,{#DBNAME}]'
|
key: 'pgmon_db[tup_fetched,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of live rows fetched by index scans in this database'
|
description: 'Number of live rows fetched by index scans in this database'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -394,7 +412,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.tup_fetched
|
- $.tup_fetched
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -404,6 +422,7 @@ zabbix_export:
|
|||||||
name: 'Tuples inserted on {#DBNAME}'
|
name: 'Tuples inserted on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[tup_inserted,{#DBNAME}]'
|
key: 'pgmon_db[tup_inserted,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of rows inserted by queries in this database'
|
description: 'Number of rows inserted by queries in this database'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -411,7 +430,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.tup_inserted
|
- $.tup_inserted
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -421,6 +440,7 @@ zabbix_export:
|
|||||||
name: 'Tuples returned by sequential scans on {#DBNAME}'
|
name: 'Tuples returned by sequential scans on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[tup_returned,{#DBNAME}]'
|
key: 'pgmon_db[tup_returned,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of live rows fetched by sequential scans and index entries returned by index scans in this database'
|
description: 'Number of live rows fetched by sequential scans and index entries returned by index scans in this database'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -428,7 +448,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.tup_returned
|
- $.tup_returned
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -438,6 +458,7 @@ zabbix_export:
|
|||||||
name: 'Tuples updated on {#DBNAME}'
|
name: 'Tuples updated on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[tup_updated,{#DBNAME}]'
|
key: 'pgmon_db[tup_updated,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of rows updated by queries in this database'
|
description: 'Number of rows updated by queries in this database'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -445,7 +466,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.tup_updated
|
- $.tup_updated
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -455,6 +476,7 @@ zabbix_export:
|
|||||||
name: 'Total number of commits on {#DBNAME}'
|
name: 'Total number of commits on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[xact_commit,{#DBNAME}]'
|
key: 'pgmon_db[xact_commit,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of transactions in this database that have been committed'
|
description: 'Number of transactions in this database that have been committed'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -462,7 +484,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.xact_commit
|
- $.xact_commit
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -472,6 +494,7 @@ zabbix_export:
|
|||||||
name: 'Total number of rollbacks on {#DBNAME}'
|
name: 'Total number of rollbacks on {#DBNAME}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_db[xact_rollback,{#DBNAME}]'
|
key: 'pgmon_db[xact_rollback,{#DBNAME}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
description: 'Number of transactions in this database that have been rolled back'
|
description: 'Number of transactions in this database that have been rolled back'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
@@ -479,7 +502,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.xact_rollback
|
- $.xact_rollback
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -497,6 +520,8 @@ zabbix_export:
|
|||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
- tag: Database
|
- tag: Database
|
||||||
value: '{#DBNAME}'
|
value: '{#DBNAME}'
|
||||||
|
- tag: Type
|
||||||
|
value: Calculated
|
||||||
- uuid: a81ba5a7c96d40bca9cc0861da574b49
|
- uuid: a81ba5a7c96d40bca9cc0861da574b49
|
||||||
name: 'Tuples fetched by index scans on {#DBNAME} - 1m delta'
|
name: 'Tuples fetched by index scans on {#DBNAME} - 1m delta'
|
||||||
type: CALCULATED
|
type: CALCULATED
|
||||||
@@ -508,6 +533,8 @@ zabbix_export:
|
|||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
- tag: Database
|
- tag: Database
|
||||||
value: '{#DBNAME}'
|
value: '{#DBNAME}'
|
||||||
|
- tag: Type
|
||||||
|
value: Calculated
|
||||||
- uuid: 3bfa9b7db9394b6d9e0cac6255524f50
|
- uuid: 3bfa9b7db9394b6d9e0cac6255524f50
|
||||||
name: 'Tuples fetched by index scans on {#DBNAME} - 5m delta'
|
name: 'Tuples fetched by index scans on {#DBNAME} - 5m delta'
|
||||||
type: CALCULATED
|
type: CALCULATED
|
||||||
@@ -519,6 +546,8 @@ zabbix_export:
|
|||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
- tag: Database
|
- tag: Database
|
||||||
value: '{#DBNAME}'
|
value: '{#DBNAME}'
|
||||||
|
- tag: Type
|
||||||
|
value: Calculated
|
||||||
- uuid: 261e83e1e87c42d587a9409a8a26f971
|
- uuid: 261e83e1e87c42d587a9409a8a26f971
|
||||||
name: 'Tuples returned by sequential scans on {#DBNAME} - 1h delta'
|
name: 'Tuples returned by sequential scans on {#DBNAME} - 1h delta'
|
||||||
type: CALCULATED
|
type: CALCULATED
|
||||||
@@ -531,6 +560,8 @@ zabbix_export:
|
|||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
- tag: Database
|
- tag: Database
|
||||||
value: '{#DBNAME}'
|
value: '{#DBNAME}'
|
||||||
|
- tag: Type
|
||||||
|
value: Calculated
|
||||||
- uuid: e46c4ec2a21d48288337b90549fbf757
|
- uuid: e46c4ec2a21d48288337b90549fbf757
|
||||||
name: 'Tuples returned by sequential scans on {#DBNAME} - 1m delta'
|
name: 'Tuples returned by sequential scans on {#DBNAME} - 1m delta'
|
||||||
type: CALCULATED
|
type: CALCULATED
|
||||||
@@ -543,6 +574,8 @@ zabbix_export:
|
|||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
- tag: Database
|
- tag: Database
|
||||||
value: '{#DBNAME}'
|
value: '{#DBNAME}'
|
||||||
|
- tag: Type
|
||||||
|
value: Calculated
|
||||||
- uuid: 26a42b55d7f949588f0739388ff52831
|
- uuid: 26a42b55d7f949588f0739388ff52831
|
||||||
name: 'Tuples returned by sequential scans on {#DBNAME} - 5m delta'
|
name: 'Tuples returned by sequential scans on {#DBNAME} - 5m delta'
|
||||||
type: CALCULATED
|
type: CALCULATED
|
||||||
@@ -555,24 +588,88 @@ zabbix_export:
|
|||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
- tag: Database
|
- tag: Database
|
||||||
value: '{#DBNAME}'
|
value: '{#DBNAME}'
|
||||||
|
- tag: Type
|
||||||
|
value: Calculated
|
||||||
- uuid: 492b3cac15f348c2b85f97b69c114d1b
|
- uuid: 492b3cac15f348c2b85f97b69c114d1b
|
||||||
name: 'Database Stats for {#DBNAME}'
|
name: 'Database Stats for {#DBNAME}'
|
||||||
key: 'web.page.get[localhost,/db_stats?dbname={#DBNAME},{$AGENT_PORT}]'
|
type: HTTP_AGENT
|
||||||
|
key: 'pgmon_db_stats[{#DBNAME}]'
|
||||||
history: '0'
|
history: '0'
|
||||||
value_type: TEXT
|
value_type: TEXT
|
||||||
preprocessing:
|
trends: '0'
|
||||||
- type: REGEX
|
url: 'http://localhost:{$AGENT_PORT}/db_stats'
|
||||||
parameters:
|
query_fields:
|
||||||
- '\n\s?\n([\s\S]*)'
|
- name: dbname
|
||||||
- \1
|
value: '{#DBNAME}'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
- tag: Database
|
- tag: Database
|
||||||
value: '{#DBNAME}'
|
value: '{#DBNAME}'
|
||||||
graph_prototypes:
|
graph_prototypes:
|
||||||
|
- uuid: 1f7de43b77714f819e61c31273712b70
|
||||||
|
name: 'DML Totals for {#DBNAME}'
|
||||||
|
graph_items:
|
||||||
|
- color: 199C0D
|
||||||
|
calc_fnc: ALL
|
||||||
|
item:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
key: 'pgmon_db[tup_deleted,{#DBNAME}]'
|
||||||
|
- sortorder: '1'
|
||||||
|
color: F63100
|
||||||
|
calc_fnc: ALL
|
||||||
|
item:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
key: 'pgmon_db[tup_inserted,{#DBNAME}]'
|
||||||
|
- sortorder: '2'
|
||||||
|
color: 2774A4
|
||||||
|
calc_fnc: ALL
|
||||||
|
item:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
key: 'pgmon_db[tup_updated,{#DBNAME}]'
|
||||||
|
- uuid: aaec1de6f2314cbd875980fc1be3a2db
|
||||||
|
name: 'Sessions for {#DBNAME}'
|
||||||
|
graph_items:
|
||||||
|
- color: 199C0D
|
||||||
|
calc_fnc: ALL
|
||||||
|
item:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
key: 'pgmon_db[sessions_fatal,{#DBNAME}]'
|
||||||
|
- sortorder: '1'
|
||||||
|
color: F63100
|
||||||
|
calc_fnc: ALL
|
||||||
|
item:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
key: 'pgmon_db[xact_rollback,{#DBNAME}]'
|
||||||
|
- sortorder: '2'
|
||||||
|
color: 2774A4
|
||||||
|
calc_fnc: ALL
|
||||||
|
item:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
key: 'pgmon_db[sessions,{#DBNAME}]'
|
||||||
|
- sortorder: '3'
|
||||||
|
color: F7941D
|
||||||
|
calc_fnc: ALL
|
||||||
|
item:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
key: 'pgmon_db[sessions_killed,{#DBNAME}]'
|
||||||
|
- uuid: a08d60bd0ffb4f90b5411d30a057a85e
|
||||||
|
name: 'Temp Files for {#DBNAME}'
|
||||||
|
graph_items:
|
||||||
|
- color: 199C0D
|
||||||
|
calc_fnc: ALL
|
||||||
|
item:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
key: 'pgmon_db[temp_files,{#DBNAME}]'
|
||||||
|
- sortorder: '1'
|
||||||
|
color: F63100
|
||||||
|
yaxisside: RIGHT
|
||||||
|
calc_fnc: ALL
|
||||||
|
item:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
key: 'pgmon_db[temp_bytes,{#DBNAME}]'
|
||||||
- uuid: 4949cdfbda614af796a2856fdfa9ac3f
|
- uuid: 4949cdfbda614af796a2856fdfa9ac3f
|
||||||
name: 'Time breakdown for {#DBNAME} on {#CLUSTER}'
|
name: 'Time breakdown for {#DBNAME}'
|
||||||
graph_items:
|
graph_items:
|
||||||
- color: 199C0D
|
- color: 199C0D
|
||||||
calc_fnc: ALL
|
calc_fnc: ALL
|
||||||
@@ -597,17 +694,14 @@ zabbix_export:
|
|||||||
item:
|
item:
|
||||||
host: 'PostgreSQL by pgmon'
|
host: 'PostgreSQL by pgmon'
|
||||||
key: 'pgmon_db[blk_write_time,{#DBNAME}]'
|
key: 'pgmon_db[blk_write_time,{#DBNAME}]'
|
||||||
|
url: 'http://localhost:{$AGENT_PORT}/discover_dbs'
|
||||||
lld_macro_paths:
|
lld_macro_paths:
|
||||||
- lld_macro: '{#DBNAME}'
|
- lld_macro: '{#DBNAME}'
|
||||||
path: $.dbname
|
path: $.dbname
|
||||||
preprocessing:
|
|
||||||
- type: REGEX
|
|
||||||
parameters:
|
|
||||||
- '\n\s?\n([\s\S]*)'
|
|
||||||
- \1
|
|
||||||
- uuid: 8ec029d577ae4872858e2e5cfd1cc40e
|
- uuid: 8ec029d577ae4872858e2e5cfd1cc40e
|
||||||
name: 'Discover Replication'
|
name: 'Discover Replication'
|
||||||
key: 'web.page.get[localhost,/discover_rep,{$AGENT_PORT}]'
|
type: HTTP_AGENT
|
||||||
|
key: pgmon_discover_rep
|
||||||
delay: 10m
|
delay: 10m
|
||||||
lifetime: 30d
|
lifetime: 30d
|
||||||
enabled_lifetime_type: DISABLE_NEVER
|
enabled_lifetime_type: DISABLE_NEVER
|
||||||
@@ -616,6 +710,7 @@ zabbix_export:
|
|||||||
name: 'Flush lag for {#REPID}'
|
name: 'Flush lag for {#REPID}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_rep[flush_lag,repid={#REPID}]'
|
key: 'pgmon_rep[flush_lag,repid={#REPID}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: FLOAT
|
value_type: FLOAT
|
||||||
description: 'Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it (but not yet applied it). This can be used to gauge the delay that synchronous_commit level on incurred while committing if this server was configured as a synchronous standby.'
|
description: 'Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it (but not yet applied it). This can be used to gauge the delay that synchronous_commit level on incurred while committing if this server was configured as a synchronous standby.'
|
||||||
@@ -624,7 +719,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.flush_lag
|
- $.flush_lag
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/rep_stats?repid={#REPID},{$AGENT_PORT}]'
|
key: 'pgmon_rep_stats[{#REPID}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -636,15 +731,17 @@ zabbix_export:
|
|||||||
name: 'Last flush LSN for {#REPID}'
|
name: 'Last flush LSN for {#REPID}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_rep[flush_lsn,repid={#REPID}]'
|
key: 'pgmon_rep[flush_lsn,repid={#REPID}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: TEXT
|
value_type: TEXT
|
||||||
|
trends: '0'
|
||||||
description: 'Last write-ahead log location flushed to disk by this standby server'
|
description: 'Last write-ahead log location flushed to disk by this standby server'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
- type: JSONPATH
|
- type: JSONPATH
|
||||||
parameters:
|
parameters:
|
||||||
- $.flush_lsn
|
- $.flush_lsn
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/rep_stats?repid={#REPID},{$AGENT_PORT}]'
|
key: 'pgmon_rep_stats[{#REPID}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -656,6 +753,7 @@ zabbix_export:
|
|||||||
name: 'Replay lag for {#REPID}'
|
name: 'Replay lag for {#REPID}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_rep[replay_lag,repid={#REPID}]'
|
key: 'pgmon_rep[replay_lag,repid={#REPID}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: FLOAT
|
value_type: FLOAT
|
||||||
description: 'Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it. This can be used to gauge the delay that synchronous_commit level remote_apply incurred while committing if this server was configured as a synchronous standby.'
|
description: 'Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it. This can be used to gauge the delay that synchronous_commit level remote_apply incurred while committing if this server was configured as a synchronous standby.'
|
||||||
@@ -664,7 +762,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.replay_lag
|
- $.replay_lag
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/rep_stats?repid={#REPID},{$AGENT_PORT}]'
|
key: 'pgmon_rep_stats[{#REPID}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -676,15 +774,17 @@ zabbix_export:
|
|||||||
name: 'Last replay LSN for {#REPID}'
|
name: 'Last replay LSN for {#REPID}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_rep[replay_lsn,repid={#REPID}]'
|
key: 'pgmon_rep[replay_lsn,repid={#REPID}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: TEXT
|
value_type: TEXT
|
||||||
|
trends: '0'
|
||||||
description: 'Last write-ahead log location replayed into the database on this standby server'
|
description: 'Last write-ahead log location replayed into the database on this standby server'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
- type: JSONPATH
|
- type: JSONPATH
|
||||||
parameters:
|
parameters:
|
||||||
- $.replay_lsn
|
- $.replay_lsn
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/rep_stats?repid={#REPID},{$AGENT_PORT}]'
|
key: 'pgmon_rep_stats[{#REPID}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -696,15 +796,17 @@ zabbix_export:
|
|||||||
name: 'Last sent LSN for {#REPID}'
|
name: 'Last sent LSN for {#REPID}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_rep[sent_lsn,repid={#REPID}]'
|
key: 'pgmon_rep[sent_lsn,repid={#REPID}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: TEXT
|
value_type: TEXT
|
||||||
|
trends: '0'
|
||||||
description: 'Last write-ahead log location sent on this connection'
|
description: 'Last write-ahead log location sent on this connection'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
- type: JSONPATH
|
- type: JSONPATH
|
||||||
parameters:
|
parameters:
|
||||||
- $.sent_lsn
|
- $.sent_lsn
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/rep_stats?repid={#REPID},{$AGENT_PORT}]'
|
key: 'pgmon_rep_stats[{#REPID}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -716,8 +818,10 @@ zabbix_export:
|
|||||||
name: 'Replication state for {#REPID}'
|
name: 'Replication state for {#REPID}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_rep[state,repid={#REPID}]'
|
key: 'pgmon_rep[state,repid={#REPID}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: TEXT
|
value_type: TEXT
|
||||||
|
trends: '0'
|
||||||
description: |
|
description: |
|
||||||
Current WAL sender state. Possible values are:
|
Current WAL sender state. Possible values are:
|
||||||
* startup: This WAL sender is starting up.
|
* startup: This WAL sender is starting up.
|
||||||
@@ -730,7 +834,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.state
|
- $.state
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/rep_stats?repid={#REPID},{$AGENT_PORT}]'
|
key: 'pgmon_rep_stats[{#REPID}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -742,6 +846,7 @@ zabbix_export:
|
|||||||
name: 'Write lag for {#REPID}'
|
name: 'Write lag for {#REPID}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_rep[write_lag,repid={#REPID}]'
|
key: 'pgmon_rep[write_lag,repid={#REPID}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: FLOAT
|
value_type: FLOAT
|
||||||
description: 'Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it (but not yet flushed it or applied it). This can be used to gauge the delay that synchronous_commit level remote_write incurred while committing if this server was configured as a synchronous standby.'
|
description: 'Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it (but not yet flushed it or applied it). This can be used to gauge the delay that synchronous_commit level remote_write incurred while committing if this server was configured as a synchronous standby.'
|
||||||
@@ -750,7 +855,7 @@ zabbix_export:
|
|||||||
parameters:
|
parameters:
|
||||||
- $.write_lag
|
- $.write_lag
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/rep_stats?repid={#REPID},{$AGENT_PORT}]'
|
key: 'pgmon_rep_stats[{#REPID}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -762,15 +867,17 @@ zabbix_export:
|
|||||||
name: 'Last write LSN for {#REPID}'
|
name: 'Last write LSN for {#REPID}'
|
||||||
type: DEPENDENT
|
type: DEPENDENT
|
||||||
key: 'pgmon_rep[write_lsn,repid={#REPID}]'
|
key: 'pgmon_rep[write_lsn,repid={#REPID}]'
|
||||||
|
delay: '0'
|
||||||
history: 90d
|
history: 90d
|
||||||
value_type: TEXT
|
value_type: TEXT
|
||||||
|
trends: '0'
|
||||||
description: 'Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it (but not yet flushed it or applied it). This can be used to gauge the delay that synchronous_commit level remote_write incurred while committing if this server was configured as a synchronous standby.'
|
description: 'Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it (but not yet flushed it or applied it). This can be used to gauge the delay that synchronous_commit level remote_write incurred while committing if this server was configured as a synchronous standby.'
|
||||||
preprocessing:
|
preprocessing:
|
||||||
- type: JSONPATH
|
- type: JSONPATH
|
||||||
parameters:
|
parameters:
|
||||||
- $.write_lsn
|
- $.write_lsn
|
||||||
master_item:
|
master_item:
|
||||||
key: 'web.page.get[localhost,/rep_stats?repid={#REPID},{$AGENT_PORT}]'
|
key: 'pgmon_rep_stats[{#REPID}]'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -780,14 +887,15 @@ zabbix_export:
|
|||||||
value: '{#DBNAME}'
|
value: '{#DBNAME}'
|
||||||
- uuid: efbe11f37c2f499488bdc5853c3d89e6
|
- uuid: efbe11f37c2f499488bdc5853c3d89e6
|
||||||
name: 'Replication Stats for {#REPID}'
|
name: 'Replication Stats for {#REPID}'
|
||||||
key: 'web.page.get[localhost,/rep_stats?repid={#REPID},{$AGENT_PORT}]'
|
type: HTTP_AGENT
|
||||||
|
key: 'pgmon_rep_stats[{#REPID}]'
|
||||||
history: '0'
|
history: '0'
|
||||||
value_type: TEXT
|
value_type: TEXT
|
||||||
preprocessing:
|
trends: '0'
|
||||||
- type: REGEX
|
url: 'http://localhost:{$AGENT_PORT}/rep_stats'
|
||||||
parameters:
|
query_fields:
|
||||||
- '\n\s?\n([\s\S]*)'
|
- name: repid
|
||||||
- \1
|
value: '{#REPID}'
|
||||||
tags:
|
tags:
|
||||||
- tag: Application
|
- tag: Application
|
||||||
value: PostgreSQL
|
value: PostgreSQL
|
||||||
@@ -795,6 +903,7 @@ zabbix_export:
|
|||||||
value: Replication
|
value: Replication
|
||||||
- tag: Database
|
- tag: Database
|
||||||
value: '{#DBNAME}'
|
value: '{#DBNAME}'
|
||||||
|
url: 'http://localhost:{$AGENT_PORT}/discover_rep'
|
||||||
lld_macro_paths:
|
lld_macro_paths:
|
||||||
- lld_macro: '{#CLIENT_ADDR}'
|
- lld_macro: '{#CLIENT_ADDR}'
|
||||||
path: $.client_addr
|
path: $.client_addr
|
||||||
@@ -802,12 +911,80 @@ zabbix_export:
|
|||||||
path: $.repid
|
path: $.repid
|
||||||
- lld_macro: '{#STATE}'
|
- lld_macro: '{#STATE}'
|
||||||
path: $.state
|
path: $.state
|
||||||
preprocessing:
|
|
||||||
- type: REGEX
|
|
||||||
parameters:
|
|
||||||
- '\n\s?\n([\s\S]*)'
|
|
||||||
- \1
|
|
||||||
macros:
|
macros:
|
||||||
- macro: '{$AGENT_PORT}'
|
- macro: '{$AGENT_PORT}'
|
||||||
value: '5400'
|
value: '5400'
|
||||||
description: 'The port the agent listens on'
|
description: 'The port the agent listens on'
|
||||||
|
dashboards:
|
||||||
|
- uuid: a818cfb97d654c75a3d70ae7f942bb89
|
||||||
|
name: 'PostgreSQL - Overview'
|
||||||
|
pages:
|
||||||
|
- widgets:
|
||||||
|
- type: graphprototype
|
||||||
|
name: 'Time Breakdown'
|
||||||
|
width: '39'
|
||||||
|
height: '5'
|
||||||
|
fields:
|
||||||
|
- type: INTEGER
|
||||||
|
name: columns
|
||||||
|
value: '1'
|
||||||
|
- type: GRAPH_PROTOTYPE
|
||||||
|
name: graphid.0
|
||||||
|
value:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
name: 'Time breakdown for {#DBNAME}'
|
||||||
|
- type: STRING
|
||||||
|
name: reference
|
||||||
|
value: ZAWWD
|
||||||
|
- type: graphprototype
|
||||||
|
name: 'Session Breakdown'
|
||||||
|
'y': '5'
|
||||||
|
width: '39'
|
||||||
|
height: '6'
|
||||||
|
fields:
|
||||||
|
- type: INTEGER
|
||||||
|
name: columns
|
||||||
|
value: '1'
|
||||||
|
- type: GRAPH_PROTOTYPE
|
||||||
|
name: graphid.0
|
||||||
|
value:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
name: 'Sessions for {#DBNAME}'
|
||||||
|
- type: STRING
|
||||||
|
name: reference
|
||||||
|
value: WBVFA
|
||||||
|
- type: graphprototype
|
||||||
|
name: 'Temp Files'
|
||||||
|
x: '39'
|
||||||
|
width: '33'
|
||||||
|
height: '5'
|
||||||
|
fields:
|
||||||
|
- type: INTEGER
|
||||||
|
name: columns
|
||||||
|
value: '1'
|
||||||
|
- type: GRAPH_PROTOTYPE
|
||||||
|
name: graphid.0
|
||||||
|
value:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
name: 'Temp Files for {#DBNAME}'
|
||||||
|
- type: STRING
|
||||||
|
name: reference
|
||||||
|
value: CFQAN
|
||||||
|
- type: graphprototype
|
||||||
|
name: 'DML Totals'
|
||||||
|
x: '39'
|
||||||
|
'y': '5'
|
||||||
|
width: '33'
|
||||||
|
height: '6'
|
||||||
|
fields:
|
||||||
|
- type: INTEGER
|
||||||
|
name: columns
|
||||||
|
value: '1'
|
||||||
|
- type: GRAPH_PROTOTYPE
|
||||||
|
name: graphid.0
|
||||||
|
value:
|
||||||
|
host: 'PostgreSQL by pgmon'
|
||||||
|
name: 'DML Totals for {#DBNAME}'
|
||||||
|
- type: STRING
|
||||||
|
name: reference
|
||||||
|
value: XSCMZ
|
||||||
|
|||||||
Reference in New Issue
Block a user