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, <);
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