]> git.ipfire.org Git - people/ms/python-rrdtool.git/commitdiff
* Added fetch and graph function.
authorChristian Jurk <commx@commx.ws>
Mon, 17 Sep 2012 02:08:55 +0000 (04:08 +0200)
committerChristian Jurk <commx@commx.ws>
Mon, 17 Sep 2012 02:08:55 +0000 (04:08 +0200)
* Initial code added for keeping graphs in memory (when the filename for
  the graph functions is being "-". It will then redirect stdout to a
  buffer and return it as a Python bytes object as part of the return
  tuple.

rrdtool-py3k.c

index 76bc1b3bfc3af9c36f9653e6b3593ca626c9b8bc..9d2736520dbb5e0238d925b9fec898ebeb5f1f81 100644 (file)
@@ -1,5 +1,7 @@
 /*
- * rrdtool-py3k, rrdtool bindings for Python 3
+ * rrdtool-py3k, rrdtool bindings for Python 3.
+ * Based on the rrdtool Python bindings for Python 2 from
+ * Hye-Shik Chang <perky@fallin.lv>.
  *
  * Copyright 2012 Christian Jurk <commx@commx.ws>
  *
@@ -18,7 +20,6 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  * MA 02110-1301, USA.
  *
- *
  */
 
 #include <Python.h>
 static PyObject *rrdtool_OperationalError;
 static PyObject *rrdtool_ProgrammingError;
 
-/* Wrapper for rrd_create() */
+static char **rrdtool_argv = NULL;
+static int rrdtool_argc = 0;
+
+static void
+destroy_args(void)
+{
+    PyMem_Del(rrdtool_argv);
+    rrdtool_argv = NULL;
+}
+
+/* Helper function to convert Python objects into a representation that the
+ * rrdtool functions can work with.
+ */
+static int
+convert_args(char *command, PyObject *args)
+{
+    PyObject *o, *lo;
+    int i, j, args_count, argv_count, element_count;
+
+    args_count = PyTuple_Size(args);
+    element_count = 0;
+    for (i = 0; i < args_count; i++)
+    {
+        o = PyTuple_GET_ITEM(args, i);
+        if (PyUnicode_Check(o) || PyBytes_Check(o))
+            element_count++;
+        else if (PyList_CheckExact(o))
+            element_count += PyList_Size(o);
+        else {
+            PyErr_Format(rrdtool_ProgrammingError,
+                         "Argument %d must be string, bytes or list of " \
+                         "string/bytes", i);
+            return -1;
+        }
+    }
+
+    rrdtool_argv = PyMem_New(char *, element_count + 1);
+
+    if (rrdtool_argv == NULL)
+        return -1;
+
+    argv_count = 0;
+    for (i = 0; i < args_count; i++) {
+        o = PyTuple_GET_ITEM(args, i);
+
+        if (PyUnicode_Check(o))
+            rrdtool_argv[++argv_count] = PyBytes_AsString(
+              PyUnicode_AsUTF8String(o));
+        else if (PyBytes_Check(o))
+            rrdtool_argv[++argv_count] = PyBytes_AS_STRING(o);
+        else if (PyList_CheckExact(o)) {
+            for (j = 0; j < PyList_Size(o); j++) {
+                lo = PyList_GetItem(o, j);
+                if (PyUnicode_Check(lo))
+                    rrdtool_argv[++argv_count] = PyBytes_AS_STRING(
+                      PyUnicode_AsUTF8String(lo));
+                else if (PyBytes_Check(lo))
+                    rrdtool_argv[++argv_count] = PyBytes_AS_STRING(lo);
+                else {
+                    PyMem_Del(rrdtool_argv);
+                    PyErr_Format(rrdtool_ProgrammingError,
+                      "Element %d in argument %d must be string", j, i);
+                    return -1;
+                }
+            }
+        } else {
+            PyMem_Del(rrdtool_argv);
+            PyErr_Format(rrdtool_ProgrammingError,
+              "Argument %d must be string or list of strings", i);
+            return -1;
+        }
+    }
+
+    rrdtool_argv[0] = command;
+    rrdtool_argc = element_count + 1;
+
+    return 0;
+}
+
+static char _rrdtool_create__doc__[] = "Create a new Round Robin Database.\n\n\
+  Usage: create(args...)\n\
+  Arguments:\n\n\
+    filename\n\
+    [--start|-b start time]\n\
+    [--step|-s step]\n\
+    [DS:ds-name:DST:heartbeat:min:max]\n\
+    [RRA:CF:xff:steps:rows]\n\n\
+    Full documentation can be found at:\n\
+    http://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html";
+
 static PyObject *
 _rrdtool_create(PyObject *self, PyObject *args)
 {
-       PyObject *o;
-       char **argv;
-       int i, argc;
-
-       argc = PyTuple_Size(args);
-       argv = PyMem_New(char *, argc + 1);
-
-       for (i = 0; i < argc; i++) {
-               o = PyTuple_GET_ITEM(args, i);
-               if (!PyBytes_Check(o)) {
-                       if (!PyUnicode_Check(o)) {
-                               PyErr_Format(rrdtool_ProgrammingError,
-                                        "Argument %d must be a unicode or bytes object",
-                                        i);
-                               PyMem_Del(argv);
-                               return NULL;
-                       } else
-                               argv[i+1] = PyBytes_AS_STRING(PyUnicode_AsUTF8String(o));
-
-               } else
-                       argv[i+1] = PyBytes_AS_STRING(o);
-
-       }
-
-       if (argc == 0) {
-               PyErr_SetString(rrdtool_ProgrammingError, "No arguments specified.");
-               PyMem_Del(argv);
-               return NULL;
-       }
+    PyObject *ret;
 
-       argv[0] = "create";
+    if (convert_args("create", args) == -1)
+        return NULL;
 
-       if (rrd_create(argc + 1, argv) == -1) {
+       if (rrd_create(rrdtool_argc, rrdtool_argv) == -1) {
                PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
                rrd_clear_error();
-               PyMem_Del(argv);
-               return NULL;
-       }
+               ret = NULL;
+       } else {
+        Py_INCREF(Py_None);
+        ret = Py_None;
+    }
+
+       destroy_args();
+    return ret;
+}
+
+static char _rrdtool_update__doc__[] = "Store a new set of values into\
+ the RRD.\n\n\
+ Usage: update(args..)\n\
+ Arguments:\n\n\
+   filename\n\
+   [--template|-t ds-name[:ds-name]...]\n\
+   N|timestamp:value[:value...]\n\
+   [timestamp:value[:value...] ...]\n\n\
+   Full documentation can be found at:\n\
+   http://oss.oetiker.ch/rrdtool/doc/rrdupdate.en.html";
+
+static PyObject *
+_rrdtool_update(PyObject *self, PyObject *args)
+{
+    PyObject *ret;
+
+    if (convert_args("update", args) == -1)
+        return NULL;
+
+    if (rrd_update(rrdtool_argc, rrdtool_argv) == -1) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
+        rrd_clear_error();
+        ret = NULL;
+    } else {
+        Py_INCREF(Py_None);
+        ret = Py_None;
+    }
+
+       destroy_args();
+    return ret;
+}
+
+static char _rrdtool_fetch__doc__[] = "Fetch data from an RRD.\n\n\
+  Usage: fetch(args..)\n\
+  Arguments:\n\n\
+    filename\n\
+    CF\n\
+    [--resolution|-r resolution]\n\
+    [--start|-s start]\n\
+    [--end|-e end]\n\n\
+    Full documentation can be found at:\n\
+    http://oss.oetiker.ch/rrdtool/doc/rrdfetch.en.html";
+
+static PyObject *
+_rrdtool_fetch(PyObject *self, PyObject *args)
+{
+    PyObject *ret, *range_tup, *dsnam_tup, *data_list, *t;
+    rrd_value_t *data, *datai, dv;
+    unsigned long step, ds_cnt, i, j, row;
+    time_t start, end;
+    char **ds_namv;
+
+    if (convert_args("fetch", args) == -1)
+        return NULL;
+
+    if (rrd_fetch(rrdtool_argc, rrdtool_argv, &start, &end, &step, &ds_cnt,
+                  &ds_namv, &data) == -1) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
+        rrd_clear_error();
+        ret = NULL;
+    } else {
+        row = (end - start) / step;
+        ret = PyTuple_New(3);
+        range_tup = PyTuple_New(3);
+        dsnam_tup = PyTuple_New(ds_cnt);
+        data_list = PyList_New(row);
+
+        PyTuple_SET_ITEM(ret, 0, range_tup);
+        PyTuple_SET_ITEM(ret, 1, dsnam_tup);
+        PyTuple_SET_ITEM(ret, 2, data_list);
+
+        datai = data;
+
+        PyTuple_SET_ITEM(range_tup, 0, PyLong_FromLong((long)start));
+        PyTuple_SET_ITEM(range_tup, 1, PyLong_FromLong((long)end));
+        PyTuple_SET_ITEM(range_tup, 2, PyLong_FromLong((long)step));
+
+        for (i = 0; i < ds_cnt; i++)
+            PyTuple_SET_ITEM(dsnam_tup, i, PyUnicode_FromString(ds_namv[i]));
+
+        for (i = 0; i < row; i++) {
+            t = PyTuple_New(ds_cnt);
+            PyList_SET_ITEM(data_list, i, t);
+
+            for (j = 0; j < ds_cnt; j++) {
+                dv = *(datai++);
+                if (isnan(dv)) {
+                    PyTuple_SET_ITEM(t, j, Py_None);
+                    Py_INCREF(Py_None);
+                } else
+                    PyTuple_SET_ITEM(t, j, PyFloat_FromDouble((double)dv));
+            }
+        }
+
+        for (i = 0; i < ds_cnt; i++)
+            rrd_freemem(ds_namv[i]);
+
+        rrd_freemem(ds_namv);
+        rrd_freemem(data);
+    }
+
+    destroy_args();
+    return ret;
+}
+
+static char _rrdtool_graph__doc__[] = "Create a graph based on one or more " \
+  "RRDs.\n\n\
+  Usage: graph(args..)\n\
+  Arguments:\n\n\
+    filename | -\n\
+    [-s|--start start]\n\
+    [-e|--end end]\n\
+    [-S|--step step]\n\
+    [-t|--title string]\n\
+    [-v|--vertical-label string]\n\
+    [-w|--width pixels]\n\
+    [-h|--height pixels]\n\
+    [-j|--only-graph]\n\
+    [-D|--full-size-mode]\n\
+    [-u|--upper-limit value]\n\
+    [-l|--lower-limit value]\n\
+    [-r|--rigid]\n\
+    [-A|--alt-autoscale]\n\
+    [-J|--alt-autoscale-min]\n\
+    [-M|--alt-autoscale-max]\n\
+    [-N|--no-gridfit]\n\
+    [-x|--x-grid (GTM:GST:MTM:MST:LTM:LST:LPR:LFM|none)]\n\
+    [-y|--y-grid (grid step:label factor|none)]\n\
+    [-Y|--alt-y-grid]\n\
+    [-o|--logarithmic]\n\
+    [-X|--units-exponent value]\n\
+    [-L|--units-length value]\n\
+    [--units=si]\n\
+    [--right-axis scale:shift]\n\
+    [--right-axis-label label]\n\
+    [--right-axis-format format-string]\n\
+    [-g|--no-legend]\n\
+    [-F|--force-rules-legend]\n\
+    [--legend-position=(north|south|west|east)]\n\
+    [--legend-direction=(topdown|bottomup)]\n\
+    [-z|--lazy]\n\
+    [--daemon address]\n\
+    [-f|--imginfo printfstr]\n\
+    [-c|--color COLORTAG#rrggbb[aa]]\n\
+    [--grid-dash on:off]\n\
+    [--border width]\n\
+    [--dynamic-labels]\n\
+    [-m|--zoom factor]\n\
+    [-n|--font FONTTAG:size:[font]]\n\
+    [-R|--font-render-mode {normal,light,mono}]\n\
+    [-B|--font-smoothing-threshold size]\n\
+    [-P|--pango-markup]\n\
+    [-G|--graph-render-mode {normal,mono}]\n\
+    [-E|--slope-mode]\n\
+    [-a|--imgformat {PNG,SVG,EPS,PDF}]\n\
+    [-T|--tabwidth value]\n\
+    [-b|--base value]\n\
+    [-W|--watermark string]\n\
+    DEF:vname=rrdfile:ds-name:CF[:step=step][:start=time][:end=time]\n\
+    CDEF:vname=RPN expression\n\
+    VDEF=vname:RPN expression\n\n\
+    Full documentation can be found at:\n\
+    http://oss.oetiker.ch/rrdtool/doc/rrdgraph.en.html";
+
+static PyObject *
+_rrdtool_graph(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    PyObject *ret;
+    int orig_stdout, op[2], xsize, ysize, i;
+    int keep_in_mem = 0;
+    double ymin, ymax;
+    char **calcpr;
+
+    if (convert_args("graph", args) == -1)
+        return NULL;
+
+    if (rrdtool_argc >= 2 && strcmp(rrdtool_argv[1], "-") == 0)
+        keep_in_mem = 1;
+
+    if (keep_in_mem) {
+        orig_stdout = dup(STDOUT_FILENO);
+        if (pipe(op) != 0) {
+            PyErr_Format(rrdtool_OperationalError,
+            "Cannot create pipe for stdout: %s", strerror(errno));
+            ret = NULL;
+        } else {
+            dup2(op[1], STDOUT_FILENO);
+            close(op[1]);
+
+            // do something on stdout
+            // read(op[0], buffer, MAX_LEN);
+        }
+    }
+
+    if (rrd_graph(rrdtool_argc, rrdtool_argv, &calcpr, &xsize, &ysize, NULL,
+                  &ymin, &ymax) == -1) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
+        rrd_clear_error();
+        ret = NULL;
+    } else {
+        ret = PyTuple_New(keep_in_mem ? 4 : 3);
+
+        PyTuple_SET_ITEM(ret, 0, PyLong_FromLong((long)xsize));
+        PyTuple_SET_ITEM(ret, 1, PyLong_FromLong((long)ysize));
+
+        if (calcpr) {
+            PyObject *e, *t;
+
+            e = PyList_New(0);
+            PyTuple_SET_ITEM(ret, 2, e);
+
+            for (i = 0; calcpr[i]; i++) {
+                t = PyUnicode_FromString(calcpr[i]);
+                PyList_Append(e, t);
+                Py_DECREF(t);
+                rrd_freemem(calcpr[i]);
+            }
+        } else {
+            Py_INCREF(Py_None);
+            PyTuple_SET_ITEM(ret, 2, Py_None);
+        }
+
+        /* feed buffered contents into a PyBytes object */
+        if (keep_in_mem) {
+            PyObject *pb;
+            ssize_t rs;
+            char buffer[4096];
+
+            pb = PyBytes_FromStringAndSize("", 0);
+            for (;;) {
+                if ((rs = read(op[0], buffer, 4096)) <= 0)
+                    break;
+                else
+                    PyBytes_Concat(&pb, PyBytes_FromStringAndSize(buffer, rs));
+            }
+
+            PyTuple_SET_ITEM(ret, 3, pb);
+        }
+    }
+
+    if (keep_in_mem)
+        dup2(orig_stdout, STDOUT_FILENO);
 
-       Py_RETURN_TRUE;
+    destroy_args();
+    return ret;
 }
 
 static PyMethodDef rrdtool_methods[] = {
-       {"create", (PyCFunction)_rrdtool_create, METH_VARARGS | METH_KEYWORDS,
-        "Create a new Round Robin Database"},
+       {"create", (PyCFunction)_rrdtool_create,
+     METH_VARARGS, _rrdtool_create__doc__},
+    {"update", (PyCFunction)_rrdtool_update,
+     METH_VARARGS, _rrdtool_update__doc__},
+    {"fetch", (PyCFunction)_rrdtool_fetch,
+     METH_VARARGS, _rrdtool_fetch__doc__},
+    {"graph", (PyCFunction)_rrdtool_graph,
+     METH_VARARGS | METH_KEYWORDS, _rrdtool_graph__doc__},
        {NULL, NULL, 0, NULL}
 };