Code

postgresql plugin: Added a plugin to collect PostgreSQL database statistics.
authorSebastian Harl <sh@tokkee.org>
Sat, 5 Jul 2008 10:03:51 +0000 (12:03 +0200)
committerFlorian Forster <octo@huhu.verplant.org>
Sun, 6 Jul 2008 12:29:14 +0000 (14:29 +0200)
This plugin collects active server connections, transaction numbers, block IO
and table row manipulations of PostgreSQL databases. For this purpose it keeps
persistent connections to all configured databases. The statistics are
collected from PostgreSQL's "statistics collector" subsystem.

Currently, statistics are collected from the pg_stat_database,
pg_stat_user_tables and pg_statio_user_tables views. The following new types
have been introduced: pg_blks, pg_n_tup_c, pg_n_tup_g, pg_numbackends, pg_scan
and pg_xact. The type names are basically the same as the column names in the
queried views to keep things consistent. For the number of tuples, counter and
gauge values exist, so the type names have been prefixed with "_c" and "_g"
respectively.

Sample plugin configuration:

  <Plugin postgresql>
    <Database foo>
      Host "hostname"
      Port "5432"
      User "username"
      Password "secret"
      SSLMode "prefer"
      KRBSrvName "kerberos_service_name"
    </Database>
    <Database bar>
      Service "service_name"
    </Database>
  </Plugin>

Signed-off-by: Sebastian Harl <sh@tokkee.org>
Signed-off-by: Florian Forster <octo@huhu.verplant.org>
README
configure.in
src/Makefile.am
src/collectd.conf.in
src/collectd.conf.pod
src/postgresql.c [new file with mode: 0644]
src/types.db

diff --git a/README b/README
index ca2bdf6bc21ade2095dd31a406da6302260f0867..97119ac5ef70336f31e484cc171f44120fe7705b 100644 (file)
--- a/README
+++ b/README
@@ -141,6 +141,10 @@ Features
       Network latency: Time to reach the default gateway or another given
       host.
 
+    - postgresql
+      PostgreSQL database statistics: active server connections, transaction
+      numbers, block IO, table row manipulations.
+
     - processes
       Process counts: Number of running, sleeping, zombie, ... processes.
 
@@ -364,6 +368,9 @@ Prerequisites
     Obviously used by the `perl' plugin. The library has to be compiled with
     ithread support (introduced in Perl 5.6.0).
 
+  * libpq (optional)
+    The PostgreSQL C client library used by the `postgresql' plugin.
+
   * librrd (optional; headers and library; rrdtool 1.0 and 1.2 both work fine)
     If built without `librrd' the resulting binary will be `client only', i.e.
     will send its values via multicast and not create any RRD files itself.
index 73fa841057c4e3a7a58944b39769331cc616b101..6873d8f1e3d254692ef26f3fe6476a4c7425edd2 100644 (file)
@@ -2113,6 +2113,93 @@ then
        AC_SUBST(BUILD_WITH_OPENIPMI_LIBS)
 fi
 
