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