├── .gitignore ├── .travis.yml ├── COPYING ├── ChangeLog ├── Makefile.am ├── README.md ├── autogen.sh ├── conf └── libzbxpgsql.conf ├── configure.ac ├── m4 ├── ax_compare_version.m4 ├── ax_lib_postgresql.m4 └── ax_lib_zabbix.m4 ├── src ├── Makefile.am ├── libzbxpgsql.c ├── libzbxpgsql.h ├── pg_backends.c ├── pg_bgwriter.c ├── pg_config.c ├── pg_connect.c ├── pg_database.c ├── pg_discovery.c ├── pg_index.c ├── pg_namespace.c ├── pg_params.c ├── pg_query.c ├── pg_server.c ├── pg_setting.c ├── pg_table.c └── pg_tablespace.c └── templates ├── Makefile ├── Template_PostgreSQL_Server_2.0.xml ├── Template_PostgreSQL_Server_2.4.xml └── Template_PostgreSQL_Server_3.0.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.build 2 | *.changes 3 | *.deb 4 | *.dsc 5 | *.in 6 | *.la 7 | *.lineno 8 | *.lo 9 | *.o 10 | *.orig 11 | *.rpm 12 | *.so 13 | *.tar.gz 14 | *.tgz 15 | *.xz 16 | *~ 17 | .deps/ 18 | .libs/ 19 | .vagrant/ 20 | /Makefile 21 | aclocal.m4 22 | ar-lib 23 | autom4te.cache/ 24 | compile 25 | config.guess 26 | config.h 27 | config.h.in 28 | config.log 29 | config.status 30 | config.sub 31 | configure 32 | configure.in 33 | depcomp 34 | docker/*.sql 35 | docker/dell* 36 | install-sh 37 | libtool 38 | libzbxpgsql-*/ 39 | ltmain.sh 40 | m4/ 41 | missing 42 | src/**/Makefile 43 | stamp-h1 44 | zabbix-*/ 45 | zabbix_agent_bench 46 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | # allow containerized build 4 | sudo: false 5 | 6 | # Build deps 7 | addons: 8 | apt: 9 | packages: 10 | - devscripts 11 | - libconfig-dev 12 | - libpq-dev 13 | 14 | # before build script, run autoreconf 15 | before_script: ./autogen.sh 16 | 17 | # Default is "./configure && make && make test", but no tests yet 18 | script: 19 | - "curl -sL -o /tmp/zabbix-3.0.0.tar.gz http://sourceforge.net/projects/zabbix/files/ZABBIX%20Latest%20Stable/3.0.0/zabbix-3.0.0.tar.gz" 20 | - "tar -xzC /tmp -f /tmp/zabbix-3.0.0.tar.gz" 21 | - "./configure --with-zabbix=/tmp/zabbix-3.0.0/include && make" 22 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | libzbxpgsql - A PostgreSQL monitoring module for Zabbix 2 | Copyright (C) 2016 - Ryan Armstrong 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | v1.1.0 19 Aug 2016 2 | 3 | * Implemented `pg.query2.*` keys - Rob Brucks 4 | 5 | v1.0.0 26 Jun 2016 6 | 7 | * Added support for Zabbix v3 8 | * Added multi-database support for discovering schema, tables and indexes 9 | * Added error messages to failed requests 10 | * Monitoring connections are no longer counted when monitoring backend 11 | connection counts 12 | * Added `pg.db.xid_age` to monitor the allocation of Transaction IDs 13 | * Added `pg.table.*_perc` keys to measure cache hit ratios for tables 14 | * Added `pg.checkpoint_avg_interval` to return average interval between 15 | checkpoint operations in seconds 16 | * Added `pg.checkpoint_time_perc` to measure the percentage of time spent 17 | in checkpoint operations since last reset 18 | * Added `pg.stats_reset_interval` to return seconds since background writer 19 | stats were reset 20 | * Added `pg.table.n_mod_since_analyze` to return the estimated number of rows 21 | that have been modified since the last table analyze 22 | * Added support for `pg.queries.longest` in PostgreSQL versions prior to 9.2 23 | * Added `pg.prepared_xacts_count` to return the number of transactions currently 24 | prepared for two phase commit 25 | * Added `pg.prepared_xacts_ratio` to return the number of transactions currently 26 | prepared for two phase commit as a ratio of the maximum permitted prepared 27 | transaction count 28 | * Added `pg.prepared_xacts_age` to return the age of the oldest transaction 29 | currently prepared for two phase commit 30 | * Added `pg.backends.free` to return the number of available backend connections 31 | * Added `pg.backends.ratio` to return the ratio of used available backend 32 | connections 33 | * Added `--with-postgresql` switch to source configuration script 34 | * Added `--with-zabbix` switch to source configuration script 35 | * Fixed misreporting in `pg.queries.longest` when no queries were in progress 36 | * Fixed build dependencies on Debian (thanks darkweaver87) 37 | * Moved build scripts to a new repository (cavaliercoder/libzbxpgsql-build) 38 | 39 | v0.2.1 14 Sep 2015 40 | 41 | * Fixed connection leak in pg_version() 42 | * Fixed query error in pg.index.rows key 43 | * Removed noisy logging in pg.query.* keys 44 | 45 | v0.2.0 16 Aug 2015 46 | 47 | * Improved connections parameters on all item keys 48 | * Add custom discovery rules via `pg.query.discovery` 49 | * Fixed compatability issues with < v9.2 50 | * Added support for OpenSUSE v13.2 51 | * Added SQL injection prevention 52 | * Added `pg.uptime` and `pg.starttime` keys 53 | * Added `pg.modver` key to monitor the installed `libzbxpgsql` version 54 | * Reduced required privileges for all keys to just `LOGIN` 55 | * Fixed integer overflow issues on large objects 56 | * Improved automated testing and packaging using Docker and `zabbix_agent_bench` 57 | 58 | v0.1.3 17 Mar 2015 59 | 60 | * Added configuration directive discovery 61 | 62 | v0.1.2 20 Feb 2015 63 | 64 | * Fixed module installation path 65 | * Added git reference to library version info 66 | * Added project and RPM build to Travis CI 67 | * Improved detection of PostgreSQL OIDs and IP addresses in parameter values 68 | 69 | v0.1.1 16 Feb 2015 70 | 71 | * Added `pg.queries.longest` key 72 | * Added `pg.setting` key 73 | * Added `pg.query.*` keys 74 | * Improved documentation 75 | 76 | v0.1.0 16 Feb 2015 77 | 78 | * Implemented `pg.query.*` and `pg.queries.longest` keys 79 | * Implemented `pg.backends.*` keys 80 | * Improved documentation 81 | 82 | v0.1.0 7 Feb 2015 83 | 84 | * Initial release 85 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 2 | 3 | SUBDIRS = \ 4 | src 5 | 6 | EXTRA_DIST = \ 7 | COPYING \ 8 | README.md \ 9 | conf/libzbxpgsql.conf \ 10 | templates/*.xml 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libzbxpgsql [![Build Status](https://travis-ci.org/cavaliercoder/libzbxpgsql.svg?branch=master)](https://travis-ci.org/cavaliercoder/libzbxpgsql) 2 | 3 | This project provides comprehensive monitoring of PostgreSQL servers using a 4 | natively compiled Zabbix agent module, written in C. 5 | 6 | **N.B.** Zabbix 6+ users will probably prefer the official support for PostrgreSQL 7 | available in `zabbix_agent2`, [documented here](https://git.zabbix.com/projects/AP/repos/postgresql/browse/README.md). 8 | 9 | A preconfigured Zabbix Template is also included for your convenience. 10 | 11 | Sources in this project are used to compile `libzbxpgsql.so` which may be 12 | loaded by a Zabbix agent using the `LoadModule` directive. The module enables 13 | discovery and monitoring of tablespaces, databases, namespaces, tables, 14 | indexes, etc. 15 | 16 | * Read the [documentation](http://cavaliercoder.com/libzbxpgsql/) 17 | * Download the [packages](http://cavaliercoder.com/libzbxpgsql/download) 18 | * Clone the [sources](http://github.com/cavaliercoder/libzbxpgsql) 19 | * Follow the [author](http://cavaliercoder.com) 20 | 21 | 22 | ## Installation 23 | 24 | To compile the agent module the following items are required: 25 | 26 | * GNU build tools (`make`, `gcc`, `autoconf`, `automake`, `libtool`, `m4`, etc.) 27 | * [Zabbix sources](http://www.zabbix.com/download.php) 28 | * [libpq development headers](http://www.postgresql.org/download/) 29 | 30 | If you are building from sources cloned from GitHub, you first need to 31 | regenerate the build scripts using `./autogen.sh`. Otherwise: 32 | 33 | ./configure --with-zabbix=/usr/src/zabbix 34 | make 35 | sudo make install 36 | 37 | Module file `libzbxpgsql.so` will then be installed in `/usr/local/lib`. 38 | 39 | If you are using a packaged version of Zabbix, you may with to redirect the 40 | installation directories as follows: 41 | 42 | $ sudo make prefix=/usr sysconfdir=/etc libdir=/usr/lib64 install 43 | 44 | __Note:__ Please use a clean copy of the Zabbix source code. Once you configure 45 | or build the Zabbix sources, they are no longer useful for building this module. 46 | 47 | To build the RPM package on a RHEL6+ family system with `rpm-build` installed: 48 | 49 | make rpm 50 | 51 | 52 | ## License 53 | 54 | libzbxpgsql - A PostgreSQL monitoring module for Zabbix 55 | Copyright (C) 2016 - Ryan Armstrong 56 | 57 | This program is free software; you can redistribute it and/or modify 58 | it under the terms of the GNU General Public License as published by 59 | the Free Software Foundation; either version 2 of the License, or 60 | (at your option) any later version. 61 | 62 | This program is distributed in the hope that it will be useful, 63 | but WITHOUT ANY WARRANTY; without even the implied warranty of 64 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 65 | GNU General Public License for more details. 66 | 67 | You should have received a copy of the GNU General Public License 68 | along with this program; if not, write to the Free Software 69 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 70 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | libtoolize -c -f || exit 1 3 | aclocal --force --verbose || exit 1 4 | autoheader -fv || exit 1 5 | automake -acfv || exit 1 6 | autoreconf -ifv || exit 1 7 | # ./configure 8 | -------------------------------------------------------------------------------- /conf/libzbxpgsql.conf: -------------------------------------------------------------------------------- 1 | # File: /etc/zabbix/libzbxpgsql.conf 2 | # 3 | # This file contains configuration for all pg.* keys. 4 | # 5 | # By default, this file is loaded from /etc/zabbix/libzbxpgsql.conf, unless 6 | # the PGCONFIGFILE environment variable is set to a different path. 7 | # 8 | # The config file is only read at startup of Zabbix agent. If you modify the 9 | # config file, you will need to restart the Zabbix agent for it to take effect. 10 | # 11 | # Syntax errors in the config file will prevent Zabbix from starting. 12 | # 13 | # The config files are parsed by the C libconfig module: 14 | # http://www.hyperrealm.com/main.php?s=libconfig 15 | # 16 | # Comment lines begin with a hash '#'. 17 | # 18 | # The format for defining named SQL queries is: 19 | # queries = { 20 | # SQLkey = "SQL statement"; 21 | # }; 22 | # 23 | # Requirements: 24 | # - The SQL key must be alphanumeric and can contain dashes and underscores 25 | # (-DO NOT- use asterisks or spaces in the key name). 26 | # - The entire SQL statement must be enclosed in double quotes. 27 | # - If your SQL statement needs to utilize double-quotes, then they MUST be 28 | # escaped by a backslash: 29 | # "SELECT \"UPPERCASECOLUMN\" from table;"; 30 | # - A semicolon is required at the end of each config entry. 31 | # 32 | # Example Query Setup (with substitution variables): 33 | # * Zabbix agent key, including a named query: 34 | # pg_query.integer[,,myquery,45,200] 35 | # 36 | # * Matching query from the config file: 37 | # myquery = "Select $1::int + $2::int;"; 38 | # 39 | # * The agent will return the integer: 245 40 | # 41 | # SQL statements can span multiple lines, and may optionally contain extra 42 | # begin/end quotes on each line. The following two examples are both valid: 43 | # 44 | # GoodSQL1 = "select count(*) 45 | # from pg_stat_activity;"; 46 | # 47 | # AlsoGood = "select count(*) " 48 | # " from pg_stat_activity;"; 49 | # 50 | 51 | # Example Queries 52 | queries = { 53 | teststr = "SELECT $1::text || $2::text;"; 54 | testint = "SELECT $1::int;"; 55 | testdbl = "SELECT $1::decimal;"; 56 | testdsc = "SELECT * FROM pg_database;"; 57 | }; 58 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([libzbxpgsql], [1.1.0], [ryan@cavaliercoder.com]) 2 | AM_INIT_AUTOMAKE([-Wall -Werror -Wno-portability foreign]) 3 | m4_pattern_allow([AM_PROG_AR]) 4 | AM_PROG_AR 5 | AC_PROG_LIBTOOL # seem to need this on CentOS 5 (libtool v1.5) 6 | LT_INIT 7 | AC_PROG_CC 8 | AC_CONFIG_MACRO_DIR([m4]) 9 | AC_CONFIG_HEADERS([config.h]) 10 | 11 | # check for stdlib header files 12 | AC_HEADER_STDC 13 | 14 | # check for libconfig 15 | AC_CHECK_HEADERS(libconfig.h, [], [ AC_MSG_ERROR("Unable to locate libconfig development headers.")]) 16 | AC_CHECK_LIB([config],[config_init],[],AC_MSG_ERROR(["Unable to locate libconfig library."])) 17 | 18 | # Checking for PostgreSQL support 19 | AX_LIB_POSTGRESQL([8.1]) 20 | AS_IF( 21 | [test "x$found_postgresql" = "xno"], 22 | [ 23 | AS_IF( 24 | [test ! -z "$POSTGRESQL_VERSION"], 25 | [AC_MSG_ERROR([PostgreSQL version mismatch])], 26 | [AC_MSG_ERROR([PostgreSQL library not found])] 27 | ) 28 | ] 29 | ) 30 | 31 | 32 | 33 | 34 | #AS_IF([test "x$found_postgresql" = "xyes"], 35 | # [ AS_IF([test "Xfound_postgresql_req_version" = "Xno"], 36 | # [ AC_MSG_ERROR([PostgreSQL version mismatch])])], [ 37 | # AC_MSG_ERROR([PostgreSQL library not found])]) 38 | 39 | # Checking for Zabbix headers 40 | AX_LIB_ZABBIX 41 | if test ! "x$found_zabbix" = "xyes"; then 42 | AC_MSG_ERROR([Zabbix headers not found]) 43 | fi 44 | 45 | # Checking for libdl 46 | AC_CHECK_LIB(dl,dlopen) 47 | 48 | # output 49 | AC_CONFIG_FILES([ 50 | Makefile 51 | src/Makefile 52 | ]) 53 | AC_OUTPUT 54 | -------------------------------------------------------------------------------- /m4/ax_compare_version.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_compare_version.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # This macro compares two version strings. Due to the various number of 12 | # minor-version numbers that can exist, and the fact that string 13 | # comparisons are not compatible with numeric comparisons, this is not 14 | # necessarily trivial to do in a autoconf script. This macro makes doing 15 | # these comparisons easy. 16 | # 17 | # The six basic comparisons are available, as well as checking equality 18 | # limited to a certain number of minor-version levels. 19 | # 20 | # The operator OP determines what type of comparison to do, and can be one 21 | # of: 22 | # 23 | # eq - equal (test A == B) 24 | # ne - not equal (test A != B) 25 | # le - less than or equal (test A <= B) 26 | # ge - greater than or equal (test A >= B) 27 | # lt - less than (test A < B) 28 | # gt - greater than (test A > B) 29 | # 30 | # Additionally, the eq and ne operator can have a number after it to limit 31 | # the test to that number of minor versions. 32 | # 33 | # eq0 - equal up to the length of the shorter version 34 | # ne0 - not equal up to the length of the shorter version 35 | # eqN - equal up to N sub-version levels 36 | # neN - not equal up to N sub-version levels 37 | # 38 | # When the condition is true, shell commands ACTION-IF-TRUE are run, 39 | # otherwise shell commands ACTION-IF-FALSE are run. The environment 40 | # variable 'ax_compare_version' is always set to either 'true' or 'false' 41 | # as well. 42 | # 43 | # Examples: 44 | # 45 | # AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8]) 46 | # AX_COMPARE_VERSION([3.15],[lt],[3.15.8]) 47 | # 48 | # would both be true. 49 | # 50 | # AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8]) 51 | # AX_COMPARE_VERSION([3.15],[gt],[3.15.8]) 52 | # 53 | # would both be false. 54 | # 55 | # AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8]) 56 | # 57 | # would be true because it is only comparing two minor versions. 58 | # 59 | # AX_COMPARE_VERSION([3.15.7],[eq0],[3.15]) 60 | # 61 | # would be true because it is only comparing the lesser number of minor 62 | # versions of the two values. 63 | # 64 | # Note: The characters that separate the version numbers do not matter. An 65 | # empty string is the same as version 0. OP is evaluated by autoconf, not 66 | # configure, so must be a string, not a variable. 67 | # 68 | # The author would like to acknowledge Guido Draheim whose advice about 69 | # the m4_case and m4_ifvaln functions make this macro only include the 70 | # portions necessary to perform the specific comparison specified by the 71 | # OP argument in the final configure script. 72 | # 73 | # LICENSE 74 | # 75 | # Copyright (c) 2008 Tim Toolan 76 | # 77 | # Copying and distribution of this file, with or without modification, are 78 | # permitted in any medium without royalty provided the copyright notice 79 | # and this notice are preserved. This file is offered as-is, without any 80 | # warranty. 81 | 82 | #serial 13 83 | 84 | dnl ######################################################################### 85 | AC_DEFUN([AX_COMPARE_VERSION], [ 86 | AC_REQUIRE([AC_PROG_AWK]) 87 | 88 | # Used to indicate true or false condition 89 | ax_compare_version=false 90 | 91 | # Convert the two version strings to be compared into a format that 92 | # allows a simple string comparison. The end result is that a version 93 | # string of the form 1.12.5-r617 will be converted to the form 94 | # 0001001200050617. In other words, each number is zero padded to four 95 | # digits, and non digits are removed. 96 | AS_VAR_PUSHDEF([A],[ax_compare_version_A]) 97 | A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ 98 | -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ 99 | -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ 100 | -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ 101 | -e 's/[[^0-9]]//g'` 102 | 103 | AS_VAR_PUSHDEF([B],[ax_compare_version_B]) 104 | B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ 105 | -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ 106 | -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ 107 | -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ 108 | -e 's/[[^0-9]]//g'` 109 | 110 | dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary 111 | dnl # then the first line is used to determine if the condition is true. 112 | dnl # The sed right after the echo is to remove any indented white space. 113 | m4_case(m4_tolower($2), 114 | [lt],[ 115 | ax_compare_version=`echo "x$A 116 | x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` 117 | ], 118 | [gt],[ 119 | ax_compare_version=`echo "x$A 120 | x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` 121 | ], 122 | [le],[ 123 | ax_compare_version=`echo "x$A 124 | x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` 125 | ], 126 | [ge],[ 127 | ax_compare_version=`echo "x$A 128 | x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` 129 | ],[ 130 | dnl Split the operator from the subversion count if present. 131 | m4_bmatch(m4_substr($2,2), 132 | [0],[ 133 | # A count of zero means use the length of the shorter version. 134 | # Determine the number of characters in A and B. 135 | ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'` 136 | ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'` 137 | 138 | # Set A to no more than B's length and B to no more than A's length. 139 | A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` 140 | B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` 141 | ], 142 | [[0-9]+],[ 143 | # A count greater than zero means use only that many subversions 144 | A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` 145 | B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` 146 | ], 147 | [.+],[ 148 | AC_WARNING( 149 | [invalid OP numeric parameter: $2]) 150 | ],[]) 151 | 152 | # Pad zeros at end of numbers to make same length. 153 | ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" 154 | B="$B`echo $A | sed 's/./0/g'`" 155 | A="$ax_compare_version_tmp_A" 156 | 157 | # Check for equality or inequality as necessary. 158 | m4_case(m4_tolower(m4_substr($2,0,2)), 159 | [eq],[ 160 | test "x$A" = "x$B" && ax_compare_version=true 161 | ], 162 | [ne],[ 163 | test "x$A" != "x$B" && ax_compare_version=true 164 | ],[ 165 | AC_WARNING([invalid OP parameter: $2]) 166 | ]) 167 | ]) 168 | 169 | AS_VAR_POPDEF([A])dnl 170 | AS_VAR_POPDEF([B])dnl 171 | 172 | dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. 173 | if test "$ax_compare_version" = "true" ; then 174 | m4_ifvaln([$4],[$4],[:])dnl 175 | m4_ifvaln([$5],[else $5])dnl 176 | fi 177 | ]) dnl AX_COMPARE_VERSION 178 | 179 | -------------------------------------------------------------------------------- /m4/ax_lib_postgresql.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_lib_postgresql.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_LIB_POSTGRESQL([MINIMUM-VERSION]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # This macro provides tests of availability of PostgreSQL 'libpq' library 12 | # of particular version or newer. 13 | # 14 | # AX_LIB_POSTGRESQL macro takes only one argument which is optional. If 15 | # there is no required version passed, then macro does not run version 16 | # test. 17 | # 18 | # The --with-postgresql option takes one of three possible values: 19 | # 20 | # no - do not check for PostgreSQL client library 21 | # 22 | # yes - do check for PostgreSQL library in standard locations (pg_config 23 | # should be in the PATH) 24 | # 25 | # path - complete path to pg_config utility, use this option if pg_config 26 | # can't be found in the PATH (You could set also PG_CONFIG variable) 27 | # 28 | # This macro calls: 29 | # 30 | # AC_SUBST(POSTGRESQL_CPPFLAGS) 31 | # AC_SUBST(POSTGRESQL_LDFLAGS) 32 | # AC_SUBST(POSTGRESQL_LIBS) 33 | # AC_SUBST(POSTGRESQL_VERSION) 34 | # 35 | # And sets: 36 | # 37 | # HAVE_POSTGRESQL 38 | # 39 | # LICENSE 40 | # 41 | # Copyright (c) 2008 Mateusz Loskot 42 | # Copyright (c) 2014 Sree Harsha Totakura 43 | # Copyright (c) 2018 Bastien Roucaries 44 | # 45 | # Copying and distribution of this file, with or without modification, are 46 | # permitted in any medium without royalty provided the copyright notice 47 | # and this notice are preserved. This file is offered as-is, without any 48 | # warranty. 49 | 50 | #serial 16 51 | 52 | AC_DEFUN([_AX_LIB_POSTGRESQL_OLD],[ 53 | AC_CACHE_CHECK([for the pg_config program], [ac_cv_path_PG_CONFIG], 54 | [AC_PATH_PROGS_FEATURE_CHECK([PG_CONFIG], [pg_config], 55 | [[$ac_path_PG_CONFIG --includedir > /dev/null \ 56 | && ac_cv_path_PG_CONFIG=$ac_path_PG_CONFIG ac_path_PG_CONFIG_found=:]], 57 | [AC_MSG_FAILURE([could not find pg_config program. You could specify it throught variable PG_CONFIG])])]) 58 | PG_CONFIG=$ac_cv_path_PG_CONFIG 59 | 60 | AC_CACHE_CHECK([for the PostgreSQL libraries CPPFLAGS],[ac_cv_POSTGRESQL_CPPFLAGS], 61 | [ac_cv_POSTGRESQL_CPPFLAGS="-I`$PG_CONFIG --includedir`"]) 62 | POSTGRESQL_CPPFLAGS="$ac_cv_POSTGRESQL_CPPFLAGS" 63 | 64 | AC_CACHE_CHECK([for the PostgreSQL libraries LDFLAGS],[ac_cv_POSTGRESQL_LDFLAGS], 65 | [ac_cv_POSTGRESQL_LDFLAGS="-L`$PG_CONFIG --libdir`"]) 66 | POSTGRESQL_LDFLAGS="$ac_cv_POSTGRESQL_LDFLAGS" 67 | 68 | AC_CACHE_CHECK([for the PostgreSQL libraries LIBS],[ac_cv_POSTGRESQL_LIBS], 69 | [ac_cv_POSTGRESQL_LIBS="-lpq"]) 70 | POSTGRESQL_LIBS="$ac_cv_POSTGRESQL_LIBS" 71 | 72 | AC_CACHE_CHECK([for the PostgreSQL version],[ac_cv_POSTGRESQL_VERSION], 73 | [ac_cv_POSTGRESQL_VERSION=`$PG_CONFIG --version | sed "s/^PostgreSQL[[[:space:]]][[[:space:]]]*\([[0-9.]][[0-9.]]*\).*/\1/"`]) 74 | POSTGRESQL_VERSION=$ac_cv_POSTGRESQL_VERSION 75 | found_postgresql="yes" 76 | ]) 77 | 78 | AC_DEFUN([AX_LIB_POSTGRESQL], 79 | [ 80 | AC_ARG_WITH([postgresql], 81 | AS_HELP_STRING([--with-postgresql=@<:@ARG@:>@], 82 | [use PostgreSQL library @<:@default=yes@:>@, optionally specify path to pg_config] 83 | ), 84 | [ 85 | AS_CASE([$withval], 86 | [[[nN]][[oO]]],[want_postgresql="no"], 87 | [[[yY]][[eE]][[sS]]],[want_postgresql="yes"], 88 | [ 89 | want_postgresql="yes" 90 | PG_CONFIG="$withval" 91 | ]) 92 | ], 93 | [want_postgresql="yes"] 94 | ) 95 | found_postgresql="no" 96 | 97 | POSTGRESQL_CPPFLAGS="" 98 | POSTGRESQL_LDFLAGS="" 99 | POSTGRESQL_LIBS="" 100 | POSTGRESQL_VERSION="" 101 | 102 | dnl 103 | dnl Check PostgreSQL libraries (libpq) 104 | dnl 105 | AS_IF([test X"$want_postgresql" = "Xyes"], 106 | [_AX_LIB_POSTGRESQL_OLD]) 107 | 108 | AS_IF([test X"$found_postgresql" = Xyes],[ 109 | dnl try to compile 110 | _AX_LIB_POSTGRESQL_OLD_CPPFLAGS="$CPPFLAGS" 111 | CPPFLAGS="$CPPFLAGS $POSTGRESQL_CPPFLAGS" 112 | _AX_LIB_POSTGRESQL_OLD_LDFLAGS="$LDFLAGS" 113 | LDFLAGS="$LDFLAGS $POSTGRESQL_LDFLAGS" 114 | _AX_LIB_POSTGRESQL_OLD_LIBS="$LIBS" 115 | LIBS="$LIBS $POSTGRESQL_LIBS" 116 | AC_CHECK_HEADER([libpq-fe.h],[],[AC_MSG_FAILURE([could not find libpq-fe.h header])]) 117 | 118 | dnl try now to link 119 | AC_CACHE_CHECK([for the PostgreSQL library linking is working],[ac_cv_postgresql_found], 120 | [ 121 | AC_LINK_IFELSE([ 122 | AC_LANG_PROGRAM( 123 | [ 124 | #include 125 | ], 126 | [ 127 | char conninfo[]="dbname = postgres"; 128 | PGconn *conn; 129 | conn = PQconnectdb(conninfo); 130 | ] 131 | ) 132 | ],[ac_cv_postgresql_found=yes],[AC_MSG_FAILURE([could not find libpq-fe.h header])]) 133 | ] 134 | ) 135 | CPPFLAGS="$_AX_LIB_POSTGRESQL_OLD_CPPFLAGS" 136 | LDFLAGS="$_AX_LIB_POSTGRESQL_OLD_LDFLAGS" 137 | LIBS="$_AX_LIB_POSTGRESQL_OLD_LIBS" 138 | ]) 139 | 140 | dnl 141 | dnl Check if required version of PostgreSQL is available 142 | dnl 143 | 144 | 145 | postgresql_version_req=ifelse([$1], [], [], [$1]) 146 | 147 | if test "$found_postgresql" = "yes" -a -n "$postgresql_version_req"; then 148 | AC_MSG_CHECKING([if PostgreSQL version $POSTGRESQL_VERSION is >= $postgresql_version_req]) 149 | AX_COMPARE_VERSION([$POSTGRESQL_VERSION],[ge],[$postgresql_version_req], 150 | [found_postgresql=yes],[found_postgresql=no]) 151 | AC_MSG_RESULT([$found_postgresql]) 152 | fi 153 | 154 | AS_IF([test "x$found_postgresql" = "xyes"],[ 155 | AC_DEFINE([HAVE_POSTGRESQL], [1], 156 | [Define to 1 if PostgreSQL libraries are available])]) 157 | 158 | AC_SUBST([POSTGRESQL_VERSION]) 159 | AC_SUBST([POSTGRESQL_CPPFLAGS]) 160 | AC_SUBST([POSTGRESQL_LDFLAGS]) 161 | AC_SUBST([POSTGRESQL_LIBS]) 162 | ]) 163 | 164 | -------------------------------------------------------------------------------- /m4/ax_lib_zabbix.m4: -------------------------------------------------------------------------------- 1 | # 2 | # SYNOPSIS 3 | # 4 | # AX_LIB_ZABBIX 5 | # 6 | # DESCRIPTION 7 | # 8 | # This macro provides tests of availability of Zabbix source headers. 9 | # 10 | # AX_LIB_ZABBIX macro takes no arguments. 11 | # 12 | # The --with-zabbix option takes one of three possible values: 13 | # 14 | # no - do not check for Zabbix headers 15 | # 16 | # yes - do check for Zabbix headers in the default location 17 | # 18 | # path - complete path to Zabbix source headers 19 | # 20 | # This macro calls: 21 | # 22 | # AC_SUBST(ZABBIX_CPPFLAGS) 23 | # AC_SUBST(ZABBIX_HEADERS) 24 | # 25 | # And sets: 26 | # 27 | # HAVE_ZABBIX 28 | # 29 | # LICENSE 30 | # 31 | # Copyright (c) 2016 Ryan Armstrong 32 | # 33 | # Copying and distribution of this file, with or without modification, are 34 | # permitted in any medium without royalty provided the copyright notice 35 | # and this notice are preserved. This file is offered as-is, without any 36 | # warranty. 37 | 38 | #serial 1 39 | 40 | AC_DEFUN([AX_LIB_ZABBIX], 41 | [ 42 | ZABBIX_HEADERS="/usr/src/zabbix/include" 43 | 44 | AC_ARG_WITH([zabbix], 45 | AS_HELP_STRING([--with-zabbix=@<:@ARG@:>@], 46 | [use Zabbix headers @<:@default=/usr/src/zabbix/include@:>@, optionally specify path to Zabbix source headers] 47 | ), 48 | [ 49 | if test "$withval" = "no"; then 50 | want_zabbix="no" 51 | elif test "$withval" = "yes"; then 52 | want_zabbix="yes" 53 | else 54 | want_zabbix="yes" 55 | ZABBIX_HEADERS="$withval" 56 | fi 57 | ], 58 | [want_zabbix="yes"] 59 | ) 60 | 61 | ZABBIX_CPPFLAGS="" 62 | 63 | dnl 64 | dnl Check Zabbix headers 65 | dnl 66 | 67 | if test "$want_zabbix" = "yes"; then 68 | AC_MSG_CHECKING([for Zabbix header files]) 69 | 70 | if test ! -f "$ZABBIX_HEADERS/module.h"; then 71 | dnl test in .../include 72 | if test -f "$ZABBIX_HEADERS/include/module.h"; then 73 | ZABBIX_HEADERS="$ZABBIX_HEADERS/include" 74 | else 75 | found_zabbix="no" 76 | AC_MSG_RESULT([no]) 77 | 78 | AC_MSG_ERROR([$ZABBIX_HEADERS/module.h does not exist]) 79 | ZABBIX_HEADERS="no" 80 | fi 81 | fi 82 | 83 | if test "$ZABBIX_HEADERS" != "no"; then 84 | ZABBIX_CPPFLAGS="-I$ZABBIX_HEADERS" 85 | AC_DEFINE([HAVE_ZABBIX], [1], 86 | [Define to 1 if Zabbix headers are available]) 87 | 88 | found_zabbix="yes" 89 | AC_MSG_RESULT([yes]) 90 | fi 91 | fi 92 | 93 | AC_SUBST([ZABBIX_CPPFLAGS]) 94 | AC_SUBST([ZABBIX_HEADERS]) 95 | ]) 96 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | lib_LTLIBRARIES = libzbxpgsql.la 2 | 3 | libzbxpgsql_la_SOURCES = \ 4 | libzbxpgsql.h \ 5 | libzbxpgsql.c \ 6 | pg_config.c \ 7 | pg_connect.c \ 8 | pg_discovery.c \ 9 | pg_backends.c \ 10 | pg_bgwriter.c \ 11 | pg_database.c \ 12 | pg_index.c \ 13 | pg_namespace.c \ 14 | pg_params.c \ 15 | pg_query.c \ 16 | pg_server.c \ 17 | pg_setting.c \ 18 | pg_table.c \ 19 | pg_tablespace.c 20 | 21 | libzbxpgsql_la_CFLAGS = \ 22 | $(ZABBIX_CPPFLAGS) \ 23 | $(POSTGRESQL_CPPFLAGS) \ 24 | $(POSTGRESQL_LDFLAGS) \ 25 | $(POSTGRESQL_LIBS) 26 | 27 | libzbxpgsql_la_LDFLAGS = \ 28 | -shared \ 29 | -avoid-version 30 | 31 | # Prevent install of the redundant *.la files 32 | install-exec-hook: 33 | rm -f $(DESTDIR)$(libdir)/libzbxpgsql.la 34 | 35 | # Fix "files left after uninstall" issue 36 | uninstall-local: 37 | rm -f $(DESTDIR)$(libdir)/libzbxpgsql.so 38 | -------------------------------------------------------------------------------- /src/libzbxpgsql.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | /* 21 | * See: 22 | * LibPQ: http://www.postgresql.org/docs/9.4/static/libpq.html 23 | * Statistics: http://www.postgresql.org/docs/9.4/static/monitoring-stats.html 24 | */ 25 | 26 | #include "libzbxpgsql.h" 27 | 28 | #include 29 | 30 | size_t (*zbx_snprintf)(char *str, size_t count, const char *fmt, ...) = NULL; 31 | 32 | int (*real_zabbix_check_log_level)(int level) = NULL; 33 | int *real_zbx_log_level = NULL; 34 | void (*real_zabbix_log)(int level, const char *fmt, ...) = NULL; 35 | 36 | // Define custom keys 37 | static ZBX_METRIC keys[] = 38 | /* KEY FLAG FUNCTION TEST PARAMETERS */ 39 | { 40 | {"pg.modver", 0, MODVER, NULL}, 41 | {"pg.connect", CF_HAVEPARAMS, PG_CONNECT, NULL}, 42 | {"pg.version", CF_HAVEPARAMS, PG_VERSION, NULL}, 43 | {"pg.starttime", CF_HAVEPARAMS, PG_STARTTIME, NULL}, 44 | {"pg.uptime", CF_HAVEPARAMS, PG_UPTIME, NULL}, 45 | {"pg.prepared_xacts_count", CF_HAVEPARAMS, PG_PREPARED_XACTS_COUNT, NULL}, 46 | {"pg.prepared_xacts_ratio", CF_HAVEPARAMS, PG_PREPARED_XACTS_RATIO, NULL}, 47 | {"pg.prepared_xacts_age", CF_HAVEPARAMS, PG_PREPARED_XACTS_AGE, NULL}, 48 | 49 | {"pg.setting", CF_HAVEPARAMS, PG_SETTING, ",,data_directory"}, 50 | {"pg.setting.discovery", CF_HAVEPARAMS, PG_SETTING_DISCOVERY, NULL}, 51 | 52 | // User queries 53 | {"pg.query.string", CF_HAVEPARAMS, PG_QUERY, ",,SELECT 'Lorem ipsum dolor';"}, 54 | {"pg.query.integer", CF_HAVEPARAMS, PG_QUERY, ",,SELECT pg_backend_pid();"}, 55 | {"pg.query.double", CF_HAVEPARAMS, PG_QUERY, ",,SELECT CAST(1234 AS double precision);"}, 56 | {"pg.query.discovery", CF_HAVEPARAMS, PG_QUERY, ",,SELECT * FROM pg_database;"}, 57 | 58 | // Client connection statistics 59 | {"pg.backends.count", CF_HAVEPARAMS, PG_BACKENDS_COUNT, NULL}, 60 | {"pg.backends.free", CF_HAVEPARAMS, PG_BACKENDS_FREE, NULL}, 61 | {"pg.backends.ratio", CF_HAVEPARAMS, PG_BACKENDS_RATIO, NULL}, 62 | {"pg.queries.longest", CF_HAVEPARAMS, PG_QUERIES_LONGEST, NULL}, 63 | 64 | // Server statistics (as per pg_stat_bgwriter) 65 | {"pg.checkpoints_timed", CF_HAVEPARAMS, PG_STAT_BGWRITER, NULL}, 66 | {"pg.checkpoints_req", CF_HAVEPARAMS, PG_STAT_BGWRITER, NULL}, 67 | {"pg.checkpoint_write_time", CF_HAVEPARAMS, PG_STAT_BGWRITER, NULL}, 68 | {"pg.checkpoint_sync_time", CF_HAVEPARAMS, PG_STAT_BGWRITER, NULL}, 69 | {"pg.checkpoint_avg_interval", CF_HAVEPARAMS, PG_BG_AVG_INTERVAL, NULL}, 70 | {"pg.checkpoint_time_ratio", CF_HAVEPARAMS, PG_BG_TIME_RATIO, NULL}, 71 | {"pg.buffers_checkpoint", CF_HAVEPARAMS, PG_STAT_BGWRITER, NULL}, 72 | {"pg.buffers_clean", CF_HAVEPARAMS, PG_STAT_BGWRITER, NULL}, 73 | {"pg.maxwritten_clean", CF_HAVEPARAMS, PG_STAT_BGWRITER, NULL}, 74 | {"pg.buffers_backend", CF_HAVEPARAMS, PG_STAT_BGWRITER, NULL}, 75 | {"pg.buffers_backend_fsync", CF_HAVEPARAMS, PG_STAT_BGWRITER, NULL}, 76 | {"pg.buffers_alloc", CF_HAVEPARAMS, PG_STAT_BGWRITER, NULL}, 77 | {"pg.stats_reset", CF_HAVEPARAMS, PG_STAT_BGWRITER, NULL}, 78 | {"pg.stats_reset_interval", CF_HAVEPARAMS, PG_BG_STATS_RESET_INTERVAL, NULL}, 79 | 80 | // Asset discovery 81 | {"pg.db.discovery", CF_HAVEPARAMS, PG_DB_DISCOVERY, NULL}, 82 | {"pg.namespace.discovery", CF_HAVEPARAMS, PG_NAMESPACE_DISCOVERY, NULL}, 83 | {"pg.schema.discovery", CF_HAVEPARAMS, PG_NAMESPACE_DISCOVERY, NULL}, // Alias for pg.namespace.discovery 84 | {"pg.tablespace.discovery", CF_HAVEPARAMS, PG_TABLESPACE_DISCOVERY, NULL}, 85 | {"pg.table.discovery", CF_HAVEPARAMS, PG_TABLE_DISCOVERY, NULL}, 86 | {"pg.table.children.discovery", CF_HAVEPARAMS, PG_TABLE_CHILDREN_DISCOVERY, ",,pg_proc"}, 87 | {"pg.index.discovery", CF_HAVEPARAMS, PG_INDEX_DISCOVERY, NULL}, 88 | 89 | // Asset class sizes 90 | {"pg.db.size", CF_HAVEPARAMS, PG_DB_SIZE, NULL}, 91 | {"pg.table.size", CF_HAVEPARAMS, PG_TABLE_SIZE, NULL}, 92 | {"pg.table.rows", CF_HAVEPARAMS, PG_TABLE_ROWS, NULL}, 93 | {"pg.table.children", CF_HAVEPARAMS, PG_TABLE_CHILDREN, ",,pg_database"}, 94 | {"pg.table.children.size", CF_HAVEPARAMS, PG_TABLE_CHILDREN_SIZE, ",,pg_database"}, 95 | {"pg.table.children.rows", CF_HAVEPARAMS, PG_TABLE_CHILDREN_ROWS, ",,pg_database"}, 96 | {"pg.index.size", CF_HAVEPARAMS, PG_INDEX_SIZE, NULL}, 97 | {"pg.index.rows", CF_HAVEPARAMS, PG_INDEX_ROWS, NULL}, 98 | {"pg.tablespace.size", CF_HAVEPARAMS, PG_TABLESPACE_SIZE, ",,pg_default"}, 99 | {"pg.namespace.size", CF_HAVEPARAMS, PG_NAMESPACE_SIZE, ",,pg_catalog"}, 100 | {"pg.schema.size", CF_HAVEPARAMS, PG_NAMESPACE_SIZE, ",,pg_catalog"}, // Alias for pg.namespace.size 101 | 102 | // Database statistics (as per pg_stat_database) 103 | {"pg.db.numbackends", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 104 | {"pg.db.xact_commit", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 105 | {"pg.db.xact_rollback", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 106 | {"pg.db.blks_read", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 107 | {"pg.db.blks_hit", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 108 | {"pg.db.blks_ratio", CF_HAVEPARAMS, PG_DB_BLKS_RATIO, NULL}, 109 | {"pg.db.tup_returned", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 110 | {"pg.db.tup_fetched", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 111 | {"pg.db.tup_inserted", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 112 | {"pg.db.tup_updated", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 113 | {"pg.db.tup_deleted", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 114 | {"pg.db.conflicts", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 115 | {"pg.db.temp_files", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 116 | {"pg.db.temp_bytes", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 117 | {"pg.db.deadlocks", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 118 | {"pg.db.blk_read_time", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 119 | {"pg.db.blk_write_time", CF_HAVEPARAMS, PG_STAT_DATABASE, NULL}, 120 | {"pg.db.stats_reset", CF_HAVEPARAMS, PG_STAT_DATABASE, ",,postgres,,,"}, 121 | {"pg.db.xid_age", CF_HAVEPARAMS, PG_DB_XID_AGE, NULL}, 122 | 123 | // Table statistics (as per pg_stat_all_tables) 124 | {"pg.table.seq_scan", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 125 | {"pg.table.seq_tup_read", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 126 | {"pg.table.idx_scan", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 127 | {"pg.table.idx_scan_ratio", CF_HAVEPARAMS, PG_TABLE_IDX_SCAN_RATIO, NULL}, 128 | {"pg.table.idx_tup_fetch", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 129 | {"pg.table.n_tup_ins", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 130 | {"pg.table.n_tup_upd", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 131 | {"pg.table.n_tup_del", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 132 | {"pg.table.n_tup_hot_upd", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 133 | {"pg.table.n_live_tup", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 134 | {"pg.table.n_dead_tup", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 135 | {"pg.table.n_mod_since_analyze",CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 136 | {"pg.table.last_vacuum", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, ",,pg_database"}, 137 | {"pg.table.last_autovacuum", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, ",,pg_database"}, 138 | {"pg.table.last_analyze", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, ",,pg_database"}, 139 | {"pg.table.last_autoanalyze", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, ",,pg_database"}, 140 | {"pg.table.vacuum_count", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 141 | {"pg.table.autovacuum_count", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 142 | {"pg.table.analyze_count", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 143 | {"pg.table.autoanalyze_count", CF_HAVEPARAMS, PG_STAT_ALL_TABLES, NULL}, 144 | 145 | // Table IO Statistics (as per pg_statio_all_tables) 146 | {"pg.table.heap_blks_read", CF_HAVEPARAMS, PG_STATIO_ALL_TABLES, NULL}, 147 | {"pg.table.heap_blks_hit", CF_HAVEPARAMS, PG_STATIO_ALL_TABLES, NULL}, 148 | {"pg.table.heap_blks_ratio", CF_HAVEPARAMS, PG_TABLE_HEAP_BLKS_RATIO, NULL}, 149 | {"pg.table.idx_blks_read", CF_HAVEPARAMS, PG_STATIO_ALL_TABLES, NULL}, 150 | {"pg.table.idx_blks_hit", CF_HAVEPARAMS, PG_STATIO_ALL_TABLES, NULL}, 151 | {"pg.table.idx_blks_ratio", CF_HAVEPARAMS, PG_TABLE_IDX_BLKS_RATIO, NULL}, 152 | {"pg.table.toast_blks_read", CF_HAVEPARAMS, PG_STATIO_ALL_TABLES, NULL}, 153 | {"pg.table.toast_blks_hit", CF_HAVEPARAMS, PG_STATIO_ALL_TABLES, NULL}, 154 | {"pg.table.toast_blks_ratio", CF_HAVEPARAMS, PG_TABLE_TOAST_BLKS_RATIO, NULL}, 155 | {"pg.table.tidx_blks_read", CF_HAVEPARAMS, PG_STATIO_ALL_TABLES, NULL}, 156 | {"pg.table.tidx_blks_hit", CF_HAVEPARAMS, PG_STATIO_ALL_TABLES, NULL}, 157 | {"pg.table.tidx_blks_ratio", CF_HAVEPARAMS, PG_TABLE_TIDX_BLKS_RATIO, NULL}, 158 | 159 | // Index statistics (as per pg_stat_all_indexes) 160 | {"pg.index.idx_scan", CF_HAVEPARAMS, PG_STAT_ALL_INDEXES, NULL}, 161 | {"pg.index.idx_tup_read", CF_HAVEPARAMS, PG_STAT_ALL_INDEXES, NULL}, 162 | {"pg.index.idx_tup_fetch", CF_HAVEPARAMS, PG_STAT_ALL_INDEXES, NULL}, 163 | 164 | // Index IO statistics (as per pg_statio_all_indexes) 165 | {"pg.index.idx_blks_read", CF_HAVEPARAMS, PG_STATIO_ALL_INDEXES, NULL}, 166 | {"pg.index.idx_blks_hit", CF_HAVEPARAMS, PG_STATIO_ALL_INDEXES, NULL}, 167 | {"pg.index.idx_blks_ratio", CF_HAVEPARAMS, PG_INDEX_IDX_BLKS_RATIO, NULL}, 168 | 169 | // Null terminator 170 | {NULL} 171 | }; 172 | 173 | // Required Zabbix module functions 174 | int zbx_module_api_version() { return ZBX_MODULE_API_VERSION_ONE; } 175 | void zbx_module_item_timeout(int timeout) { return; } 176 | ZBX_METRIC *zbx_module_item_list() { return keys; } 177 | 178 | /* 179 | * zbx_module_uninit 180 | */ 181 | 182 | int zbx_module_uninit() { 183 | return uninit_config(); 184 | } 185 | 186 | /* 187 | * zbx_module_init 188 | */ 189 | 190 | int zbx_module_init() { 191 | void *handle; 192 | 193 | printf("starting agent module %s", PACKAGE_STRING); 194 | 195 | if (NULL == (handle = dlopen(NULL, RTLD_LAZY | RTLD_NOLOAD))) 196 | { 197 | fprintf(stderr, "failed to dlopen() Zabbix binary: %s", dlerror()); 198 | return ZBX_MODULE_FAIL; 199 | } 200 | 201 | real_zabbix_check_log_level = dlsym(handle, "zabbix_check_log_level"); /* was available before ZBX-10889 */ 202 | real_zbx_log_level = dlsym(handle, "zbx_log_level"); /* is available since ZBX-10889 */ 203 | 204 | if (NULL == real_zabbix_check_log_level && NULL == real_zbx_log_level) 205 | { 206 | fprintf(stderr, "failed to find both zabbix_check_log_level() and zbx_log_level," 207 | " be aware that module may spam with log messages"); 208 | /* not a critical error, we can continue */ 209 | } 210 | 211 | if (NULL == (real_zabbix_log = dlsym(handle, "__zbx_zabbix_log"))) 212 | { 213 | fprintf(stderr, "failed to find __zbx_zabbix_log(): %s", dlerror()); 214 | return ZBX_MODULE_FAIL; 215 | } 216 | 217 | if (NULL == (zbx_snprintf = dlsym(handle, "zbx_snprintf")) && 218 | NULL == (zbx_snprintf = dlsym(handle, "__zbx_zbx_snprintf"))) 219 | { 220 | zabbix_log(LOG_LEVEL_ERR, "failed to find zbx_snprintf() or __zbx_zbx_snprintf(): %s", dlerror()); 221 | dlclose(handle); 222 | return ZBX_MODULE_FAIL; 223 | } 224 | 225 | dlclose(handle); 226 | 227 | return init_config(); 228 | } 229 | 230 | /* 231 | * Custom key: pg.modver 232 | * 233 | * Returns the version string of the libzbxpgsql module. 234 | * 235 | * Parameters: 236 | * 237 | * Returns: s 238 | */ 239 | int MODVER(AGENT_REQUEST *request, AGENT_RESULT *result) 240 | { 241 | int ret = SYSINFO_RET_FAIL; // Request result code 242 | const char *__function_name = "MODVER"; // Function name for log file 243 | 244 | char buffer[MAX_STRING_LEN]; 245 | 246 | zabbix_log(LOG_LEVEL_DEBUG, "In %s", __function_name); 247 | 248 | zbx_snprintf( 249 | buffer, 250 | sizeof(buffer), 251 | "%s, compiled for Zabbix %s", 252 | PACKAGE_STRING, 253 | ZABBIX_VERSION 254 | ); 255 | 256 | // Set result 257 | SET_STR_RESULT(result, strdup(buffer)); 258 | ret = SYSINFO_RET_OK; 259 | 260 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s", __function_name); 261 | return ret; 262 | } 263 | 264 | /* 265 | * Function: pg_exec 266 | * 267 | * Wrapper for PQexecParams. Only supports text parameters as binary parameters 268 | * are not possible in Zabbix item keys. 269 | * 270 | * Returns: PGresult 271 | */ 272 | PGresult *pg_exec(PGconn *conn, const char *command, PGparams params) { 273 | PGresult *res = NULL; 274 | int i = 0, nparams = 0; 275 | 276 | // count parameters 277 | nparams = param_len(params); 278 | 279 | // log the query 280 | zabbix_log(LOG_LEVEL_DEBUG, "Executing query with %i parameters: %s", nparams, command); 281 | for (i = 0; i < nparams; i++) 282 | zabbix_log(LOG_LEVEL_DEBUG, " $%i: %s", i, params[i]); 283 | 284 | // execute query with escaped parameters 285 | res = PQexecParams(conn, command, nparams, NULL, (const char * const*) params, NULL, NULL, 0); 286 | 287 | // free up the params array which would have been alloc'ed for this request 288 | param_free(params); 289 | 290 | return res; 291 | } 292 | 293 | /* 294 | * Function: pg_scalar 295 | * 296 | * Executes a PostgreSQL Query using connection details from a Zabbix agent 297 | * request structure and fills the given buffer with the value of the first 298 | * column of the first row returned. The connection is closed before returning. 299 | * 300 | * Parameter [request]: Zabbix agent request structure. 301 | * Passed to pg_connect_request to fetch a valid PostgreSQL server 302 | * connection 303 | * 304 | * Parameter [result]: Zabbix agent result structure used to set any errors 305 | * that may occur. 306 | * 307 | * Parameter [query]: PostgreSQL query to execute. Query should return a 308 | * single scalar string value. 309 | * 310 | * Parameter [params]: Query parameters 311 | * 312 | * Parameter [buffer]: Buffer to the filled with the query response 313 | * 314 | * Parameter [bufferlen]: Size in bytes of the buffer to fill 315 | * 316 | * Returns: SYSINFO_RET_OK or SYSINFO_RET_FAIL on error 317 | */ 318 | int pg_scalar(AGENT_REQUEST *request, AGENT_RESULT *result, const char *query, PGparams params, char *buffer, size_t bufferlen) { 319 | const char *__function_name = "pg_scalar"; 320 | 321 | PGconn *conn = NULL; 322 | PGresult *res = NULL; 323 | int ret = SYSINFO_RET_FAIL; 324 | 325 | zabbix_log(LOG_LEVEL_DEBUG, "In %s", __function_name); 326 | 327 | // connect to PostgreSQL 328 | conn = pg_connect_request(request, result); 329 | if (NULL == conn) 330 | goto out; 331 | 332 | // execute scalar query 333 | res = pg_exec(conn, query, params); 334 | if(PQresultStatus(res) != PGRES_TUPLES_OK) { 335 | set_err_result(result, "PostgreSQL error for query \"%s\": %s", query, PQresultErrorMessage(res)); 336 | goto out; 337 | } 338 | 339 | if(0 == PQntuples(res)) { 340 | set_err_result(result, "No results returned for query \"%s\"", query); 341 | goto out; 342 | } 343 | 344 | // copy result to buffer 345 | zbx_strlcpy(buffer, PQgetvalue(res, 0, 0), bufferlen); 346 | ret = SYSINFO_RET_OK; 347 | 348 | out: 349 | PQclear(res); 350 | PQfinish(conn); 351 | 352 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s", __function_name); 353 | 354 | return ret; 355 | } 356 | 357 | /* 358 | * Function: pg_get_result 359 | * 360 | * Executes a PostgreSQL Query using connection details from a Zabbix agent 361 | * request structure and updates the agent result structure with the value of 362 | * the first column of the first row returned. 363 | * 364 | * type may be 365 | * 366 | * Query parameters may be provided as a NULL terminated sequence of *char 367 | * values in the ... parameter. 368 | * 369 | * Parameter [request]: Zabbix agent request structure. 370 | * Passed to pg_connect_request to fetch a valid PostgreSQL server 371 | * connection 372 | * 373 | * Parameter [result]: Zabbix agent result structure 374 | * 375 | * Parameter [type]: Result type to set. May be one of AR_STRING, AR_UINT64 376 | * or AR_DOUBLE. 377 | * 378 | * Parameter [query]: PostgreSQL query to execute. Query should return a 379 | * single scalar string value. Parameters defined using PostgreSQL's 380 | * '$n' notation will be replaced with the corresponding variadic 381 | * argument provided in ... 382 | * 383 | * Returns: SYSINFO_RET_OK or SYSINFO_RET_FAIL on error 384 | */ 385 | int pg_get_result(AGENT_REQUEST *request, AGENT_RESULT *result, int type, const char *query, PGparams params) 386 | { 387 | int ret = SYSINFO_RET_FAIL; // Request result code 388 | const char *__function_name = "pg_get_result"; // Function name for log file 389 | 390 | char value[MAX_STRING_LEN]; 391 | 392 | zabbix_log(LOG_LEVEL_DEBUG, "In %s(%s)", __function_name, request->key); 393 | 394 | // execute scalar query 395 | if(SYSINFO_RET_FAIL == pg_scalar(request, result, query, params, &value[0], sizeof(value))) 396 | goto out; 397 | 398 | // Set result 399 | switch(type) { 400 | case AR_STRING: 401 | // string result (zabbix will clean the strdup'd buffer) 402 | SET_STR_RESULT(result, strdup(value)); 403 | break; 404 | 405 | case AR_UINT64: 406 | // integer result 407 | // Convert E Notation 408 | if(1 < strlen(value) && '.' == value[1]) { 409 | double dbl = strtod(value, NULL); 410 | SET_UI64_RESULT(result, (unsigned long long) dbl); 411 | } else { 412 | SET_UI64_RESULT(result, strtoull(value, NULL, 10)); 413 | } 414 | break; 415 | 416 | case AR_DOUBLE: 417 | // double result 418 | SET_DBL_RESULT(result, strtold(value, NULL)); 419 | break; 420 | 421 | default: 422 | // unknown result type 423 | set_err_result(result, "Unsupported result type: 0x%0X in %s", type, __function_name); 424 | goto out; 425 | } 426 | 427 | ret = SYSINFO_RET_OK; 428 | 429 | out: 430 | 431 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s(%s)", __function_name, request->key); 432 | return ret; 433 | } 434 | 435 | /* 436 | * Function: pg_get_percentage 437 | * 438 | * Executes a PostgreSQL query on the given table using connection details from 439 | * a Zabbix agent request structure and calculates the quotient the given 440 | * columns. 441 | * 442 | * Parameter [request]: Zabbix agent request structure. 443 | * Passed to pg_connect_request to fetch a valid PostgreSQL server 444 | * connection 445 | * 446 | * Parameter [result]: Zabbix agent result structure in which the quotient will 447 | * will be set or an error message on failure 448 | * 449 | * Parameter [table]: The PostgreSQL table to query 450 | * 451 | * Parameter [col1]: The column containing the dividend to be divided 452 | * 453 | * Parameter [col2]: The column containing the divisor 454 | * 455 | * Parameter [colFilter]: The column to filter by if desired 456 | * 457 | * Parameter [filter]: Value to filter by (`where [colFilter] = [filter]`) 458 | * 459 | * Parameter [type]: Result type to set. May be one of AR_STRING, AR_UINT64 460 | * or AR_DOUBLE. 461 | * 462 | * Returns: SYSINFO_RET_OK or SYSINFO_RET_FAIL on error 463 | */ 464 | int pg_get_percentage(AGENT_REQUEST *request, AGENT_RESULT *result, char *table, char *col1, char *col2, char *colFilter, char *filter) 465 | { 466 | int ret = SYSINFO_RET_FAIL; // Request result code 467 | const char *__function_name = "pg_get_percentage"; // Function name for log file 468 | 469 | int qlen = 0; 470 | char query[MAX_STRING_LEN], *c = NULL; 471 | 472 | zabbix_log(LOG_LEVEL_DEBUG, "In %s(%s)", __function_name, request->key); 473 | 474 | zbx_snprintf( 475 | query, 476 | sizeof(query), 477 | "SELECT CASE WHEN (%s) = 0 THEN 1 ELSE (%s)::float / (%s) END FROM %s", 478 | col2, 479 | col1, 480 | col2, 481 | table 482 | ); 483 | 484 | if (!strisnull(colFilter)) { 485 | qlen = strlen(query); 486 | c = &query[qlen]; 487 | zbx_snprintf(c, (sizeof(query) / sizeof(char)) - qlen, " WHERE %s = $1", colFilter); 488 | } 489 | 490 | ret = pg_get_dbl(request, result, query, param_new(filter)); 491 | 492 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s(%s)", __function_name, request->key); 493 | return ret; 494 | } 495 | 496 | /* 497 | * Function: pg_version 498 | * 499 | * Returns a comparable version number (e.g 80200 or 90400) for the connected 500 | * PostgreSQL server version. 501 | * 502 | * Returns: int 503 | */ 504 | long int pg_version(AGENT_REQUEST *request, AGENT_RESULT *result) { 505 | const char *__function_name = "pg_version"; // Function name for log file 506 | 507 | char buffer[MAX_STRING_LEN]; 508 | long int version = 0; 509 | 510 | zabbix_log(LOG_LEVEL_DEBUG, "In %s", __function_name); 511 | 512 | // execute query 513 | if (SYSINFO_RET_OK == pg_scalar( 514 | request, 515 | result, 516 | "SELECT setting FROM pg_settings WHERE name='server_version_num'", 517 | NULL, 518 | &buffer[0], 519 | sizeof(buffer) 520 | )) { 521 | // convert to integer 522 | version = atol(buffer); 523 | zabbix_log(LOG_LEVEL_DEBUG, "PostgreSQL server version: %lu", version); 524 | } 525 | 526 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s", __function_name); 527 | 528 | return version; 529 | } 530 | 531 | /* 532 | * Log an error to the agent log file and set the result message sent back to 533 | * the server. 534 | */ 535 | int set_err_result(AGENT_RESULT *result, const char *format, ...) 536 | { 537 | va_list args; 538 | char msg[MAX_STRING_LEN]; 539 | 540 | // parse message string 541 | va_start (args, format); 542 | zbx_vsnprintf(msg, sizeof(msg), format, args); 543 | 544 | // log message 545 | zabbix_log(LOG_LEVEL_ERR, "PostgreSQL: %s", msg); 546 | 547 | if (NULL != result) 548 | SET_MSG_RESULT(result, strdup(msg)); 549 | 550 | return SYSINFO_RET_FAIL; 551 | } 552 | 553 | /* 554 | * Function: is_oid 555 | * 556 | * Returns: 1 if the specified string is a valid PostgreSQL OID 557 | * 558 | * See also: http://www.postgresql.org/docs/9.4/static/datatype-oid.html 559 | */ 560 | int is_oid(char *str) 561 | { 562 | char *p = NULL; 563 | int res = 0; 564 | 565 | for(p = str; '\0' != *p; p++) { 566 | if (0 == isdigit(*p)) 567 | return 0; 568 | res = 1; 569 | } 570 | 571 | return res; 572 | } 573 | 574 | /* 575 | * Function: is_valid_ip 576 | * 577 | * Returns: 1 if the specified string is a valid IPv4 or IPv6 address 578 | */ 579 | int is_valid_ip(char *str) 580 | { 581 | struct in6_addr in; 582 | int res = 0; 583 | 584 | // test for valid IPv4 address 585 | if(1 == inet_pton(AF_INET, str, &(in))) 586 | res = 1; 587 | 588 | // test for valid IPv6 address 589 | if(1 == inet_pton(AF_INET6, str, &(in))) 590 | res = 1; 591 | 592 | return res; 593 | } 594 | 595 | /* 596 | * Function: strcat2 597 | * 598 | * An attempt to improve the performance and usability of strcat. 599 | * Buffer sizing is the responsibility of the caller. 600 | * 601 | * Returns: pointer to the last character of the updated destination string 602 | */ 603 | char *strcat2(char *dest, const char *src) 604 | { 605 | if (NULL == dest || NULL == src) 606 | return dest; 607 | 608 | // seek to the end of the dest string 609 | while (*dest) dest++; 610 | 611 | // copy one char at a time from source 612 | while ((*dest++ = *src++)); 613 | 614 | // return the last character 615 | return --dest; 616 | } 617 | -------------------------------------------------------------------------------- /src/libzbxpgsql.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | #ifndef LIBZBXPGSQL_H 21 | #define LIBZBXPGSQL_H 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #ifdef __FreeBSD__ 28 | #include 29 | #include 30 | #endif 31 | 32 | // PostgreSQL headers 33 | #include 34 | 35 | // Reading Config Files 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | // Zabbix source headers 43 | #define HAVE_TIME_H 1 44 | #include 45 | #include 46 | #define zbx_snprintf any_name_is_better /* don't want common.h to declare zbx_snprintf() function */ 47 | #include 48 | #undef zbx_snprintf /* forget macro definition from old common.h and/or our own macro definition two lines above */ 49 | #define zbx_snprintf pgsql_snprintf /* prevent symbol conflict with zbx_snprintf() function in new Zabbix binaries */ 50 | extern size_t (*zbx_snprintf)(char *str, size_t count, const char *fmt, ...); /* use old name to avoid changing much of our code */ 51 | #include 52 | /* zabbix_log() macro has always been a wrapper of __zbx_zabbix_log() function which used to perform log level check, it's not guaranteed now */ 53 | #undef zabbix_log /* it's time to stop using zabbix_log() provided by Zabbix and define our own */ 54 | extern int (*real_zabbix_check_log_level)(int level); 55 | extern int *real_zbx_log_level; 56 | extern void (*real_zabbix_log)(int level, const char *fmt, ...); 57 | #define zabbix_log(level, ...) \ 58 | do \ 59 | { \ 60 | if (NULL != real_zabbix_check_log_level && SUCCEED != real_zabbix_check_log_level(level)) \ 61 | break; \ 62 | \ 63 | if (NULL != real_zbx_log_level && LOG_LEVEL_INFORMATION != level && (level > *real_zbx_log_level || LOG_LEVEL_EMPTY == level)) \ 64 | break; \ 65 | \ 66 | real_zabbix_log(level, __VA_ARGS__); \ 67 | } \ 68 | while(0) 69 | #include 70 | #include 71 | 72 | // Default memory usage 73 | #define MAX_GLOBBING_PATH_LENGTH 512 74 | #define MAX_NUMBER_CONFIG_FILES 100 75 | #define MAX_NUMBER_SQL_STATEMENT_IN_RAM 500 76 | 77 | // Default connection settings 78 | #define LOCALHOST "localhost" 79 | #define PSQL_PORT "5432" 80 | #define PSQL_USER "postgres" 81 | 82 | // Index of connection params in user requests 83 | #define PARAM_CONN_STRING 0 84 | #define PARAM_DBNAME 1 85 | #define PARAM_FIRST 2 86 | 87 | #define DEFAULT_CONN_STRING "" 88 | #define DEFAULT_CONN_DBNAME NULL 89 | 90 | #define PG_WHERE "WHERE" 91 | #define PG_AND "AND" 92 | 93 | #define PG_RELKIND_TABLE "r" 94 | #define PG_RELKIND_INDEX "i" 95 | #define PG_RELKIND_SEQUENCE "s" 96 | #define PG_RELKIND_VIEW "v" 97 | #define PG_RELKIND_MATVIEW "m" 98 | #define PG_RELKIND_COMPTYPE "c" 99 | #define PG_RELKIND_TOAST "t" 100 | #define PG_RELKIND_FGNTABLE "f" 101 | 102 | // function to determine if a string is null or empty 103 | #define strisnull(c) (NULL == c || '\0' == *c) 104 | 105 | typedef char** PGparams; 106 | 107 | // Local helper functions 108 | int set_err_result(AGENT_RESULT *result, const char *format, ...); 109 | char *build_connstring(const char *connstring, const char *dbname); 110 | PGconn *pg_connect(const char *connstring, AGENT_RESULT *result); 111 | PGconn *pg_connect_request(AGENT_REQUEST *request, AGENT_RESULT *result); 112 | PGresult *pg_exec(PGconn *conn, const char *command, PGparams params); 113 | int pg_scalar(AGENT_REQUEST *request, AGENT_RESULT *result, const char *query, PGparams params, char *buffer, size_t bufferlen); 114 | long int pg_version(AGENT_REQUEST *request, AGENT_RESULT *result); 115 | 116 | int pg_get_result(AGENT_REQUEST *request, AGENT_RESULT *result, int type, const char *query, PGparams params); 117 | int pg_get_discovery(AGENT_REQUEST *request, AGENT_RESULT *result, const char *query, PGparams params); 118 | int pg_get_discovery_wide(AGENT_REQUEST *request, AGENT_RESULT *result, const char *query, PGparams params); 119 | int pg_get_percentage(AGENT_REQUEST *request, AGENT_RESULT *result, char *table, char *col1, char *col2, char *colFilter, char *filter); 120 | 121 | #define pg_get_string(request, result, query, params) pg_get_result(request, result, AR_STRING, query, params) 122 | #define pg_get_int(request, result, query, params) pg_get_result(request, result, AR_UINT64, query, params) 123 | #define pg_get_dbl(request, result, query, params) pg_get_result(request, result, AR_DOUBLE, query, params) 124 | 125 | int is_valid_ip(char *str); 126 | int is_oid(char *str); 127 | char *strcat2(char *destination, const char *source); 128 | 129 | int param_len(PGparams params); 130 | PGparams param_append(PGparams params, char *s); 131 | void param_free(PGparams params); 132 | #define param_new(s) param_append(NULL, s) 133 | 134 | int PG_GET_CLASS_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result, char *relkind, char *relname); 135 | 136 | // config file handling 137 | int init_config(); 138 | int uninit_config(); 139 | const char *get_query_by_name(const char *key); 140 | 141 | // Define agent key functions 142 | int MODVER(AGENT_REQUEST *request, AGENT_RESULT *result); 143 | int PG_CONNECT(AGENT_REQUEST *request, AGENT_RESULT *result); 144 | int PG_VERSION(AGENT_REQUEST *request, AGENT_RESULT *result); 145 | int PG_STARTTIME(AGENT_REQUEST *request, AGENT_RESULT *result); 146 | int PG_UPTIME(AGENT_REQUEST *request, AGENT_RESULT *result); 147 | int PG_PREPARED_XACTS_COUNT(AGENT_REQUEST *request, AGENT_RESULT *result); 148 | int PG_PREPARED_XACTS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result); 149 | int PG_PREPARED_XACTS_AGE(AGENT_REQUEST *request, AGENT_RESULT *result); 150 | 151 | int PG_SETTING(AGENT_REQUEST *request, AGENT_RESULT *result); 152 | int PG_SETTING_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result); 153 | 154 | int PG_QUERY(AGENT_REQUEST *request, AGENT_RESULT *result); 155 | 156 | int PG_BACKENDS_COUNT(AGENT_REQUEST *request, AGENT_RESULT *result); 157 | int PG_BACKENDS_FREE(AGENT_REQUEST *request, AGENT_RESULT *result); 158 | int PG_BACKENDS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result); 159 | int PG_QUERIES_LONGEST(AGENT_REQUEST *request, AGENT_RESULT *result); 160 | 161 | int PG_STAT_BGWRITER(AGENT_REQUEST *request, AGENT_RESULT *result); 162 | int PG_BG_AVG_INTERVAL(AGENT_REQUEST *request, AGENT_RESULT *result); 163 | int PG_BG_TIME_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result); 164 | int PG_BG_STATS_RESET_INTERVAL(AGENT_REQUEST *request, AGENT_RESULT *result); 165 | 166 | int PG_DB_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result); 167 | int PG_NAMESPACE_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result); 168 | int PG_TABLESPACE_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result); 169 | int PG_TABLE_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result); 170 | int PG_TABLE_CHILDREN_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result); 171 | int PG_INDEX_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result); 172 | 173 | int PG_STAT_DATABASE(AGENT_REQUEST *request, AGENT_RESULT *result); 174 | int PG_DB_BLKS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result); 175 | int PG_DB_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result); 176 | int PG_DB_XID_AGE(AGENT_REQUEST *request, AGENT_RESULT *result); 177 | 178 | int PG_TABLESPACE_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result); 179 | 180 | int PG_NAMESPACE_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result); 181 | 182 | int PG_STAT_ALL_TABLES(AGENT_REQUEST *request, AGENT_RESULT *result); 183 | int PG_TABLE_IDX_SCAN_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result); 184 | 185 | int PG_STATIO_ALL_TABLES(AGENT_REQUEST *request, AGENT_RESULT *result); 186 | int PG_TABLE_HEAP_BLKS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result); 187 | int PG_TABLE_IDX_BLKS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result); 188 | int PG_TABLE_TOAST_BLKS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result); 189 | int PG_TABLE_TIDX_BLKS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result); 190 | int PG_TABLE_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result); 191 | int PG_TABLE_ROWS(AGENT_REQUEST *request, AGENT_RESULT *result); 192 | int PG_TABLE_CHILDREN(AGENT_REQUEST *request, AGENT_RESULT *result); 193 | int PG_TABLE_CHILDREN_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result); 194 | int PG_TABLE_CHILDREN_ROWS(AGENT_REQUEST *request, AGENT_RESULT *result); 195 | 196 | int PG_STAT_ALL_INDEXES(AGENT_REQUEST *request, AGENT_RESULT *result); 197 | int PG_STATIO_ALL_INDEXES(AGENT_REQUEST *request, AGENT_RESULT *result); 198 | int PG_INDEX_IDX_BLKS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result); 199 | int PG_INDEX_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result); 200 | int PG_INDEX_ROWS(AGENT_REQUEST *request, AGENT_RESULT *result); 201 | 202 | #endif 203 | -------------------------------------------------------------------------------- /src/pg_backends.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | #include "libzbxpgsql.h" 21 | 22 | #define MAX_QUERY_LEN 4096 23 | #define MAX_CLAUSE_LEN 4096 24 | 25 | #define PGSQL_GET_BACKENDS "SELECT COUNT(datid) FROM pg_stat_activity WHERE %s != pg_backend_pid()%s;" 26 | 27 | #define PGSQL_GET_LONGEST_QUERY_92 "\ 28 | SELECT \ 29 | COALESCE( \ 30 | (SELECT \ 31 | EXTRACT(EPOCH FROM NOW()) - EXTRACT(EPOCH FROM query_start) AS duration \ 32 | FROM pg_stat_activity \ 33 | WHERE \ 34 | state = 'active' \ 35 | AND pid != pg_backend_pid() %s\ 36 | ORDER BY duration DESC \ 37 | LIMIT 1), 0);" 38 | 39 | #define PGSQL_GET_LONGEST_QUERY "\ 40 | SELECT \ 41 | COALESCE( \ 42 | (SELECT \ 43 | EXTRACT(EPOCH FROM NOW()) - EXTRACT(EPOCH FROM query_start) AS duration \ 44 | FROM pg_stat_activity \ 45 | WHERE \ 46 | current_query NOT IN ('', '', '') \ 47 | AND procpid != pg_backend_pid() %s\ 48 | ORDER BY duration DESC \ 49 | LIMIT 1), 0);" 50 | 51 | // select * from pg_stat_activity WHERE current_query NOT IN (''::text, ''::text, ''::text); 52 | /* 53 | * build_activity_clause takes agent request parameters for an item key which 54 | * targets the pg_stat_actity table and creates an SQL clause to filter 55 | * results. 56 | * 57 | * This function should be reused for all items which query the pg_stat_activity 58 | * table. 59 | * 60 | * request is the agent request containing the user parameters. 61 | * buf is the character buffer to which the clause is written. 62 | * params is a pointer to a PGparams type which stores query parameters. 63 | * has_clause determines if the the clause starts with "WITH" or "AND". 64 | * 65 | * Returns non-zero on success. 66 | */ 67 | static int build_activity_clause(const AGENT_REQUEST *request, AGENT_RESULT *result, char *buf, PGparams *params, int has_clause) { 68 | const char *__function_name = "build_activity_clause"; // Function name for log file 69 | 70 | int i = 0; 71 | char *param = NULL; 72 | char *clause = (0 < has_clause ? PG_AND : PG_WHERE); 73 | int pgi = 0; 74 | 75 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 76 | 77 | // iterate over the available parameters 78 | for(i = 0; i < 4; i++) { 79 | param = get_rparam(request, PARAM_FIRST + i); 80 | if(!strisnull(param)) { 81 | switch(i) { 82 | case 0: // 83 | *params = param_append(*params, param); 84 | if(is_oid(param)) 85 | zbx_snprintf(buf, MAX_CLAUSE_LEN, " %s datid = $%i", clause, ++pgi); 86 | else 87 | zbx_snprintf(buf, MAX_CLAUSE_LEN, " %s datname = $%i", clause, ++pgi); 88 | break; 89 | 90 | case 1: // 91 | *params = param_append(*params, param); 92 | if(is_oid(param)) 93 | zbx_snprintf(buf, MAX_CLAUSE_LEN, " %s usesysid = $%i", clause, ++pgi); 94 | else 95 | zbx_snprintf(buf, MAX_CLAUSE_LEN, " %s usename = $%i", clause, ++pgi); 96 | break; 97 | 98 | case 2: // 99 | *params = param_append(*params, param); 100 | if(is_valid_ip(param)) 101 | zbx_snprintf(buf, MAX_CLAUSE_LEN, " %s client_addr = $%i::inet", clause, ++pgi); 102 | else 103 | // requires v9.1+ 104 | zbx_snprintf(buf, MAX_CLAUSE_LEN, " %s client_hostname = $%i", clause, ++pgi); 105 | break; 106 | 107 | case 3: // 108 | if(0 == strncmp("true\0", param, 5)) { 109 | zbx_snprintf(buf, MAX_CLAUSE_LEN, " %s waiting = TRUE", clause); 110 | } else if(0 == strncmp("false\0", param, 6)) { 111 | zbx_snprintf(buf, MAX_CLAUSE_LEN, " %s waiting = FALSE", clause); 112 | } else { 113 | set_err_result(result, "Unsupported parameter value: \"%s\" in %s", param, request->key); 114 | return 0; 115 | } 116 | 117 | break; 118 | } 119 | 120 | buf += strlen(buf); 121 | clause = PG_AND; 122 | } 123 | } 124 | 125 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 126 | 127 | return 1; 128 | } 129 | 130 | /* 131 | * Custom key pg.backends.count 132 | * 133 | * Returns statistics for connected backends (remote clients) 134 | * 135 | * Parameters: 136 | * 0: connection string 137 | * 1: connection database 138 | * 2: filter by database oid name 139 | * 3: filter by user OID or name 140 | * 4: filter by hostname or IP address of the connected host 141 | * 5: return only waiting backends 142 | * 143 | * Returns: u 144 | */ 145 | int PG_BACKENDS_COUNT(AGENT_REQUEST *request, AGENT_RESULT *result) 146 | { 147 | int ret = SYSINFO_RET_FAIL; 148 | const char *__function_name = "PG_BACKENDS_COUNT"; 149 | 150 | char query[MAX_QUERY_LEN]; 151 | char clause[MAX_CLAUSE_LEN]; 152 | char *pid = "pid"; 153 | int version = 0; 154 | PGparams params = NULL; // freed later in pg_exec 155 | 156 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 157 | 158 | // pid column is named 'procpid' in < v9.2 159 | if (0 == (version = pg_version(request, result))) 160 | goto out; 161 | else if (version < 90200) 162 | pid = "procpid"; 163 | 164 | // build the filter clause 165 | memset(clause, 0, sizeof(clause)); 166 | if (0 == build_activity_clause(request, result, clause, ¶ms, 1)) 167 | goto out; 168 | 169 | // build the full sql query 170 | memset(query, 0, sizeof(query)); 171 | zbx_snprintf(query, MAX_QUERY_LEN, PGSQL_GET_BACKENDS, pid, clause); 172 | 173 | // get results 174 | ret = pg_get_int(request, result, query, params); 175 | 176 | out: 177 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 178 | return ret; 179 | } 180 | 181 | /* 182 | * Custom key pg.backends.ratio 183 | * 184 | * Returns the ratio of used available backend connections 185 | * 186 | * Parameters: 187 | * 0: connection string 188 | * 1: connection database 189 | * 190 | * Returns: d 191 | */ 192 | int PG_BACKENDS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result) 193 | { 194 | int ret = SYSINFO_RET_FAIL; 195 | const char *__function_name = "PG_BACKENDS_RATIO"; 196 | 197 | char query[MAX_QUERY_LEN]; 198 | char *pid = "pid"; 199 | int version = 0; 200 | 201 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 202 | 203 | // pid column is named 'procpid' in < v9.2 204 | if (0 == (version = pg_version(request, result))) 205 | goto out; 206 | else if (version < 90200) 207 | pid = "procpid"; 208 | 209 | // build the full sql query 210 | memset(query, 0, sizeof(query)); 211 | zbx_snprintf( 212 | query, 213 | MAX_QUERY_LEN, 214 | "\ 215 | SELECT \ 216 | CASE \ 217 | WHEN COUNT(datid) = 0 THEN 0.00 \ 218 | ELSE (COUNT(datid)::float / current_setting('max_connections')::integer) \ 219 | END \ 220 | FROM pg_stat_activity \ 221 | WHERE %s != pg_backend_pid();", 222 | pid 223 | ); 224 | 225 | // get results 226 | ret = pg_get_dbl(request, result, query, NULL); 227 | 228 | out: 229 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 230 | return ret; 231 | } 232 | 233 | /* 234 | * Custom key pg.backends.free 235 | * 236 | * Returns the number of available backend connections. 237 | * 238 | * Parameters: 239 | * 0: connection string 240 | * 1: connection database 241 | * 242 | * Returns: d 243 | */ 244 | int PG_BACKENDS_FREE(AGENT_REQUEST *request, AGENT_RESULT *result) 245 | { 246 | int ret = SYSINFO_RET_FAIL; 247 | const char *__function_name = "PG_BACKENDS_FREE"; 248 | 249 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 250 | 251 | // The +1 is to account for the connection used by this query. 252 | ret = pg_get_dbl( 253 | request, 254 | result, 255 | "\ 256 | SELECT \ 257 | current_setting('max_connections')::integer - COUNT(datid) \ 258 | FROM pg_stat_activity;", 259 | NULL 260 | ); 261 | 262 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 263 | return ret; 264 | } 265 | 266 | /* 267 | * Custom key pg.queries.longest 268 | * 269 | * Returns the duration in seconds of the longest running current query 270 | * 271 | * Parameters: 272 | * 0: connection string 273 | * 1: connection database 274 | * 2: filter by database oid name 275 | * 3: filter by user OID or name 276 | * 4: filter by hostname or IP address of the connected host 277 | * 5: return only waiting backends 278 | * 279 | * Returns: d 280 | * 281 | * Support: Requires PostgreSQL v9.2 or above 282 | * 283 | * TODO: allow filtering in pg.queries.longest similar to pg.backends.count 284 | */ 285 | int PG_QUERIES_LONGEST(AGENT_REQUEST *request, AGENT_RESULT *result) 286 | { 287 | int ret = SYSINFO_RET_FAIL; // Request result code 288 | const char *__function_name = "PG_QUERIES_LONGEST"; // Function name for log file 289 | 290 | char query[MAX_QUERY_LEN]; 291 | char clause[MAX_CLAUSE_LEN]; 292 | PGparams params = NULL; // freed later in pg_exec 293 | int version = 0; 294 | 295 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 296 | 297 | // build the filter clause 298 | memset(clause, 0, sizeof(clause)); 299 | if (0 == build_activity_clause(request, result, clause, ¶ms, 1)) 300 | goto out; 301 | 302 | // build the full sql query 303 | memset(query, 0, MAX_QUERY_LEN); 304 | 305 | if (0 == (version = pg_version(request, result))) 306 | goto out; 307 | else if (version < 90200) 308 | zbx_snprintf(query, MAX_QUERY_LEN, PGSQL_GET_LONGEST_QUERY, clause); 309 | else 310 | zbx_snprintf(query, MAX_QUERY_LEN, PGSQL_GET_LONGEST_QUERY_92, clause); 311 | 312 | ret = pg_get_dbl(request, result, query, params); 313 | 314 | out: 315 | 316 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 317 | return ret; 318 | } 319 | -------------------------------------------------------------------------------- /src/pg_bgwriter.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | #include "libzbxpgsql.h" 21 | 22 | /* 23 | * Custom keys pg.* (for each field in pg_stat_bgwriter) 24 | * 25 | * Returns the requested global statistic for the PostgreSQL server 26 | * 27 | * Parameters: 28 | * 0: connection string 29 | * 1: connection database 30 | * 31 | * Returns: u 32 | */ 33 | int PG_STAT_BGWRITER(AGENT_REQUEST *request, AGENT_RESULT *result) 34 | { 35 | int ret = SYSINFO_RET_FAIL; // Request result code 36 | const char *__function_name = "PG_STAT_BGWRITER"; // Function name for log file 37 | 38 | const char *pgsql_bgwriter_stat = "SELECT %s FROM pg_stat_bgwriter;"; 39 | 40 | char *field; 41 | char query[MAX_STRING_LEN]; 42 | 43 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 44 | 45 | // Get stat field from requested key name "pb.table." 46 | field = &request->key[3]; 47 | 48 | // Build query 49 | zbx_snprintf(query, sizeof(query), pgsql_bgwriter_stat, field); 50 | 51 | // Get field value 52 | if(0 == strncmp(field, "checkpoint_", 11)) 53 | ret = pg_get_dbl(request, result, query, NULL); 54 | 55 | else if(0 == strncmp(field, "stats_reset", 11)) 56 | ret = pg_get_string(request, result, query, NULL); 57 | 58 | else 59 | ret = pg_get_int(request, result, query, NULL); 60 | 61 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 62 | return ret; 63 | } 64 | 65 | /* 66 | * Custom key pg.stats_reset_interval 67 | * 68 | * Returns the interval in seconds since the BG writer stats were last reset. 69 | * 70 | * Parameters: 71 | * 0: connection string 72 | * 1: connection database 73 | * 74 | * Returns: i 75 | */ 76 | int PG_BG_STATS_RESET_INTERVAL(AGENT_REQUEST *request, AGENT_RESULT *result) 77 | { 78 | int ret = SYSINFO_RET_FAIL; // Request result code 79 | const char *__function_name = "PG_BG_STATS_RESET_INTERVAL"; // Function name for log file 80 | 81 | const char *pgsql_stats_reset_interval = 82 | "SELECT EXTRACT(EPOCH FROM NOW() - stats_reset) from pg_stat_bgwriter;"; 83 | 84 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 85 | 86 | ret = pg_get_int(request, result, pgsql_stats_reset_interval, NULL); 87 | 88 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 89 | return ret; 90 | } 91 | 92 | /* 93 | * Custom key pg.checkpoint_avg_interval 94 | * 95 | * Returns the average interval in seconds between all checkpoints that have 96 | * run since statistics were reset. 97 | * 98 | * Parameters: 99 | * 0: connection string 100 | * 1: connection database 101 | * 102 | * Returns: d 103 | */ 104 | int PG_BG_AVG_INTERVAL(AGENT_REQUEST *request, AGENT_RESULT *result) 105 | { 106 | int ret = SYSINFO_RET_FAIL; // Request result code 107 | const char *__function_name = "PG_BG_AVG_INTERVAL"; // Function name for log file 108 | 109 | const char *pgsql_bg_avg_interval = "\ 110 | SELECT \ 111 | CASE checkpoints_timed + checkpoints_req \ 112 | WHEN 0 THEN 0 \ 113 | ELSE EXTRACT(EPOCH FROM (NOW() - stats_reset)) / (checkpoints_timed + checkpoints_req) \ 114 | END \ 115 | FROM pg_stat_bgwriter;"; 116 | 117 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 118 | 119 | ret = pg_get_dbl(request, result, pgsql_bg_avg_interval, NULL); 120 | 121 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 122 | return ret; 123 | } 124 | 125 | /* 126 | * Custom key pg.checkpoint_time_ratio 127 | * 128 | * Returns the percentage of time spent writing or syncing checkpoints since 129 | * statistics were reset. 130 | * 131 | * Parameters: 132 | * 0: connection string 133 | * 1: connection database 134 | * 2: action: all (default) | write | sync 135 | * 136 | * Returns: d 137 | */ 138 | int PG_BG_TIME_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result) 139 | { 140 | int ret = SYSINFO_RET_FAIL; // Request result code 141 | const char *__function_name = "PG_BG_TIME_RATIO"; // Function name for log file 142 | 143 | const char *pgsql_bg_time_ratio = "\ 144 | SELECT \ 145 | (%s / 1000) / EXTRACT(EPOCH FROM NOW() - stats_reset) \ 146 | FROM pg_stat_bgwriter;"; 147 | 148 | char query[MAX_STRING_LEN]; 149 | char *action = NULL, *field = NULL; 150 | 151 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 152 | 153 | // parse action parameter 154 | action = get_rparam(request, PARAM_FIRST); 155 | if (strisnull(action) || 0 == strcmp(action, "all")) 156 | field = "(checkpoint_write_time + checkpoint_sync_time)"; 157 | else if (0 == strcmp(action, "write")) 158 | field = "checkpoint_write_time"; 159 | else if (0 == strcmp(action, "sync")) 160 | field = "checkpoint_sync_time"; 161 | else { 162 | set_err_result(result, "Invalid action parameter: %s", action); 163 | return ret; 164 | } 165 | 166 | // build query 167 | zbx_snprintf(query, sizeof(query), pgsql_bg_time_ratio, field); 168 | 169 | // get result 170 | ret = pg_get_dbl(request, result, query, NULL); 171 | 172 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 173 | return ret; 174 | } 175 | -------------------------------------------------------------------------------- /src/pg_config.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | #include "libzbxpgsql.h" 21 | 22 | // Default query config file location 23 | #ifdef __FreeBSD__ 24 | #define DEFAULT_PGCONFIGFILE "/usr/local/etc/zabbix/libzbxpgsql.conf" 25 | #else 26 | #define DEFAULT_PGCONFIGFILE "/etc/zabbix/libzbxpgsql.conf" 27 | #endif 28 | 29 | char **query_keys = NULL; 30 | char **query_values = NULL; 31 | int query_count = 0; 32 | 33 | /* 34 | * Function getPGCONFIGFILE 35 | * 36 | * Returns the config file path. 37 | * 38 | * If the environment variable PGCONFIGFILE is set then that is 39 | * used, otherwise DEFAULT_PGCONFIGFILE is used. 40 | * 41 | * Returns: pointer to const char 42 | */ 43 | static inline const char * getPGCONFIGFILE() { 44 | char *path = NULL; 45 | if('\0' == (path = getenv("PGCONFIGFILE"))) { 46 | path = DEFAULT_PGCONFIGFILE; 47 | } else if (strlen(path) > MAX_GLOBBING_PATH_LENGTH) { 48 | zabbix_log(LOG_LEVEL_ERR, "PGCONFIGFILE exceeds maximum length of %i", MAX_GLOBBING_PATH_LENGTH); 49 | return NULL; 50 | } 51 | 52 | return path; 53 | } 54 | 55 | static inline int add_named_query(const char *name, const char *query) 56 | { 57 | int i = query_count - 1; 58 | while(i >= 0 && (NULL == query_keys[i] || 0 > strcmp(name, query_keys[i]))) { 59 | query_keys[i+1] = query_keys[i]; 60 | query_values[i+1] = query_values[i]; 61 | query_keys[i] = NULL; 62 | query_values[i] = NULL; 63 | i--; 64 | } 65 | 66 | query_keys[i+1] = strdup(name); 67 | query_values[i+1] = strdup(query); 68 | 69 | return EXIT_SUCCESS; 70 | } 71 | 72 | /* 73 | * Function get_query_by_name 74 | * 75 | * Searches the key array to find the 76 | * corresponding SQL stmt using binary 77 | * search. 78 | * 79 | * Returns: 80 | * If Key Found: pointer to query string 81 | * If Not Found: NULL 82 | */ 83 | const char *get_query_by_name(const char *key) { 84 | int top = query_count - 1; 85 | int mid = 0; 86 | int bottom = 0; 87 | int cmp = -1; 88 | 89 | while (bottom <= top) { 90 | mid = (bottom + top)/2; 91 | cmp = strcmp(query_keys[mid], key); 92 | if (cmp == 0) { 93 | return query_values[mid]; 94 | } else if (cmp > 0) { 95 | top = mid - 1; 96 | } else if (cmp < 0) { 97 | bottom = mid + 1; 98 | } 99 | } 100 | 101 | return NULL; 102 | } 103 | 104 | static int read_config_queries(const config_setting_t *root) 105 | { 106 | int i = 0; 107 | const char *key = NULL, *value = NULL; 108 | config_setting_t *node = NULL; 109 | 110 | if (CONFIG_TYPE_GROUP != config_setting_type(root)) { 111 | zabbix_log(LOG_LEVEL_ERR, "queries is not a valid configuration group"); 112 | return EXIT_FAILURE; 113 | } 114 | 115 | query_count = config_setting_length(root); 116 | query_keys = (char**) zbx_calloc(query_keys, query_count + 1, sizeof(char*)); 117 | query_values = (char**) zbx_calloc(query_values, query_count + 1, sizeof(char*)); 118 | 119 | for (i = 0; i < query_count; i++) { 120 | node = config_setting_get_elem(root, i); 121 | key = config_setting_name(node); 122 | if (CONFIG_TYPE_STRING != config_setting_type(node)) { 123 | zabbix_log(LOG_LEVEL_ERR, "query '%s' is not a valid string", key); 124 | return EXIT_FAILURE; 125 | } 126 | 127 | value = config_setting_get_string_elem(root, i); 128 | if (EXIT_SUCCESS != (add_named_query(key, value))) 129 | return EXIT_FAILURE; 130 | } 131 | 132 | return EXIT_SUCCESS; 133 | } 134 | 135 | static int read_config(const char *cfgfile) 136 | { 137 | int i = 0; 138 | int res = EXIT_FAILURE; 139 | int cfglen = 0; 140 | const char *key = NULL; 141 | config_t cfg; 142 | config_setting_t *root, *node; 143 | 144 | config_init(&cfg); 145 | if (CONFIG_TRUE != (config_read_file(&cfg, cfgfile))) { 146 | zabbix_log(LOG_LEVEL_ERR, "%s in %s:%i", 147 | config_error_text(&cfg), cfgfile, config_error_line(&cfg)); 148 | goto out; 149 | } 150 | 151 | root = config_root_setting(&cfg); 152 | cfglen = config_setting_length(root); 153 | for (i = 0; i < cfglen; i++) { 154 | node = config_setting_get_elem(root, i); 155 | key = config_setting_name(node); 156 | 157 | if (0 == strncmp(key, "queries", 8)) { 158 | if (EXIT_SUCCESS != (read_config_queries(node))) 159 | goto out; 160 | } else { 161 | zabbix_log(LOG_LEVEL_ERR, "unrecognised configuration parameter: %s", key); 162 | goto out; 163 | } 164 | } 165 | 166 | res = EXIT_SUCCESS; 167 | 168 | out: 169 | config_destroy(&cfg); 170 | return res; 171 | } 172 | 173 | int init_config() 174 | { 175 | const char *cfgfile = getPGCONFIGFILE(); 176 | zabbix_log(LOG_LEVEL_INFORMATION, "using module configuration file: %s", cfgfile); 177 | if (EXIT_SUCCESS != (read_config(cfgfile))) 178 | return ZBX_MODULE_FAIL; 179 | 180 | return ZBX_MODULE_OK; 181 | } 182 | 183 | int uninit_config() 184 | { 185 | return EXIT_SUCCESS; 186 | } 187 | -------------------------------------------------------------------------------- /src/pg_connect.c: -------------------------------------------------------------------------------- 1 | #include "libzbxpgsql.h" 2 | 3 | /* 4 | * Function: build_connstring 5 | * 6 | * Allocates and returns a libpq compatible connection string. This function 7 | * takes as input, a libpq compatible connection string with the `dbname` field 8 | * omitted and the desired database name as the second parameter. This enables 9 | * connection strings to be built from Zabbix discovery rules where the 10 | * connected database may not be known when configuring Zabbix. 11 | * 12 | * Returns: libpq compatible connection string. Must be freed by the caller 13 | * using zbx_free() 14 | */ 15 | char *build_connstring(const char *connstring, const char *dbname) 16 | { 17 | char *res = NULL, *c = NULL; 18 | int bufferlen = 0; 19 | 20 | bufferlen = 21 | (NULL == connstring ? 0 : strlen(connstring)) 22 | + (NULL == dbname ? 0 : strlen(dbname)) 23 | + 9; // + ' dbname=\0' 24 | 25 | res = zbx_malloc(res, sizeof(char) * bufferlen); 26 | memset(res, 0, sizeof(char) * bufferlen); 27 | 28 | c = res; 29 | c = strcat2(c, strisnull(connstring) ? DEFAULT_CONN_STRING : connstring); 30 | c = strcat2(c, strisnull(connstring) ? NULL : " "); 31 | c = strcat2(c, "dbname="); 32 | c = strcat2(c, strisnull(dbname) ? DEFAULT_CONN_DBNAME : dbname); 33 | 34 | return res; 35 | } 36 | 37 | /* 38 | * Function: pg_connect 39 | * 40 | * Connect to PostgreSQL server 41 | * 42 | * See: http://www.postgresql.org/docs/9.4/static/libpq-connect.html#LIBPQ-PQCONNECTDB 43 | * 44 | * Parameter [connstring]: libpq compatible connection string 45 | * 46 | * Parameter [result]: result structure to send errors to the server 47 | * 48 | * Returns: Valid PostgreSQL connection or NULL on error 49 | */ 50 | PGconn *pg_connect(const char *connstring, AGENT_RESULT *result) 51 | { 52 | const char *__function_name = "pg_connect"; 53 | 54 | PGconn *conn = NULL; 55 | 56 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 57 | 58 | /* 59 | * Breaks in < v9.0 60 | // append application name 61 | if (!strisnull(connstring)) 62 | c = strcat2(c, " "); 63 | c = strcat(c, "application_name='" STRVER "'"); 64 | */ 65 | 66 | // connect 67 | zabbix_log(LOG_LEVEL_DEBUG, "Connecting to PostgreSQL with: %s", connstring); 68 | conn = PQconnectdb(connstring); 69 | if(CONNECTION_OK != PQstatus(conn)) { 70 | set_err_result(result, PQerrorMessage(conn)); 71 | PQfinish(conn); 72 | conn = NULL; 73 | } 74 | 75 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 76 | return conn; 77 | } 78 | 79 | /* 80 | * Function: pg_connect_request 81 | * 82 | * Parses a Zabbix agent request and returns a PostgreSQL connection. 83 | * 84 | * See: http://www.postgresql.org/docs/9.4/static/libpq-connect.html#LIBPQ-PQCONNECTDB 85 | * 86 | * Parameter [request]: Zabbix agent request structure. 87 | * The following parameters may be set: 88 | * 89 | * 0: connection string (default: DEFAULT_CONN_STRING) 90 | * 1: connection database (default: DEFAULT_CONN_DBNAME) 91 | * 92 | * Parameter [result]: result structure to send errors to the server 93 | * 94 | * Returns: Valid PostgreSQL connection or NULL on error 95 | */ 96 | PGconn *pg_connect_request(AGENT_REQUEST *request, AGENT_RESULT *result) 97 | { 98 | PGconn *conn = NULL; 99 | char *connstring = NULL; 100 | 101 | // connect using params from agent request 102 | connstring = build_connstring( 103 | get_rparam(request, PARAM_CONN_STRING), 104 | get_rparam(request, PARAM_DBNAME)); 105 | 106 | conn = pg_connect(connstring, result); 107 | zbx_free(connstring); 108 | 109 | return conn; 110 | } 111 | -------------------------------------------------------------------------------- /src/pg_database.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | #include "libzbxpgsql.h" 21 | 22 | #define PGSQL_DISCOVER_DBS "\ 23 | SELECT \ 24 | d.oid as oid, \ 25 | d.datname as path, \ 26 | d.datname as database, \ 27 | pg_catalog.pg_encoding_to_char(d.encoding) as encoding, \ 28 | d.datcollate as lc_collate, \ 29 | d.datctype as lc_ctype, \ 30 | pg_catalog.pg_get_userbyid(d.datdba) as owner, \ 31 | t.spcname as tablespace, \ 32 | pg_catalog.shobj_description(d.oid, 'pg_database') as description \ 33 | FROM pg_catalog.pg_database d \ 34 | JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid \ 35 | WHERE \ 36 | d.datallowconn = 't' \ 37 | AND d.datistemplate = 'n' \ 38 | ORDER BY 1;" 39 | 40 | #define PGSQL_GET_DB_STAT "SELECT %s FROM pg_stat_database WHERE datname = $1" 41 | 42 | #define PGSQL_GET_DB_STAT_SUM "SELECT SUM(%s::bigint) FROM pg_stat_database" 43 | 44 | #define PGSQL_GET_DB_SIZE "SELECT pg_catalog.pg_database_size(d.datname) FROM pg_catalog.pg_database d WHERE d.datname = $1" 45 | 46 | #define PGSQL_GET_DB_SIZE_SUM "SELECT SUM(pg_catalog.pg_database_size(d.datname)::bigint) FROM pg_catalog.pg_database d" 47 | 48 | /* 49 | * Custom key pg.db.discovery 50 | * 51 | * Parameters: 52 | * 0: connection string 53 | * 1: connection database 54 | * 55 | * Returns all known Databases in a PostgreSQL instances 56 | * 57 | * Returns: 58 | * { 59 | * "data":[ 60 | * { 61 | * "{#OID}":"1234", 62 | * "{#DATABASE}":"MyDatabase", 63 | * "{#ENCODING}":"UTF8", 64 | * "{#LC_COLLATE}":"en_US.UTF-8", 65 | * "{#LC_CTYPE}":"en_US.UTF-8", 66 | * "{#TEMPLATE}":"1|0", 67 | * "{#TABLESPACE":"pg_default", 68 | * "{#DESCRIPTION}":"something or other"}]} 69 | */ 70 | int PG_DB_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result) 71 | { 72 | int ret = SYSINFO_RET_FAIL; // Request result code 73 | const char *__function_name = "PG_DB_DISCOVERY"; // Function name for log file 74 | 75 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 76 | 77 | ret = pg_get_discovery(request, result, PGSQL_DISCOVER_DBS, NULL); 78 | 79 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 80 | return ret; 81 | } 82 | 83 | /* 84 | * Custom keys pg.* (for each field in pg_stat_database) 85 | * 86 | * Returns the requested statistic for the specified database 87 | * 88 | * Parameters: 89 | * 0: connection string 90 | * 1: connection database 91 | * 2: filter by database name (default: sum of all databases) 92 | * 93 | * Returns: u 94 | */ 95 | int PG_STAT_DATABASE(AGENT_REQUEST *request, AGENT_RESULT *result) 96 | { 97 | int ret = SYSINFO_RET_FAIL; // Request result code 98 | const char *__function_name = "PG_STAT_DATABASE"; // Function name for log file 99 | 100 | char *datname = NULL; 101 | char *field; 102 | char query[MAX_STRING_LEN]; 103 | 104 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 105 | 106 | // Get stat field from requested key name "pb.db." 107 | // No escaping needed as the fields are hard coded 108 | field = &request->key[6]; 109 | 110 | // Build query 111 | datname = get_rparam(request, PARAM_FIRST); 112 | if(strisnull(datname)) { 113 | // no database name provided. get sum of all 114 | zbx_snprintf(query, sizeof(query), PGSQL_GET_DB_STAT_SUM, field); 115 | datname = NULL; // ensure datname is NULL, not '\0' 116 | } else { 117 | zbx_snprintf(query, sizeof(query), PGSQL_GET_DB_STAT, field); 118 | } 119 | 120 | // Get results based on type 121 | if (0 == strncmp(field, "stats_reset", 11)) { 122 | // stats_reset is a string 123 | if(strisnull(datname)) { 124 | // Can't do SUMs on text fields! 125 | set_err_result(result, "No database specified"); 126 | goto out; 127 | } 128 | 129 | ret = pg_get_string(request, result, query, param_new(datname)); 130 | } 131 | else if(0 == strncmp(field, "blk_", 4)) 132 | // all blk_* fields are doubles 133 | ret = pg_get_dbl(request, result, query, param_new(datname)); 134 | else 135 | // all other fields are integers 136 | ret = pg_get_int(request, result, query, param_new(datname)); 137 | 138 | out: 139 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 140 | return ret; 141 | } 142 | 143 | int PG_DB_BLKS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result) 144 | { 145 | int ret = SYSINFO_RET_FAIL; // Request result code 146 | const char *__function_name = "PG_DB_BLKS_RATIO"; // Function name for log file 147 | 148 | char *dbname = NULL; 149 | 150 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 151 | 152 | dbname = get_rparam(request, PARAM_FIRST); 153 | 154 | if(strisnull(dbname)) { 155 | ret = pg_get_percentage( 156 | request, 157 | result, 158 | "pg_stat_database", 159 | "sum(blks_hit)", 160 | "sum(blks_hit) + sum(blks_read)", 161 | NULL, 162 | NULL); 163 | 164 | } else { 165 | ret = pg_get_percentage( 166 | request, 167 | result, 168 | "pg_stat_database", 169 | "blks_hit", 170 | "blks_hit + blks_read", 171 | "datname", 172 | dbname); 173 | } 174 | 175 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 176 | return ret; 177 | } 178 | 179 | /* 180 | * Custom key pg.db.size 181 | * 182 | * Returns the size of the specified database in bytes 183 | * 184 | * See: https://wiki.postgresql.org/wiki/Disk_Usage 185 | * 186 | * Parameters: 187 | * 0: connection string 188 | * 1: connection database 189 | * 2: filter by database name (default: sum of all databases) 190 | * 191 | * Returns: u 192 | */ 193 | int PG_DB_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result) 194 | { 195 | int ret = SYSINFO_RET_FAIL; // Request result code 196 | const char *__function_name = "PG_DB_SIZE"; // Function name for log file 197 | 198 | char *datname = NULL; 199 | char *query = NULL; 200 | 201 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 202 | 203 | // Build query 204 | datname = get_rparam(request, PARAM_FIRST); 205 | if (strisnull(datname)) { 206 | query = PGSQL_GET_DB_SIZE_SUM; 207 | datname = NULL; // ensure datname is NULL, not '\0' 208 | } else { 209 | query = PGSQL_GET_DB_SIZE; 210 | } 211 | 212 | ret = pg_get_int(request, result, query, param_new(datname)); 213 | 214 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 215 | return ret; 216 | } 217 | 218 | /* 219 | * Custom key pg.db.xid_age 220 | * 221 | * Returns the age (in transaction count) of the current transaction ID for the 222 | * given database. If no database is given, the oldest transaction ID is returned. 223 | * 224 | * See: 225 | * https://www.postgresql.org/docs/9.5/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND 226 | * https://github.com/postgres/postgres/blob/master/src/backend/access/transam/varsup.c#L267 227 | * 228 | * Parameters: 229 | * 0: connection string 230 | * 1: connection database 231 | * 2: filter by database name (default: max value for all databases) 232 | * 233 | * Returns: f 234 | */ 235 | int PG_DB_XID_AGE(AGENT_REQUEST *request, AGENT_RESULT *result) 236 | { 237 | int ret = SYSINFO_RET_FAIL; 238 | const char *__function_name = "PG_DB_XID_AGE"; 239 | 240 | char *datname = NULL; 241 | char *query = NULL; 242 | 243 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 244 | 245 | datname = get_rparam(request, PARAM_FIRST); 246 | if (strisnull(datname)) { 247 | query = "SELECT MAX(AGE(datfrozenxid)) FROM pg_database;"; 248 | datname = NULL; // ensure datname is NULL, not '\0' 249 | } else { 250 | query = "SELECT AGE(datfrozenxid) FROM pg_database WHERE datname = $1;"; 251 | } 252 | 253 | ret = pg_get_int(request, result, query, param_new(datname)); 254 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 255 | return ret; 256 | } 257 | 258 | -------------------------------------------------------------------------------- /src/pg_discovery.c: -------------------------------------------------------------------------------- 1 | #include "libzbxpgsql.h" 2 | 3 | /* 4 | * Function: pg_get_databases 5 | * 6 | * Returns a null delimited list of database names which the connected 7 | * PostgreSQL user is allowed to connect to (i.e. has been granted 'CONNECT'). 8 | * 9 | * Returns: Multi-string E.g. "database1\0database2\0database3\0\0" 10 | */ 11 | static char *pg_get_databases(AGENT_REQUEST *request, AGENT_RESULT *result) { 12 | const char *__function_name = "pg_get_databases"; // Function name for log file 13 | 14 | PGconn *conn = NULL; 15 | PGresult *res = NULL; 16 | 17 | char *databases = NULL, *c = NULL; 18 | int rows = 0, i = 0, bufferlen = 0; 19 | 20 | zabbix_log(LOG_LEVEL_DEBUG, "In %s", __function_name); 21 | 22 | // connect to PostgreSQL 23 | conn = pg_connect_request(request, result); 24 | if (NULL == conn) 25 | goto out; 26 | 27 | // get connectable databases 28 | res = pg_exec(conn, "SELECT datname FROM pg_database WHERE datallowconn = 't' AND pg_catalog.has_database_privilege(current_user, oid, 'CONNECT');", NULL); 29 | if(0 == PQntuples(res)) { 30 | set_err_result(result, "Failed to enumerate connectable PostgreSQL databases"); 31 | goto out; 32 | } 33 | 34 | rows = PQntuples(res); 35 | 36 | // iterate over each row to calculate buffer size 37 | bufferlen = 1; // 1 for null terminator 38 | for(i = 0; i < rows; i++) { 39 | bufferlen += strlen(PQgetvalue(res, i, 0)) + 1; 40 | } 41 | 42 | // allocate databases multi-string 43 | databases = zbx_malloc(databases, sizeof(char) * bufferlen); 44 | memset(databases, '\0', sizeof(char) * bufferlen); 45 | 46 | // iterate over each row and copy the results 47 | c = databases; 48 | for(i = 0; i < rows; i++) { 49 | c = strcat2(c, PQgetvalue(res, i, 0)) + 1; 50 | } 51 | 52 | out: 53 | PQclear(res); 54 | PQfinish(conn); 55 | 56 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s", __function_name); 57 | 58 | return databases; 59 | } 60 | 61 | /* 62 | * Function: pg_get_discovery 63 | * 64 | * Executes a PostgreSQL Query using connection details from a Zabbix agent 65 | * request structure and updates the agent result structure with the JSON 66 | * discovery data for each returned row. 67 | * 68 | * Query parameters may be provided as a NULL terminated sequence of *char 69 | * values in the ... parameter. 70 | * 71 | * Parameter [request]: Zabbix agent request structure. 72 | * Passed to pg_connect_request to fetch as valid PostgreSQL 73 | * server connection 74 | * 75 | * Parameter [result]: Zabbix agent result structure 76 | * 77 | * Parameter [query]: PostgreSQL query to execute. Query should column names 78 | * that match the desired discovery fields. 79 | * 80 | * Parameter [deep]: Execute against all connectable databases 81 | * 82 | * Returns: SYSINFO_RET_OK or SYSINFO_RET_FAIL on error 83 | */ 84 | int pg_get_discovery(AGENT_REQUEST *request, AGENT_RESULT *result, const char *query, PGparams params) 85 | { 86 | int ret = SYSINFO_RET_FAIL; // Request result code 87 | const char *__function_name = "pg_get_discovery"; // Function name for log file 88 | 89 | struct zbx_json j; // JSON response for discovery rule 90 | 91 | int i = 0, x = 0, columns = 0, rows = 0; 92 | char *c = NULL; 93 | char buffer[MAX_STRING_LEN]; 94 | 95 | PGconn *conn = NULL; 96 | PGresult *res = NULL; 97 | 98 | zabbix_log(LOG_LEVEL_DEBUG, "In %s(%s)", __function_name, request->key); 99 | 100 | // Connect to PostgreSQL 101 | if(NULL == (conn = pg_connect_request(request, result))) 102 | goto out; 103 | 104 | // Execute a query 105 | res = pg_exec(conn, query, params); 106 | if(PQresultStatus(res) != PGRES_TUPLES_OK) { 107 | set_err_result(result, "PostgreSQL query error: %s", PQresultErrorMessage(res)); 108 | goto out; 109 | } 110 | 111 | // count rows and columns 112 | rows = PQntuples(res); 113 | columns = PQnfields(res); 114 | 115 | // Create JSON array of discovered objects 116 | zbx_json_init(&j, ZBX_JSON_STAT_BUF_LEN); 117 | zbx_json_addarray(&j, ZBX_PROTO_TAG_DATA); 118 | 119 | // create discovery instance for each row 120 | for(i = 0; i < rows; i++) { 121 | zbx_json_addobject(&j, NULL); 122 | 123 | // add each row field as a discovery field 124 | for(x = 0; x < columns; x++) { 125 | // set discovery key name to uppercase column name 126 | zbx_snprintf(buffer, sizeof(buffer), "{#%s}", PQfname(res, x)); 127 | for(c = &buffer[0]; *c; c++) 128 | *c = toupper(*c); 129 | 130 | zbx_json_addstring(&j, buffer, PQgetvalue(res, i, x), ZBX_JSON_TYPE_STRING); 131 | } 132 | 133 | zbx_json_close(&j); 134 | } 135 | 136 | // Finalize JSON response 137 | zbx_json_close(&j); 138 | SET_STR_RESULT(result, strdup(j.buffer)); 139 | zbx_json_free(&j); 140 | 141 | ret = SYSINFO_RET_OK; 142 | 143 | out: 144 | 145 | PQclear(res); 146 | PQfinish(conn); 147 | 148 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s(%s)", __function_name, request->key); 149 | return ret; 150 | } 151 | 152 | /* 153 | * Function: pg_get_discovery_wide 154 | * 155 | * Executes a PostgreSQL Query on all accessible databases, using connection 156 | * details from a Zabbix agent request structure and updates the agent result 157 | * structure with the JSON discovery data for each returned row. 158 | * 159 | * Query parameters may be provided as a NULL terminated sequence of *char 160 | * values in the ... parameter. 161 | * 162 | * Parameter [request]: Zabbix agent request structure. 163 | * Passed to pg_connect_request to fetch as valid PostgreSQL 164 | * server connection 165 | * 166 | * Parameter [result]: Zabbix agent result structure 167 | * 168 | * Parameter [query]: PostgreSQL query to execute. Query should column names 169 | * that match the desired discovery fields. 170 | * 171 | * Parameter [deep]: Execute against all connectable databases 172 | * 173 | * Returns: SYSINFO_RET_OK or SYSINFO_RET_FAIL on error 174 | */ 175 | int pg_get_discovery_wide(AGENT_REQUEST *request, AGENT_RESULT *result, const char *query, PGparams params) 176 | { 177 | int ret = SYSINFO_RET_FAIL; // Request result code 178 | const char *__function_name = "pg_get_discovery_wide"; // Function name for log file 179 | 180 | struct zbx_json j; // JSON response for discovery rule 181 | 182 | int i = 0, x = 0, columns = 0, rows = 0; 183 | char *databases = NULL, *db = NULL, *c = NULL; 184 | char *connstring = NULL; 185 | char buffer[MAX_STRING_LEN]; 186 | 187 | PGconn *conn = NULL; 188 | PGresult *res = NULL; 189 | 190 | zabbix_log(LOG_LEVEL_DEBUG, "In %s(%s)", __function_name, request->key); 191 | 192 | // get a list of databases 193 | databases = pg_get_databases(request, result); 194 | if (NULL == databases) 195 | goto out; 196 | 197 | // Create JSON array of discovered objects 198 | zbx_json_init(&j, ZBX_JSON_STAT_BUF_LEN); 199 | zbx_json_addarray(&j, ZBX_PROTO_TAG_DATA); 200 | 201 | // query each accessible database 202 | for (db = databases; *db; db += strlen(db) + 1) { 203 | // build connection string 204 | zbx_free(connstring); 205 | connstring = build_connstring(get_rparam(request, PARAM_CONN_STRING), db); 206 | 207 | // Connect to PostgreSQL 208 | if(NULL == (conn = pg_connect(connstring, result))) 209 | goto out; 210 | 211 | // Execute a query 212 | res = pg_exec(conn, query, params); 213 | if(PQresultStatus(res) != PGRES_TUPLES_OK) { 214 | set_err_result(result, "PostgreSQL query error: %s", PQresultErrorMessage(res)); 215 | goto out; 216 | } 217 | 218 | // count rows and columns 219 | rows = PQntuples(res); 220 | columns = PQnfields(res); 221 | 222 | // create discovery instance for each row 223 | for(i = 0; i < rows; i++) { 224 | zbx_json_addobject(&j, NULL); 225 | 226 | // add each row field as a discovery field 227 | for(x = 0; x < columns; x++) { 228 | // set discovery key name to uppercase column name 229 | zbx_snprintf(buffer, sizeof(buffer), "{#%s}", PQfname(res, x)); 230 | for(c = &buffer[0]; *c; c++) 231 | *c = toupper(*c); 232 | 233 | zbx_json_addstring(&j, buffer, PQgetvalue(res, i, x), ZBX_JSON_TYPE_STRING); 234 | } 235 | 236 | zbx_json_close(&j); 237 | } 238 | 239 | // clean up 240 | zbx_free(connstring); 241 | PQclear(res); 242 | PQfinish(conn); 243 | conn = NULL; // bypass 2nd PQfinish 244 | } 245 | 246 | // Finalize JSON response 247 | zbx_json_close(&j); 248 | SET_STR_RESULT(result, strdup(j.buffer)); 249 | zbx_json_free(&j); 250 | 251 | ret = SYSINFO_RET_OK; 252 | 253 | out: 254 | zbx_free(connstring); 255 | zbx_free(databases); 256 | if (NULL != conn) 257 | PQfinish(conn); 258 | 259 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s(%s)", __function_name, request->key); 260 | return ret; 261 | } 262 | 263 | -------------------------------------------------------------------------------- /src/pg_index.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | #include "libzbxpgsql.h" 21 | 22 | #define PGSQL_DISCOVER_INDEXES "\ 23 | SELECT \ 24 | ic.oid AS oid \ 25 | , current_database() || '.' || n.nspname || '.' || t.relname || '.' || ic.relname AS path \ 26 | , ic.relname AS index \ 27 | , current_database() AS database \ 28 | , n.nspname AS schema \ 29 | , t.relname AS table \ 30 | , a.rolname AS owner \ 31 | , m.amname AS access \ 32 | FROM pg_index i \ 33 | JOIN pg_class ic ON ic.oid = i.indexrelid \ 34 | JOIN pg_namespace n ON n.oid = ic.relnamespace \ 35 | JOIN pg_roles a ON a.oid = ic.relowner \ 36 | JOIN pg_class t ON t.oid = i.indrelid \ 37 | JOIN pg_am m ON m.oid = ic.relam \ 38 | WHERE \ 39 | n.nspname <> 'pg_catalog' \ 40 | AND n.nspname <> 'information_schema' \ 41 | AND n.nspname !~ '^pg_toast'" 42 | 43 | #define PGSQL_GET_INDEX_STATIO_SUM "\ 44 | SELECT SUM(%s::bigint) FROM pg_statio_all_indexes \ 45 | WHERE \ 46 | schemaname !~ '^pg_toast' \ 47 | AND schemaname <> 'pg_catalog' \ 48 | AND schemaname <> 'information_schema'" 49 | 50 | #define PGSQL_GET_INDEX_SIZE "\ 51 | SELECT \ 52 | relpages::bigint * 8192 \ 53 | FROM pg_class \ 54 | WHERE \ 55 | relkind='i' \ 56 | AND relname = $1" 57 | 58 | #define PGSQL_GET_INDEX_SIZE_SUM "\ 59 | SELECT \ 60 | SUM(relpages::bigint * 8192) \ 61 | FROM pg_class WHERE relkind='i'" 62 | 63 | #define PGSQL_GET_INDEX_ROWS "\ 64 | SELECT \ 65 | reltuples \ 66 | FROM pg_class \ 67 | WHERE \ 68 | relkind='i' \ 69 | AND relname = $1" 70 | 71 | #define PGSQL_GET_INDEX_ROWS_SUM "\ 72 | SELECT \ 73 | SUM(reltuples::bigint) \ 74 | FROM pg_class \ 75 | WHERE relkind='i'" 76 | 77 | /* 78 | * Custom key pg.index.discovery 79 | * 80 | * Parameters: 81 | * 0: connection string 82 | * 1: connection database 83 | * 2: search mode: deep (default) | shallow 84 | * 3: filter by schema name 85 | * 4: filter by table name 86 | * 87 | * Returns all known indexes in a PostgreSQL database 88 | * 89 | * Returns: 90 | * { 91 | * "data":[ 92 | * { 93 | * "{#OID}":"12345", 94 | * "{#INDEX}":"MyIndex", 95 | * "{#DATABASE}":"MyDatabase", 96 | * "{#SCHEMA}":"public", 97 | * "{#TABLE}":"MyTable", 98 | * "{#OWNER}":"postgres", 99 | * "{#ACCESS}":"btree|hash"}]} 100 | */ 101 | int PG_INDEX_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result) 102 | { 103 | int ret = SYSINFO_RET_FAIL; // Request result code 104 | const char *__function_name = "PG_DB_DISCOVERY"; // Function name for log file 105 | 106 | char query[MAX_STRING_LEN], buffer[MAX_STRING_LEN]; 107 | char *c = NULL; 108 | 109 | char *param_mode = NULL, *param_table = NULL, *param_schema = NULL; 110 | 111 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 112 | 113 | // build the query 114 | zbx_strlcpy(query, PGSQL_DISCOVER_INDEXES, sizeof(query)); 115 | c = query; 116 | 117 | // filter by schema name 118 | param_schema = get_rparam(request, PARAM_FIRST + 1); 119 | if(!strisnull(param_schema)) { 120 | zbx_snprintf(buffer, sizeof(buffer), " AND n.nspname = '%s'", param_schema); 121 | c = strcat2(c, buffer); 122 | } 123 | 124 | // filter by table name 125 | param_table = get_rparam(request, PARAM_FIRST + 2); 126 | if(!strisnull(param_table)) { 127 | zbx_snprintf(buffer, sizeof(buffer), " AND t.relname = '%s'", param_table); 128 | c = strcat2(c, buffer); 129 | } 130 | 131 | // build results 132 | param_mode = get_rparam(request, PARAM_FIRST); 133 | if (strisnull(param_mode) || 0 == strcmp(param_mode, "deep")) { 134 | ret = pg_get_discovery_wide(request, result, query, NULL); 135 | } else if (0 == strcmp(param_mode, "shallow")) { 136 | ret = pg_get_discovery(request, result, query, NULL); 137 | } else { 138 | set_err_result(result, "Invalid search mode parameter: %s", param_mode); 139 | } 140 | 141 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 142 | return ret; 143 | } 144 | 145 | /* 146 | * Custom keys pg.index.* (for each field in pg_stat_all_indexes) 147 | * 148 | * Returns the requested statistic for the specified index 149 | * 150 | * Parameters: 151 | * 0: connection string 152 | * 1: connection database 153 | * 2: filter by index name (default: sum of all indexes) 154 | * 155 | * Returns: u 156 | */ 157 | int PG_STAT_ALL_INDEXES(AGENT_REQUEST *request, AGENT_RESULT *result) 158 | { 159 | int ret = SYSINFO_RET_FAIL; // Request result code 160 | const char *__function_name = "PG_STAT_ALL_INDEXES"; // Function name for log file 161 | 162 | char *index = NULL; 163 | 164 | PGconn *conn = NULL; 165 | PGresult *res = NULL; 166 | 167 | char *field; 168 | char query[MAX_STRING_LEN]; 169 | char *buffer = NULL; 170 | 171 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 172 | 173 | // Get stat field from requested key name "pb.table." 174 | field = &request->key[9]; 175 | 176 | // Build query 177 | index = get_rparam(request, PARAM_FIRST); 178 | if(strisnull(index)) 179 | zbx_snprintf(query, sizeof(query), "SELECT SUM(%s) FROM pg_stat_all_indexes", field); 180 | else 181 | zbx_snprintf(query, sizeof(query), "SELECT %s FROM pg_stat_all_indexes WHERE indexrelname = $1", field); 182 | 183 | // Connect to PostgreSQL 184 | if(NULL == (conn = pg_connect_request(request, result))) 185 | goto out; 186 | 187 | // Execute a query 188 | res = pg_exec(conn, query, param_new(index)); 189 | if(PQresultStatus(res) != PGRES_TUPLES_OK) { 190 | set_err_result(result, "PostgreSQL query error: %s", PQresultErrorMessage(res)); 191 | goto out; 192 | } 193 | 194 | if(0 == PQntuples(res)) { 195 | set_err_result(result, "No results returned for query: %s", query); 196 | goto out; 197 | } 198 | 199 | // Set result 200 | buffer = strdup(PQgetvalue(res, 0, 0)); 201 | SET_UI64_RESULT(result, atoi(buffer)); 202 | ret = SYSINFO_RET_OK; 203 | 204 | out: 205 | PQclear(res); 206 | PQfinish(conn); 207 | 208 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 209 | return ret; 210 | } 211 | 212 | /* 213 | * Custom keys pg.index.* (for each field in pg_statio_all_indexes) 214 | * 215 | * Returns the requested IO statistic for the specified index 216 | * 217 | * Parameters: 218 | * 0: connection string 219 | * 1: connection database 220 | * 2: filter by index name (default: sum of all indexes) 221 | * 222 | * Returns: u 223 | */ 224 | int PG_STATIO_ALL_INDEXES(AGENT_REQUEST *request, AGENT_RESULT *result) 225 | { 226 | int ret = SYSINFO_RET_FAIL; // Request result code 227 | const char *__function_name = "PG_STAT_ALL_INDEXES"; // Function name for log file 228 | 229 | char *index = NULL; 230 | 231 | char *field; 232 | char query[MAX_STRING_LEN]; 233 | 234 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 235 | 236 | // Get stat field from requested key name "pb.index." 237 | field = &request->key[9]; 238 | 239 | // Build query 240 | index = get_rparam(request, PARAM_FIRST); 241 | if(strisnull(index)) 242 | zbx_snprintf(query, sizeof(query), PGSQL_GET_INDEX_STATIO_SUM, field); 243 | else 244 | zbx_snprintf(query, sizeof(query), "SELECT %s FROM pg_statio_all_indexes WHERE indexrelname = $1", field); 245 | 246 | ret = pg_get_int(request, result, query, param_new(index)); 247 | 248 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 249 | return ret; 250 | } 251 | 252 | int PG_INDEX_IDX_BLKS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result) 253 | { 254 | int ret = SYSINFO_RET_FAIL; // Request result code 255 | const char *__function_name = "PG_INDEX_IDX_BLKS_RATIO"; // Function name for log file 256 | 257 | char *index = NULL; 258 | 259 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 260 | 261 | index = get_rparam(request, PARAM_FIRST); 262 | 263 | if(strisnull(index)) { 264 | ret = pg_get_percentage( 265 | request, 266 | result, 267 | "pg_statio_all_indexes", 268 | "sum(idx_blks_hit)", 269 | "sum(idx_blks_hit) + sum(idx_blks_read)", 270 | NULL, 271 | NULL); 272 | 273 | } else { 274 | ret = pg_get_percentage( 275 | request, 276 | result, 277 | "pg_statio_all_indexes", 278 | "idx_blks_hit", 279 | "idx_blks_hit + idx_blks_read", 280 | "indexrelname", 281 | index); 282 | } 283 | 284 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 285 | return ret; 286 | } 287 | /* 288 | * Custom key pg.index.size 289 | * 290 | * Returns the disk usage in bytes for the specified index 291 | * 292 | * Parameters: 293 | * 0: connection string 294 | * 1: connection database 295 | * 2: filter by index name (default: sum of all indexes) 296 | * 297 | * Returns: u 298 | */ 299 | int PG_INDEX_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result) 300 | { 301 | int ret = SYSINFO_RET_FAIL; // Request result code 302 | const char *__function_name = "PG_INDEX_SIZE"; // Function name for log file 303 | char *index = NULL; //, *include = NULL; 304 | 305 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 306 | 307 | // Parse parameters 308 | index = get_rparam(request, PARAM_FIRST); 309 | 310 | // Build query 311 | if(strisnull(index)) 312 | ret = pg_get_int(request, result, PGSQL_GET_INDEX_SIZE_SUM, NULL); 313 | else 314 | ret = pg_get_int(request, result, PGSQL_GET_INDEX_SIZE, param_new(index)); 315 | 316 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 317 | return ret; 318 | } 319 | 320 | /* 321 | * Custom key pg.index.rows 322 | * 323 | * Returns the estimated row count for the specified index 324 | * 325 | * Parameters: 326 | * 0: connection string 327 | * 1: connection database 328 | * 2: filter by index name (default: sum of all indexes) 329 | * 330 | * Returns: u 331 | */ 332 | int PG_INDEX_ROWS(AGENT_REQUEST *request, AGENT_RESULT *result) 333 | { 334 | int ret = SYSINFO_RET_FAIL; // Request result code 335 | const char *__function_name = "PG_INDEX_ROWS"; // Function name for log file 336 | 337 | char *query = NULL; 338 | char *index = NULL; //, *include = NULL; 339 | 340 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 341 | 342 | // Parse parameters 343 | index = get_rparam(request, PARAM_FIRST); 344 | 345 | // Build query 346 | if(strisnull(index)) 347 | query = PGSQL_GET_INDEX_ROWS_SUM; 348 | else 349 | query = PGSQL_GET_INDEX_ROWS; 350 | 351 | ret = pg_get_int(request, result, query, param_new(index)); 352 | 353 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 354 | return ret; 355 | } 356 | -------------------------------------------------------------------------------- /src/pg_namespace.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | #include "libzbxpgsql.h" 21 | 22 | #define PGSQL_DISCOVER_NAMESPACES "\ 23 | SELECT \ 24 | n.oid AS oid, \ 25 | current_database() || '.' || n.nspname AS path, \ 26 | n.nspname AS schema, \ 27 | n.nspname AS namespace, \ 28 | current_database() AS database, \ 29 | pg_catalog.pg_get_userbyid(n.nspowner) AS owner, \ 30 | pg_catalog.obj_description(n.oid, 'pg_namespace') AS description \ 31 | FROM pg_catalog.pg_namespace n \ 32 | WHERE \ 33 | n.nspname !~ '^pg_' \ 34 | AND n.nspname <> 'information_schema' \ 35 | ORDER BY namespace;" 36 | 37 | #define PGSQL_GET_NS_SIZE "\ 38 | SELECT \ 39 | SUM(pg_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename))::bigint) \ 40 | FROM pg_tables \ 41 | WHERE schemaname = $1" 42 | 43 | /* 44 | * Custom key pg.namespace.discovery 45 | * 46 | * Returns all known schemas/namespaces in a PostgreSQL database 47 | * 48 | * Parameters: 49 | * 0: connection string 50 | * 1: connection database 51 | * 2: search mode: deep (default) | shallow 52 | * 53 | * Returns: 54 | * { 55 | * "data":[ 56 | * { 57 | * "{#OID}":"12345", 58 | * "{#SCHEMA}":"public", 59 | * "{#DATABASE}:"MyDb", 60 | * "{#OWNER}":"postgres"}]} 61 | */ 62 | int PG_NAMESPACE_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result) 63 | { 64 | int ret = SYSINFO_RET_FAIL; // Request result code 65 | const char *__function_name = "PG_NAMESPACE_DISCOVERY"; // Function name for log file 66 | char *mode = NULL; 67 | 68 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 69 | 70 | mode = get_rparam(request, PARAM_FIRST); 71 | 72 | if (strisnull(mode) || 0 == strcmp(mode, "deep")) { 73 | // search all connectable databases 74 | ret = pg_get_discovery_wide(request, result, PGSQL_DISCOVER_NAMESPACES, NULL); 75 | } else if (0 == strcmp(mode, "shallow")) { 76 | // search only connected database 77 | ret = pg_get_discovery(request, result, PGSQL_DISCOVER_NAMESPACES, NULL); 78 | } else { 79 | set_err_result(result, "Invalid search mode parameter: %s", mode); 80 | } 81 | 82 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 83 | return ret; 84 | } 85 | 86 | /* 87 | * Custom key pg.namespace.size 88 | * 89 | * Returns the disk usage in bytes for the specified namespace/schema 90 | * 91 | * Parameters: 92 | * 0: connection string 93 | * 1: connection database 94 | * 2: filter by schema name (default: sum of all schema) 95 | * 96 | * Returns: u 97 | */ 98 | int PG_NAMESPACE_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result) 99 | { 100 | int ret = SYSINFO_RET_FAIL; // Request result code 101 | const char *__function_name = "PG_NAMESPACE_SIZE"; // Function name for log file 102 | char *schema = NULL; 103 | 104 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 105 | 106 | // Parse parameters 107 | schema = get_rparam(request, PARAM_FIRST); 108 | 109 | // Build query 110 | if(strisnull(schema)) { 111 | set_err_result(result, "No schema name specified"); 112 | goto out; 113 | } 114 | 115 | ret = pg_get_int(request, result, PGSQL_GET_NS_SIZE, param_new(schema)); 116 | 117 | out: 118 | 119 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 120 | return ret; 121 | } 122 | -------------------------------------------------------------------------------- /src/pg_params.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | #include "libzbxpgsql.h" 21 | 22 | int param_len(PGparams params) 23 | { 24 | int len = 0; 25 | PGparams c = NULL; 26 | 27 | if (NULL == params) 28 | return 0; 29 | 30 | for (c = params; *c; c++) 31 | len++; 32 | 33 | return len; 34 | } 35 | 36 | char **param_append(PGparams params, char *s) 37 | { 38 | int len = 0; 39 | 40 | // never append nulls or empty strings 41 | if(NULL == s || '\0' == *s) 42 | return params; 43 | 44 | // allocate new array 45 | if(NULL == params) { 46 | params = zbx_malloc(params, sizeof(PGparams) * 2); 47 | params[0] = strdup(s); 48 | params[1] = NULL; 49 | return params; 50 | } 51 | 52 | // extend array and append 53 | len = param_len(params); 54 | params = zbx_realloc(params, sizeof(PGparams) * (len + 2)); 55 | params[len] = strdup(s); // dup so we can free everything later 56 | params[len + 1] = NULL; 57 | 58 | return params; 59 | } 60 | 61 | void param_free(PGparams params) 62 | { 63 | PGparams p = NULL; 64 | 65 | if (NULL == params) 66 | return; 67 | 68 | for (p = params; *p; p++) 69 | zbx_free(*p); 70 | 71 | zbx_free(params); 72 | } -------------------------------------------------------------------------------- /src/pg_query.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | #include "libzbxpgsql.h" 21 | 22 | /* 23 | * Custom key pg.query.* 24 | * 25 | * Returns the value of the first column of the first row returned by the 26 | * specified SQL query. 27 | * 28 | * Parameters: 29 | * 0: connection string 30 | * 1: connection database 31 | * 2: scalar SQL query to execute 32 | * n: query parameters 33 | * 34 | * Returns: u 35 | */ 36 | int PG_QUERY(AGENT_REQUEST *request, AGENT_RESULT *result) 37 | { 38 | int ret = SYSINFO_RET_FAIL; // Request result code 39 | const char *__function_name = "PG_QUERY"; // Function name for log file 40 | const char *queryKey = NULL, *query = NULL; 41 | int i = 0; 42 | PGparams params = NULL; 43 | 44 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 45 | 46 | // Get the user SQL query parameter 47 | queryKey = get_rparam(request, PARAM_FIRST); 48 | if (strisnull(queryKey)) { 49 | set_err_result(result, "No query or query-key specified"); 50 | goto out; 51 | } 52 | 53 | // Check if query comes from configs 54 | query = get_query_by_name(queryKey); 55 | if(NULL == query) { 56 | zabbix_log(LOG_LEVEL_DEBUG, "No query found for %s", queryKey); 57 | query = queryKey; 58 | } 59 | 60 | // parse user params 61 | zabbix_log(LOG_LEVEL_DEBUG, "Appending %i params to query", request->nparam - 3); 62 | for (i = 3; i < request->nparam; i++) { 63 | params = param_append(params, get_rparam(request, i)); 64 | } 65 | 66 | // Return the appropriate result type for this key 67 | // as per `pg.query.{type}` 68 | if(0 == strncmp(&request->key[9], "string", 5)) 69 | ret = pg_get_string(request, result, query, params); 70 | 71 | else if(0 == strncmp(&request->key[9], "integer", 7)) 72 | ret = pg_get_int(request, result, query, params); 73 | 74 | else if(0 == strncmp(&request->key[9], "double", 6)) 75 | ret = pg_get_dbl(request, result, query, params); 76 | 77 | else if(0 == strncmp(&request->key[9], "discovery", 9)) 78 | ret = pg_get_discovery(request, result, query, params); 79 | 80 | else 81 | set_err_result(result, "Unsupported query type: %s", request->key[9]); 82 | 83 | out: 84 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 85 | return ret; 86 | } 87 | -------------------------------------------------------------------------------- /src/pg_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | #include "libzbxpgsql.h" 21 | 22 | #define PGSQL_GET_VERSION "SELECT version();" 23 | 24 | #define PGSQL_GET_STARTTIME "SELECT pg_postmaster_start_time();" 25 | 26 | #define PGSQL_GET_UPTIME "SELECT EXTRACT(EPOCH FROM NOW()) - EXTRACT(EPOCH FROM pg_postmaster_start_time());" 27 | 28 | /* 29 | * Custom key pg.connect 30 | * 31 | * Returns 1 if the Zabbix Agent can connect to PostgreSQL instance 32 | * 33 | * Parameters: 34 | * 0: connection string 35 | * 1: connection database 36 | * 37 | * Returns: b 38 | */ 39 | int PG_CONNECT(AGENT_REQUEST *request, AGENT_RESULT *result) 40 | { 41 | int ret = SYSINFO_RET_FAIL; 42 | const char *__function_name = "PG_CONNECT"; 43 | PGconn *conn = NULL; 44 | 45 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 46 | 47 | // connect without setting an error message on failure 48 | conn = pg_connect_request(request, NULL); 49 | 50 | if(NULL != conn && CONNECTION_OK == PQstatus(conn)) { 51 | SET_UI64_RESULT(result, 1); 52 | PQfinish(conn); 53 | } 54 | else { 55 | SET_UI64_RESULT(result, 0); 56 | } 57 | 58 | // Set result 59 | ret = SYSINFO_RET_OK; 60 | 61 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 62 | return ret; 63 | } 64 | 65 | /* 66 | * Custom key pg.version 67 | * 68 | * Returns the version string of the connection PostgreSQL Server, E.g.: 69 | * PostgreSQL 9.4.4 on x86_64-unknown-linux-gnu, compiled by gcc 70 | * (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3, 64-bit 71 | * 72 | * Parameters: 73 | * 0: connection string 74 | * 1: connection database 75 | * 76 | * Returns: s 77 | */ 78 | int PG_VERSION(AGENT_REQUEST *request, AGENT_RESULT *result) 79 | { 80 | int ret = SYSINFO_RET_FAIL; // Request result code 81 | const char *__function_name = "PG_VERSION"; // Function name for log file 82 | 83 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 84 | 85 | ret = pg_get_string(request, result, PGSQL_GET_VERSION, NULL); 86 | 87 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 88 | return ret; 89 | } 90 | 91 | /* 92 | * Custom key pg.starttime 93 | * 94 | * Returns the start time of the postmaster daemon. E.g. 95 | * 2015-08-08 08:00:17.894706+00 96 | * 97 | * Parameters: 98 | * 0: connection string 99 | * 1: connection database 100 | * 101 | * Returns: s 102 | */ 103 | int PG_STARTTIME(AGENT_REQUEST *request, AGENT_RESULT *result) 104 | { 105 | int ret = SYSINFO_RET_FAIL; // Request result code 106 | const char *__function_name = "PG_STARTTIME"; // Function name for log file 107 | 108 | char *query = PGSQL_GET_STARTTIME; 109 | 110 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 111 | 112 | ret = pg_get_string(request, result, query, NULL); 113 | 114 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 115 | return ret; 116 | } 117 | 118 | /* 119 | * Custom key pg.uptime 120 | * 121 | * Returns the uptime of the postmaster daemon in second. E.g. 122 | * 86400 123 | * 124 | * Parameters: 125 | * 0: connection string 126 | * 1: connection database 127 | * 128 | * Returns: u 129 | */ 130 | int PG_UPTIME(AGENT_REQUEST *request, AGENT_RESULT *result) 131 | { 132 | int ret = SYSINFO_RET_FAIL; // Request result code 133 | const char *__function_name = "PG_UPTIME"; // Function name for log file 134 | 135 | char *query = PGSQL_GET_UPTIME; 136 | 137 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 138 | 139 | ret = pg_get_int(request, result, query, NULL); 140 | 141 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 142 | return ret; 143 | } 144 | 145 | /* 146 | * Custom key pg.prepared_xacts_count 147 | * 148 | * Returns the number of transactions that are currently prepared for two phase 149 | * commit. 150 | * 151 | * Parameters: 152 | * 0: connection string 153 | * 1: connection database 154 | * 2: filter by database 155 | * 156 | * Returns: u 157 | */ 158 | int PG_PREPARED_XACTS_COUNT(AGENT_REQUEST *request, AGENT_RESULT *result) 159 | { 160 | int ret = SYSINFO_RET_FAIL; 161 | const char *__function_name = "PG_PREPARED_XACTS_COUNT"; 162 | 163 | char *datname = NULL; 164 | 165 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 166 | 167 | // get requested database 168 | datname = get_rparam(request, PARAM_FIRST); 169 | if(strisnull(datname)) { 170 | ret = pg_get_int(request, result, "SELECT COUNT (transaction) FROM pg_prepared_xacts;", NULL); 171 | } else { 172 | ret = pg_get_int(request, result, "SELECT COUNT (transaction) FROM pg_prepared_xacts WHERE database = $1;", param_new(datname)); 173 | } 174 | 175 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 176 | return ret; 177 | } 178 | 179 | /* 180 | * Custom key pg.prepared_xacts_ratio 181 | * 182 | * Returns the number of transactions that are currently prepared for two phase 183 | * commit as ratio of the maximum allowed prepared transactions. 184 | * 185 | * Parameters: 186 | * 0: connection string 187 | * 1: connection database 188 | * 189 | * Returns: d 190 | */ 191 | int PG_PREPARED_XACTS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result) 192 | { 193 | int ret = SYSINFO_RET_FAIL; 194 | const char *__function_name = "PG_PREPARED_XACTS_RATIO"; 195 | 196 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 197 | 198 | ret = pg_get_dbl( 199 | request, 200 | result, 201 | "\ 202 | SELECT \ 203 | CASE \ 204 | WHEN setting::integer = 0 THEN 0.00 \ 205 | ELSE ((SELECT COUNT (transaction) FROM pg_prepared_xacts)::float / setting::integer) \ 206 | END \ 207 | FROM pg_settings \ 208 | WHERE name = 'max_prepared_transactions';", 209 | NULL 210 | ); 211 | 212 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 213 | return ret; 214 | } 215 | 216 | /* 217 | * Custom key pg.prepared_xacts_age 218 | * 219 | * Returns the age in seconds of the oldest transaction currently prepared for 220 | * two phase commit. 221 | * 222 | * Parameters: 223 | * 0: connection string 224 | * 1: connection database 225 | * 2: filter by database 226 | * 227 | * Returns: d 228 | */ 229 | int PG_PREPARED_XACTS_AGE(AGENT_REQUEST *request, AGENT_RESULT *result) 230 | { 231 | int ret = SYSINFO_RET_FAIL; 232 | const char *__function_name = "PG_PREPARED_XACTS_AGE"; 233 | 234 | char *datname = NULL; 235 | 236 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 237 | 238 | // get requested database 239 | datname = get_rparam(request, PARAM_FIRST); 240 | if(strisnull(datname)) { 241 | ret = pg_get_int( 242 | request, 243 | result, 244 | "\ 245 | SELECT \ 246 | COALESCE(MAX((EXTRACT(EPOCH FROM NOW()) - EXTRACT(EPOCH FROM prepared))::integer), 0) \ 247 | FROM pg_prepared_xacts;", 248 | NULL 249 | ); 250 | } else { 251 | ret = pg_get_int( 252 | request, 253 | result, 254 | "\ 255 | SELECT \ 256 | COALESCE(MAX((EXTRACT(EPOCH FROM NOW()) - EXTRACT(EPOCH FROM prepared))::integer), 0) \ 257 | FROM pg_prepared_xacts \ 258 | WHERE database = $1;", 259 | param_new(datname) 260 | ); 261 | } 262 | 263 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 264 | return ret; 265 | } 266 | -------------------------------------------------------------------------------- /src/pg_setting.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | /* 21 | * See: http://www.postgresql.org/docs/9.4/static/view-pg-settings.html 22 | */ 23 | 24 | #include "libzbxpgsql.h" 25 | 26 | #define PGSQL_DISCOVER_SETTINGS "\ 27 | SELECT \ 28 | name AS setting\ 29 | , unit AS unit \ 30 | , category AS category \ 31 | , short_desc AS description \ 32 | , context AS context \ 33 | , vartype AS vartype \ 34 | FROM pg_settings;" 35 | 36 | #define PGSQL_GET_SETTING "SELECT setting,vartype FROM pg_settings WHERE name=$1;" 37 | 38 | /* 39 | * Custom key pg.setting.discovery 40 | * 41 | * Returns all known configuration settings 42 | * 43 | * Parameters: 44 | * 0: connection string 45 | * 1: connection database 46 | * 47 | * Returns: 48 | * { 49 | * "data":[ 50 | * { 51 | * "{#SETTING}":"MyDatabase", 52 | * "{#UNIT}":"s|kB|etc.", 53 | * "{#CATEGORY}":"File locations|Autovacuum|etc.", 54 | * "{#DESCRIPTION}":"Sets the server's main configuration file.", 55 | * "{#CONTEXT}":"postmaster|sighup|etc.", 56 | * "{#VARTYPE}":"bool|string|integer|enum|real"]} 57 | */ 58 | int PG_SETTING_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result) 59 | { 60 | int ret = SYSINFO_RET_FAIL; // Request result code 61 | const char *__function_name = "PG_SETTING_DISCOVERY"; // Function name for log file 62 | 63 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 64 | 65 | ret = pg_get_discovery(request, result, PGSQL_DISCOVER_SETTINGS, NULL); 66 | 67 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 68 | return ret; 69 | } 70 | 71 | /* 72 | * Function: PG_SETTING 73 | * 74 | * Provides access to run-time parameters of the server such as those returned 75 | * by `SHOW` commands. 76 | * 77 | * Parameters: 78 | * 0: connection string 79 | * 1: connection database 80 | * 2: run-time configuration parameter name 81 | * 82 | * Returns: determined by parameter vartype 83 | */ 84 | int PG_SETTING(AGENT_REQUEST *request, AGENT_RESULT *result) 85 | { 86 | int ret = SYSINFO_RET_FAIL; // Request result code 87 | const char *__function_name = "PG_SETTING"; // Function name for log file 88 | 89 | PGconn *conn = NULL; 90 | PGresult *res = NULL; 91 | 92 | char *setting = NULL; 93 | char *value = NULL; 94 | char *type = NULL; 95 | 96 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 97 | 98 | // parse parameters 99 | setting = get_rparam(request, PARAM_FIRST); 100 | if(NULL == setting || '\0' == *setting) { 101 | set_err_result(result, "No setting name specified"); 102 | goto out; 103 | } 104 | 105 | // Connect to PostgreSQL 106 | if(NULL == (conn = pg_connect_request(request, result))) 107 | goto out; 108 | 109 | // Execute the query 110 | res = pg_exec(conn, PGSQL_GET_SETTING, param_new(setting)); 111 | if(PQresultStatus(res) != PGRES_TUPLES_OK) { 112 | set_err_result(result, "PostgreSQL query error: %s", PQresultErrorMessage(res)); 113 | goto out; 114 | } 115 | 116 | if(0 == PQntuples(res)) { 117 | zabbix_log(LOG_LEVEL_DEBUG, "No results returned for query \"%s\" in %s()", PGSQL_GET_SETTING, __function_name); 118 | goto out; 119 | } 120 | 121 | // Set result by type 122 | value = strdup(PQgetvalue(res, 0, 0)); 123 | type = strdup(PQgetvalue(res, 0, 1)); 124 | 125 | if(0 == strncmp("integer", type, 7)) 126 | SET_UI64_RESULT(result, strtoull(value, NULL, 10)); 127 | else if(0 == strncmp("real", type, 4)) 128 | SET_DBL_RESULT(result, strtold(value, NULL)); 129 | else 130 | SET_STR_RESULT(result, value); 131 | 132 | ret = SYSINFO_RET_OK; 133 | 134 | out: 135 | PQclear(res); 136 | PQfinish(conn); 137 | 138 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s(%s)", __function_name, request->key); 139 | return ret; 140 | } 141 | -------------------------------------------------------------------------------- /src/pg_table.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | #include "libzbxpgsql.h" 21 | 22 | #define PGSQL_DISCOVER_TABLES "\ 23 | SELECT \ 24 | c.oid AS oid \ 25 | , current_database() || '.' || n.nspname || '.' || c.relname AS path\ 26 | , current_database() AS database \ 27 | , n.nspname AS schema \ 28 | , CASE c.reltablespace \ 29 | WHEN 0 THEN (SELECT ds.spcname FROM pg_tablespace ds JOIN pg_database d ON d.dattablespace = ds.oid WHERE d.datname = current_database()) \ 30 | ELSE (SELECT spcname FROM pg_tablespace WHERE oid = c.reltablespace) \ 31 | END AS tablespace \ 32 | , c.relname AS table \ 33 | ,t.typname AS type \ 34 | , pg_catalog.pg_get_userbyid(c.relowner) AS owner \ 35 | , (SELECT COUNT(inhparent) FROM pg_inherits WHERE inhrelid = c.oid) AS issubclass \ 36 | , pg_catalog.obj_description(c.oid, 'pg_class') as description \ 37 | FROM pg_class c \ 38 | JOIN pg_namespace n ON c.relnamespace = n.oid \ 39 | JOIN pg_type t ON c.reltype = t.oid \ 40 | WHERE \ 41 | c.relkind='r' \ 42 | AND n.nspname <> 'pg_catalog' \ 43 | AND n.nspname <> 'information_schema' \ 44 | AND n.nspname !~ '^pg_toast' \ 45 | ORDER BY c.relname;" 46 | 47 | #define PGSQL_DISCOVER_TABLE_CHILDREN "\ 48 | SELECT \ 49 | c.oid AS oid \ 50 | , current_database() || '.' || n.nspname || '.' || c.relname AS path \ 51 | , c.relname AS table \ 52 | , n.nspname AS schema \ 53 | FROM pg_inherits i \ 54 | JOIN pg_class c ON i.inhrelid = c.oid \ 55 | JOIN pg_namespace n ON c.relnamespace = n.oid \ 56 | WHERE i.inhparent = $1::regclass" 57 | 58 | #define PGSQL_GET_TABLE_STAT_SUM "\ 59 | SELECT SUM(%s::bigint) FROM pg_stat_all_tables \ 60 | WHERE \ 61 | schemaname <> 'pg_catalog' \ 62 | AND schemaname <> 'information_schema' \ 63 | AND schemaname !~ '^pg_toast'" 64 | 65 | #define PGSQL_GET_TABLE_STAT "SELECT %s FROM pg_stat_all_tables WHERE relname = $1" 66 | 67 | #define PGSQL_GET_TABLE_STATIO "SELECT %s FROM pg_statio_all_tables WHERE relname = $1" 68 | 69 | #define PGSQL_GET_TABLE_STATIO_SUM "\ 70 | SELECT SUM(%s::bigint) FROM pg_statio_all_tables \ 71 | WHERE \ 72 | schemaname <> 'pg_catalog' \ 73 | AND schemaname <> 'information_schema' \ 74 | AND schemaname !~ '^pg_toast'" 75 | 76 | #define PGSQL_GET_TABLE_SIZE "\ 77 | SELECT \ 78 | relpages::bigint * 8192 \ 79 | FROM pg_class \ 80 | WHERE relkind='r' AND relname = $1" 81 | 82 | #define PGSQL_GET_TABLE_SIZE_SUM "\ 83 | SELECT \ 84 | SUM(relpages::bigint) * 8192 \ 85 | FROM pg_class t \ 86 | JOIN pg_namespace n ON n.oid = t.relnamespace \ 87 | WHERE \ 88 | t.relkind = 'r' \ 89 | AND n.nspname <> 'pg_catalog' \ 90 | AND n.nspname <> 'information_schema' \ 91 | AND n.nspname !~ '^pg_toast'" 92 | 93 | #define PGSQL_GET_TABLE_ROWS_SUM "\ 94 | SELECT \ 95 | SUM(reltuples::bigint) \ 96 | FROM pg_class t \ 97 | JOIN pg_namespace n ON n.oid = t.relnamespace \ 98 | WHERE \ 99 | t.relkind = 'r' \ 100 | AND n.nspname <> 'pg_catalog' \ 101 | AND n.nspname <> 'information_schema' \ 102 | AND n.nspname !~ '^pg_toast'" 103 | 104 | #define PGSQL_GET_TABLE_ROWS "\ 105 | SELECT reltuples \ 106 | FROM pg_class \ 107 | WHERE \ 108 | relkind='r' \ 109 | AND relname = $1" 110 | 111 | #define PGSQL_GET_TABLE_CHILD_COUNT "\ 112 | SELECT \ 113 | COUNT(i.inhrelid) \ 114 | FROM pg_inherits i \ 115 | WHERE i.inhparent = $1::regclass" 116 | 117 | #define PGSQL_GET_CHILDREN_SIZE "\ 118 | SELECT \ 119 | SUM(c.relpages::bigint) * 8192 \ 120 | FROM pg_inherits i \ 121 | JOIN pg_class c ON inhrelid = c.oid \ 122 | WHERE i.inhparent = $1::regclass" 123 | 124 | #define PGSQL_GET_CHILDREN_ROWS "\ 125 | SELECT \ 126 | SUM(c.reltuples::bigint) \ 127 | FROM pg_inherits i \ 128 | JOIN pg_class c ON inhrelid = c.oid \ 129 | WHERE i.inhparent = $1::regclass" 130 | 131 | /* 132 | * Custom key pg.table.discovery 133 | * 134 | * Returns all known Tables in a PostgreSQL database 135 | * 136 | * Parameters: 137 | * 0: connection string 138 | * 1: connection database 139 | * 2: search mode: deep (default) | shallow 140 | * 141 | * Returns: 142 | * { 143 | * "data":[ 144 | * { 145 | * "{#DATABASE}":"MyDatabase", 146 | * "{#SCHEMA}":"public", 147 | * "{#TABLESPACE}":"pg_default", 148 | * "{#TABLE}":"MyTable", 149 | * "{#TYPE}":"MyTable", 150 | * "{#OWNER}":"postgres", 151 | * "{#PERSISTENCE":"permanent|temporary", 152 | * "{#ISSUBCLASS}":"0"}]} 153 | */ 154 | int PG_TABLE_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result) 155 | { 156 | int ret = SYSINFO_RET_FAIL; // Request result code 157 | const char *__function_name = "PG_TABLE_DISCOVERY"; // Function name for log file 158 | char *param_mode = NULL; 159 | 160 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 161 | 162 | param_mode = get_rparam(request, PARAM_FIRST); 163 | 164 | if (strisnull(param_mode) || 0 == strcmp(param_mode, "deep")) { 165 | ret = pg_get_discovery_wide(request, result, PGSQL_DISCOVER_TABLES, NULL); 166 | } else if (0 == strcmp(param_mode, "shallow")) { 167 | ret = pg_get_discovery(request, result, PGSQL_DISCOVER_TABLES, NULL); 168 | } else { 169 | set_err_result(result, "Invalid search mode parameter: %s", param_mode); 170 | } 171 | 172 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 173 | return ret; 174 | } 175 | 176 | /* 177 | * Custom key pg.table.children.discovery 178 | * 179 | * Returns all known child tables for the specified parent table 180 | * 181 | * Parameters: 182 | * 0: connection string 183 | * 1: connection database 184 | * 2: parent table name 185 | * 186 | * Returns: 187 | * { 188 | * "data":[ 189 | * { 190 | * "{#OID}":"12345", 191 | * "{#SCHEMA}":"public", 192 | * "{#TABLE}":"MyTable"}]} 193 | */ 194 | int PG_TABLE_CHILDREN_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result) 195 | { 196 | int ret = SYSINFO_RET_FAIL; // Request result code 197 | const char *__function_name = "PG_TABLE_CHILDREN_DISCOVERY"; // Function name for log file 198 | char *tablename = NULL; 199 | 200 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 201 | 202 | // Parse parameters 203 | tablename = get_rparam(request, PARAM_FIRST); 204 | if(NULL == tablename || '\0' == *tablename) { 205 | set_err_result(result, "No table name specified"); 206 | goto out; 207 | } 208 | 209 | ret = pg_get_discovery(request, result, PGSQL_DISCOVER_TABLE_CHILDREN, param_new(tablename)); 210 | 211 | out: 212 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 213 | return ret; 214 | } 215 | 216 | /* 217 | * Custom keys pg.table.* (for each field in pg_stat_all_tables) 218 | * 219 | * Returns the requested statistic for the specified data table 220 | * 221 | * Parameters: 222 | * 0: connection string 223 | * 1: connection database 224 | * 2: table name (default: sum for all) 225 | * 226 | * Returns: u 227 | */ 228 | int PG_STAT_ALL_TABLES(AGENT_REQUEST *request, AGENT_RESULT *result) 229 | { 230 | int ret = SYSINFO_RET_FAIL; // Request result code 231 | const char *__function_name = "PG_STAT_ALL_TABLES"; // Function name for log file 232 | 233 | char *tablename = NULL; 234 | char *field; 235 | char query[MAX_STRING_LEN]; 236 | 237 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 238 | 239 | // Get stat field from requested key name "pb.table." 240 | field = &request->key[9]; 241 | 242 | // Build query 243 | tablename = get_rparam(request, PARAM_FIRST); 244 | if(strisnull(tablename)) { 245 | zbx_snprintf(query, sizeof(query), PGSQL_GET_TABLE_STAT_SUM, field); 246 | } else { 247 | zbx_snprintf(query, sizeof(query), PGSQL_GET_TABLE_STAT, field); 248 | } 249 | 250 | // Set result 251 | if(0 == strncmp(field, "last_", 5)) { 252 | if(strisnull(tablename)) { 253 | // Can't do SUMs on text fields! 254 | set_err_result(result, "No table name specified"); 255 | goto out; 256 | } 257 | 258 | ret = pg_get_string(request, result, query, param_new(tablename)); 259 | } 260 | else { 261 | ret = pg_get_int(request, result, query, param_new(tablename)); 262 | } 263 | 264 | out: 265 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 266 | return ret; 267 | } 268 | 269 | int PG_TABLE_IDX_SCAN_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result) 270 | { 271 | int ret = SYSINFO_RET_FAIL; // Request result code 272 | const char *__function_name = "PG_TABLE_IDX_SCAN_RATIO"; // Function name for log file 273 | 274 | char *tablename = NULL; 275 | 276 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 277 | 278 | tablename = get_rparam(request, PARAM_FIRST); 279 | 280 | if(strisnull(tablename)) { 281 | ret = pg_get_percentage( 282 | request, 283 | result, 284 | "pg_stat_all_tables", 285 | "sum(idx_scan)", 286 | "sum(seq_scan) + sum(idx_scan)", 287 | NULL, 288 | NULL); 289 | 290 | } else { 291 | ret = pg_get_percentage( 292 | request, 293 | result, 294 | "pg_stat_all_tables", 295 | "idx_scan", 296 | "seq_scan + idx_scan", 297 | "relname", 298 | tablename); 299 | } 300 | 301 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 302 | return ret; 303 | } 304 | 305 | /* 306 | * Custom keys pg.table.* (for each field in pg_statio_all_tables) 307 | * 308 | * Returns the requested IO statistic for the specified data table 309 | * 310 | * Parameters: 311 | * 0: connection string 312 | * 1: connection database 313 | * 2: table name (default: sum for all) 314 | * 315 | * Returns: u 316 | */ 317 | int PG_STATIO_ALL_TABLES(AGENT_REQUEST *request, AGENT_RESULT *result) 318 | { 319 | int ret = SYSINFO_RET_FAIL; // Request result code 320 | const char *__function_name = "PG_STATIO_ALL_TABLES"; // Function name for log file 321 | 322 | char *tablename = NULL; 323 | 324 | char *field; 325 | char query[MAX_STRING_LEN]; 326 | 327 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 328 | 329 | // Get stat field from requested key name "pb.table." 330 | field = &request->key[9]; 331 | 332 | // Build query 333 | tablename = get_rparam(request, PARAM_FIRST); 334 | if(NULL == tablename || '\0' == *tablename) 335 | zbx_snprintf(query, sizeof(query), PGSQL_GET_TABLE_STATIO_SUM, field); 336 | else 337 | zbx_snprintf(query, sizeof(query), PGSQL_GET_TABLE_STATIO, field); 338 | 339 | ret = pg_get_int(request, result, query, param_new(tablename)); 340 | 341 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 342 | return ret; 343 | } 344 | 345 | int PG_TABLE_HEAP_BLKS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result) 346 | { 347 | int ret = SYSINFO_RET_FAIL; // Request result code 348 | const char *__function_name = "PG_TABLE_HEAP_BLKS_RATIO"; // Function name for log file 349 | 350 | char *tablename = NULL; 351 | 352 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 353 | 354 | tablename = get_rparam(request, PARAM_FIRST); 355 | 356 | if(strisnull(tablename)) { 357 | ret = pg_get_percentage( 358 | request, 359 | result, 360 | "pg_statio_all_tables", 361 | "sum(heap_blks_hit)", 362 | "sum(heap_blks_hit) + sum(heap_blks_read)", 363 | NULL, 364 | NULL); 365 | 366 | } else { 367 | ret = pg_get_percentage( 368 | request, 369 | result, 370 | "pg_statio_all_tables", 371 | "heap_blks_hit", 372 | "heap_blks_hit + heap_blks_read", 373 | "relname", 374 | tablename); 375 | } 376 | 377 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 378 | return ret; 379 | } 380 | 381 | int PG_TABLE_IDX_BLKS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result) 382 | { 383 | int ret = SYSINFO_RET_FAIL; // Request result code 384 | const char *__function_name = "PG_TABLE_IDX_BLKS_RATIO"; // Function name for log file 385 | 386 | char *tablename = NULL; 387 | 388 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 389 | 390 | tablename = get_rparam(request, PARAM_FIRST); 391 | 392 | if(strisnull(tablename)) { 393 | ret = pg_get_percentage( 394 | request, 395 | result, 396 | "pg_statio_all_tables", 397 | "sum(idx_blks_hit)", 398 | "sum(idx_blks_hit) + sum(idx_blks_read)", 399 | NULL, 400 | NULL); 401 | 402 | } else { 403 | ret = pg_get_percentage( 404 | request, 405 | result, 406 | "pg_statio_all_tables", 407 | "idx_blks_hit", 408 | "idx_blks_hit + idx_blks_read", 409 | "relname", 410 | tablename); 411 | } 412 | 413 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 414 | return ret; 415 | } 416 | 417 | int PG_TABLE_TOAST_BLKS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result) 418 | { 419 | int ret = SYSINFO_RET_FAIL; // Request result code 420 | const char *__function_name = "PG_TABLE_TOAST_BLKS_RATIO"; // Function name for log file 421 | 422 | char *tablename = NULL; 423 | 424 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 425 | 426 | tablename = get_rparam(request, PARAM_FIRST); 427 | 428 | if(strisnull(tablename)) { 429 | ret = pg_get_percentage( 430 | request, 431 | result, 432 | "pg_statio_all_tables", 433 | "sum(toast_blks_hit)", 434 | "sum(toast_blks_hit) + sum(toast_blks_read)", 435 | NULL, 436 | NULL); 437 | 438 | } else { 439 | ret = pg_get_percentage( 440 | request, 441 | result, 442 | "pg_statio_all_tables", 443 | "toast_blks_hit", 444 | "toast_blks_hit + toast_blks_read", 445 | "relname", 446 | tablename); 447 | } 448 | 449 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 450 | return ret; 451 | } 452 | 453 | int PG_TABLE_TIDX_BLKS_RATIO(AGENT_REQUEST *request, AGENT_RESULT *result) 454 | { 455 | int ret = SYSINFO_RET_FAIL; // Request result code 456 | const char *__function_name = "PG_TABLE_TIDX_BLKS_RATIO"; // Function name for log file 457 | 458 | char *tablename = NULL; 459 | 460 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 461 | 462 | tablename = get_rparam(request, PARAM_FIRST); 463 | 464 | if(strisnull(tablename)) { 465 | ret = pg_get_percentage( 466 | request, 467 | result, 468 | "pg_statio_all_tables", 469 | "sum(tidx_blks_hit)", 470 | "sum(tidx_blks_hit) + sum(tidx_blks_read)", 471 | NULL, 472 | NULL); 473 | 474 | } else { 475 | ret = pg_get_percentage( 476 | request, 477 | result, 478 | "pg_statio_all_tables", 479 | "tidx_blks_hit", 480 | "tidx_blks_hit + tidx_blks_read", 481 | "relname", 482 | tablename); 483 | } 484 | 485 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 486 | return ret; 487 | } 488 | 489 | /* 490 | * Custom key pg.table.size 491 | * 492 | * Returns the disk usage in bytes for the specified data table 493 | * 494 | * Parameters: 495 | * 0: connection string 496 | * 1: connection database 497 | * 2: table name (default: sum for all) 498 | * 499 | * TODO: implement sizing of parent/child/all for tables 500 | * 3: include statistics for on of: 501 | * table: named table only (default) 502 | * children: children of the named table only 503 | * all: named table and its children 504 | * 505 | * Returns: u 506 | */ 507 | int PG_TABLE_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result) 508 | { 509 | int ret = SYSINFO_RET_FAIL; // Request result code 510 | const char *__function_name = "PG_TABLE_SIZE"; // Function name for log file 511 | 512 | char *query = NULL; 513 | char *tablename = NULL; 514 | 515 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 516 | 517 | // Parse parameters 518 | tablename = get_rparam(request, PARAM_FIRST); 519 | 520 | // Build query 521 | if(strisnull(tablename)) 522 | query = PGSQL_GET_TABLE_SIZE_SUM; 523 | else 524 | query = PGSQL_GET_TABLE_SIZE; 525 | 526 | ret = pg_get_int(request, result, query, param_new(tablename)); 527 | 528 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 529 | return ret; 530 | } 531 | 532 | /* 533 | * Custom key pg.table.rows 534 | * 535 | * Returns the estimated row count for the specified class (table, index, etc.) 536 | * 537 | * See: http://www.postgresql.org/docs/9.4/static/catalog-pg-class.html 538 | * https://wiki.postgresql.org/wiki/Disk_Usage 539 | * 540 | * Parameters: 541 | * 0: connection string 542 | * 1: connection database 543 | * 2: table name (default: sum for all) 544 | * 545 | * Returns: u 546 | */ 547 | int PG_TABLE_ROWS(AGENT_REQUEST *request, AGENT_RESULT *result) 548 | { 549 | int ret = SYSINFO_RET_FAIL; // Request result code 550 | const char *__function_name = "PG_TABLE_ROWS"; // Function name for log file 551 | 552 | char *tablename = NULL; 553 | char *query = NULL; 554 | 555 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 556 | 557 | tablename = get_rparam(request, PARAM_FIRST); 558 | if(strisnull(tablename)) 559 | query = PGSQL_GET_TABLE_ROWS_SUM; 560 | else 561 | query = PGSQL_GET_TABLE_ROWS; 562 | 563 | ret = pg_get_int(request, result, query, param_new(tablename)); 564 | 565 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 566 | return ret; 567 | } 568 | 569 | /* 570 | * Custom key pg.table.children 571 | * 572 | * Returns the number of tables that inherit from the specified table 573 | * 574 | * Parameters: 575 | * 0: connection string 576 | * 1: connection database 577 | * 2: table name 578 | * 579 | * Returns: u 580 | */ 581 | int PG_TABLE_CHILDREN(AGENT_REQUEST *request, AGENT_RESULT *result) 582 | { 583 | int ret = SYSINFO_RET_FAIL; // Request result code 584 | const char *__function_name = "PG_TABLE_CHILDREN"; // Function name for log file 585 | char *tablename = NULL; 586 | 587 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 588 | 589 | tablename = get_rparam(request, PARAM_FIRST); 590 | if(strisnull(tablename)) { 591 | set_err_result(result, "Invalid parameter count. Please specify a table name."); 592 | goto out; 593 | } 594 | 595 | ret = pg_get_int(request, result, PGSQL_GET_TABLE_CHILD_COUNT, param_new(tablename)); 596 | 597 | out: 598 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 599 | return ret; 600 | } 601 | 602 | /* 603 | * Custom key pg.table.children.size 604 | * 605 | * Returns the sum size in bytes of all tables that inherit from the specified table 606 | * 607 | * Parameters: 608 | * 0: connection string 609 | * 1: connection database 610 | * 2: table name 611 | * 612 | * Returns: u 613 | */ 614 | int PG_TABLE_CHILDREN_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result) 615 | { 616 | int ret = SYSINFO_RET_FAIL; // Request result code 617 | const char *__function_name = "PG_TABLE_CHILDREN_SIZE"; // Function name for log file 618 | 619 | char *tablename = NULL; 620 | 621 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 622 | 623 | tablename = get_rparam(request, PARAM_FIRST); 624 | if(NULL == tablename || '\0' == *tablename) { 625 | set_err_result(result, "Invalid parameter count. Please specify a table name."); 626 | goto out; 627 | } 628 | 629 | ret = pg_get_int(request, result, PGSQL_GET_CHILDREN_SIZE, param_new(tablename)); 630 | 631 | out: 632 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 633 | return ret; 634 | } 635 | 636 | /* 637 | * Custom key pg.table.children.rows 638 | * 639 | * Returns the sum estimated row count of all tables that inherit from the specified table 640 | * 641 | * Parameters: 642 | * 0: connection string 643 | * 1: connection database 644 | * 2: table name 645 | * 646 | * Returns: u 647 | */ 648 | int PG_TABLE_CHILDREN_ROWS(AGENT_REQUEST *request, AGENT_RESULT *result) 649 | { 650 | int ret = SYSINFO_RET_FAIL; // Request result code 651 | const char *__function_name = "PG_TABLE_CHILDREN_TUPLES"; // Function name for log file 652 | char *tablename = NULL; 653 | 654 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 655 | 656 | tablename = get_rparam(request, PARAM_FIRST); 657 | if(strisnull(tablename)) { 658 | set_err_result(result, "Invalid parameter count. Please specify a table name."); 659 | goto out; 660 | } 661 | 662 | ret = pg_get_int(request, result, PGSQL_GET_CHILDREN_ROWS, param_new(tablename)); 663 | 664 | out: 665 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 666 | return ret; 667 | } 668 | -------------------------------------------------------------------------------- /src/pg_tablespace.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** libzbxpgsql - A PostgreSQL monitoring module for Zabbix 3 | ** Copyright (C) 2015 - Ryan Armstrong 4 | ** 5 | ** This program is free software; you can redistribute it and/or modify 6 | ** it under the terms of the GNU General Public License as published by 7 | ** the Free Software Foundation; either version 2 of the License, or 8 | ** (at your option) any later version. 9 | ** 10 | ** This program is distributed in the hope that it will be useful, 11 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ** GNU General Public License for more details. 14 | ** 15 | ** You should have received a copy of the GNU General Public License 16 | ** along with this program; if not, write to the Free Software 17 | ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | **/ 19 | 20 | #include "libzbxpgsql.h" 21 | 22 | #define PGSQL_DISCOVER_TABLESPACES "\ 23 | SELECT \ 24 | t.oid AS oid \ 25 | , t.spcname AS tablespace \ 26 | , pg_catalog.pg_get_userbyid(spcowner) AS owner \ 27 | , pg_catalog.shobj_description(oid, 'pg_tablespace') AS description \ 28 | FROM pg_catalog.pg_tablespace t \ 29 | ORDER BY t.spcname;" 30 | 31 | #define PGSQL_DISCOVER_TABLESPACES_92 "\ 32 | SELECT \ 33 | t.oid AS oid \ 34 | , t.spcname AS tablespace \ 35 | , pg_catalog.pg_get_userbyid(spcowner) AS owner \ 36 | , pg_catalog.pg_tablespace_location(oid) AS location \ 37 | , pg_catalog.shobj_description(oid, 'pg_tablespace') AS description \ 38 | FROM pg_catalog.pg_tablespace t \ 39 | ORDER BY t.spcname;" 40 | 41 | #define PGSQL_GET_TS_SIZE "SELECT pg_tablespace_size($1)" 42 | 43 | /* 44 | * Custom key pg.tablespace.discovery 45 | * 46 | * Returns all known tablespaces in a PostgreSQL instance 47 | * 48 | * Parameters: 49 | * 0: connection string 50 | * 51 | * Returns: 52 | * { 53 | * "data":[ 54 | * { 55 | * "{#OID}":"12345", 56 | * "{#TABLESPACE}":"MyTableSpace", 57 | * "{#OWNER}":"postgres", 58 | * "{#LOCATION}":"/var/lib/pgsql/9.4/data", 59 | * "{#DESCRIPTION}":"Default tablespace"}]} 60 | */ 61 | int PG_TABLESPACE_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result) 62 | { 63 | int ret = SYSINFO_RET_FAIL; // Request result code 64 | const char *__function_name = "PG_TABLESPACE_DISCOVERY"; // Function name for log file 65 | 66 | int version = 0; 67 | 68 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 69 | 70 | if (0 == (version = pg_version(request, result))) 71 | goto out; 72 | else if (version >= 90200) 73 | // tablespace location introduced in v9.2 74 | ret = pg_get_discovery(request, result, PGSQL_DISCOVER_TABLESPACES_92, NULL); 75 | else 76 | ret = pg_get_discovery(request, result, PGSQL_DISCOVER_TABLESPACES, NULL); 77 | 78 | out: 79 | 80 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 81 | return ret; 82 | } 83 | 84 | /* 85 | * Custom key pg.tablespace.size 86 | * 87 | * Returns the size of the specified tablespace in bytes 88 | * 89 | * Parameters: 90 | * 0: connection string 91 | * 1: tablespace name 92 | * 93 | * Returns: u 94 | */ 95 | int PG_TABLESPACE_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result) 96 | { 97 | int ret = SYSINFO_RET_FAIL; // Request result code 98 | const char *__function_name = "PG_TABLESPACE_SIZE"; // Function name for log file 99 | char *tablespace = NULL; 100 | 101 | zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name); 102 | 103 | // Build query 104 | tablespace = get_rparam(request, PARAM_FIRST); 105 | if(strisnull(tablespace)) { 106 | set_err_result(result, "No tablespace specified"); 107 | goto out; 108 | } 109 | 110 | // execute query 111 | ret = pg_get_int(request, result, PGSQL_GET_TS_SIZE, param_new(tablespace)); 112 | 113 | out: 114 | zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name); 115 | return ret; 116 | } 117 | -------------------------------------------------------------------------------- /templates/Makefile: -------------------------------------------------------------------------------- 1 | # update older templates automatically, from the latest template 2 | 3 | LATEST = Template_PostgreSQL_Server_3.0.xml 4 | 5 | all: \ 6 | Template_PostgreSQL_Server_2.0.xml \ 7 | Template_PostgreSQL_Server_2.4.xml 8 | 9 | Template_PostgreSQL_Server_2.0.xml: $(LATEST) 10 | zabbix-template-convertor \ 11 | $(LATEST) -o 2.0.0 \ 12 | > Template_PostgreSQL_Server_2.0.xml 13 | 14 | Template_PostgreSQL_Server_2.4.xml: $(LATEST) 15 | zabbix-template-convertor \ 16 | $(LATEST) -o 2.4.0 \ 17 | > Template_PostgreSQL_Server_2.4.xml 18 | 19 | clean: 20 | rm -vf \ 21 | Template_PostgreSQL_Server_2.0.xml \ 22 | Template_PostgreSQL_Server_2.4.xml 23 | 24 | .PHONY: all clean 25 | --------------------------------------------------------------------------------