+dnl Check for libpq.
+with_pg_config="pg_config"
+with_libpq_includedir=""
+with_libpq_libdir=""
+with_libpq_cppflags=""
+with_libpq_ldflags=""
+AC_ARG_WITH(libpq, [AS_HELP_STRING([--with-libpq@<:@=PREFIX@:>@],
+       [Path to libpq.])],
+[
+       if test "x$withval" = "xno"
+       then
+               with_libpq="no"
+       else if test "x$withval" = "xyes"
+       then
+               with_libpq="yes"
+       else
+               if test -f "$withval" && test -x "$withval";
+               then
+                       with_pg_config="$withval"
+               else if test -x "$withval/bin/pg_config"
+               then
+                       with_pg_config="$withval/bin/pg_config"
+               fi; fi
+               with_libpq="yes"
+       fi; fi
+],
+[
+       with_libpq="yes"
+])
+if test "x$with_libpq" = "xyes"
+then
+       with_libpq_includedir=`$with_pg_config --includedir 2> /dev/null`
+       pg_config_status=$?
+
+       if test $pg_config_status -eq 0
+       then
+               if test -n "$with_libpq_includedir"; then
+                       for dir in $with_libpq_includedir; do
+                               with_libpq_cppflags="$with_libpq_cppflags -I$dir"
+                       done
+               fi
+       else
+               AC_MSG_WARN([$with_pg_config returned with status $pg_config_status])
+       fi
+
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libpq_cppflags"
+
+       AC_CHECK_HEADERS(libpq-fe.h, [],
+               [with_libpq="no (libpq-fe.h not found)"], [])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libpq" = "xyes"
+then
+       with_libpq_libdir=`$with_pg_config --libdir 2> /dev/null`
+       pg_config_status=$?
+
+       if test $pg_config_status -eq 0
+       then
+               if test -n "$with_libpq_libdir"; then
+                       for dir in $with_libpq_libdir; do
+                               with_libpq_ldflags="$with_libpq_ldflags -L$dir"
+                       done
+               fi
+       else
+               AC_MSG_WARN([$with_pg_config returned with status $pg_config_status])
+       fi
+
+       SAVE_LDFLAGS="$LDFLAGS"
+       LDFLAGS="$LDFLAGS $with_libpq_ldflags"
+
+       AC_CHECK_LIB(pq, PQconnectdb,
+               [with_libpq="yes"],
+               [with_libpq="no (symbol 'PQconnectdb' not found)"])
+
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libpq" = "xyes"
+then
+       BUILD_WITH_LIBPQ_CPPFLAGS="$with_libpq_cppflags"
+       BUILD_WITH_LIBPQ_LDFLAGS="$with_libpq_ldflags"
+       AC_SUBST(BUILD_WITH_LIBPQ_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBPQ_LDFLAGS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBPQ, test "x$with_libpq" = "xyes")
+
 dnl Check for libvirt and libxml2 libraries.
 with_libxml2="no (pkg-config isn't available)"
 with_libxml2_cflags=""
@@ -2544,6 +2631,7 @@ AC_PLUGIN([ntpd],        [yes],                [NTPd statistics])
 AC_PLUGIN([nut],         [$with_libupsclient], [Network UPS tools statistics])
 AC_PLUGIN([perl],        [$plugin_perl],       [Embed a Perl interpreter])
 AC_PLUGIN([ping],        [$with_liboping],     [Network latency statistics])
+AC_PLUGIN([postgresql],  [$with_libpq],        [PostgreSQL database statistics])
 AC_PLUGIN([powerdns],    [yes],                [PowerDNS statistics])
 AC_PLUGIN([processes],   [$plugin_processes],  [Process statistics])
 AC_PLUGIN([qmail],       [yes],                [QMail queue statistics])
@@ -2655,6 +2743,7 @@ Configuration:
     libpcap . . . . . . . $with_libpcap
     libperl . . . . . . . $with_libperl
     libpthread  . . . . . $with_libpthread
+    libpq . . . . . . . . $with_libpq
     librrd  . . . . . . . $with_rrdtool
     libsensors  . . . . . $with_lm_sensors
     libstatgrab . . . . . $with_libstatgrab
@@ -2709,6 +2798,7 @@ Configuration:
     nut . . . . . . . . . $enable_nut
     perl  . . . . . . . . $enable_perl
     ping  . . . . . . . . $enable_ping
+    postgresql  . . . . . $enable_postgresql
     powerdns  . . . . . . $enable_powerdns
     processes . . . . . . $enable_processes
     qmail . . . . . . . . $enable_qmail
index 4f0496dbd12b2a844da25a156df43514abe3c99a..3d8b146683bb8e2c05af72da7b40d2b60aa05f6f 100644 (file)
@@ -536,6 +536,16 @@ collectd_LDADD += "-dlopen" ping.la
 collectd_DEPENDENCIES += ping.la
 endif
 
