1 /*
2  * python-rrdtool, Python bindings for rrdtool.
3  * Based on the rrdtool Python bindings for Python 2 from
4  * Hye-Shik Chang <perky@fallin.lv>.
5  *
6  * Copyright 2012 Christian Jurk <commx@commx.ws>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as
10  * published by the Free Software Foundation; either version 2.1 of the
11  * License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21  * MA 02110-1301, USA.
22  *
23  */
24 
25 #include <Python.h>
26 #include <datetime.h>
27 #include "rrd_config.h"
28 #include "rrd_tool.h"
29 
30 /* Some macros to maintain compatibility between Python 2.x and 3.x */
31 #if PY_MAJOR_VERSION >= 3
32 #define HAVE_PY3K
33 #define PyRRD_String_Check(x)                 PyUnicode_Check(x)
34 #define PyRRD_String_FromString(x)            PyUnicode_FromString(x)
35 #define PyRRD_String_AS_STRING(x)             PyUnicode_AsUTF8(x)
36 #define PyRRD_String_FromStringAndSize(x, y)  PyBytes_FromStringAndSize(x, y)
37 #define PyRRD_String_Size(x)                  PyUnicode_Size(x)
38 #define PyRRD_Int_FromLong(x)                 PyLong_FromLong(x)
39 #define PyRRD_Int_FromString(x, y, z)         PyLong_FromString(x,y,z)
40 #define PyRRD_Long_Check(x)                   PyLong_Check(x)
41 #else
42 #define PyRRD_String_Check(x)                 PyString_Check(x)
43 #define PyRRD_String_FromString(x)            PyString_FromString(x)
44 #define PyRRD_String_AS_STRING(x)             PyString_AS_STRING(x)
45 #define PyRRD_String_FromStringAndSize(x, y)  PyString_FromStringAndSize(x, y)
46 #define PyRRD_String_Size(x)                  PyString_Size(x)
47 #define PyRRD_Int_FromLong(x)                 PyInt_FromLong(x)
48 #define PyRRD_Int_FromString(x, y, z)         PyInt_FromString(x,y,z)
49 #define PyRRD_Long_Check(x)                   (PyInt_Check(x) || PyLong_Check(x))
50 #endif
51 
52 #ifndef Py_UNUSED
53 #ifdef __GNUC__
54  #define Py_UNUSED(name) _unused_ ## name __attribute__((unused))
55 #else
56  #define Py_UNUSED(name) _unused_ ## -name
57 #endif
58 #endif
59 
60 /** Binding version. */
61 static const char *_version = "0.1.10";
62 
63 /** Exception types. */
64 static PyObject *rrdtool_OperationalError;
65 static PyObject *rrdtool_ProgrammingError;
66 
67 /**
68  * PyRRD_DateTime_FromTS: convert UNIX timestamp (time_t)
69  * to Python datetime object.
70  *
71  * @param ts UNIX timestamp (time_t)
72  * @return Pointer to new PyObject (New Reference)
73  */
74 static PyObject *
PyRRD_DateTime_FromTS(time_t ts)75 PyRRD_DateTime_FromTS(time_t ts)
76 {
77     PyObject *ret;
78     struct tm lt;
79 
80     localtime_r(&ts, &lt);
81 
82     ret = PyDateTime_FromDateAndTime(
83         lt.tm_year + 1900,
84         lt.tm_mon + 1,
85         lt.tm_mday,
86         lt.tm_hour,
87         lt.tm_min,
88         lt.tm_sec,
89         0);
90 
91     return ret;
92 }
93 
94 /**
95  * PyRRD_String_FromCF: get string representation of CF enum index
96  *
97  * @param cf enum cf_en
98  * @return Null-terminated string
99  */
100 const char *
PyRRD_String_FromCF(enum cf_en cf)101 PyRRD_String_FromCF(enum cf_en cf)
102 {
103     switch (cf) {
104         case CF_AVERAGE:
105             return "AVERAGE";
106         case CF_MINIMUM:
107             return "MIN";
108         case CF_MAXIMUM:
109             return "MAX";
110         case CF_LAST:
111             return "LAST";
112         default:
113             return "INVALID";
114     }
115 }
116 
117 /**
118  * Helper function to convert Python objects into a representation that the
119  * rrdtool functions can work with.
120  *
121  * @param command RRDtool command name
122  * @param args Command arguments
123  * @return Zero if the function succeeds, otherwise -1
124  */
125 static int
convert_args(char * command,PyObject * args,char *** rrdtool_argv,int * rrdtool_argc)126 convert_args(char *command, PyObject *args, char ***rrdtool_argv, int *rrdtool_argc)
127 {
128     PyObject *o, *lo;
129     int i, j, args_count, argv_count, element_count;
130 
131     argv_count = element_count = 0;
132     args_count = PyTuple_Size(args);
133 
134     for (i = 0; i < args_count; i++) {
135         o = PyTuple_GET_ITEM(args, i);
136 
137         if (PyRRD_String_Check(o))
138             element_count++;
139         else if (PyList_CheckExact(o))
140             element_count += PyList_Size(o);
141         else {
142             PyErr_Format(PyExc_TypeError,
143                          "Argument %d must be str or a list of str", i);
144             return -1;
145         }
146     }
147 
148     *rrdtool_argv = PyMem_New(char *, element_count + 1);
149 
150     if (*rrdtool_argv == NULL)
151         return -1;
152 
153     for (i = 0; i < args_count; i++) {
154         o = PyTuple_GET_ITEM(args, i);
155 
156         if (PyRRD_String_Check(o))
157             (*rrdtool_argv)[++argv_count] = PyRRD_String_AS_STRING(o);
158         else if (PyList_CheckExact(o)) {
159             for (j = 0; j < PyList_Size(o); j++) {
160                 lo = PyList_GetItem(o, j);
161 
162                 if (PyRRD_String_Check(lo))
163                     (*rrdtool_argv)[++argv_count] = PyRRD_String_AS_STRING(lo);
164                 else {
165                     PyMem_Del(*rrdtool_argv);
166                     PyErr_Format(PyExc_TypeError,
167                       "Element %d in argument %d must be str", j, i);
168                     return -1;
169                 }
170             }
171         } else {
172             PyMem_Del(*rrdtool_argv);
173             PyErr_Format(rrdtool_ProgrammingError,
174               "Argument %d must be str or list of str", i);
175             return -1;
176         }
177     }
178 
179     (*rrdtool_argv)[0] = command;
180     *rrdtool_argc = element_count + 1;
181 
182     return 0;
183 }
184 
185 /**
186  * Destroy argument vector.
187  */
188 static void
destroy_args(char *** rrdtool_argv)189 destroy_args(char ***rrdtool_argv)
190 {
191     PyMem_Del(*rrdtool_argv);
192     *rrdtool_argv = NULL;
193 }
194 
195 /**
196  * Convert RRDtool info to dict.
197  *
198  * @param data RRDtool info object
199  * @return Python dict object
200  */
201 static PyObject *
_rrdtool_util_info2dict(const rrd_info_t * data)202 _rrdtool_util_info2dict(const rrd_info_t *data)
203 {
204     PyObject *dict, *val;
205 
206     dict = PyDict_New();
207 
208     while (data) {
209         val = NULL;
210 
211         switch (data->type) {
212             case RD_I_VAL:
213                 if (isnan(data->value.u_val)) {
214                     Py_INCREF(Py_None);
215                     val = Py_None;
216                 } else
217                     PyFloat_FromDouble(data->value.u_val);
218                 break;
219 
220             case RD_I_CNT:
221                 val = PyLong_FromUnsignedLong(data->value.u_cnt);
222                 break;
223 
224             case RD_I_INT:
225                 val = PyLong_FromLong(data->value.u_int);
226                 break;
227 
228             case RD_I_STR:
229                 val = PyRRD_String_FromString(data->value.u_str);
230                 break;
231 
232             case RD_I_BLO:
233                 val = PyRRD_String_FromStringAndSize(
234                     (char *)data->value.u_blo.ptr,
235                     data->value.u_blo.size);
236                 break;
237             default:
238                 break;
239         }
240 
241         if (val != NULL) {
242             PyDict_SetItemString(dict, data->key, val);
243             Py_DECREF(val);
244         }
245 
246         data = data->next;
247     }
248 
249     return dict;
250 }
251 
252 static char _rrdtool_create__doc__[] = "Create a new Round Robin Database.\n\n\
253   Usage: create(args...)\n\
254   Arguments:\n\n\
255     filename\n\
256     [-b|--start start time]\n\
257     [-s|--step step]\n\
258     [-t|--template template-file]\n\
259     [-r|--source source-file]\n\
260     [-O|--no-overwrite]\n\
261     [-d|--daemon address]\n\
262     [DS:ds-name[=mapped-ds-name[source-index]]:DST:heartbeat:min:max]\n\
263     [RRA:CF:xff:steps:rows]\n\n\
264   Full documentation can be found at:\n\
265   https://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html";
266 
267 static PyObject *
_rrdtool_create(PyObject * Py_UNUSED (self),PyObject * args)268 _rrdtool_create(PyObject *Py_UNUSED(self), PyObject *args)
269 {
270     char **rrdtool_argv = NULL;
271     int    rrdtool_argc = 0;
272     PyObject *ret;
273     int status;
274 
275     if (convert_args("create", args, &rrdtool_argv, &rrdtool_argc) == -1)
276         return NULL;
277 
278     Py_BEGIN_ALLOW_THREADS
279     status = rrd_create(rrdtool_argc, rrdtool_argv);
280     Py_END_ALLOW_THREADS
281 
282     if (status == -1) {
283         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
284         rrd_clear_error();
285         ret = NULL;
286     } else {
287         Py_INCREF(Py_None);
288         ret = Py_None;
289     }
290 
291     destroy_args(&rrdtool_argv);
292     return ret;
293 }
294 
295 static char _rrdtool_dump__doc__[] = "Dump an RRD to XML.\n\n\
296   Usage: dump(args..)\n\
297   Arguments:\n\n\
298     [-h|--header {none,xsd,dtd}\n\
299     [-n|--no-header]\n\
300     [-d|--daemon address]\n\
301     file.rrd\n\
302     [file.xml]\n\n\
303   Full documentation can be found at:\n\
304   https://oss.oetiker.ch/rrdtool/doc/rrddump.en.html";
305 
306 static PyObject *
_rrdtool_dump(PyObject * Py_UNUSED (self),PyObject * args)307 _rrdtool_dump(PyObject *Py_UNUSED(self), PyObject *args)
308 {
309     char **rrdtool_argv = NULL;
310     int    rrdtool_argc = 0;
311     PyObject *ret;
312     int status;
313 
314     if (convert_args("dump", args, &rrdtool_argv, &rrdtool_argc) == -1)
315         return NULL;
316 
317     Py_BEGIN_ALLOW_THREADS
318     status = rrd_dump(rrdtool_argc, rrdtool_argv);
319     Py_END_ALLOW_THREADS
320 
321     if (status != 0) {
322         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
323         rrd_clear_error();
324         ret = NULL;
325     } else {
326         Py_INCREF(Py_None);
327         ret = Py_None;
328     }
329 
330     destroy_args(&rrdtool_argv);
331     return ret;
332 }
333 
334 static char _rrdtool_update__doc__[] = "Store a new set of values into\
335  the RRD.\n\n\
336  Usage: update(args..)\n\
337  Arguments:\n\n\
338    filename\n\
339    [--template|-t ds-name[:ds-name]...]\n\
340    N|timestamp:value[:value...]\n\
341    [timestamp:value[:value...] ...]\n\n\
342   Full documentation can be found at:\n\
343   https://oss.oetiker.ch/rrdtool/doc/rrdupdate.en.html";
344 
345 static PyObject *
_rrdtool_update(PyObject * Py_UNUSED (self),PyObject * args)346 _rrdtool_update(PyObject *Py_UNUSED(self), PyObject *args)
347 {
348     char **rrdtool_argv = NULL;
349     int    rrdtool_argc = 0;
350     PyObject *ret;
351     int status;
352 
353     if (convert_args("update", args, &rrdtool_argv, &rrdtool_argc) == -1)
354         return NULL;
355 
356     Py_BEGIN_ALLOW_THREADS
357     status = rrd_update(rrdtool_argc, rrdtool_argv);
358     Py_END_ALLOW_THREADS
359 
360     if (status == -1) {
361         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
362         rrd_clear_error();
363         ret = NULL;
364     } else {
365         Py_INCREF(Py_None);
366         ret = Py_None;
367     }
368 
369     destroy_args(&rrdtool_argv);
370     return ret;
371 }
372 
373 static char _rrdtool_updatev__doc__[] = "Store a new set of values into "\
374   "the Round Robin Database and return an info dictionary.\n\n\
375   This function works in the same manner as 'update', but will return an\n\
376   info dictionary instead of None.";
377 
378 static PyObject *
_rrdtool_updatev(PyObject * Py_UNUSED (self),PyObject * args)379 _rrdtool_updatev(PyObject *Py_UNUSED(self), PyObject *args)
380 {
381     char **rrdtool_argv = NULL;
382     int    rrdtool_argc = 0;
383     PyObject *ret;
384     rrd_info_t *data;
385 
386     if (convert_args("updatev", args, &rrdtool_argv, &rrdtool_argc) == -1)
387         return NULL;
388 
389     Py_BEGIN_ALLOW_THREADS
390     data = rrd_update_v(rrdtool_argc, rrdtool_argv);
391     Py_END_ALLOW_THREADS
392 
393     if (data == NULL) {
394         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
395         rrd_clear_error();
396         ret = NULL;
397     } else {
398         ret = _rrdtool_util_info2dict(data);
399         rrd_info_free(data);
400     }
401 
402     destroy_args(&rrdtool_argv);
403     return ret;
404 }
405 
406 static char _rrdtool_fetch__doc__[] = "Fetch data from an RRD.\n\n\
407   Usage: fetch(args..)\n\
408   Arguments:\n\n\
409     filename\n\
410     CF\n\
411     [-r|--resolution resolution]\n\
412     [-s|--start start]\n\
413     [-e|--end end]\n\
414     [-a|--align-start]\n\
415     [-d|--daemon address]\n\n\
416   Full documentation can be found at:\n\
417   https://oss.oetiker.ch/rrdtool/doc/rrdfetch.en.html";
418 
419 static PyObject *
_rrdtool_fetch(PyObject * Py_UNUSED (self),PyObject * args)420 _rrdtool_fetch(PyObject *Py_UNUSED(self), PyObject *args)
421 {
422     char **rrdtool_argv = NULL;
423     int    rrdtool_argc = 0;
424     PyObject *ret, *range_tup, *dsnam_tup, *data_list, *t;
425     rrd_value_t *data, *datai, dv;
426     unsigned long step, ds_cnt, i, j, row;
427     time_t start, end;
428     char **ds_namv;
429     int status;
430 
431     if (convert_args("fetch", args, &rrdtool_argv, &rrdtool_argc) == -1)
432         return NULL;
433 
434     Py_BEGIN_ALLOW_THREADS
435     status = rrd_fetch(rrdtool_argc, rrdtool_argv, &start, &end, &step,
436                        &ds_cnt, &ds_namv, &data);
437     Py_END_ALLOW_THREADS
438 
439     if (status == -1) {
440         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
441         rrd_clear_error();
442         ret = NULL;
443     } else {
444         row = (end - start) / step;
445         ret = PyTuple_New(3);
446         range_tup = PyTuple_New(3);
447         dsnam_tup = PyTuple_New(ds_cnt);
448         data_list = PyList_New(row);
449 
450         PyTuple_SET_ITEM(ret, 0, range_tup);
451         PyTuple_SET_ITEM(ret, 1, dsnam_tup);
452         PyTuple_SET_ITEM(ret, 2, data_list);
453 
454         datai = data;
455 
456         PyTuple_SET_ITEM(range_tup, 0, PyRRD_Int_FromLong((long) start));
457         PyTuple_SET_ITEM(range_tup, 1, PyRRD_Int_FromLong((long) end));
458         PyTuple_SET_ITEM(range_tup, 2, PyRRD_Int_FromLong((long) step));
459 
460         for (i = 0; i < ds_cnt; i++)
461             PyTuple_SET_ITEM(dsnam_tup, i, PyRRD_String_FromString(ds_namv[i]));
462 
463         for (i = 0; i < row; i++) {
464             t = PyTuple_New(ds_cnt);
465             PyList_SET_ITEM(data_list, i, t);
466 
467             for (j = 0; j < ds_cnt; j++) {
468                 dv = *(datai++);
469                 if (isnan(dv)) {
470                     PyTuple_SET_ITEM(t, j, Py_None);
471                     Py_INCREF(Py_None);
472                 } else
473                     PyTuple_SET_ITEM(t, j, PyFloat_FromDouble((double) dv));
474             }
475         }
476 
477         for (i = 0; i < ds_cnt; i++)
478             rrd_freemem(ds_namv[i]);
479     }
480 
481     rrd_freemem(ds_namv);
482     rrd_freemem(data);
483     destroy_args(&rrdtool_argv);
484     return ret;
485 }
486 
487 static char _rrdtool_flushcached__doc__[] = "Flush RRD files from memory.\n\n\
488   Usage: flushcached(args..)\n\
489   Arguments:\n\n\
490     [-d|--daemon address]\n\
491     filename\n\
492     [filename ...]\n\n\
493   Full documentation can be found at:\n\
494   https://oss.oetiker.ch/rrdtool/doc/rrdflushcached.en.html";
495 
496 static PyObject *
_rrdtool_flushcached(PyObject * Py_UNUSED (self),PyObject * args)497 _rrdtool_flushcached(PyObject *Py_UNUSED(self), PyObject *args)
498 {
499     char **rrdtool_argv = NULL;
500     int    rrdtool_argc = 0;
501     PyObject *ret;
502     int status;
503 
504     if (convert_args("flushcached", args, &rrdtool_argv, &rrdtool_argc) == -1)
505         return NULL;
506 
507     Py_BEGIN_ALLOW_THREADS
508     status = rrd_flushcached(rrdtool_argc, rrdtool_argv);
509     Py_END_ALLOW_THREADS
510 
511     if (status != 0) {
512         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
513         rrd_clear_error();
514         ret = NULL;
515     } else {
516         Py_INCREF(Py_None);
517         ret = Py_None;
518     }
519 
520     destroy_args(&rrdtool_argv);
521     return ret;
522 }
523 
524 #ifdef HAVE_RRD_GRAPH
525 
526 static char _rrdtool_graph__doc__[] = "Create a graph based on one or more " \
527   "RRDs.\n\n\
528   Usage: graph(args..)\n\
529   Arguments:\n\n\
530     filename | -\n\
531     [-s|--start start]\n\
532     [-e|--end end]\n\
533     [-S|--step step]\n\
534     [-t|--title string]\n\
535     [-v|--vertical-label string]\n\
536     [-w|--width pixels]\n\
537     [-h|--height pixels]\n\
538     [-j|--only-graph]\n\
539     [-D|--full-size-mode]\n\
540     [-u|--upper-limit value]\n\
541     [-l|--lower-limit value]\n\
542     [-r|--rigid]\n\
543     [-A|--alt-autoscale]\n\
544     [-J|--alt-autoscale-min]\n\
545     [-M|--alt-autoscale-max]\n\
546     [-N|--no-gridfit]\n\
547     [-x|--x-grid (GTM:GST:MTM:MST:LTM:LST:LPR:LFM|none)]\n\
548     [-y|--y-grid (grid step:label factor|none)]\n\
549     [--week-fmt strftime format string]\n\
550     [--left-axis-formatter formatter-name]\n\
551     [--left-axis-format format-string]\n\
552     [-Y|--alt-y-grid]\n\
553     [-o|--logarithmic]\n\
554     [-X|--units-exponent value]\n\
555     [-L|--units-length value]\n\
556     [--units=si]\n\
557     [--right-axis scale:shift]\n\
558     [--right-axis-label label]\n\
559     [--right-axis-format format-string]\n\
560     [-g|--no-legend]\n\
561     [-F|--force-rules-legend]\n\
562     [--legend-position=(north|south|west|east)]\n\
563     [--legend-direction=(topdown|bottomup)]\n\
564     [-z|--lazy]\n\
565     [-d|--daemon address]\n\
566     [-f|--imginfo printfstr]\n\
567     [-c|--color COLORTAG#rrggbb[aa]]\n\
568     [--grid-dash on:off]\n\
569     [--border width]\n\
570     [--dynamic-labels]\n\
571     [-m|--zoom factor]\n\
572     [-n|--font FONTTAG:size:[font]]\n\
573     [-R|--font-render-mode {normal,light,mono}]\n\
574     [-B|--font-smoothing-threshold size]\n\
575     [-P|--pango-markup]\n\
576     [-G|--graph-render-mode {normal,mono}]\n\
577     [-E|--slope-mode]\n\
578     [-a|--imgformat {PNG,SVG,EPS,PDF,XML,XMLENUM,JSON,JSONTIME,CSV,TSV,SSV}]\n\
579     [-i|--interlaced]\n\
580     [-T|--tabwidth value]\n\
581     [-b|--base value]\n\
582     [-W|--watermark string]\n\
583     [-Z|--use-nan-for-all-missing-data]\n\
584     DEF:vname=rrdfile:ds-name:CF[:step=step][:start=time][:end=time]\n\
585     CDEF:vname=RPN expression\n\
586     VDEF=vname:RPN expression\n\n\
587   Full documentation can be found at:\n\
588   https://oss.oetiker.ch/rrdtool/doc/rrdgraph.en.html";
589 
590 static PyObject *
_rrdtool_graph(PyObject * Py_UNUSED (self),PyObject * args)591 _rrdtool_graph(PyObject *Py_UNUSED(self), PyObject *args)
592 {
593     char **rrdtool_argv = NULL;
594     int    rrdtool_argc = 0;
595     PyObject *ret;
596     int xsize, ysize, i, status;
597     double ymin, ymax;
598     char **calcpr = NULL;
599 
600     if (convert_args("graph", args, &rrdtool_argv, &rrdtool_argc) == -1)
601         return NULL;
602 
603     Py_BEGIN_ALLOW_THREADS
604     status = rrd_graph(rrdtool_argc, rrdtool_argv, &calcpr, &xsize, &ysize,
605                        NULL, &ymin, &ymax);
606     Py_END_ALLOW_THREADS
607 
608     if (status == -1) {
609         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
610         rrd_clear_error();
611         ret = NULL;
612     } else {
613         ret = PyTuple_New(3);
614 
615         PyTuple_SET_ITEM(ret, 0, PyRRD_Int_FromLong((long) xsize));
616         PyTuple_SET_ITEM(ret, 1, PyRRD_Int_FromLong((long) ysize));
617 
618         if (calcpr) {
619             PyObject *e, *t;
620 
621             e = PyList_New(0);
622             PyTuple_SET_ITEM(ret, 2, e);
623 
624             for (i = 0; calcpr[i]; i++) {
625                 t = PyRRD_String_FromString(calcpr[i]);
626                 PyList_Append(e, t);
627                 Py_DECREF(t);
628                 rrd_freemem(calcpr[i]);
629             }
630         } else {
631             Py_INCREF(Py_None);
632             PyTuple_SET_ITEM(ret, 2, Py_None);
633         }
634     }
635 
636     rrd_freemem(calcpr);
637     destroy_args(&rrdtool_argv);
638     return ret;
639 }
640 
641 static char _rrdtool_graphv__doc__[] = "Create a graph based on one or more " \
642   "RRDs and return data in RRDtool info format.\n\n\
643   This function works the same way as 'graph', but will return a info\n\
644   dictionary instead of None.\n\n\
645   Full documentation can be found at (graphv section):\n\
646   https://oss.oetiker.ch/rrdtool/doc/rrdgraph.en.html";
647 
648 static PyObject *
_rrdtool_graphv(PyObject * Py_UNUSED (self),PyObject * args)649 _rrdtool_graphv(PyObject *Py_UNUSED(self), PyObject *args)
650 {
651     char **rrdtool_argv = NULL;
652     int    rrdtool_argc = 0;
653     PyObject *ret;
654     rrd_info_t *data;
655 
656     if (convert_args("graphv", args, &rrdtool_argv, &rrdtool_argc) == -1)
657         return NULL;
658 
659     Py_BEGIN_ALLOW_THREADS
660     data = rrd_graph_v(rrdtool_argc, rrdtool_argv);
661     Py_END_ALLOW_THREADS
662 
663     if (data == NULL) {
664         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
665         rrd_clear_error();
666         ret = NULL;
667     } else {
668         ret = _rrdtool_util_info2dict(data);
669         rrd_info_free(data);
670     }
671 
672     destroy_args(&rrdtool_argv);
673     return ret;
674 }
675 
676 static char _rrdtool_xport__doc__[] = "Dictionary representation of data " \
677   "stored in RRDs.\n\n\
678   Usage: xport(args..)\n\
679   Arguments:\n\n\
680     [-s[--start seconds]\n\
681     [-e|--end seconds]\n\
682     [-m|--maxrows rows]\n\
683     [--step value]\n\
684     [--json]\n\
685     [--enumds]\n\
686     [--daemon address]\n\
687     [DEF:vname=rrd:ds-name:CF]\n\
688     [CDEF:vname=rpn-expression]\n\
689     [XPORT:vname[:legend]]\n\n\
690   Full documentation can be found at:\n\
691   https://oss.oetiker.ch/rrdtool/doc/rrdxport.en.html";
692 
693 static PyObject *
_rrdtool_xport(PyObject * Py_UNUSED (self),PyObject * args)694 _rrdtool_xport(PyObject *Py_UNUSED(self), PyObject *args)
695 {
696     char **rrdtool_argv = NULL;
697     int    rrdtool_argc = 0;
698     PyObject *ret;
699     int xsize, status;
700     char **legend_v;
701     time_t start, end;
702     unsigned long step, col_cnt;
703     rrd_value_t *data, *datai;
704 
705     if (convert_args("xport", args, &rrdtool_argv, &rrdtool_argc) == -1)
706         return NULL;
707 
708     Py_BEGIN_ALLOW_THREADS
709     status = rrd_xport(rrdtool_argc, rrdtool_argv, &xsize, &start, &end, &step,
710                        &col_cnt, &legend_v, &data);
711     Py_END_ALLOW_THREADS
712 
713     if (status == -1) {
714         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
715         rrd_clear_error();
716         ret = NULL;
717     } else {
718         PyObject *meta_dict, *data_list, *legend_list, *t;
719         rrd_value_t dv;
720         unsigned long i, j, row_cnt = (end - start) / step;
721 
722         ret = PyDict_New();
723         meta_dict = PyDict_New();
724         legend_list = PyList_New(col_cnt);
725         data_list = PyList_New(row_cnt);
726 
727         PyDict_SetItem(ret, PyRRD_String_FromString("meta"), meta_dict);
728         PyDict_SetItem(ret, PyRRD_String_FromString("data"), data_list);
729 
730         datai = data;
731 
732         PyDict_SetItem(meta_dict,
733             PyRRD_String_FromString("start"),
734             PyRRD_Int_FromLong((long) start));
735         PyDict_SetItem(meta_dict,
736             PyRRD_String_FromString("end"),
737             PyRRD_Int_FromLong((long) end));
738         PyDict_SetItem(meta_dict,
739             PyRRD_String_FromString("step"),
740             PyRRD_Int_FromLong((long) step));
741         PyDict_SetItem(meta_dict,
742             PyRRD_String_FromString("rows"),
743             PyRRD_Int_FromLong((long) row_cnt));
744         PyDict_SetItem(meta_dict,
745             PyRRD_String_FromString("columns"),
746             PyRRD_Int_FromLong((long) col_cnt));
747         PyDict_SetItem(meta_dict,
748             PyRRD_String_FromString("legend"),
749             legend_list);
750 
751         for (i = 0; i < col_cnt; i++)
752             PyList_SET_ITEM(legend_list, i, PyRRD_String_FromString(legend_v[i]));
753 
754         for (i = 0; i < row_cnt; i++) {
755             t = PyTuple_New(col_cnt);
756             PyList_SET_ITEM(data_list, i, t);
757 
758             for (j = 0; j < col_cnt; j++) {
759                 dv = *(datai++);
760 
761                 if (isnan(dv)) {
762                     PyTuple_SET_ITEM(t, j, Py_None);
763                     Py_INCREF(Py_None);
764                 } else {
765                     PyTuple_SET_ITEM(t, j, PyFloat_FromDouble((double) dv));
766                 }
767             }
768         }
769 
770         for (i = 0; i < col_cnt; i++)
771             rrd_freemem(legend_v[i]);
772 
773         rrd_freemem(legend_v);
774         rrd_freemem(data);
775     }
776 
777     destroy_args(&rrdtool_argv);
778 
779     return ret;
780 }
781 
782 #endif /* HAVE_RRD_GRAPH */
783 
784 static char _rrdtool_list__doc__[] = "List RRDs in storage.\n\n" \
785   "Usage: list(args..)\n\
786   Arguments:\n\n\
787     dirname\n\
788     [-r|--recursive]\n\
789     [-d|--daemon address]";
790 
791 static PyObject *
_rrdtool_list(PyObject * Py_UNUSED (self),PyObject * args)792 _rrdtool_list(PyObject *Py_UNUSED(self), PyObject *args)
793 {
794     char **rrdtool_argv = NULL;
795     int    rrdtool_argc = 0;
796     PyObject *ret, *str;
797     char *data, *ptr, *end;
798 
799     if (convert_args("list", args, &rrdtool_argv, &rrdtool_argc) == -1)
800         return NULL;
801 
802     Py_BEGIN_ALLOW_THREADS
803     data = rrd_list(rrdtool_argc, rrdtool_argv);
804     Py_END_ALLOW_THREADS
805 
806     if (data == NULL) {
807         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
808         rrd_clear_error();
809         ret = NULL;
810     } else {
811         ret = PyList_New(0);
812         ptr = data;
813         end = strchr(ptr, '\n');
814 
815         while (end) {
816             *end = '\0';
817             str = PyRRD_String_FromString(ptr);
818 
819             if (PyList_Append(ret, str)) {
820                 PyErr_SetString(rrdtool_OperationalError, "Failed to append to list");
821                 ret = NULL;
822                 break;
823             }
824 
825             ptr = end + 1;
826 
827             if (strlen(ptr) == 0)
828                 break;
829 
830             end = strchr(ptr, '\n');
831         }
832 
833         rrd_freemem(data);
834     }
835 
836     destroy_args(&rrdtool_argv);
837     return ret;
838 }
839 
840 static char _rrdtool_tune__doc__[] = "Modify some basic properties of a " \
841   "Round Robin Database.\n\n\
842   Usage: tune(args..)\n\
843   Arguments:\n\n\
844     filename\n\
845     [-h|--heartbeat ds-name:heartbeat]\n\
846     [-i|--minimum ds-name:min]\n\
847     [-a|--maximum ds-name:max]\n\
848     [-d|--data-source-type ds-name:DST]\n\
849     [-r|--data-source-rename old-name:new-name]\n\n\
850   Full documentation can be found at:\n\
851   https://oss.oetiker.ch/rrdtool/doc/rrdtune.en.html";
852 
853 static PyObject *
_rrdtool_tune(PyObject * Py_UNUSED (self),PyObject * args)854 _rrdtool_tune(PyObject *Py_UNUSED(self), PyObject *args)
855 {
856     char **rrdtool_argv = NULL;
857     int    rrdtool_argc = 0;
858     PyObject *ret;
859     int status;
860 
861     if (convert_args("tune", args, &rrdtool_argv, &rrdtool_argc) == -1)
862         return NULL;
863 
864     Py_BEGIN_ALLOW_THREADS
865     status = rrd_tune(rrdtool_argc, rrdtool_argv);
866     Py_END_ALLOW_THREADS
867 
868     if (status == -1) {
869         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
870         rrd_clear_error();
871         ret = NULL;
872     } else {
873         Py_INCREF(Py_None);
874         ret = Py_None;
875     }
876 
877     destroy_args(&rrdtool_argv);
878     return ret;
879 }
880 
881 static char _rrdtool_first__doc__[] = "Get the first UNIX timestamp of the "\
882   "first data sample in an Round Robin Database.\n\n\
883   Usage: first(args..)\n\
884   Arguments:\n\n\
885     filename\n\
886     [--rraindex number]\n\
887     [-d|--daemon address]\n\n\
888   Full documentation can be found at:\n\
889   https://oss.oetiker.ch/rrdtool/doc/rrdfirst.en.html";
890 
891 static PyObject *
_rrdtool_first(PyObject * Py_UNUSED (self),PyObject * args)892 _rrdtool_first(PyObject *Py_UNUSED(self), PyObject *args)
893 {
894     char **rrdtool_argv = NULL;
895     int    rrdtool_argc = 0;
896     PyObject *ret;
897     int ts;
898 
899     if (convert_args("first", args, &rrdtool_argv, &rrdtool_argc) == -1)
900         return NULL;
901 
902     Py_BEGIN_ALLOW_THREADS
903     ts = rrd_first(rrdtool_argc, rrdtool_argv);
904     Py_END_ALLOW_THREADS
905 
906     if (ts == -1) {
907         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
908         rrd_clear_error();
909         ret = NULL;
910     } else
911         ret = PyRRD_Int_FromLong((long) ts);
912 
913     destroy_args(&rrdtool_argv);
914     return ret;
915 }
916 
917 static char _rrdtool_last__doc__[] = "Get the UNIX timestamp of the most "\
918   "recent data sample in an Round Robin Database.\n\n\
919   Usage: last(args..)\n\
920   Arguments:\n\n\
921     filename\n\
922     [-d|--daemon address]\n\n\
923   Full documentation can be found at:\n\
924   https://oss.oetiker.ch/rrdtool/doc/rrdlast.en.html";
925 
926 static PyObject *
_rrdtool_last(PyObject * Py_UNUSED (self),PyObject * args)927 _rrdtool_last(PyObject *Py_UNUSED(self), PyObject *args)
928 {
929     char **rrdtool_argv = NULL;
930     int    rrdtool_argc = 0;
931     PyObject *ret;
932     int ts;
933 
934     if (convert_args("last", args, &rrdtool_argv, &rrdtool_argc) == -1)
935         return NULL;
936 
937     Py_BEGIN_ALLOW_THREADS
938     ts = rrd_last(rrdtool_argc, rrdtool_argv);
939     Py_END_ALLOW_THREADS
940 
941     if (ts == -1) {
942         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
943         rrd_clear_error();
944         ret = NULL;
945     } else
946         ret = PyRRD_Int_FromLong((long) ts);
947 
948     destroy_args(&rrdtool_argv);
949     return ret;
950 }
951 
952 static char _rrdtool_resize__doc__[] = "Modify the number of rows in a "\
953  "Round Robin Database.\n\n\
954   Usage: resize(args..)\n\
955   Arguments:\n\n\
956     filename\n\
957     rra-num\n\
958     GROW|SHRINK\n\
959     rows\n\n\
960   Full documentation can be found at:\n\
961   https://oss.oetiker.ch/rrdtool/doc/rrdlast.en.html";
962 
963 static PyObject *
_rrdtool_resize(PyObject * Py_UNUSED (self),PyObject * args)964 _rrdtool_resize(PyObject *Py_UNUSED(self), PyObject *args)
965 {
966     char **rrdtool_argv = NULL;
967     int    rrdtool_argc = 0;
968     PyObject *ret;
969     int status;
970 
971     if (convert_args("resize", args, &rrdtool_argv, &rrdtool_argc) == -1)
972         return NULL;
973 
974     Py_BEGIN_ALLOW_THREADS
975     status = rrd_resize(rrdtool_argc, rrdtool_argv);
976     Py_END_ALLOW_THREADS
977 
978     if (status == -1) {
979         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
980         rrd_clear_error();
981         ret = NULL;
982     } else {
983         Py_INCREF(Py_None);
984         ret = Py_None;
985     }
986 
987     destroy_args(&rrdtool_argv);
988     return ret;
989 }
990 
991 static char _rrdtool_info__doc__[] = "Extract header information from an "\
992  "Round Robin Database.\n\n\
993   Usage: info(filename, ...)\n\
994   Arguments:\n\n\
995     filename\n\
996     [-d|--daemon address]\n\
997     [-F|--noflush]\n\n\
998   Full documentation can be found at:\n\
999   https://oss.oetiker.ch/rrdtool/doc/rrdinfo.en.html";
1000 
1001 static PyObject *
_rrdtool_info(PyObject * Py_UNUSED (self),PyObject * args)1002 _rrdtool_info(PyObject *Py_UNUSED(self), PyObject *args)
1003 {
1004     char **rrdtool_argv = NULL;
1005     int    rrdtool_argc = 0;
1006     PyObject *ret;
1007     rrd_info_t *data;
1008 
1009     if (convert_args("info", args, &rrdtool_argv, &rrdtool_argc) == -1)
1010         return NULL;
1011 
1012     Py_BEGIN_ALLOW_THREADS
1013     data = rrd_info(rrdtool_argc, rrdtool_argv);
1014     Py_END_ALLOW_THREADS
1015 
1016     if (data == NULL) {
1017         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
1018         rrd_clear_error();
1019         ret = NULL;
1020     } else {
1021         ret = _rrdtool_util_info2dict(data);
1022         rrd_info_free(data);
1023     }
1024 
1025     destroy_args(&rrdtool_argv);
1026     return ret;
1027 }
1028 
1029 static char _rrdtool_lastupdate__doc__[] = "Returns datetime and value stored "\
1030  "for each datum in the most recent update of an RRD.\n\n\
1031   Usage: lastupdate(filename, ...)\n\
1032   Arguments:\n\n\
1033     filename\n\
1034     [-d|--daemon address]\n\n\
1035   Full documentation can be found at:\n\
1036   https://oss.oetiker.ch/rrdtool/doc/rrdlastupdate.en.html";
1037 
1038 static PyObject *
_rrdtool_lastupdate(PyObject * Py_UNUSED (self),PyObject * args)1039 _rrdtool_lastupdate(PyObject *Py_UNUSED(self), PyObject *args)
1040 {
1041     char **rrdtool_argv = NULL;
1042     int    rrdtool_argc = 0;
1043     PyObject *ret, *ds_dict, *lastupd;
1044     int status;
1045     time_t last_update;
1046     char **ds_names, **last_ds;
1047     unsigned long ds_cnt, i;
1048 
1049     if (convert_args("lastupdate", args, &rrdtool_argv, &rrdtool_argc) == -1)
1050         return NULL;
1051     else if (rrdtool_argc < 2) {
1052         PyErr_SetString(rrdtool_ProgrammingError, "Missing filename argument");
1053         return NULL;
1054     }
1055 
1056     Py_BEGIN_ALLOW_THREADS
1057     status = rrd_lastupdate_r(rrdtool_argv[1],
1058                               &last_update,
1059                               &ds_cnt,
1060                               &ds_names,
1061                               &last_ds);
1062     Py_END_ALLOW_THREADS
1063 
1064     if (status != 0) {
1065         PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
1066         rrd_clear_error();
1067         ret = NULL;
1068     } else {
1069         /* convert last_update to Python datetime object */
1070         ret = PyDict_New();
1071         ds_dict = PyDict_New();
1072         lastupd = PyRRD_DateTime_FromTS(last_update);
1073 
1074         PyDict_SetItemString(ret, "date", lastupd);
1075         PyDict_SetItemString(ret, "ds", ds_dict);
1076 
1077         Py_DECREF(lastupd);
1078         Py_DECREF(ds_dict);
1079 
1080         for (i = 0; i < ds_cnt; i++) {
1081             PyObject* val = Py_None;
1082 
1083             double num;
1084             if (sscanf(last_ds[i], "%lf", &num) == 1) {
1085                 val = PyFloat_FromDouble(num);
1086             }
1087 
1088             if (!val) {
1089             	free(last_ds[i]);
1090             	free(last_ds);
1091             	free(ds_names);
1092                 return NULL;
1093             }
1094 
1095             PyDict_SetItemString(ds_dict, ds_names[i], val);
1096 
1097             if (val != Py_None)
1098                 Py_DECREF(val);
1099 
1100             free(last_ds[i]);
1101             free(ds_names[i]);
1102         }
1103 
1104         free(last_ds);
1105         free(ds_names);
1106 
1107     }
1108 
1109     destroy_args(&rrdtool_argv);
1110 
1111     return ret;
1112 }
1113 
1114 
1115 /** An Python object which will hold an callable for fetch callbacks */
1116 static PyObject *_rrdtool_fetch_callable = NULL;
1117 
1118 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)1119 _rrdtool_fetch_cb_wrapper(
1120     const char *filename,
1121     enum cf_en cf_idx,
1122     time_t *start,
1123     time_t *end,
1124     unsigned long *step,
1125     unsigned long *ds_cnt,
1126     char ***ds_namv,
1127     rrd_value_t **data)
1128 {
1129     PyObject *args;
1130     PyObject *kwargs;
1131     PyObject *ret = NULL;
1132     PyObject *tmp;
1133     PyObject *tmp_min_ts;
1134     PyGILState_STATE gstate;
1135     Py_ssize_t rowcount = 0;
1136     int rc = -1;
1137     unsigned int i, ii;
1138 
1139     gstate = PyGILState_Ensure();
1140 
1141     if (_rrdtool_fetch_callable == NULL) {
1142         rrd_set_error("use rrdtool.register_fetch_cb to register a fetch callback");
1143         goto gil_release_err;
1144     }
1145 
1146     args = PyTuple_New(0);
1147     kwargs = PyDict_New();
1148 
1149     /* minimum possible UNIX datetime */
1150     tmp_min_ts = PyLong_FromLong(0);
1151 
1152     PyObject *po_filename = PyRRD_String_FromString(filename);
1153     PyDict_SetItemString(kwargs, "filename", po_filename);
1154     Py_DECREF(po_filename);
1155 
1156     PyObject *po_cfstr = PyRRD_String_FromString(PyRRD_String_FromCF(cf_idx));
1157     PyDict_SetItemString(kwargs, "cf", po_cfstr);
1158     Py_DECREF(po_cfstr);
1159 
1160     PyObject *po_start = PyLong_FromLong(*start);
1161     PyDict_SetItemString(kwargs, "start", po_start);
1162     Py_DECREF(po_start);
1163 
1164     PyObject *po_end = PyLong_FromLong(*end);
1165     PyDict_SetItemString(kwargs, "end", po_end);
1166     Py_DECREF(po_end);
1167 
1168     PyObject *po_step = PyLong_FromUnsignedLong(*step);
1169     PyDict_SetItemString(kwargs, "step", po_step);
1170     Py_DECREF(po_step);
1171 
1172     /* execute Python callback method */
1173     ret = PyObject_Call(_rrdtool_fetch_callable, args, kwargs);
1174     Py_DECREF(args);
1175     Py_DECREF(kwargs);
1176 
1177     if (ret == NULL) {
1178         rrd_set_error("calling python callback failed");
1179         goto gil_release_err;
1180     }
1181 
1182     /* handle return value of callback */
1183     if (!PyDict_Check(ret)) {
1184         rrd_set_error("expected callback method to be a dict");
1185         goto gil_release_err;
1186     }
1187 
1188     tmp = PyDict_GetItemString(ret, "step");
1189     if (tmp == NULL) {
1190         rrd_set_error("expected 'step' key in callback return value");
1191         goto gil_release_err;
1192     } else if (!PyRRD_Long_Check(tmp)) {
1193         rrd_set_error("the 'step' key in callback return value must be int");
1194         goto gil_release_err;
1195     } else
1196         *step = PyLong_AsLong(tmp);
1197 
1198     tmp = PyDict_GetItemString(ret, "start");
1199     if (tmp == NULL) {
1200         rrd_set_error("expected 'start' key in callback return value");
1201         goto gil_release_err;
1202     } else if (!PyRRD_Long_Check(tmp)) {
1203         rrd_set_error("expected 'start' key in callback return value to be "
1204             "of type int");
1205         goto gil_release_err;
1206     } else if (PyObject_RichCompareBool(tmp, tmp_min_ts, Py_EQ) ||
1207                PyObject_RichCompareBool(tmp, po_start, Py_LT)) {
1208         rrd_set_error("expected 'start' value in callback return dict to be "
1209             "equal or earlier than passed start timestamp");
1210         goto gil_release_err;
1211     } else {
1212         *start = PyLong_AsLong(po_start);
1213 
1214         if (*start == -1) {
1215             rrd_set_error("expected 'start' value in callback return value to"
1216                 " not exceed LONG_MAX");
1217             goto gil_release_err;
1218         }
1219     }
1220 
1221     tmp = PyDict_GetItemString(ret, "data");
1222     if (tmp == NULL) {
1223         rrd_set_error("expected 'data' key in callback return value");
1224         goto gil_release_err;
1225     } else if (!PyDict_Check(tmp)) {
1226         rrd_set_error("expected 'data' key in callback return value of type "
1227             "dict");
1228         goto gil_release_err;
1229     } else {
1230         *ds_cnt = (unsigned long)PyDict_Size(tmp);
1231         *ds_namv = (char **)calloc(*ds_cnt, sizeof(char *));
1232 
1233         if (*ds_namv == NULL) {
1234             rrd_set_error("an error occurred while allocating memory for "
1235                 "ds_namv when allocating memory for python callback");
1236             goto gil_release_err;
1237         }
1238 
1239         PyObject *key, *value;
1240         Py_ssize_t pos = 0;  /* don't use pos for indexing */
1241         unsigned int x = 0;
1242 
1243         while (PyDict_Next(tmp, &pos, &key, &value)) {
1244             char *key_str = PyRRD_String_AS_STRING(key);
1245 
1246             if (key_str == NULL) {
1247                 rrd_set_error("key of 'data' element from callback return "
1248                     "value is not a string");
1249                 goto gil_release_free_dsnamv_err;
1250             } else if (strlen(key_str) > DS_NAM_SIZE) {
1251                 rrd_set_error("key '%s' longer than the allowed maximum of %d "
1252                     "byte", key_str, DS_NAM_SIZE - 1);
1253                 goto gil_release_free_dsnamv_err;
1254             }
1255 
1256             if ((((*ds_namv)[x]) = (char *)malloc(sizeof(char) * DS_NAM_SIZE)) == NULL) {
1257                 rrd_set_error("malloc fetch ds_namv entry");
1258                 goto gil_release_free_dsnamv_err;
1259             }
1260 
1261             strncpy((*ds_namv)[x], key_str, DS_NAM_SIZE - 1);
1262             (*ds_namv)[x][DS_NAM_SIZE - 1] = '\0';
1263 
1264             if (!PyList_Check(value)) {
1265                 rrd_set_error("expected 'data' dict values in callback return "
1266                     "value of type list");
1267                 goto gil_release_free_dsnamv_err;
1268             } else if (PyList_Size(value) > rowcount)
1269                 rowcount = PyList_Size(value);
1270 
1271             ++x;
1272         }
1273 
1274         *end = *start + *step * rowcount;
1275 
1276         if (((*data) = (rrd_value_t *)malloc(*ds_cnt * rowcount * sizeof(rrd_value_t))) == NULL) {
1277             rrd_set_error("malloc fetch data area");
1278             goto gil_release_free_dsnamv_err;
1279         }
1280 
1281         for (i = 0; i < *ds_cnt; i++) {
1282             for (ii = 0; ii < (unsigned int)rowcount; ii++) {
1283                 char *ds_namv_i = (*ds_namv)[i];
1284                 double va;
1285                 PyObject *lstv = PyList_GetItem(PyDict_GetItemString(tmp, ds_namv_i), ii);
1286 
1287                 /* lstv may be NULL here in case an IndexError has been raised;
1288                    in such case the rowcount is higher than the number of elements for
1289                    the list of that ds. use DNAN as value for these then */
1290                 if (lstv == NULL || lstv == Py_None) {
1291                     if (lstv == NULL)
1292                         PyErr_Clear();
1293                     va = DNAN;
1294                 }
1295                 else {
1296                     va = PyFloat_AsDouble(lstv);
1297                     if (va == -1.0 && PyErr_Occurred()) {
1298                         PyObject *exc_type, *exc_value, *exc_value_str = NULL, *exc_tb;
1299                         PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
1300 
1301                         if (exc_value != NULL) {
1302                             exc_value_str = PyObject_Str(exc_value);
1303                             char *exc_str = PyRRD_String_AS_STRING(exc_value_str);
1304                             rrd_set_error(exc_str);
1305                             Py_DECREF(exc_value);
1306                         }
1307 
1308                         Py_DECREF(exc_type);
1309                         if (exc_value_str != NULL)
1310                             Py_DECREF(exc_value_str);
1311                         if (exc_tb != NULL)
1312                             Py_DECREF(exc_tb);
1313                         goto gil_release_free_dsnamv_err;
1314                     }
1315                 }
1316 
1317                 (*data)[i + ii * (*ds_cnt)] = va;
1318             }
1319         }
1320     }
1321 
1322     /* success */
1323     rc = 1;
1324     goto gil_release;
1325 
1326 gil_release_free_dsnamv_err:
1327     for (i = 0; i < *ds_cnt; i++) {
1328         if ((*ds_namv)[i]) {
1329             free((*ds_namv)[i]);
1330         }
1331     }
1332 
1333     free(*ds_namv);
1334 
1335 gil_release_err:
1336     rc = -1;
1337 
1338 gil_release:
1339     if (ret != NULL)
1340         Py_DECREF(ret);
1341     PyGILState_Release(gstate);
1342     return rc;
1343 }
1344 
1345 static char _rrdtool_register_fetch_cb__doc__[] = "Register callback for "
1346     "fetching data";
1347 
1348 static PyObject *
_rrdtool_register_fetch_cb(PyObject * Py_UNUSED (self),PyObject * args)1349 _rrdtool_register_fetch_cb(PyObject *Py_UNUSED(self), PyObject *args)
1350 {
1351     PyObject *callable;
1352 
1353     if (!PyArg_ParseTuple(args, "O", &callable))
1354         return NULL;
1355     else if (!PyCallable_Check(callable)) {
1356         PyErr_SetString(rrdtool_ProgrammingError, "first argument must be callable");
1357         return NULL;
1358     } else {
1359         _rrdtool_fetch_callable = callable;
1360         rrd_fetch_cb_register(_rrdtool_fetch_cb_wrapper);
1361         Py_RETURN_NONE;
1362     }
1363 }
1364 
1365 static char _rrdtool_clear_fetch_cb__doc__[] = "Clear callback for "
1366     "fetching data";
1367 
1368 static PyObject *
_rrdtool_clear_fetch_cb(PyObject * Py_UNUSED (self),PyObject * Py_UNUSED (args))1369 _rrdtool_clear_fetch_cb(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
1370 {
1371     if (_rrdtool_fetch_callable == NULL) {
1372         PyErr_SetString(rrdtool_ProgrammingError, "no callback set");
1373         return NULL;
1374     }
1375 
1376     _rrdtool_fetch_callable = NULL;
1377     rrd_fetch_cb_register(NULL);
1378     Py_RETURN_NONE;
1379 }
1380 
1381 static char _rrdtool_lib_version__doc__[] = "Get the version this binding "\
1382   "was compiled against.";
1383 
1384 /**
1385  * Returns a str object that contains the librrd version.
1386  *
1387  * @return librrd version (Python str object)
1388  */
1389 static PyObject *
_rrdtool_lib_version(PyObject * Py_UNUSED (self),PyObject * Py_UNUSED (args))1390 _rrdtool_lib_version(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
1391 {
1392     return PyRRD_String_FromString(rrd_strversion());
1393 }
1394 
1395 /** Method table. */
1396 static PyMethodDef rrdtool_methods[] = {
1397     {"create", (PyCFunction)_rrdtool_create,
1398      METH_VARARGS, _rrdtool_create__doc__},
1399     {"dump", (PyCFunction)_rrdtool_dump,
1400      METH_VARARGS, _rrdtool_dump__doc__},
1401     {"update", (PyCFunction)_rrdtool_update,
1402      METH_VARARGS, _rrdtool_update__doc__},
1403     {"updatev", (PyCFunction)_rrdtool_updatev,
1404      METH_VARARGS, _rrdtool_updatev__doc__},
1405     {"fetch", (PyCFunction)_rrdtool_fetch,
1406      METH_VARARGS, _rrdtool_fetch__doc__},
1407     {"flushcached", (PyCFunction)_rrdtool_flushcached,
1408      METH_VARARGS, _rrdtool_flushcached__doc__},
1409 #ifdef HAVE_RRD_GRAPH
1410     {"graph", (PyCFunction)_rrdtool_graph,
1411      METH_VARARGS, _rrdtool_graph__doc__},
1412     {"graphv", (PyCFunction)_rrdtool_graphv,
1413      METH_VARARGS, _rrdtool_graphv__doc__},
1414     {"xport", (PyCFunction)_rrdtool_xport,
1415      METH_VARARGS, _rrdtool_xport__doc__},
1416 #endif
1417     {"list", (PyCFunction)_rrdtool_list,
1418      METH_VARARGS, _rrdtool_list__doc__},
1419     {"tune", (PyCFunction)_rrdtool_tune,
1420      METH_VARARGS, _rrdtool_tune__doc__},
1421     {"first", (PyCFunction)_rrdtool_first,
1422      METH_VARARGS, _rrdtool_first__doc__},
1423     {"last", (PyCFunction)_rrdtool_last,
1424      METH_VARARGS, _rrdtool_last__doc__},
1425     {"resize", (PyCFunction)_rrdtool_resize,
1426      METH_VARARGS, _rrdtool_resize__doc__},
1427     {"info", (PyCFunction)_rrdtool_info,
1428      METH_VARARGS, _rrdtool_info__doc__},
1429     {"lastupdate", (PyCFunction)_rrdtool_lastupdate,
1430      METH_VARARGS, _rrdtool_lastupdate__doc__},
1431     {"register_fetch_cb", (PyCFunction)_rrdtool_register_fetch_cb,
1432      METH_VARARGS, _rrdtool_register_fetch_cb__doc__},
1433     {"clear_fetch_cb", (PyCFunction)_rrdtool_clear_fetch_cb,
1434      METH_NOARGS, _rrdtool_clear_fetch_cb__doc__},
1435     {"lib_version", (PyCFunction)_rrdtool_lib_version,
1436      METH_NOARGS, _rrdtool_lib_version__doc__},
1437     {NULL, NULL, 0, NULL}
1438 };
1439 
1440 /** Library init function. */
1441 #ifdef HAVE_PY3K
1442 static struct PyModuleDef rrdtoolmodule = {
1443     .m_base = PyModuleDef_HEAD_INIT,
1444     .m_name = "rrdtool",
1445     .m_doc = "Python bindings for rrdtool",
1446     .m_size = -1,
1447     .m_methods = rrdtool_methods
1448 };
1449 
1450 #endif
1451 
1452 #ifdef HAVE_PY3K
1453 PyMODINIT_FUNC
PyInit_rrdtool(void)1454 PyInit_rrdtool(void)
1455 #else
1456 void
1457 initrrdtool(void)
1458 #endif
1459 {
1460     PyObject *m;
1461 
1462     PyDateTime_IMPORT;  /* initialize PyDateTime_ functions */
1463 
1464 #ifdef HAVE_PY3K
1465     m = PyModule_Create(&rrdtoolmodule);
1466 #else
1467     m = Py_InitModule3("rrdtool",
1468                        rrdtool_methods,
1469                        "Python bindings for rrdtool");
1470 #endif
1471 
1472     if (m == NULL)
1473 #ifdef HAVE_PY3K
1474         return NULL;
1475 #else
1476         return;
1477 #endif
1478 
1479     rrdtool_ProgrammingError = PyErr_NewException("rrdtool.ProgrammingError",
1480                                                   NULL, NULL);
1481     Py_INCREF(rrdtool_ProgrammingError);
1482     PyModule_AddObject(m, "ProgrammingError", rrdtool_ProgrammingError);
1483 
1484     rrdtool_OperationalError = PyErr_NewException("rrdtool.OperationalError",
1485                                                   NULL, NULL);
1486     Py_INCREF(rrdtool_OperationalError);
1487     PyModule_AddObject(m, "OperationalError", rrdtool_OperationalError);
1488     PyModule_AddStringConstant(m, "__version__", _version);
1489 
1490 #ifdef HAVE_PY3K
1491     return m;
1492 #endif
1493 }
1494