From: Christian Jurk Date: Mon, 17 Sep 2012 02:08:55 +0000 (+0200) Subject: * Added fetch and graph function. X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=618986c4e63af42c94f34527a820a14c8239646f;p=people%2Fms%2Fpython-rrdtool.git * Added fetch and graph function. * 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. --- diff --git a/rrdtool-py3k.c b/rrdtool-py3k.c index 76bc1b3..9d27365 100644 --- a/rrdtool-py3k.c +++ b/rrdtool-py3k.c @@ -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 . * * Copyright 2012 Christian Jurk * @@ -18,7 +20,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * - * */ #include @@ -28,55 +29,373 @@ 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} };