+if BUILD_PLUGIN_POSTGRESQL
+pkglib_LTLIBRARIES += postgresql.la
+postgresql_la_SOURCES = postgresql.c
+postgresql_la_CPPFLAGS = $(BUILD_WITH_LIBPQ_CPPFLAGS)
+postgresql_la_LDFLAGS = -module -avoid-version \
+               $(BUILD_WITH_LIBPQ_LDFLAGS) -lpq
+collectd_LDADD += "-dlopen" postgresql.la
+collectd_DEPENDENCIES += postgresql.la
+endif
+
 if BUILD_PLUGIN_POWERDNS
 pkglib_LTLIBRARIES += powerdns.la
 powerdns_la_SOURCES = powerdns.c
index e76f997a3e3474515ac1e7104e70d32b0d8a6fd1..f73ac99c5c73603e0a9c2025b7de3ce6e226d77a 100644 (file)
@@ -51,6 +51,7 @@ FQDNLookup   true
 @BUILD_PLUGIN_NUT_TRUE@LoadPlugin nut
 @BUILD_PLUGIN_PERL_TRUE@LoadPlugin perl
 @BUILD_PLUGIN_PING_TRUE@LoadPlugin ping
+@BUILD_PLUGIN_POSTGRESQL_TRUE@LoadPlugin postgresql
 @BUILD_PLUGIN_POWERDNS_TRUE@LoadPlugin powerdns
 @BUILD_PLUGIN_PROCESSES_TRUE@LoadPlugin processes
 @BUILD_PLUGIN_RRDTOOL_TRUE@LoadPlugin rrdtool
@@ -247,6 +248,22 @@ FQDNLookup   true
 #      TTL 255
 #</Plugin>
 
+#<Plugin postgresql>
+#      <Database foo>
+#              Host "hostname"
+#              Port 5432
+#              User "username"
+#              Password "secret"
+#
+#              SSLMode "prefer"
+#              KRBSrvName "kerberos_service_name"
+#      </Database>
+#
+#      <Database bar>
+#              Service "service_name"
+#      </Database>
+#</Plugin>
+
 #<Plugin powerdns>
 #  <Server "server_name">
 #    Collect "latency"
index 107b3afaad62c0118d32301c2d1ed040a6ef821b..faff2ffe35bb30f2818e21ef6a18c695630da464 100644 (file)
@@ -948,6 +948,110 @@ Sets the Time-To-Live of generated ICMP packets.
 
 =back
 
+=head2 Plugin C<postgresql>
+
+The C<postgresql> plugin queries statistics from PostgreSQL databases. It
+keeps a persistent connection to all configured databases and tries to
+reconnect if the connection has been interrupted. The statistics are collected
+from PostgreSQL's B<statistics collector> which thus has to be enabled for
+this plugin to work correctly. This should usually be the case by default.
+See the section "The Statistics Collector" of the B<PostgreSQL Documentation>
+for details.
+
+The B<PostgreSQL Documentation> manual can be found at
+L<http://www.postgresql.org/docs/manuals/>.
+
+  <Plugin postgresql>
+    <Database foo>
+      Host "hostname"
+      Port "5432"
+      User "username"
+      Password "secret"
+      SSLMode "prefer"
+      KRBSrvName "kerberos_service_name"
+    </Database>
+    <Database bar>
+      Service "service_name"
+    </Database>
+  </Plugin>
+
+=over 4
+
+=item B<Database> block
+
+The B<Database> block defines one PostgreSQL database for which to collect
+statistics. It accepts a single mandatory argument which specifies the
+database name. None of the other options are required. PostgreSQL will use
+default values as documented in the section "CONNECTING TO A DATABASE" in the
+L<psql(1)> manpage. However, be aware that those defaults may be influenced by
+the user collectd is run as and special environment variables. See the manpage
+for details.
+
+=item B<Host> I<hostname>
+
+Specify the hostname or IP of the PostgreSQL server to connect to. If the
+value begins with a slash, it is interpreted as the directory name in which to
+look for the UNIX domain socket.
+
+This option is also used to determine the hostname that is associated with a
+collected data set. If it has been omitted or either begins with with a slash
+or equals B<localhost> it will be replaced with the global hostname definition
+of collectd. Any other value will be passed literally to collectd when
+dispatching values. Also see the global B<Hostname> and B<FQDNLookup> options.
+
+=item B<Port> I<port>
+
+Specify the TCP port or the local UNIX domain socket file extension of the
+server.
+
+=item B<User> I<username>
+
+Specify the username to be used when connecting to the server.
+
+=item B<Password> I<password>
+
+Specify the password to be used when connecting to the server.
+
+=item B<SSLMode> I<disable>|I<allow>|I<prefer>|I<require>
+
+Specify whether to use an SSL connection when contacting the server. The
+following modes are supported:
+
+=over 4
+
+=item I<disable>
+
+Do not use SSL at all.
+
+=item I<allow>
+
+First, try to connect without using SSL. If that fails, try using SSL.
+
+=item I<prefer> (default)
+
+First, try to connect using SSL. If that fails, try without using SSL.
+
+=item I<require>
+
+Use SSL only.
+
+=back
+
+=item B<KRBSrvName> I<kerberos_service_name>
+
+Specify the Kerberos service name to use when authenticating with Kerberos 5
+or GSSAPI. See the sections "Kerberos authentication" and "GSSAPI" of the
+B<PostgreSQL Documentation> for details.
+
+=item B<Service> I<service_name>
+
+Specify the PostgreSQL service name to use for additional parameters. That
+service has to be defined in F<pg_service.conf> and holds additional
+connection parameters. See the section "The Connection Service File" in the
+B<PostgreSQL Documentation> for details.
+
+=back
+
 =head2 Plugin C<powerdns>
 
 The C<powerdns> plugin queries statistics from an authoritative PowerDNS
