Added a C stored function to get the system load average.
authorMark Wong <markwkm@gmail.com>
Sat, 23 Aug 2008 05:47:54 +0000 (22:47 -0700)
committerMark Wong <markwkm@gmail.com>
Sat, 23 Aug 2008 05:47:54 +0000 (22:47 -0700)
README
pg_loadavg.c [new file with mode: 0644]
pg_loadavg.sql.in [new file with mode: 0644]

diff --git a/README b/README
index c04b45f247ceaa2b064f266cd9ee0fce4b88ae31..641e10bd8237373da1848667b546707e5a292c80 100644 (file)
--- a/README
+++ b/README
@@ -5,6 +5,7 @@ Install
 make
 make install
 psql -f pg_proctab.sql
+psql -f pg_loadavg.sql
 
 Examples
 --------
diff --git a/pg_loadavg.c b/pg_loadavg.c
new file mode 100644 (file)
index 0000000..bad1ebe
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2008 Mark Wong
+ *
+ */
+
+#include "postgres.h"
+#include <string.h>
+#include "fmgr.h"
+#include "funcapi.h"
+#include <sys/vfs.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/param.h>
+#include <executor/spi.h>
+
+#if 0
+#include <linux/proc_fs.h>
+#else
+#define PROC_SUPER_MAGIC 0x9fa0
+#endif
+
+#ifdef PG_MODULE_MAGIC
+PG_MODULE_MAGIC;
+#endif
+
+#define PROCFS "/proc"
+
+#define FLOAT_LEN 20
+#define INTEGER_LEN 10
+
+#define GET_NEXT_VALUE(p, q, value, length, msg, delim) \
+               if ((q = strchr(p, delim)) == NULL) \
+               { \
+                       elog(ERROR, msg); \
+                       SRF_RETURN_DONE(funcctx); \
+               } \
+               length = q - p; \
+               strncpy(value, p, length); \
+               value[length] = '\0'; \
+               p = q + 1;
+
+Datum pg_loadavg(PG_FUNCTION_ARGS);
+static inline char *skip_token(const char *);
+
+PG_FUNCTION_INFO_V1(pg_loadavg);
+
+Datum pg_loadavg(PG_FUNCTION_ARGS)
+{
+       FuncCallContext *funcctx;
+       int call_cntr;
+       int max_calls;
+       TupleDesc tupdesc;
+       AttInMetadata *attinmeta;
+
+       struct statfs sb;
+       int fd;
+       int len;
+       char buffer[4096];
+       char *p;
+       char *q;
+
+       enum loadavg {i_load1, i_load5, i_load15, i_last_pid};
+
+       elog(DEBUG5, "pg_loadavg: Entering stored function.");
+
+       /* stuff done only on the first call of the function */
+       if (SRF_IS_FIRSTCALL())
+       {
+               MemoryContext oldcontext;
+
+               /* create a function context for cross-call persistence */
+               funcctx = SRF_FIRSTCALL_INIT();
+
+               /* switch to memory context appropriate for multiple function calls */
+               oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+               /* Build a tuple descriptor for our result type */
+               if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                       errmsg("function returning record called in context "
+                                                       "that cannot accept type record")));
+
+               /*
+                * generate attribute metadata needed later to produce tuples from raw
+                * C strings
+                */
+               attinmeta = TupleDescGetAttInMetadata(tupdesc);
+               funcctx->attinmeta = attinmeta;
+
+               /* Check if /proc is mounted. */
+               if (statfs(PROCFS, &sb) < 0 || sb.f_type != PROC_SUPER_MAGIC)
+               {
+                       elog(ERROR, "proc filesystem not mounted on " PROCFS "\n");
+                       SRF_RETURN_DONE(funcctx);
+               } 
+               funcctx->max_calls = 1;
+
+               MemoryContextSwitchTo(oldcontext);
+       }
+
+       /* stuff done on every call of the function */
+       funcctx = SRF_PERCALL_SETUP();
+
+       call_cntr = funcctx->call_cntr;
+       max_calls = funcctx->max_calls;
+       attinmeta = funcctx->attinmeta;
+
+       if (call_cntr < max_calls) /* do when there is more left to send */
+       {
+               HeapTuple tuple;
+               Datum result;
+
+               int length;
+
+               char **values = NULL;
+
+               chdir(PROCFS);
+
+               /*
+                * Sanity check, make sure we read the pid information that we're
+                * asking for.
+                */ 
+               sprintf(buffer, "loadavg");
+               fd = open(buffer, O_RDONLY);
+               if (fd == -1)
+               {
+                       elog(ERROR, "loadavg not found");
+                       SRF_RETURN_DONE(funcctx);
+               }
+               len = read(fd, buffer, sizeof(buffer) - 1);
+               close(fd);
+               buffer[len] = '\0';
+               elog(DEBUG5, "pg_loadavg: %s", buffer);
+
+               values = (char **) palloc(4 * sizeof(char *));
+               values[i_load1] = (char *) palloc((FLOAT_LEN + 1) * sizeof(char));
+               values[i_load5] = (char *) palloc((FLOAT_LEN + 1) * sizeof(char));
+               values[i_load15] = (char *) palloc((FLOAT_LEN + 1) * sizeof(char));
+               values[i_last_pid] = (char *) palloc((INTEGER_LEN + 1) * sizeof(char));
+
+               p = buffer;
+
+               /* load1 */
+               GET_NEXT_VALUE(p, q, values[i_load1], length, "load1 not found", ' ');
+               elog(DEBUG5, "pg_loadavg: load1 = %s", values[i_load1]);
+
+               /* load5 */
+               GET_NEXT_VALUE(p, q, values[i_load5], length, "load5 not found", ' ');
+               elog(DEBUG5, "pg_loadavg: load5 = %s", values[i_load5]);
+
+               /* load15 */
+               GET_NEXT_VALUE(p, q, values[i_load15], length, "load15 not found",
+                               ' ');
+               elog(DEBUG5, "pg_loadavg: load15 = %s", values[i_load15]);
+
+               p = skip_token(p);                      /* skip running/tasks */
+               ++p;
+
+               /* last_pid */
+               /*
+                * It appears sometimes this is the last item in /proc/PID/stat and
+                * sometimes it's not, depending on the version of the kernel and
+                * possibly the architecture.  So first test if it is the last item
+                * before determining how to deliminate it.
+                */
+               if (strchr(p, ' ') == NULL)
+               {
+                       GET_NEXT_VALUE(p, q, values[i_last_pid], length,
+                                       "last_pid not found", '\n');
+               }
+               else
+               {
+                       GET_NEXT_VALUE(p, q, values[i_last_pid], length,
+                                       "last_pid not found", ' ');
+               }
+               elog(DEBUG5, "pg_loadavg: last_pid = %s",
+                               values[i_last_pid]);
+
+               /* build a tuple */
+               tuple = BuildTupleFromCStrings(attinmeta, values);
+
+               /* make the tuple into a datum */
+               result = HeapTupleGetDatum(tuple);
+
+               SRF_RETURN_NEXT(funcctx, result);
+       }
+       else /* do when there is no more left */
+       {
+               SRF_RETURN_DONE(funcctx);
+       }
+}
+
+static inline char *
+skip_token(const char *p)
+{
+       while (isspace(*p))
+               p++;
+       while (*p && !isspace(*p))
+               p++;
+       return (char *) p;
+}
diff --git a/pg_loadavg.sql.in b/pg_loadavg.sql.in
new file mode 100644 (file)
index 0000000..7d47d43
--- /dev/null
@@ -0,0 +1,9 @@
+-- Copyright (C) 2008 Mark Wong
+CREATE OR REPLACE FUNCTION pg_loadavg(
+               OUT load1 FLOAT,
+               OUT load5 FLOAT,
+               OUT load15 FLOAT,
+               OUT last_pid INTEGER)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'pg_loadavg'
+LANGUAGE C IMMUTABLE STRICT;