From: Christian Kröger Date: Thu, 16 Feb 2017 16:06:37 +0000 (+0100) Subject: python-rrdtool as replacement for the upstream Python bindings (#755) X-Git-Tag: v1.7.0~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6863af515b16826ed66f29f2c4f796575cc60e24;p=thirdparty%2Frrdtool-1.x.git python-rrdtool as replacement for the upstream Python bindings (#755) * Replaced Python bindings with python-rrdtool * Updated Makefile.in contents * Added missing import * Path correction * Python 3 compatible print statements in acinclude.m4 * Updated configure script * Added LIST command to Python binding * Added missing var decls * Added callback support for Python binding and initial tests suite for the binding * Fixed some gcc compiler errors in pre-C99 mode * setuptools is required to build the Python bindings * Directory traversal --- diff --git a/.travis.yml b/.travis.yml index 02e41910..fdce9819 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ perl: install: - sudo apt-get update || true - - sudo apt-get install autopoint libdbi-dev tcl-dev lua5.1 liblua5.1-0-dev valgrind dc + - sudo apt-get install autopoint libdbi-dev tcl-dev lua5.1 liblua5.1-0-dev valgrind dc python-pip python-setuptools - sudo pip install cpp-coveralls before_script: @@ -24,6 +24,7 @@ script: - make check TESTS_STYLE="valgrind-logfile" - sudo make install - cd bindings/perl-shared && make test + - cd ../python && python setup.py test - /opt/rrdtool-master/bin/rrdtool - /opt/rrdtool-master/share/rrdtool/examples/4charts.pl diff --git a/bindings/Makefile.am b/bindings/Makefile.am index 547f041f..aaa66794 100644 --- a/bindings/Makefile.am +++ b/bindings/Makefile.am @@ -19,7 +19,7 @@ EXTRA_DIST = perl-piped/MANIFEST perl-piped/README perl-piped/Makefile.PL perl-p perl-shared/ntmake-build perl-shared/MANIFEST perl-shared/README perl-shared/Makefile.PL perl-shared/RRDs.pm perl-shared/RRDs.ppd perl-shared/RRDs.xs perl-shared/t/base.t perl-shared/t/callback-long.t perl-shared/t/callback.t \ ruby/CHANGES ruby/README ruby/extconf.rb ruby/main.c ruby/test.rb \ dotnet/rrdlib.cs dotnet/rrd_binding_test.cs dotnet/rrdlib.sln dotnet/favicon.ico dotnet/dnrrdlib.csproj dotnet/Properties/AssemblyInfo.cs dotnet/dnrrd_binding_test.csproj dotnet/RrdException.cs \ - python/ACKNOWLEDGEMENT python/AUTHORS python/COPYING python/README python/rrdtoolmodule.c python/setup.py + python/COPYING python/README.md python/rrdtoolmodule.c python/setup.py # add the following to the all target diff --git a/bindings/Makefile.in b/bindings/Makefile.in index 3918dec8..22c22faa 100644 --- a/bindings/Makefile.in +++ b/bindings/Makefile.in @@ -402,7 +402,7 @@ EXTRA_DIST = perl-piped/MANIFEST perl-piped/README perl-piped/Makefile.PL perl-p perl-shared/ntmake-build perl-shared/MANIFEST perl-shared/README perl-shared/Makefile.PL perl-shared/RRDs.pm perl-shared/RRDs.ppd perl-shared/RRDs.xs perl-shared/t/base.t perl-shared/t/callback-long.t perl-shared/t/callback.t \ ruby/CHANGES ruby/README ruby/extconf.rb ruby/main.c ruby/test.rb \ dotnet/rrdlib.cs dotnet/rrd_binding_test.cs dotnet/rrdlib.sln dotnet/favicon.ico dotnet/dnrrdlib.csproj dotnet/Properties/AssemblyInfo.cs dotnet/dnrrd_binding_test.csproj dotnet/RrdException.cs \ - python/ACKNOWLEDGEMENT python/AUTHORS python/COPYING python/README python/rrdtoolmodule.c python/setup.py + python/COPYING python/README.md python/rrdtoolmodule.c python/setup.py all: all-recursive diff --git a/bindings/python/ACKNOWLEDGEMENT b/bindings/python/ACKNOWLEDGEMENT deleted file mode 100644 index 8cf36589..00000000 --- a/bindings/python/ACKNOWLEDGEMENT +++ /dev/null @@ -1,7 +0,0 @@ -ACKNOWLEDGMENT -============== - -This is a list of people who have made contributions to py-rrdtool. - -Matthew W. Samsonoff -Brian E. Gallew diff --git a/bindings/python/AUTHORS b/bindings/python/AUTHORS deleted file mode 100644 index b3b5713d..00000000 --- a/bindings/python/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -Hye-Shik Chang diff --git a/bindings/python/README b/bindings/python/README deleted file mode 100644 index c234a6bb..00000000 --- a/bindings/python/README +++ /dev/null @@ -1,34 +0,0 @@ -Based on Python-RRDtool 0.2.1 ------------------------------ - -The python-rrdtool provides a interface to rrdtool, the wonderful -graphing and logging utility. This wrapper implementation has -worked from the scratch (without SWIG), and it's under LGPL. - -This module have not documented yet. Please refer pydoc. - - -Project -------- - -Homepage: http://www.nongnu.org/py-rrdtool/ - -Mailing Lists: - - CVS Checkins py-rrdtool-cvs@nongnu.org - Users & Hackers py-rrdtool-users@nongnu.org - - -Original Author ---------------- - -Hye-Shik Chang - -Any comments, suggestions, and/or patches are very welcome. -Thank you for using py-rrdtool! - - -CHANGES -------- -2008-05-19 - tobi -* rewrote the info method to conform to rrdtool info standard diff --git a/bindings/python/README.md b/bindings/python/README.md new file mode 100644 index 00000000..cf325ef7 --- /dev/null +++ b/bindings/python/README.md @@ -0,0 +1,37 @@ +python-rrdtool +============== + +Python bindings for [RRDtool](http://oss.oetiker.ch/rrdtool) with a native C extension. + +Supported Python versions: 2.6+, 3.3+. + +The bindings are based on the code of the original Python 2 bindings for rrdtool by Hye-Shik Chang, which are currently shipped as official bindings with rrdtool. + +Installation +------------ + +The easy way: + + # pip install rrdtool + +**Note:** This requires rrdtool and it's development files (headers, libraries, dependencies) to be installed. + +In case you'd like to build the module on your own, you can obtain a copy of the repository and run `python setup.py install` in it's destination folder to build the native C extension. + +Usage +----- + +```python +import rrdtool + +# Create Round Robin Database +rrdtool.create('test.rrd', '--start', 'now', '--step', '300', 'RRA:AVERAGE:0.5:1:1200', 'DS:temp:GAUGE:600:-273:5000') + +# Feed updates to the RRD +rrdtool.update('test.rrd', 'N:32') +``` + +Documentation +------------- + +You can find the latest documentation for this project at http://pythonhosted.org/rrdtool. \ No newline at end of file diff --git a/bindings/python/rrdtoolmodule.c b/bindings/python/rrdtoolmodule.c index 8e571d05..4a762abd 100644 --- a/bindings/python/rrdtoolmodule.c +++ b/bindings/python/rrdtoolmodule.c @@ -1,246 +1,457 @@ /* - * rrdtoolmodule.c + * python-rrdtool, Python bindings for rrdtool. + * Based on the rrdtool Python bindings for Python 2 from + * Hye-Shik Chang . * - * RRDTool Python binding + * Copyright 2012 Christian Jurk * - * Author : Hye-Shik Chang - * Date : $Date: 2003/02/22 07:41:19 $ - * Created : 23 May 2002 + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. * - * $Revision: 1.14 $ + * 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. * - * ========================================================================== - * This file is part of py-rrdtool. - * - * py-rrdtool is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * py-rrdtool 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Foobar; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. * */ -#ifdef UNUSED -#elif defined(__GNUC__) -# define UNUSED(x) x __attribute__((unused)) -#elif defined(__LCLINT__) -# define UNUSED(x) /*@unused@*/ x +#include +#include +#include "rrd_config.h" +#include "rrd_tool.h" + +/* Some macros to maintain compatibility between Python 2.x and 3.x */ +#if PY_MAJOR_VERSION >= 3 +#define HAVE_PY3K +#define PyRRD_String_Check(x) PyUnicode_Check(x) +#define PyRRD_String_FromString(x) PyUnicode_FromString(x) +#define PyRRD_String_AS_STRING(x) PyUnicode_AsUTF8(x) +#define PyRRD_String_FromStringAndSize(x, y) PyBytes_FromStringAndSize(x, y) +#define PyRRD_String_Size(x) PyUnicode_Size(x) +#define PyRRD_Int_FromLong(x) PyLong_FromLong(x) +#define PyRRD_Int_FromString(x, y, z) PyLong_FromString(x,y,z) +#define PyRRD_Long_Check(x) PyLong_Check(x) #else -# define UNUSED(x) x +#define PyRRD_String_Check(x) PyString_Check(x) +#define PyRRD_String_FromString(x) PyString_FromString(x) +#define PyRRD_String_AS_STRING(x) PyString_AS_STRING(x) +#define PyRRD_String_FromStringAndSize(x, y) PyString_FromStringAndSize(x, y) +#define PyRRD_String_Size(x) PyString_Size(x) +#define PyRRD_Int_FromLong(x) PyInt_FromLong(x) +#define PyRRD_Int_FromString(x, y, z) PyInt_FromString(x,y,z) +#define PyRRD_Long_Check(x) (PyInt_Check(x) || PyLong_Check(x)) #endif +#ifndef Py_UNUSED +#ifdef __GNUC__ + #define Py_UNUSED(name) _unused_ ## name __attribute__((unused)) +#else + #define Py_UNUSED(name) _unused_ ## -name +#endif +#endif -#include "rrd_config.h" -static const char *__version__ = PACKAGE_VERSION; +/** Binding version. */ +static const char *_version = "0.1.10"; -#include "Python.h" -#include "rrd_tool.h" -//#include "rrd.h" -//#include "rrd_extra.h" +/** Exception types. */ +static PyObject *rrdtool_OperationalError; +static PyObject *rrdtool_ProgrammingError; -static PyObject *ErrorObject; +static char **rrdtool_argv = NULL; +static int rrdtool_argc = 0; -/* forward declaration to keep compiler happy */ -void initrrdtool( - void); +/** + * PyRRD_DateTime_FromTS: convert UNIX timestamp (time_t) + * to Python datetime object. + * + * @param ts UNIX timestamp (time_t) + * @return Pointer to new PyObject (New Reference) + */ +static PyObject * +PyRRD_DateTime_FromTS(time_t ts) +{ + PyObject *ret; + struct tm lt; + + localtime_r(&ts, <); + + ret = PyDateTime_FromDateAndTime( + lt.tm_year + 1900, + lt.tm_mon + 1, + lt.tm_mday, + lt.tm_hour, + lt.tm_min, + lt.tm_sec, + 0); -static int create_args( - char *command, - PyObject * args, - int *argc, - char ***argv) + return ret; +} + +/** + * PyRRD_String_FromCF: get string representation of CF enum index + * + * @param cf enum cf_en + * @return Null-terminated string + */ +const char * +PyRRD_String_FromCF(enum cf_en cf) +{ + switch (cf) { + case CF_AVERAGE: + return "AVERAGE"; + case CF_MINIMUM: + return "MIN"; + case CF_MAXIMUM: + return "MAX"; + case CF_LAST: + return "LAST"; + default: + return "INVALID"; + } +} + +/** + * Helper function to convert Python objects into a representation that the + * rrdtool functions can work with. + * + * @param command RRDtool command name + * @param args Command arguments + * @return Zero if the function succeeds, otherwise -1 + */ +static int +convert_args(char *command, PyObject *args) { PyObject *o, *lo; - int args_count, - argv_count, - element_count, - i, j; + int i, j, args_count, argv_count, element_count; + argv_count = element_count = 0; args_count = PyTuple_Size(args); - element_count = 0; + for (i = 0; i < args_count; i++) { o = PyTuple_GET_ITEM(args, i); - if (PyString_Check(o)) + + if (PyRRD_String_Check(o)) element_count++; else if (PyList_CheckExact(o)) - element_count += PyList_Size(o); - else { - PyErr_Format(PyExc_TypeError, "argument %d must be string or list of strings", i); - return -1; - } + element_count += PyList_Size(o); + else { + PyErr_Format(PyExc_TypeError, + "Argument %d must be str or a list of str", i); + return -1; + } } - - *argv = PyMem_New(char *, - element_count + 1); - if (*argv == NULL) + 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 (PyString_Check(o)) { - argv_count++; - (*argv)[argv_count] = PyString_AS_STRING(o); - } else if (PyList_CheckExact(o)) - for (j = 0; j < PyList_Size(o); j++) { - lo = PyList_GetItem(o, j); - if (PyString_Check(lo)) { - argv_count++; - (*argv)[argv_count] = PyString_AS_STRING(lo); - } else { - PyMem_Del(*argv); - PyErr_Format(PyExc_TypeError, "element %d in argument %d must be string", j, i); - return -1; - } - } - else { - PyMem_Del(*argv); - PyErr_Format(PyExc_TypeError, "argument %d must be string or list of strings", i); - return -1; - } + + if (PyRRD_String_Check(o)) + rrdtool_argv[++argv_count] = PyRRD_String_AS_STRING(o); + else if (PyList_CheckExact(o)) { + for (j = 0; j < PyList_Size(o); j++) { + lo = PyList_GetItem(o, j); + + if (PyRRD_String_Check(lo)) + rrdtool_argv[++argv_count] = PyRRD_String_AS_STRING(lo); + else { + PyMem_Del(rrdtool_argv); + PyErr_Format(PyExc_TypeError, + "Element %d in argument %d must be str", j, i); + return -1; + } + } + } else { + PyMem_Del(rrdtool_argv); + PyErr_Format(rrdtool_ProgrammingError, + "Argument %d must be str or list of str", i); + return -1; + } } - (*argv)[0] = command; - *argc = element_count + 1; + rrdtool_argv[0] = command; + rrdtool_argc = element_count + 1; return 0; } -static void destroy_args( - char ***argv) +/** + * Destroy argument vector. + */ +static void +destroy_args(void) { - PyMem_Del(*argv); - *argv = NULL; + PyMem_Del(rrdtool_argv); + rrdtool_argv = NULL; } -static char PyRRD_create__doc__[] = - "create(args..): Set up a new Round Robin Database\n\ - create filename [--start|-b start time] \ -[--step|-s step] [DS:ds-name:DST:heartbeat:min:max] \ -[RRA:CF:xff:steps:rows]"; +/** + * Convert RRDtool info to dict. + * + * @param data RRDtool info object + * @return Python dict object + */ +static PyObject * +_rrdtool_util_info2dict(const rrd_info_t *data) +{ + PyObject *dict, *val; -static PyObject *PyRRD_create( - PyObject UNUSED(*self), - PyObject * args) + dict = PyDict_New(); + + while (data) { + val = NULL; + + switch (data->type) { + case RD_I_VAL: + if (isnan(data->value.u_val)) { + Py_INCREF(Py_None); + val = Py_None; + } else + PyFloat_FromDouble(data->value.u_val); + break; + + case RD_I_CNT: + val = PyLong_FromUnsignedLong(data->value.u_cnt); + break; + + case RD_I_INT: + val = PyLong_FromLong(data->value.u_int); + break; + + case RD_I_STR: + val = PyRRD_String_FromString(data->value.u_str); + break; + + case RD_I_BLO: + val = PyRRD_String_FromStringAndSize( + (char *)data->value.u_blo.ptr, + data->value.u_blo.size); + break; + default: + break; + } + + if (val != NULL) { + PyDict_SetItemString(dict, data->key, val); + Py_DECREF(val); + } + + data = data->next; + } + + return dict; +} + +static char _rrdtool_create__doc__[] = "Create a new Round Robin Database.\n\n\ + Usage: create(args...)\n\ + Arguments:\n\n\ + filename\n\ + [-b|--start start time]\n\ + [-s|--step step]\n\ + [-t|--template template-file]\n\ + [-r|--source source-file]\n\ + [-O|--no-overwrite]\n\ + [-d|--daemon address]\n\ + [DS:ds-name[=mapped-ds-name[source-index]]: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 *Py_UNUSED(self), PyObject *args) { - PyObject *r; - char **argv; - int argc, status; + PyObject *ret; + int status; - if (create_args("create", args, &argc, &argv) < 0) + if (convert_args("create", args) == -1) return NULL; Py_BEGIN_ALLOW_THREADS - status = rrd_create(argc, argv); + status = rrd_create(rrdtool_argc, rrdtool_argv); Py_END_ALLOW_THREADS if (status == -1) { - PyErr_SetString(ErrorObject, rrd_get_error()); + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); rrd_clear_error(); - r = NULL; + ret = NULL; } else { Py_INCREF(Py_None); - r = Py_None; + ret = Py_None; } - destroy_args(&argv); - return r; + destroy_args(); + return ret; } -static char PyRRD_update__doc__[] = - "update(args..): Store a new set of values into the rrd\n" - " update filename [--template|-t ds-name[:ds-name]...] " - "N|timestamp:value[:value...] [timestamp:value[:value...] ...]"; +static char _rrdtool_dump__doc__[] = "Dump an RRD to XML.\n\n\ + Usage: dump(args..)\n\ + Arguments:\n\n\ + [-h|--header {none,xsd,dtd}\n\ + [-n|--no-header]\n\ + [-d|--daemon address]\n\ + file.rrd\n\ + [file.xml]\n\n\ + Full documentation can be found at:\n\ + http://oss.oetiker.ch/rrdtool/doc/rrddump.en.html"; + +static PyObject * +_rrdtool_dump(PyObject *Py_UNUSED(self), PyObject *args) +{ + PyObject *ret; + int status; + + if (convert_args("dump", args) == -1) + return NULL; + + Py_BEGIN_ALLOW_THREADS + status = rrd_dump(rrdtool_argc, rrdtool_argv); + Py_END_ALLOW_THREADS + + if (status != 0) { + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); + rrd_clear_error(); + ret = NULL; + } else { + Py_INCREF(Py_None); + ret = Py_None; + } -static PyObject *PyRRD_update( - PyObject UNUSED(*self), - PyObject * args) + 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 *Py_UNUSED(self), PyObject *args) { - PyObject *r; - char **argv; - int argc, status; + PyObject *ret; + int status; - if (create_args("update", args, &argc, &argv) < 0) + if (convert_args("update", args) == -1) return NULL; Py_BEGIN_ALLOW_THREADS - status = rrd_update(argc, argv); + status = rrd_update(rrdtool_argc, rrdtool_argv); Py_END_ALLOW_THREADS if (status == -1) { - PyErr_SetString(ErrorObject, rrd_get_error()); + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); rrd_clear_error(); - r = NULL; + ret = NULL; } else { Py_INCREF(Py_None); - r = Py_None; + ret = Py_None; } - destroy_args(&argv); - return r; + destroy_args(); + return ret; } -static char PyRRD_fetch__doc__[] = - "fetch(args..): fetch data from an rrd.\n" - " fetch filename CF [--resolution|-r resolution] " - "[--start|-s start] [--end|-e end]"; +static char _rrdtool_updatev__doc__[] = "Store a new set of values into "\ + "the Round Robin Database and return an info dictionary.\n\n\ + This function works in the same manner as 'update', but will return an\n\ + info dictionary instead of None."; -static PyObject *PyRRD_fetch( - PyObject UNUSED(*self), - PyObject * args) +static PyObject * +_rrdtool_updatev(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r; - rrd_value_t *data, *datai; - unsigned long step, ds_cnt; - time_t start, end; - int argc, status; - char **argv, **ds_namv; + PyObject *ret; + rrd_info_t *data; - if (create_args("fetch", args, &argc, &argv) < 0) + if (convert_args("updatev", args) == -1) return NULL; Py_BEGIN_ALLOW_THREADS - status = rrd_fetch(argc, argv, &start, &end, &step, &ds_cnt, &ds_namv, &data); + data = rrd_update_v(rrdtool_argc, rrdtool_argv); Py_END_ALLOW_THREADS - if (status == -1) { - PyErr_SetString(ErrorObject, rrd_get_error()); + if (data == NULL) { + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); rrd_clear_error(); - r = NULL; + ret = NULL; } else { - /* Return : - ((start, end, step), (name1, name2, ...), [(data1, data2, ..), ...]) */ - PyObject *range_tup, *dsnam_tup, *data_list, *t; - unsigned long i, j, row; - rrd_value_t dv; + ret = _rrdtool_util_info2dict(data); + rrd_info_free(data); + } - row = (end - start) / step; + destroy_args(); + return ret; +} - r = PyTuple_New(3); +static char _rrdtool_fetch__doc__[] = "Fetch data from an RRD.\n\n\ + Usage: fetch(args..)\n\ + Arguments:\n\n\ + filename\n\ + CF\n\ + [-r|--resolution resolution]\n\ + [-s|--start start]\n\ + [-e|--end end]\n\ + [-a|--align-start]\n\ + [-d|--daemon address]\n\n\ + Full documentation can be found at:\n\ + http://oss.oetiker.ch/rrdtool/doc/rrdfetch.en.html"; + +static PyObject * +_rrdtool_fetch(PyObject *Py_UNUSED(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; + int status; + + if (convert_args("fetch", args) == -1) + return NULL; + + Py_BEGIN_ALLOW_THREADS + status = rrd_fetch(rrdtool_argc, rrdtool_argv, &start, &end, &step, + &ds_cnt, &ds_namv, &data); + Py_END_ALLOW_THREADS + + if (status == -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(r, 0, range_tup); - PyTuple_SET_ITEM(r, 1, dsnam_tup); - PyTuple_SET_ITEM(r, 2, data_list); + + 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, PyInt_FromLong((long) start)); - PyTuple_SET_ITEM(range_tup, 1, PyInt_FromLong((long) end)); - PyTuple_SET_ITEM(range_tup, 2, PyInt_FromLong((long) step)); + PyTuple_SET_ITEM(range_tup, 0, PyRRD_Int_FromLong((long) start)); + PyTuple_SET_ITEM(range_tup, 1, PyRRD_Int_FromLong((long) end)); + PyTuple_SET_ITEM(range_tup, 2, PyRRD_Int_FromLong((long) step)); for (i = 0; i < ds_cnt; i++) - PyTuple_SET_ITEM(dsnam_tup, i, PyString_FromString(ds_namv[i])); + PyTuple_SET_ITEM(dsnam_tup, i, PyRRD_String_FromString(ds_namv[i])); for (i = 0; i < row; i++) { t = PyTuple_New(ds_cnt); @@ -251,616 +462,995 @@ static PyObject *PyRRD_fetch( if (isnan(dv)) { PyTuple_SET_ITEM(t, j, Py_None); Py_INCREF(Py_None); - } else { + } 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); /* rrdtool don't use PyMem_Malloc :) */ + + rrd_freemem(ds_namv); rrd_freemem(data); } - destroy_args(&argv); - return r; + destroy_args(); + return ret; +} + +static char _rrdtool_flushcached__doc__[] = "Flush RRD files from memory.\n\n\ + Usage: flushcached(args..)\n\ + Arguments:\n\n\ + [-d|--daemon address]\n\ + filename\n\ + [filename ...]\n\n\ + Full documentation can be found at:\n\ + http://oss.oetiker.ch/rrdtool/doc/rrdflushcached.en.html"; + +static PyObject * +_rrdtool_flushcached(PyObject *Py_UNUSED(self), PyObject *args) +{ + PyObject *ret; + int status; + + if (convert_args("flushcached", args) == -1) + return NULL; + + Py_BEGIN_ALLOW_THREADS + status = rrd_flushcached(rrdtool_argc, rrdtool_argv); + Py_END_ALLOW_THREADS + + if (status != 0) { + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); + rrd_clear_error(); + ret = NULL; + } else { + Py_INCREF(Py_None); + ret = Py_None; + } + + destroy_args(); + return ret; } #ifdef HAVE_RRD_GRAPH -static char PyRRD_graph__doc__[] = - "graph(args..): Create a graph based on data from one or several RRD\n" - " graph filename [-s|--start seconds] " - "[-e|--end seconds] [-x|--x-grid x-axis grid and label] " - "[-y|--y-grid y-axis grid and label] [--alt-y-grid] [--alt-y-mrtg] " - "[--alt-autoscale] [--alt-autoscale-max] [--units-exponent] value " - "[-v|--vertical-label text] [-w|--width pixels] [-h|--height pixels] " - "[-i|--interlaced] " - "[-f|--imginfo formatstring] [-a|--imgformat GIF|PNG|GD] " - "[-B|--background value] [-O|--overlay value] " - "[-U|--unit value] [-z|--lazy] [-o|--logarithmic] " - "[-u|--upper-limit value] [-l|--lower-limit value] " - "[-g|--no-legend] [-r|--rigid] [--step value] " - "[-b|--base value] [-c|--color COLORTAG#rrggbb] " - "[-t|--title title] [DEF:vname=rrd:ds-name:CF] " - "[CDEF:vname=rpn-expression] [PRINT:vname:CF:format] " - "[GPRINT:vname:CF:format] [COMMENT:text] " - "[HRULE:value#rrggbb[:legend]] [VRULE:time#rrggbb[:legend]] " - "[LINE{1|2|3}:vname[#rrggbb[:legend]]] " - "[AREA:vname[#rrggbb[:legend]]] " "[STACK:vname[#rrggbb[:legend]]]"; - -static PyObject *PyRRD_graph( - PyObject UNUSED(*self), - PyObject * args) +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\ + [--week-fmt strftime format string]\n\ + [--left-axis-formatter formatter-name]\n\ + [--left-axis-format format-string]\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\ + [-d|--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,XML,XMLENUM,JSON,JSONTIME,CSV,TSV,SSV}]\n\ + [-i|--interlaced]\n\ + [-T|--tabwidth value]\n\ + [-b|--base value]\n\ + [-W|--watermark string]\n\ + [-Z|--use-nan-for-all-missing-data]\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 *Py_UNUSED(self), PyObject *args) { - PyObject *r; - char **argv, **calcpr; - int argc, status, xsize, ysize, i; - double ymin, ymax; + PyObject *ret; + int xsize, ysize, i, status; + double ymin, ymax; + char **calcpr; - if (create_args("graph", args, &argc, &argv) < 0) + if (convert_args("graph", args) == -1) return NULL; Py_BEGIN_ALLOW_THREADS - status = rrd_graph(argc, argv, &calcpr, &xsize, &ysize, NULL, &ymin, &ymax); + status = rrd_graph(rrdtool_argc, rrdtool_argv, &calcpr, &xsize, &ysize, + NULL, &ymin, &ymax); Py_END_ALLOW_THREADS if (status == -1) { - PyErr_SetString(ErrorObject, rrd_get_error()); + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); rrd_clear_error(); - r = NULL; + ret = NULL; } else { - r = PyTuple_New(3); + ret = PyTuple_New(3); - PyTuple_SET_ITEM(r, 0, PyInt_FromLong((long) xsize)); - PyTuple_SET_ITEM(r, 1, PyInt_FromLong((long) ysize)); + PyTuple_SET_ITEM(ret, 0, PyRRD_Int_FromLong((long) xsize)); + PyTuple_SET_ITEM(ret, 1, PyRRD_Int_FromLong((long) ysize)); if (calcpr) { PyObject *e, *t; e = PyList_New(0); - PyTuple_SET_ITEM(r, 2, e); + PyTuple_SET_ITEM(ret, 2, e); for (i = 0; calcpr[i]; i++) { - t = PyString_FromString(calcpr[i]); + t = PyRRD_String_FromString(calcpr[i]); PyList_Append(e, t); Py_DECREF(t); rrd_freemem(calcpr[i]); } - rrd_freemem(calcpr); } else { Py_INCREF(Py_None); - PyTuple_SET_ITEM(r, 2, Py_None); + PyTuple_SET_ITEM(ret, 2, Py_None); } } - destroy_args(&argv); - return r; + destroy_args(); + return ret; } -#endif /* HAVE_RRD_GRAPH */ - -static char PyRRD_tune__doc__[] = - "tune(args...): Modify some basic properties of a Round Robin Database\n" - " tune filename [--heartbeat|-h ds-name:heartbeat] " - "[--minimum|-i ds-name:min] [--maximum|-a ds-name:max] " - "[--data-source-type|-d ds-name:DST] [--data-source-rename|-r old-name:new-name]"; +static char _rrdtool_graphv__doc__[] = "Create a graph based on one or more " \ + "RRDs and return data in RRDtool info format.\n\n\ + This function works the same way as 'graph', but will return a info\n\ + dictionary instead of None.\n\n\ + Full documentation can be found at (graphv section):\n\ + http://oss.oetiker.ch/rrdtool/doc/rrdgraph.en.html"; -static PyObject *PyRRD_tune( - PyObject UNUSED(*self), - PyObject * args) +static PyObject * +_rrdtool_graphv(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r; - char **argv; - int argc, status; + PyObject *ret; + rrd_info_t *data; - if (create_args("tune", args, &argc, &argv) < 0) + if (convert_args("graphv", args) == -1) return NULL; Py_BEGIN_ALLOW_THREADS - status = rrd_tune(argc, argv); + data = rrd_graph_v(rrdtool_argc, rrdtool_argv); Py_END_ALLOW_THREADS - if (status == -1) { - PyErr_SetString(ErrorObject, rrd_get_error()); + if (data == NULL) { + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); rrd_clear_error(); - r = NULL; + ret = NULL; } else { - Py_INCREF(Py_None); - r = Py_None; + ret = _rrdtool_util_info2dict(data); + rrd_info_free(data); } - destroy_args(&argv); - return r; + destroy_args(); + return ret; } -static char PyRRD_first__doc__[] = - "first(filename): Return the timestamp of the first data sample in an RRD"; - -static PyObject *PyRRD_first( - PyObject UNUSED(*self), - PyObject * args) +static char _rrdtool_xport__doc__[] = "Dictionary representation of data " \ + "stored in RRDs.\n\n\ + Usage: xport(args..)\n\ + Arguments:\n\n\ + [-s[--start seconds]\n\ + [-e|--end seconds]\n\ + [-m|--maxrows rows]\n\ + [--step value]\n\ + [--json]\n\ + [--enumds]\n\ + [--daemon address]\n\ + [DEF:vname=rrd:ds-name:CF]\n\ + [CDEF:vname=rpn-expression]\n\ + [XPORT:vname[:legend]]\n\n\ + Full documentation can be found at:\n\ + http://oss.oetiker.ch/rrdtool/doc/rrdxport.en.html"; + +static PyObject * +_rrdtool_xport(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r; - int argc, ts; - char **argv; + PyObject *ret; + int xsize, status; + char **legend_v; + time_t start, end; + unsigned long step, col_cnt; + rrd_value_t *data, *datai; - if (create_args("first", args, &argc, &argv) < 0) + if (convert_args("xport", args) == -1) return NULL; Py_BEGIN_ALLOW_THREADS - ts = rrd_first(argc, argv); + status = rrd_xport(rrdtool_argc, rrdtool_argv, &xsize, &start, &end, &step, + &col_cnt, &legend_v, &data); Py_END_ALLOW_THREADS - if (ts == -1) { - PyErr_SetString(ErrorObject, rrd_get_error()); + if (status == -1) { + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); rrd_clear_error(); - r = NULL; - } else - r = PyInt_FromLong((long) ts); + ret = NULL; + } else { + PyObject *meta_dict, *data_list, *legend_list, *t; + rrd_value_t dv; + unsigned long i, j, row_cnt = (end - start) / step; - destroy_args(&argv); - return r; -} + ret = PyDict_New(); + meta_dict = PyDict_New(); + legend_list = PyList_New(col_cnt); + data_list = PyList_New(row_cnt); -static char PyRRD_last__doc__[] = - "last(filename): Return the timestamp of the last data sample in an RRD"; + PyDict_SetItem(ret, PyRRD_String_FromString("meta"), meta_dict); + PyDict_SetItem(ret, PyRRD_String_FromString("data"), data_list); -static PyObject *PyRRD_last( - PyObject UNUSED(*self), - PyObject * args) -{ - PyObject *r; - int argc, ts; - char **argv; + datai = data; - if (create_args("last", args, &argc, &argv) < 0) - return NULL; + PyDict_SetItem(meta_dict, + PyRRD_String_FromString("start"), + PyRRD_Int_FromLong((long) start)); + PyDict_SetItem(meta_dict, + PyRRD_String_FromString("end"), + PyRRD_Int_FromLong((long) end)); + PyDict_SetItem(meta_dict, + PyRRD_String_FromString("step"), + PyRRD_Int_FromLong((long) step)); + PyDict_SetItem(meta_dict, + PyRRD_String_FromString("rows"), + PyRRD_Int_FromLong((long) row_cnt)); + PyDict_SetItem(meta_dict, + PyRRD_String_FromString("columns"), + PyRRD_Int_FromLong((long) col_cnt)); + PyDict_SetItem(meta_dict, + PyRRD_String_FromString("legend"), + legend_list); + + for (i = 0; i < col_cnt; i++) + PyList_SET_ITEM(legend_list, i, PyRRD_String_FromString(legend_v[i])); - Py_BEGIN_ALLOW_THREADS - ts = rrd_last(argc, argv); - Py_END_ALLOW_THREADS + for (i = 0; i < row_cnt; i++) { + t = PyTuple_New(col_cnt); + PyList_SET_ITEM(data_list, i, t); - if (ts == -1) { - PyErr_SetString(ErrorObject, rrd_get_error()); - rrd_clear_error(); - r = NULL; - } else - r = PyInt_FromLong((long) ts); + for (j = 0; j < col_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 < col_cnt; i++) + rrd_freemem(legend_v[i]); + + rrd_freemem(legend_v); + rrd_freemem(data); + } - destroy_args(&argv); - return r; + destroy_args(); + + return ret; } -static char PyRRD_resize__doc__[] = - "resize(args...): alters the size of an RRA.\n" - " resize filename rra-num GROW|SHRINK rows"; +#endif /* HAVE_RRD_GRAPH */ + +static char _rrdtool_list__doc__[] = "List RRDs in storage.\n\n" \ + "Usage: list(args..)\n\ + Arguments:\n\n\ + dirname\n\ + [--daemon HOST]"; -static PyObject *PyRRD_resize( - PyObject UNUSED(*self), - PyObject * args) +static PyObject * +_rrdtool_list(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r; - char **argv; - int argc, ts; + PyObject *ret, *str; + char *data, *ptr, *end; - if (create_args("resize", args, &argc, &argv) < 0) + if (convert_args("list", args) == -1) return NULL; Py_BEGIN_ALLOW_THREADS - ts = rrd_resize(argc, argv); + data = rrd_list(rrdtool_argc, rrdtool_argv); Py_END_ALLOW_THREADS - if (ts == -1) { - PyErr_SetString(ErrorObject, rrd_get_error()); + if (data == NULL) { + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); rrd_clear_error(); - r = NULL; + ret = NULL; } else { - Py_INCREF(Py_None); - r = Py_None; - } - - destroy_args(&argv); - return r; -} + ret = PyList_New(0); + ptr = data; + end = strchr(ptr, '\n'); + + while (end) { + *end = '\0'; + str = PyRRD_String_FromString(ptr); + + if (PyList_Append(ret, str)) { + PyErr_SetString(rrdtool_OperationalError, "Failed to append to list"); + ret = NULL; + break; + } -static PyObject *PyDict_FromInfo( - rrd_info_t * data) -{ - PyObject *r; + ptr = end + 1; - r = PyDict_New(); - while (data) { - PyObject *val = NULL; + if (strlen(ptr) == 0) + break; - switch (data->type) { - case RD_I_VAL: - val = isnan(data->value.u_val) - ? (Py_INCREF(Py_None), Py_None) - : PyFloat_FromDouble(data->value.u_val); - break; - case RD_I_CNT: - val = PyLong_FromUnsignedLong(data->value.u_cnt); - break; - case RD_I_INT: - val = PyLong_FromLong(data->value.u_int); - break; - case RD_I_STR: - val = PyString_FromString(data->value.u_str); - break; - case RD_I_BLO: - val = - PyString_FromStringAndSize((char *) data->value.u_blo.ptr, - data->value.u_blo.size); - break; + end = strchr(ptr, '\n'); } - if (val) { - PyDict_SetItemString(r, data->key, val); - Py_DECREF(val); - } - data = data->next; + + rrd_freemem(data); } - return r; -} -static char PyRRD_info__doc__[] = - "info(filename): extract header information from an rrd"; + destroy_args(); + return ret; +} -static PyObject *PyRRD_info( - PyObject UNUSED(*self), - PyObject * args) +static char _rrdtool_tune__doc__[] = "Modify some basic properties of a " \ + "Round Robin Database.\n\n\ + Usage: tune(args..)\n\ + Arguments:\n\n\ + filename\n\ + [-h|--heartbeat ds-name:heartbeat]\n\ + [-i|--minimum ds-name:min]\n\ + [-a|--maximum ds-name:max]\n\ + [-d|--data-source-type ds-name:DST]\n\ + [-r|--data-source-rename old-name:new-name]\n\n\ + Full documentation can be found at:\n\ + http://oss.oetiker.ch/rrdtool/doc/rrdtune.en.html"; + +static PyObject * +_rrdtool_tune(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r; - int argc; - char **argv; - rrd_info_t *data; + PyObject *ret; + int status; - if (create_args("info", args, &argc, &argv) < 0) + if (convert_args("tune", args) == -1) return NULL; Py_BEGIN_ALLOW_THREADS - data = rrd_info(argc, argv); + status = rrd_tune(rrdtool_argc, rrdtool_argv); Py_END_ALLOW_THREADS - if (data == NULL) { - PyErr_SetString(ErrorObject, rrd_get_error()); + if (status == -1) { + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); rrd_clear_error(); - r = NULL; + ret = NULL; } else { - r = PyDict_FromInfo(data); - rrd_info_free(data); + Py_INCREF(Py_None); + ret = Py_None; } - destroy_args(&argv); - return r; + destroy_args(); + return ret; } -static char PyRRD_list__doc__[] = - "list(dirname): list RRDs in storage" - " list [--daemon ] dirname"; - -static PyObject *PyRRD_list(PyObject UNUSED(*self), PyObject * args) +static char _rrdtool_first__doc__[] = "Get the first UNIX timestamp of the "\ + "first data sample in an Round Robin Database.\n\n\ + Usage: first(args..)\n\ + Arguments:\n\n\ + filename\n\ + [--rraindex number]\n\ + [-d|--daemon address]\n\n\ + Full documentation can be found at:\n\ + http://oss.oetiker.ch/rrdtool/doc/rrdfirst.en.html"; + +static PyObject * +_rrdtool_first(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r = NULL; - int argc; - char **argv; - char *data; - char *ptr; - char *end; - PyObject *str; - - if (create_args("list", args, &argc, &argv) < 0) - return NULL; + PyObject *ret; + int ts; - if ((data = rrd_list(argc, argv)) == NULL) { - PyErr_SetString(ErrorObject, rrd_get_error()); - rrd_clear_error(); - - goto out_free_args; - } - - r = PyList_New(0); + if (convert_args("first", args) == -1) + return NULL; - ptr = data; - end = strchr(ptr, '\n'); + Py_BEGIN_ALLOW_THREADS + ts = rrd_first(rrdtool_argc, rrdtool_argv); + Py_END_ALLOW_THREADS - while (end) { - *end = '\0'; - str = PyString_FromString(ptr); + if (ts == -1) { + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); + rrd_clear_error(); + ret = NULL; + } else + ret = PyRRD_Int_FromLong((long) ts); - if (PyList_Append(r, str)) { - PyErr_SetString(ErrorObject, "Failed to append to list"); - r = NULL; - break; - } + destroy_args(); + return ret; +} - ptr = end + 1; +static char _rrdtool_last__doc__[] = "Get the UNIX timestamp of the most "\ + "recent data sample in an Round Robin Database.\n\n\ + Usage: last(args..)\n\ + Arguments:\n\n\ + filename\n\ + [-d|--daemon address]\n\n\ + Full documentation can be found at:\n\ + http://oss.oetiker.ch/rrdtool/doc/rrdlast.en.html"; + +static PyObject * +_rrdtool_last(PyObject *Py_UNUSED(self), PyObject *args) +{ + PyObject *ret; + int ts; - if (strlen(ptr) == 0) - break; + if (convert_args("last", args) == -1) + return NULL; - end = strchr(ptr, '\n'); - } + Py_BEGIN_ALLOW_THREADS + ts = rrd_last(rrdtool_argc, rrdtool_argv); + Py_END_ALLOW_THREADS - rrd_freemem(data); + if (ts == -1) { + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); + rrd_clear_error(); + ret = NULL; + } else + ret = PyRRD_Int_FromLong((long) ts); -out_free_args: - destroy_args(&argv); - return r; + destroy_args(); + return ret; } -#ifdef HAVE_RRD_GRAPH -static char PyRRD_graphv__doc__[] = - "graphv is called in the same manner as graph"; - -static PyObject *PyRRD_graphv( - PyObject UNUSED(*self), - PyObject * args) +static char _rrdtool_resize__doc__[] = "Modify the number of rows in a "\ + "Round Robin Database.\n\n\ + Usage: resize(args..)\n\ + Arguments:\n\n\ + filename\n\ + rra-num\n\ + GROW|SHRINK\n\ + rows\n\n\ + Full documentation can be found at:\n\ + http://oss.oetiker.ch/rrdtool/doc/rrdlast.en.html"; + +static PyObject * +_rrdtool_resize(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r; - int argc; - char **argv; - rrd_info_t *data; + PyObject *ret; + int status; - if (create_args("graphv", args, &argc, &argv) < 0) + if (convert_args("resize", args) == -1) return NULL; Py_BEGIN_ALLOW_THREADS - data = rrd_graph_v(argc, argv); + status = rrd_resize(rrdtool_argc, rrdtool_argv); Py_END_ALLOW_THREADS - if (data == NULL) { - PyErr_SetString(ErrorObject, rrd_get_error()); + if (status == -1) { + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); rrd_clear_error(); - r = NULL; + ret = NULL; } else { - r = PyDict_FromInfo(data); - rrd_info_free(data); + Py_INCREF(Py_None); + ret = Py_None; } - destroy_args(&argv); - return r; + destroy_args(); + return ret; } -#endif /* HAVE_RRD_GRAPH */ - -static char PyRRD_updatev__doc__[] = - "updatev is called in the same manner as update"; - -static PyObject *PyRRD_updatev( - PyObject UNUSED(*self), - PyObject * args) +static char _rrdtool_info__doc__[] = "Extract header information from an "\ + "Round Robin Database.\n\n\ + Usage: info(filename, ...)\n\ + Arguments:\n\n\ + filename\n\ + [-d|--daemon address]\n\ + [-F|--noflush]\n\n\ + Full documentation can be found at:\n\ + http://oss.oetiker.ch/rrdtool/doc/rrdinfo.en.html"; + +static PyObject * +_rrdtool_info(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r; - int argc; - char **argv; + PyObject *ret; rrd_info_t *data; - if (create_args("updatev", args, &argc, &argv) < 0) + if (convert_args("info", args) == -1) return NULL; Py_BEGIN_ALLOW_THREADS - data = rrd_update_v(argc, argv); + data = rrd_info(rrdtool_argc, rrdtool_argv); Py_END_ALLOW_THREADS if (data == NULL) { - PyErr_SetString(ErrorObject, rrd_get_error()); + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); rrd_clear_error(); - r = NULL; + ret = NULL; } else { - r = PyDict_FromInfo(data); + ret = _rrdtool_util_info2dict(data); rrd_info_free(data); } - destroy_args(&argv); - return r; + destroy_args(); + return ret; } -static char PyRRD_flushcached__doc__[] = - "flush(args..): flush RRD files from memory\n" - " flush [--daemon address] file [file ...]"; - -static PyObject *PyRRD_flushcached( - PyObject UNUSED(*self), - PyObject * args) +static char _rrdtool_lastupdate__doc__[] = "Returns datetime and value stored "\ + "for each datum in the most recent update of an RRD.\n\n\ + Usage: lastupdate(filename, ...)\n\ + Arguments:\n\n\ + filename\n\ + [-d|--daemon address]\n\n\ + Full documentation can be found at:\n\ + http://oss.oetiker.ch/rrdtool/doc/rrdlastupdate.en.html"; + +static PyObject * +_rrdtool_lastupdate(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r; - int argc, status; - char **argv; + PyObject *ret, *ds_dict, *lastupd; + int status; + time_t last_update; + char **ds_names, **last_ds; + unsigned long ds_cnt, i; - if (create_args("flushcached", args, &argc, &argv) < 0) + if (convert_args("lastupdate", args) == -1) return NULL; + else if (rrdtool_argc < 2) { + PyErr_SetString(rrdtool_ProgrammingError, "Missing filename argument"); + return NULL; + } Py_BEGIN_ALLOW_THREADS - status = rrd_flushcached(argc, argv); + status = rrd_lastupdate_r(rrdtool_argv[1], + &last_update, + &ds_cnt, + &ds_names, + &last_ds); Py_END_ALLOW_THREADS if (status != 0) { - PyErr_SetString(ErrorObject, rrd_get_error()); + PyErr_SetString(rrdtool_OperationalError, rrd_get_error()); rrd_clear_error(); - r = NULL; + ret = NULL; } else { - Py_INCREF(Py_None); - r = Py_None; + /* convert last_update to Python datetime object */ + ret = PyDict_New(); + ds_dict = PyDict_New(); + lastupd = PyRRD_DateTime_FromTS(last_update); + + PyDict_SetItemString(ret, "date", lastupd); + PyDict_SetItemString(ret, "ds", ds_dict); + + Py_DECREF(lastupd); + Py_DECREF(ds_dict); + + for (i = 0; i < ds_cnt; i++) { + PyObject* val = Py_None; + + double num; + if (sscanf(last_ds[i], "%lf", &num) == 1) { + val = PyFloat_FromDouble(num); + } + + if (!val) + return NULL; + + PyDict_SetItemString(ds_dict, ds_names[i], val); + Py_DECREF(val); + + free(last_ds[i]); + free(ds_names[i]); + } + + free(last_ds); + free(ds_names); + } - destroy_args(&argv); - return r; -} + destroy_args(); -#ifdef HAVE_RRD_GRAPH + return ret; +} -static char PyRRD_xport__doc__[] = - "xport(args..): dictionary representation of data stored in RRDs\n" - " [-s|--start seconds] [-e|--end seconds] [-m|--maxrows rows]" - "[--step value] [--daemon address] [DEF:vname=rrd:ds-name:CF]" - "[CDEF:vname=rpn-expression] [XPORT:vname[:legend]]"; +/** An Python object which will hold an callable for fetch callbacks */ +static PyObject *_rrdtool_fetch_callable = NULL; -static PyObject *PyRRD_xport( - PyObject UNUSED(*self), - PyObject * args) +static int +_rrdtool_fetch_cb_wrapper( + const char *filename, + enum cf_en cf_idx, + time_t *start, + time_t *end, + unsigned long *step, + unsigned long *ds_cnt, + char ***ds_namv, + rrd_value_t **data) { - PyObject *r; - int argc, status, xsize; - char **argv, **legend_v; - time_t start, end; - unsigned long step, col_cnt; - rrd_value_t *data, *datai; + PyObject *args; + PyObject *kwargs; + PyObject *ret = NULL; + PyObject *tmp; + PyObject *tmp_min_ts; + PyGILState_STATE gstate; + Py_ssize_t rowcount = 0; + int rc = -1; + unsigned int i, ii; + + gstate = PyGILState_Ensure(); + + if (_rrdtool_fetch_callable == NULL) { + rrd_set_error("use rrdtool.register_fetch_cb to register a fetch callback"); + goto gil_release_err; + } - if (create_args("xport", args, &argc, &argv) < 0) - return NULL; + args = PyTuple_New(0); + kwargs = PyDict_New(); - Py_BEGIN_ALLOW_THREADS - status = rrd_xport(argc, argv, &xsize, &start, &end, &step, &col_cnt, &legend_v, &data); - Py_END_ALLOW_THREADS + /* minimum possible UNIX datetime */ + tmp_min_ts = PyLong_FromLong(0); - if (status == -1) { - PyErr_SetString(ErrorObject, rrd_get_error()); - rrd_clear_error(); - r = NULL; + PyObject *po_filename = PyRRD_String_FromString(filename); + PyDict_SetItemString(kwargs, "filename", po_filename); + Py_DECREF(po_filename); + + PyObject *po_cfstr = PyRRD_String_FromString(PyRRD_String_FromCF(cf_idx)); + PyDict_SetItemString(kwargs, "cf", po_cfstr); + Py_DECREF(po_cfstr); + + PyObject *po_start = PyLong_FromLong(*start); + PyDict_SetItemString(kwargs, "start", po_start); + Py_DECREF(po_start); + + PyObject *po_end = PyLong_FromLong(*end); + PyDict_SetItemString(kwargs, "end", po_end); + Py_DECREF(po_end); + + PyObject *po_step = PyLong_FromUnsignedLong(*step); + PyDict_SetItemString(kwargs, "step", po_step); + Py_DECREF(po_step); + + /* execute Python callback method */ + ret = PyObject_Call(_rrdtool_fetch_callable, args, kwargs); + Py_DECREF(args); + Py_DECREF(kwargs); + + if (ret == NULL) { + rrd_set_error("calling python callback failed"); + goto gil_release_err; + } + + /* handle return value of callback */ + if (!PyDict_Check(ret)) { + rrd_set_error("expected callback method to be a dict"); + goto gil_release_err; + } + + tmp = PyDict_GetItemString(ret, "step"); + if (tmp == NULL) { + rrd_set_error("expected 'step' key in callback return value"); + goto gil_release_err; + } else if (!PyRRD_Long_Check(tmp)) { + rrd_set_error("the 'step' key in callback return value must be int"); + goto gil_release_err; + } else + *step = PyLong_AsLong(tmp); + + tmp = PyDict_GetItemString(ret, "start"); + if (tmp == NULL) { + rrd_set_error("expected 'start' key in callback return value"); + goto gil_release_err; + } else if (!PyRRD_Long_Check(tmp)) { + rrd_set_error("expected 'start' key in callback return value to be " + "of type int"); + goto gil_release_err; + } else if (PyObject_RichCompareBool(tmp, tmp_min_ts, Py_EQ) || + PyObject_RichCompareBool(tmp, po_start, Py_LT)) { + rrd_set_error("expected 'start' value in callback return dict to be " + "equal or earlier than passed start timestamp"); + goto gil_release_err; } else { - PyObject *meta_dict, *data_list, *legend_list, *t; - unsigned long i, j; - rrd_value_t dv; + *start = PyLong_AsLong(po_start); - unsigned long row_cnt = ((end - start) / step); + if (*start == -1) { + rrd_set_error("expected 'start' value in callback return value to" + " not exceed LONG_MAX"); + goto gil_release_err; + } + } - r = PyDict_New(); - meta_dict = PyDict_New(); - legend_list = PyList_New(col_cnt); - data_list = PyList_New(row_cnt); - PyDict_SetItem(r, PyString_FromString("meta"), meta_dict); - PyDict_SetItem(r, PyString_FromString("data"), data_list); + tmp = PyDict_GetItemString(ret, "data"); + if (tmp == NULL) { + rrd_set_error("expected 'data' key in callback return value"); + goto gil_release_err; + } else if (!PyDict_Check(tmp)) { + rrd_set_error("expected 'data' key in callback return value of type " + "dict"); + goto gil_release_err; + } else { + *ds_cnt = (unsigned long)PyDict_Size(tmp); + *ds_namv = (char **)calloc(*ds_cnt, sizeof(char *)); - datai = data; + if (*ds_namv == NULL) { + rrd_set_error("an error occured while allocating memory for " + "ds_namv when allocating memory for python callback"); + goto gil_release_err; + } + + PyObject *key, *value; + Py_ssize_t pos = 0; /* don't use pos for indexing */ + unsigned int x = 0; + + while (PyDict_Next(tmp, &pos, &key, &value)) { + char *key_str = PyRRD_String_AS_STRING(key); + + if (key_str == NULL) { + rrd_set_error("key of 'data' element from callback return " + "value is not a string"); + goto gil_release_free_dsnamv_err; + } else if (strlen(key_str) > DS_NAM_SIZE) { + rrd_set_error("key '%s' longer than the allowed maximum of %d " + "byte", key_str, DS_NAM_SIZE - 1); + goto gil_release_free_dsnamv_err; + } + + if ((((*ds_namv)[x]) = (char *)malloc(sizeof(char) * DS_NAM_SIZE)) == NULL) { + rrd_set_error("malloc fetch ds_namv entry"); + goto gil_release_free_dsnamv_err; + } + + strncpy((*ds_namv)[x], key_str, DS_NAM_SIZE - 1); + (*ds_namv)[x][DS_NAM_SIZE - 1] = '\0'; - PyDict_SetItem(meta_dict, PyString_FromString("start"), PyInt_FromLong((long) start)); - PyDict_SetItem(meta_dict, PyString_FromString("end"), PyInt_FromLong((long) end)); - PyDict_SetItem(meta_dict, PyString_FromString("step"), PyInt_FromLong((long) step)); - PyDict_SetItem(meta_dict, PyString_FromString("rows"), PyInt_FromLong((long) row_cnt)); - PyDict_SetItem(meta_dict, PyString_FromString("columns"), PyInt_FromLong((long) col_cnt)); - PyDict_SetItem(meta_dict, PyString_FromString("legend"), legend_list); + if (!PyList_Check(value)) { + rrd_set_error("expected 'data' dict values in callback return " + "value of type list"); + goto gil_release_free_dsnamv_err; + } else if (PyList_Size(value) > rowcount) + rowcount = PyList_Size(value); - for (i = 0; i < col_cnt; i++) { - PyList_SET_ITEM(legend_list, i, PyString_FromString(legend_v[i])); + ++x; } - for (i = 0; i < row_cnt; i++) { - t = PyTuple_New(col_cnt); - PyList_SET_ITEM(data_list, i, t); + *end = *start + *step * rowcount; - for (j = 0; j < col_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)); + if (((*data) = (rrd_value_t *)malloc(*ds_cnt * rowcount * sizeof(rrd_value_t))) == NULL) { + rrd_set_error("malloc fetch data area"); + goto gil_release_free_dsnamv_err; + } + + for (i = 0; i < *ds_cnt; i++) { + for (ii = 0; ii < (unsigned int)rowcount; ii++) { + char *ds_namv_i = (*ds_namv)[i]; + double va; + PyObject *lstv = PyList_GetItem(PyDict_GetItemString(tmp, ds_namv_i), ii); + + /* lstv may be NULL here in case an IndexError has been raised; + in such case the rowcount is higher than the number of elements for + the list of that ds. use DNAN as value for these then */ + if (lstv == NULL || lstv == Py_None) { + if (lstv == NULL) + PyErr_Clear(); + va = DNAN; + } + else { + va = PyFloat_AsDouble(lstv); + if (va == -1.0 && PyErr_Occurred()) { + PyObject *exc_type, *exc_value, *exc_value_str = NULL, *exc_tb; + PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + + if (exc_value != NULL) { + exc_value_str = PyObject_Str(exc_value); + char *exc_str = PyRRD_String_AS_STRING(exc_value_str); + rrd_set_error(exc_str); + Py_DECREF(exc_value); + } + + Py_DECREF(exc_type); + Py_DECREF(exc_value_str); + if (exc_tb != NULL) + Py_DECREF(exc_tb); + goto gil_release_free_dsnamv_err; + } } + + (*data)[i + ii * (*ds_cnt)] = va; } } + } - for (i = 0; i < col_cnt; i++) { - rrd_freemem(legend_v[i]); + /* success */ + rc = 1; + goto gil_release; + +gil_release_free_dsnamv_err: + for (i = 0; i < *ds_cnt; i++) { + if ((*ds_namv)[i]) { + free((*ds_namv)[i]); } - rrd_freemem(legend_v); - rrd_freemem(data); } - destroy_args(&argv); - return r; -} -#endif + free(*ds_namv); + +gil_release_err: + rc = -1; -static char PyRRD_dump__doc__[] = - "dump - dump an RRD to XML\n" - "[--header|-h {none,xsd,dtd}] [--no-header]file.rrd [file.xml]"; +gil_release: + if (ret != NULL) + Py_DECREF(ret); + PyGILState_Release(gstate); + return rc; +} -static PyObject *PyRRD_dump( - PyObject UNUSED(*self), - PyObject * args) +static char _rrdtool_register_fetch_cb__doc__[] = "Register callback for " + "fetching data"; + +static PyObject * +_rrdtool_register_fetch_cb(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r; - int argc, status; - char **argv; + PyObject *callable; - if (create_args("dump", args, &argc, &argv) < 0) + if (!PyArg_ParseTuple(args, "O", &callable)) + return NULL; + else if (!PyCallable_Check(callable)) { + PyErr_SetString(rrdtool_ProgrammingError, "first argument must be callable"); return NULL; + } else { + _rrdtool_fetch_callable = callable; + rrd_fetch_cb_register(_rrdtool_fetch_cb_wrapper); + Py_RETURN_NONE; + } +} - Py_BEGIN_ALLOW_THREADS - status = rrd_dump(argc, argv); - Py_END_ALLOW_THREADS +static char _rrdtool_clear_fetch_cb__doc__[] = "Clear callback for " + "fetching data"; - if (status != 0) { - PyErr_SetString(ErrorObject, rrd_get_error()); - rrd_clear_error(); - r = NULL; - } else { - Py_INCREF(Py_None); - r = Py_None; +static PyObject * +_rrdtool_clear_fetch_cb(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + if (_rrdtool_fetch_callable == NULL) { + PyErr_SetString(rrdtool_ProgrammingError, "no callback set"); + return NULL; } - destroy_args(&argv); - return r; + _rrdtool_fetch_callable = NULL; + rrd_fetch_cb_register(NULL); + Py_RETURN_NONE; +} + +static char _rrdtool_lib_version__doc__[] = "Get the version this binding "\ + "was compiled against."; + +/** + * Returns a str object that contains the librrd version. + * + * @return librrd version (Python str object) + */ +static PyObject * +_rrdtool_lib_version(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + return PyRRD_String_FromString(rrd_strversion()); } -/* List of methods defined in the module */ -#define meth(name, func, doc) {name, (PyCFunction)func, METH_VARARGS, doc} - -static PyMethodDef _rrdtool_methods[] = { - meth("create", PyRRD_create, PyRRD_create__doc__), - meth("update", PyRRD_update, PyRRD_update__doc__), - meth("fetch", PyRRD_fetch, PyRRD_fetch__doc__), - meth("tune", PyRRD_tune, PyRRD_tune__doc__), - meth("first", PyRRD_first, PyRRD_first__doc__), - meth("last", PyRRD_last, PyRRD_last__doc__), - meth("resize", PyRRD_resize, PyRRD_resize__doc__), - meth("info", PyRRD_info, PyRRD_info__doc__), - meth("list", PyRRD_list, PyRRD_list__doc__), -#ifdef HAVE_RRD_GRAPH - meth("graph", PyRRD_graph, PyRRD_graph__doc__), - meth("graphv", PyRRD_graphv, PyRRD_graphv__doc__), - meth("xport", PyRRD_xport, PyRRD_xport__doc__), +/** Method table. */ +static PyMethodDef rrdtool_methods[] = { + {"create", (PyCFunction)_rrdtool_create, + METH_VARARGS, _rrdtool_create__doc__}, + {"dump", (PyCFunction)_rrdtool_dump, + METH_VARARGS, _rrdtool_dump__doc__}, + {"update", (PyCFunction)_rrdtool_update, + METH_VARARGS, _rrdtool_update__doc__}, + {"updatev", (PyCFunction)_rrdtool_updatev, + METH_VARARGS, _rrdtool_updatev__doc__}, + {"fetch", (PyCFunction)_rrdtool_fetch, + METH_VARARGS, _rrdtool_fetch__doc__}, + {"flushcached", (PyCFunction)_rrdtool_flushcached, + METH_VARARGS, _rrdtool_flushcached__doc__}, +#ifdef HAVE_RRD_GRAPH + {"graph", (PyCFunction)_rrdtool_graph, + METH_VARARGS, _rrdtool_graph__doc__}, + {"graphv", (PyCFunction)_rrdtool_graphv, + METH_VARARGS, _rrdtool_graphv__doc__}, + {"xport", (PyCFunction)_rrdtool_xport, + METH_VARARGS, _rrdtool_xport__doc__}, #endif - meth("updatev", PyRRD_updatev, PyRRD_updatev__doc__), - meth("flushcached", PyRRD_flushcached, PyRRD_flushcached__doc__), - meth("dump", PyRRD_dump, PyRRD_dump__doc__), + {"list", (PyCFunction)_rrdtool_list, + METH_VARARGS, _rrdtool_list__doc__}, + {"tune", (PyCFunction)_rrdtool_tune, + METH_VARARGS, _rrdtool_tune__doc__}, + {"first", (PyCFunction)_rrdtool_first, + METH_VARARGS, _rrdtool_first__doc__}, + {"last", (PyCFunction)_rrdtool_last, + METH_VARARGS, _rrdtool_last__doc__}, + {"resize", (PyCFunction)_rrdtool_resize, + METH_VARARGS, _rrdtool_resize__doc__}, + {"info", (PyCFunction)_rrdtool_info, + METH_VARARGS, _rrdtool_info__doc__}, + {"lastupdate", (PyCFunction)_rrdtool_lastupdate, + METH_VARARGS, _rrdtool_lastupdate__doc__}, + {"register_fetch_cb", (PyCFunction)_rrdtool_register_fetch_cb, + METH_VARARGS, _rrdtool_register_fetch_cb__doc__}, + {"clear_fetch_cb", (PyCFunction)_rrdtool_clear_fetch_cb, + METH_NOARGS, _rrdtool_clear_fetch_cb__doc__}, + {"lib_version", (PyCFunction)_rrdtool_lib_version, + METH_NOARGS, _rrdtool_lib_version__doc__}, {NULL, NULL, 0, NULL} }; -#define SET_INTCONSTANT(dict, value) \ - t = PyInt_FromLong((long)value); \ - PyDict_SetItemString(dict, #value, t); \ - Py_DECREF(t); -#define SET_STRCONSTANT(dict, value) \ - t = PyString_FromString(value); \ - PyDict_SetItemString(dict, #value, t); \ - Py_DECREF(t); - -/* Initialization function for the module */ -void initrrdtool( - void) +/** Library init function. */ +#ifdef HAVE_PY3K +static struct PyModuleDef rrdtoolmodule = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "rrdtool", + .m_doc = "Python bindings for rrdtool", + .m_size = -1, + .m_methods = rrdtool_methods +}; + +#endif + +#ifdef HAVE_PY3K +PyMODINIT_FUNC +PyInit_rrdtool(void) +#else +void +initrrdtool(void) +#endif { - PyObject *m, *d, *t; + PyObject *m; + + PyDateTime_IMPORT; /* initialize PyDateTime_ functions */ - /* Create the module and add the functions */ - m = Py_InitModule("rrdtool", _rrdtool_methods); +#ifdef HAVE_PY3K + m = PyModule_Create(&rrdtoolmodule); +#else + m = Py_InitModule3("rrdtool", + rrdtool_methods, + "Python bindings for rrdtool"); +#endif + + if (m == NULL) +#ifdef HAVE_PY3K + return NULL; +#else + return; +#endif - /* Add some symbolic constants to the module */ - d = PyModule_GetDict(m); + rrdtool_ProgrammingError = PyErr_NewException("rrdtool.ProgrammingError", + NULL, NULL); + Py_INCREF(rrdtool_ProgrammingError); + PyModule_AddObject(m, "ProgrammingError", rrdtool_ProgrammingError); - SET_STRCONSTANT(d, __version__); - ErrorObject = PyErr_NewException("rrdtool.error", NULL, NULL); - PyDict_SetItemString(d, "error", ErrorObject); + rrdtool_OperationalError = PyErr_NewException("rrdtool.OperationalError", + NULL, NULL); + Py_INCREF(rrdtool_OperationalError); + PyModule_AddObject(m, "OperationalError", rrdtool_OperationalError); + PyModule_AddStringConstant(m, "__version__", _version); - /* Check for errors */ - if (PyErr_Occurred()) - Py_FatalError("can't initialize the rrdtool module"); +#ifdef HAVE_PY3K + return m; +#endif } - -/* - * $Id: _rrdtoolmodule.c,v 1.14 2003/02/22 07:41:19 perky Exp $ - * ex: ts=8 sts=4 et - */ diff --git a/bindings/python/setup.py b/bindings/python/setup.py index b8fbad88..d98095bd 100644 --- a/bindings/python/setup.py +++ b/bindings/python/setup.py @@ -1,60 +1,52 @@ -#! /usr/bin/env python -# -# setup.py -# -# py-rrdtool distutil setup -# -# Author : Hye-Shik Chang -# Date : $Date: 2003/02/14 02:38:16 $ -# Created : 24 May 2002 -# -# $Revision: 1.7 $ -# -# ========================================================================== -# This file is part of py-rrdtool. -# -# py-rrdtool is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# py-rrdtool 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Foobar; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# +#!/usr/bin/env python +import os try: - # Attempt to build using Distribute, which also supports bdist_wheel - from setuptools import setup - from setuptools.extension import Extension + from setuptools import setup, Extension except ImportError: - from distutils.core import setup, Extension -import sys, os + sys.exit('The setup requires setuptools.') TOP_SRCDIR = os.environ.get('ABS_TOP_SRCDIR', '../..') TOP_BUILDDIR = os.environ.get('ABS_TOP_BUILDDIR', '../..') -setup(name = "py-rrdtool", - version = "0.2.2", - description = "Python Interface to RRDTool", - author = "Hye-Shik Chang", - author_email = "perky@fallin.lv", - license = "LGPL", - url = "http://oss.oetiker.ch/rrdtool", - #packages = ['rrdtool'], - ext_modules = [ - Extension( - "rrdtool", - ["rrdtoolmodule.c"], - libraries=['rrd'], - library_dirs=[ os.path.join(TOP_BUILDDIR, 'src', '.libs') ], - include_dirs=[ os.path.join(TOP_BUILDDIR, 'src'), - os.path.join(TOP_SRCDIR, 'src') ], - ) - ] -) +# package version +package_version = '0.1.10' + + +def main(): + module = Extension('rrdtool', + sources=['rrdtoolmodule.c'], + library_dirs=[os.path.join(TOP_BUILDDIR, 'src', '.libs')], + include_dirs=[os.path.join(TOP_BUILDDIR, 'src'), + os.path.join(TOP_SRCDIR, 'src')], + libraries=['rrd']) + + kwargs = dict( + name='rrdtool', + version=package_version, + description='Python bindings for rrdtool', + keywords=['rrdtool'], + author='Christian Kroeger, Hye-Shik Chang', + author_email='commx@commx.ws', + license='LGPL', + url='https://github.com/commx/python-rrdtool', + classifiers=['License :: OSI Approved', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Operating System :: MacOS', + 'Programming Language :: C', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + ], + ext_modules=[module], + test_suite='tests' + ) + + setup(**kwargs) + + +if __name__ == '__main__': + main() diff --git a/bindings/python/tests/__init__.py b/bindings/python/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bindings/python/tests/test_fetch_cb.py b/bindings/python/tests/test_fetch_cb.py new file mode 100644 index 00000000..184df88b --- /dev/null +++ b/bindings/python/tests/test_fetch_cb.py @@ -0,0 +1,78 @@ +import base64 +import math +import os +import rrdtool +import unittest +import sys + +PY3 = sys.version_info >= (3, 0) + + +class TestFetchCallback(unittest.TestCase): + if not PY3: + def assertRaisesRegex(self, *args, **kwargs): + return self.assertRaisesRegexp(*args, **kwargs) + + def setUp(self): + self.graphv_args = [ + '-', + '--title', 'Callback Demo', + '--start', '1424540800', + '--end', 'start+24h', + '--lower-limit=0', + '--interlaced', + '--imgformat', 'PNG', + '--width=450', + 'DEF:a=cb//extrainfo:a:AVERAGE', + 'DEF:b=cb//:b:AVERAGE', + 'DEF:c=cb//:c:AVERAGE', + 'LINE:a#00b6e4:a', + 'LINE:b#10b634:b', + 'LINE:c#503d14:c', + 'VDEF:av=a,AVERAGE', + 'PRINT:av:%8.6lf' + ] + + def test_callback_return_type(self): + """ + Test whether callback return type is checked correctly. + The callback must always return a dict. + """ + def my_callback(*args, **kwargs): + return None + + rrdtool.register_fetch_cb(my_callback) + + self.assertRaisesRegex( + rrdtool.OperationalError, + 'expected callback method to be a dict', + rrdtool.graphv, + self.graphv_args + ) + + def test_callback_args(self): + """ + Test whether all required arguments are passed in kwargs. + """ + def my_callback(*args, **kwargs): + required_args = ('filename', 'cf', 'start', 'end', 'step') + for k in required_args: + self.assertIn(k, kwargs) + + items = int((kwargs['end'] - kwargs['start']) / kwargs['step']) + return { + 'start': kwargs['start'], + 'step': 300, + 'data': { + 'a': [math.sin(x / 200) for x in range(0, items)], + 'b': [math.cos(x / 200) for x in range(10, items)], + 'c': [math.sin(x / 100) for x in range(100, items)], + } + } + + rrdtool.register_fetch_cb(my_callback) + rrdtool.graphv(*self.graphv_args) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/configure b/configure index 89056fbc..bf3683d6 100755 --- a/configure +++ b/configure @@ -22420,8 +22420,8 @@ $as_echo "$am_cv_python_pyexecdir" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for headers required to compile python extensions" >&5 $as_echo_n "checking for headers required to compile python extensions... " >&6; } -py_prefix=`$PYTHON -c "import sys; print sys.prefix"` -py_exec_prefix=`$PYTHON -c "import sys; print sys.exec_prefix"` +py_prefix=`$PYTHON -c "import sys; print(sys.prefix)"` +py_exec_prefix=`$PYTHON -c "import sys; print(sys.exec_prefix)"` PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}" if test "$py_prefix" != "$py_exec_prefix"; then PYTHON_INCLUDES="$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}" diff --git a/m4/acinclude.m4 b/m4/acinclude.m4 index 4b7197c1..e6beff99 100644 --- a/m4/acinclude.m4 +++ b/m4/acinclude.m4 @@ -426,8 +426,8 @@ AC_DEFUN([AM_CHECK_PYTHON_HEADERS], [AC_REQUIRE([AM_PATH_PYTHON]) AC_MSG_CHECKING(for headers required to compile python extensions) dnl deduce PYTHON_INCLUDES -py_prefix=`$PYTHON -c "import sys; print sys.prefix"` -py_exec_prefix=`$PYTHON -c "import sys; print sys.exec_prefix"` +py_prefix=`$PYTHON -c "import sys; print(sys.prefix)"` +py_exec_prefix=`$PYTHON -c "import sys; print(sys.exec_prefix)"` PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}" if test "$py_prefix" != "$py_exec_prefix"; then PYTHON_INCLUDES="$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}"