@@ -1652,6 +1756,7 @@ L<types.db(5)>,
 L<hddtemp(8)>,
 L<kstat(3KSTAT)>,
 L<mbmon(1)>,
+L<psql(1)>,
 L<rrdtool(1)>,
 L<sensors(1)>
 
diff --git a/src/postgresql.c b/src/postgresql.c
new file mode 100644 (file)
index 0000000..40f8ec5
--- /dev/null
@@ -0,0 +1,519 @@
+/**
+ * collectd - src/postgresql.c
+ * Copyright (C) 2008  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This module collects PostgreSQL database statistics.
+ */
+
+#include "collectd.h"
+#include "common.h"
+
+#include "configfile.h"
+#include "plugin.h"
+
+#include "utils_complain.h"
+
+#include <pg_config_manual.h>
+#include <libpq-fe.h>
+
+#define log_err(...) ERROR ("postgresql: " __VA_ARGS__)
+#define log_warn(...) WARNING ("postgresql: " __VA_ARGS__)
+#define log_info(...) INFO ("postgresql: " __VA_ARGS__)
+
+/* Appends the (parameter, value) pair to the string
+ * pointed to by 'buf' suitable to be used as argument
+ * for PQconnectdb(). If value equals NULL, the pair
+ * is ignored. */
+#define C_PSQL_PAR_APPEND(buf, buf_len, parameter, value) \
+       if ((0 < (buf_len)) && (NULL != (value)) && ('\0' != *(value))) { \
+               int s = ssnprintf (buf, buf_len, " %s = '%s'", parameter, value); \
+               if (0 < s) { \
+                       buf     += s; \
+                       buf_len -= s; \
+               } \
+       }
+
+/* Returns the tuple (major, minor, patchlevel)
+ * for the given version number. */
+#define C_PSQL_SERVER_VERSION3(server_version) \
+       (server_version) / 10000, \
+       (server_version) / 100 - (int)((server_version) / 10000) * 100, \
+       (server_version) - (int)((server_version) / 100) * 100
+
+/* Returns true if the given host specifies a
+ * UNIX domain socket. */
+#define C_PSQL_IS_UNIX_DOMAIN_SOCKET(host) \
+       ((NULL == (host)) || ('\0' == *(host)) || ('/' == *(host)))
+
+/* Returns the tuple (host, delimiter, port) for a
+ * given (host, port) pair. Depending on the value of
+ * 'host' a UNIX domain socket or a TCP socket is
+ * assumed. */
+#define C_PSQL_SOCKET3(host, port) \
+       ((NULL == (host)) || ('\0' == *(host))) ? DEFAULT_PGSOCKET_DIR : host, \
+       C_PSQL_IS_UNIX_DOMAIN_SOCKET (host) ? "/.s.PGSQL." : ":", \
+       port
+
+typedef struct {
+       PGconn      *conn;
+       c_complain_t conn_complaint;
+
+       /* user configuration */
+       char *host;
+       char *port;
+       char *database;
+       char *user;
+       char *password;
+
+       char *sslmode;
+
+       char *krbsrvname;
+
+       char *service;
+} c_psql_database_t;
+
+static c_psql_database_t *databases     = NULL;
+static int                databases_num = 0;
+
+static void submit (const c_psql_database_t *db,
+               const char *type, const char *type_instance,
+               value_t *values, size_t values_len)
+{
+       value_list_t vl = VALUE_LIST_INIT;
+
+       vl.values     = values;
+       vl.values_len = values_len;
+       vl.time       = time (NULL);
+
+       if (C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
+                       || (0 == strcmp (db->host, "localhost")))
+               sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       else
+               sstrncpy (vl.host, db->host, sizeof (vl.host));
+
+       sstrncpy (vl.plugin, "postgresql", sizeof (vl.plugin));
+       sstrncpy (vl.plugin_instance, db->database, sizeof (vl.plugin_instance));
+
+       sstrncpy (vl.type, type, sizeof (vl.type));
+
+       if (NULL != type_instance)
+               sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+       return;
+} /* submit */
+
+static void submit_counter (const c_psql_database_t *db,
+               const char *type, const char *type_instance,
+               const char *value)
+{
+       value_t values[1];
+
+       if ((NULL == value) || ('\0' == *value))
+               return;
+
+       values[0].counter = atoll (value);
+       submit (db, type, type_instance, values, 1);
+       return;
+} /* submit_counter */
+
+static void submit_gauge (const c_psql_database_t *db,
+               const char *type, const char *type_instance,
+               const char *value)
+{
+       value_t values[1];
+
+       if ((NULL == value) || ('\0' == *value))
+               return;
+
+       values[0].gauge = atof (value);
+       submit (db, type, type_instance, values, 1);
+       return;
+} /* submit_gauge */
+
+static int c_psql_check_connection (c_psql_database_t *db)
+{
+       /* "ping" */
+       PQclear (PQexec (db->conn, "SELECT 42;"));
+
+       if (CONNECTION_OK != PQstatus (db->conn)) {
+               PQreset (db->conn);
+
+               /* trigger c_release() */
+               if (0 == db->conn_complaint.interval)
+                       db->conn_complaint.interval = 1;
+
+               if (CONNECTION_OK != PQstatus (db->conn)) {
+                       c_complain (LOG_ERR, &db->conn_complaint,
+                                       "Failed to connect to database %s: %s",
+                                       db->database, PQerrorMessage (db->conn));
+                       return -1;
+               }
+       }
+
+       c_release (LOG_INFO, &db->conn_complaint,
+                       "Successfully reconnected to database %s", PQdb (db->conn));
+       return 0;
+} /* c_psql_check_connection */
+
+static int c_psql_stat_database (c_psql_database_t *db)
+{
+       const char *const query =
+               "SELECT numbackends, xact_commit, xact_rollback "
+                       "FROM pg_stat_database "
+                       "WHERE datname = $1;";
+
+       PGresult *res;
+
+       int n;
+
+       res = PQexecParams (db->conn, query, /* number of parameters */ 1,
+                       NULL, (const char *const *)&db->database, NULL, NULL,
+                       /* return text data */ 0);
+
+       if (PGRES_TUPLES_OK != PQresultStatus (res)) {
+               log_err ("Failed to execute SQL query: %s",
+                               PQerrorMessage (db->conn));
+               log_info ("SQL query was: %s", query);
+               PQclear (res);
+               return -1;
+       }
+
+       n = PQntuples (res);
+       if (1 < n) {
+               log_warn ("pg_stat_database has more than one entry "
+                               "for database %s - ignoring additional results.",
+                               db->database);
+       }
+       else if (1 > n) {
+               log_err ("pg_stat_database has no entry for database %s",
+                               db->database);
+               PQclear (res);
+               return -1;
+       }
+
+       submit_gauge (db, "pg_numbackends", NULL,  PQgetvalue (res, 0, 0));
+
+       submit_counter (db, "pg_xact", "commit",   PQgetvalue (res, 0, 1));
+       submit_counter (db, "pg_xact", "rollback", PQgetvalue (res, 0, 2));
+
+       PQclear (res);
+       return 0;
+} /* c_psql_stat_database */
+
+static int c_psql_stat_user_tables (c_psql_database_t *db)
+{
+       const char *const query =
+               "SELECT sum(seq_scan), sum(seq_tup_read), "
+                               "sum(idx_scan), sum(idx_tup_fetch), "
+                               "sum(n_tup_ins), sum(n_tup_upd), sum(n_tup_del), "
+                               "sum(n_tup_hot_upd), sum(n_live_tup), sum(n_dead_tup) "
+                       "FROM pg_stat_user_tables;";
+
+       PGresult *res;
+
+       int n;
+
+       res = PQexec (db->conn, query);
+
+       if (PGRES_TUPLES_OK != PQresultStatus (res)) {
+               log_err ("Failed to execute SQL query: %s",
+                               PQerrorMessage (db->conn));
+               log_info ("SQL query was: %s", query);
+               PQclear (res);
+               return -1;
+       }
+
+       n = PQntuples (res);
+       assert (1 >= n);
+
+       if (1 > n) /* no user tables */
+               return 0;
+
+       submit_counter (db, "pg_scan", "seq",           PQgetvalue (res, 0, 0));
+       submit_counter (db, "pg_scan", "seq_tup_read",  PQgetvalue (res, 0, 1));
+       submit_counter (db, "pg_scan", "idx",           PQgetvalue (res, 0, 2));
+       submit_counter (db, "pg_scan", "idx_tup_fetch", PQgetvalue (res, 0, 3));
+
+       submit_counter (db, "pg_n_tup_c", "ins",        PQgetvalue (res, 0, 4));
+       submit_counter (db, "pg_n_tup_c", "upd",        PQgetvalue (res, 0, 5));
+       submit_counter (db, "pg_n_tup_c", "del",        PQgetvalue (res, 0, 6));
+       submit_counter (db, "pg_n_tup_c", "hot_upd",    PQgetvalue (res, 0, 7));
+
+       submit_gauge (db, "pg_n_tup_g", "live",         PQgetvalue (res, 0, 8));
+       submit_gauge (db, "pg_n_tup_g", "dead",         PQgetvalue (res, 0, 9));
+
+       PQclear (res);
+       return 0;
+} /* c_psql_stat_user_tables */
+
+static int c_psql_statio_user_tables (c_psql_database_t *db)
+{
+       const char *const query =
+               "SELECT sum(heap_blks_read), sum(heap_blks_hit), "
+                               "sum(idx_blks_read), sum(idx_blks_hit), "
+                               "sum(toast_blks_read), sum(toast_blks_hit), "
+                               "sum(tidx_blks_read), sum(tidx_blks_hit) "
+                       "FROM pg_statio_user_tables;";
+
+       PGresult *res;
+
+       int n;
+
+       res = PQexec (db->conn, query);
+
+       if (PGRES_TUPLES_OK != PQresultStatus (res)) {
+               log_err ("Failed to execute SQL query: %s",
+                               PQerrorMessage (db->conn));
+               log_info ("SQL query was: %s", query);
+               PQclear (res);
+               return -1;
+       }
+
+       n = PQntuples (res);
+       assert (1 >= n);
+
+       if (1 > n) /* no user tables */
+               return 0;
+
+       submit_counter (db, "pg_blks", "heap_read",  PQgetvalue (res, 0, 0));
+       submit_counter (db, "pg_blks", "heap_hit",   PQgetvalue (res, 0, 1));
+
+       submit_counter (db, "pg_blks", "idx_read",   PQgetvalue (res, 0, 2));
+       submit_counter (db, "pg_blks", "idx_hit",    PQgetvalue (res, 0, 3));
+
+       submit_counter (db, "pg_blks", "toast_read", PQgetvalue (res, 0, 4));
+       submit_counter (db, "pg_blks", "toast_hit",  PQgetvalue (res, 0, 5));
+
+       submit_counter (db, "pg_blks", "tidx_read",  PQgetvalue (res, 0, 6));
+       submit_counter (db, "pg_blks", "tidx_hit",   PQgetvalue (res, 0, 7));
+
+       PQclear (res);
+       return 0;
+} /* c_psql_statio_user_tables */
+
+static int c_psql_read (void)
+{
+       int success = 0;
+       int i;
+
+       for (i = 0; i < databases_num; ++i) {
+               c_psql_database_t *db = databases + i;
+
+               assert (NULL != db->database);
+
+               if (0 != c_psql_check_connection (db))
+                       continue;
+
+               c_psql_stat_database (db);
+               c_psql_stat_user_tables (db);
+               c_psql_statio_user_tables (db);
+
+               ++success;
+       }
+
+       if (! success)
+               return -1;
+       return 0;
+} /* c_psql_read */
+
+static int c_psql_shutdown (void)
+{
+       int i;
+
+       if ((NULL == databases) || (0 == databases_num))
+               return 0;
+
+       plugin_unregister_read ("postgresql");
+       plugin_unregister_shutdown ("postgresql");
+
+       for (i = 0; i < databases_num; ++i) {
+               c_psql_database_t *db = databases + i;
+
+               PQfinish (db->conn);
+
+               sfree (db->database);
+               sfree (db->host);
+               sfree (db->port);
+               sfree (db->user);
+               sfree (db->password);
+
+               sfree (db->sslmode);
+
+               sfree (db->krbsrvname);
+
+               sfree (db->service);
+       }
+
+       sfree (databases);
+       databases_num = 0;
+       return 0;
+} /* c_psql_shutdown */
+
+static int c_psql_init (void)
+{
+       int i;
+
+       if ((NULL == databases) || (0 == databases_num))
+               return 0;
+
+       for (i = 0; i < databases_num; ++i) {
+               c_psql_database_t *db = databases + i;
+
+               char  conninfo[4096];
+               char *buf     = conninfo;
+               int   buf_len = sizeof (conninfo);
+               int   status;
+
+               char *server_host;
+               int   server_version;
+
+               status = ssnprintf (buf, buf_len, "dbname = '%s'", db->database);
+               if (0 < status) {
+                       buf     += status;
+                       buf_len -= status;
+               }
+
+               C_PSQL_PAR_APPEND (buf, buf_len, "host",       db->host);
+               C_PSQL_PAR_APPEND (buf, buf_len, "port",       db->port);
+               C_PSQL_PAR_APPEND (buf, buf_len, "user",       db->user);
+               C_PSQL_PAR_APPEND (buf, buf_len, "password",   db->password);
+               C_PSQL_PAR_APPEND (buf, buf_len, "sslmode",    db->sslmode);
+               C_PSQL_PAR_APPEND (buf, buf_len, "krbsrvname", db->krbsrvname);
+               C_PSQL_PAR_APPEND (buf, buf_len, "service",    db->service);
+
+               db->conn = PQconnectdb (conninfo);
+               if (0 != c_psql_check_connection (db))
+                       continue;
+
+               server_host    = PQhost (db->conn);
+               server_version = PQserverVersion (db->conn);
+               log_info ("Sucessfully connected to database %s (user %s) "
+                               "at server %s%s%s (server version: %d.%d.%d, "
+                               "protocol version: %d, pid: %d)",
+                               PQdb (db->conn), PQuser (db->conn),
+                               C_PSQL_SOCKET3(server_host, PQport (db->conn)),
+                               C_PSQL_SERVER_VERSION3 (server_version),
+                               PQprotocolVersion (db->conn), PQbackendPID (db->conn));
+       }
+
+       plugin_register_read ("postgresql", c_psql_read);
+       plugin_register_shutdown ("postgresql", c_psql_shutdown);
+       return 0;
+} /* c_psql_init */
+
+static int config_set (char *name, char **var, const oconfig_item_t *ci)
+{
+       if ((0 != ci->children_num) || (1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("%s expects a single string argument.", name);
+               return 1;
+       }
+
+       sfree (*var);
+       *var = sstrdup (ci->values[0].value.string);
+       return 0;
+} /* config_set */
+
+static int c_psql_config_database (oconfig_item_t *ci)
+{
+       c_psql_database_t *db;
+
+       int i;
+
+       if ((1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("<Database> expects a single string argument.");
+               return 1;
+       }
+
+       ++databases_num;
+       if (NULL == (databases = (c_psql_database_t *)realloc (databases,
+                               databases_num * sizeof (*databases)))) {
+               log_err ("Out of memory.");
+               exit (5);
+       }
+
+       db = databases + (databases_num - 1);
+
+       db->conn = NULL;
+
+       db->conn_complaint.last     = 0;
+       db->conn_complaint.interval = 0;
+
+       db->database   = sstrdup (ci->values[0].value.string);
+       db->host       = NULL;
+       db->port       = NULL;
+       db->user       = NULL;
+       db->password   = NULL;
+
+       db->sslmode    = NULL;
+
+       db->krbsrvname = NULL;
+
+       db->service    = NULL;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (0 == strcasecmp (c->key, "Host"))
+                       config_set ("Host", &db->host, c);
+               else if (0 == strcasecmp (c->key, "Port"))
+                       config_set ("Port", &db->port, c);
+               else if (0 == strcasecmp (c->key, "User"))
+                       config_set ("User", &db->user, c);
+               else if (0 == strcasecmp (c->key, "Password"))
+                       config_set ("Password", &db->password, c);
+               else if (0 == strcasecmp (c->key, "SSLMode"))
+                       config_set ("SSLMode", &db->sslmode, c);
+               else if (0 == strcasecmp (c->key, "KRBSrvName"))
+                       config_set ("KRBSrvName", &db->krbsrvname, c);
+               else if (0 == strcasecmp (c->key, "Service"))
+                       config_set ("Service", &db->service, c);
+               else
+                       log_warn ("Ignoring unknown config key \"%s\".", c->key);
+       }
+       return 0;
+}
+
+static int c_psql_config (oconfig_item_t *ci)
+{
+       int i;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (0 == strcasecmp (c->key, "Database"))
+                       c_psql_config_database (c);
+               else
+                       log_warn ("Ignoring unknown config key \"%s\".", c->key);
+       }
+       return 0;
+} /* c_psql_config */
+
+void module_register (void)
+{
+       plugin_register_complex_config ("postgresql", c_psql_config);
+       plugin_register_init ("postgresql", c_psql_init);
+} /* module_register */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
index 8350fb4725714706c407d684443eb48376f26410..fabae12471cd6448d36ed83b19951e3e6a9e1cd3 100644 (file)
@@ -63,6 +63,12 @@ nfs_procedure                value:COUNTER:0:4294967295
 nginx_connections      value:GAUGE:0:U
 nginx_requests         value:COUNTER:0:134217728
 percent                        percent:GAUGE:0:100.1
+pg_blks                        value:COUNTER:0:U
+pg_n_tup_c             value:COUNTER:0:U
+pg_n_tup_g             value:GAUGE:0:U
+pg_numbackends         value:GAUGE:0:U
+pg_scan                        value:COUNTER:0:U
+pg_xact                        value:COUNTER:0:U
 ping                   ping:GAUGE:0:65535
 players                        value:GAUGE:0:1000000
 power                  value:GAUGE:0:U