fix off by 1 error
[rrdtool.git] / bindings / python / rrdtoolmodule.c
1 /*
2  * rrdtoolmodule.c
3  *
4  * RRDTool Python binding
5  *
6  * Author  : Hye-Shik Chang <perky@fallin.lv>
7  * Date    : $Date: 2003/02/22 07:41:19 $
8  * Created : 23 May 2002
9  *
10  * $Revision: 1.14 $
11  *
12  *  ==========================================================================
13  *  This file is part of py-rrdtool.
14  *
15  *  py-rrdtool is free software; you can redistribute it and/or modify
16  *  it under the terms of the GNU Lesser General Public License as published
17  *  by the Free Software Foundation; either version 2 of the License, or
18  *  (at your option) any later version.
19  *
20  *  py-rrdtool is distributed in the hope that it will be useful,
21  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  *  GNU Lesser General Public License for more details.
24  *
25  *  You should have received a copy of the GNU Lesser General Public License
26  *  along with Foobar; if not, write to the Free Software
27  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
28  *
29  */
30
31 #ifdef UNUSED
32 #elif defined(__GNUC__)
33 # define UNUSED(x) x __attribute__((unused))
34 #elif defined(__LCLINT__)
35 # define UNUSED(x) /*@unused@*/ x
36 #else
37 # define UNUSED(x) x
38 #endif
39
40
41 #include "../../rrd_config.h"
42 static const char *__version__ = PACKAGE_VERSION;
43
44 #include "Python.h"
45 #include "../../src/rrd_tool.h"
46 //#include "rrd.h"
47 //#include "rrd_extra.h"
48
49 static PyObject *ErrorObject;
50 extern int optind;
51 extern int opterr;
52
53 /* forward declaration to keep compiler happy */
54 void      initrrdtool(
55     void);
56
57 static int create_args(
58     char *command,
59     PyObject * args,
60     int *argc,
61     char ***argv)
62 {
63     PyObject *o, *lo;
64     int       args_count,
65               argv_count,
66               element_count,
67               i, j;
68
69     args_count = PyTuple_Size(args);
70     element_count = 0;
71     for (i = 0; i < args_count; i++) {
72         o = PyTuple_GET_ITEM(args, i);
73         if (PyString_Check(o))
74             element_count++;
75         else if (PyList_CheckExact(o))
76                 element_count += PyList_Size(o);
77              else {
78                  PyErr_Format(PyExc_TypeError, "argument %d must be string or list of strings", i);
79                  return -1;
80              }
81     }
82    
83     *argv = PyMem_New(char *,
84                       element_count + 1);
85
86     if (*argv == NULL)
87         return -1;
88
89     argv_count = 0;
90     for (i = 0; i < args_count; i++) {
91         o = PyTuple_GET_ITEM(args, i);
92         if (PyString_Check(o)) {
93             argv_count++;
94             (*argv)[argv_count] = PyString_AS_STRING(o);
95         } else if (PyList_CheckExact(o))
96                    for (j = 0; j < PyList_Size(o); j++) {
97                        lo = PyList_GetItem(o, j);
98                        if (PyString_Check(lo)) {
99                            argv_count++;
100                            (*argv)[argv_count] = PyString_AS_STRING(lo);
101                        } else {
102                              PyMem_Del(*argv);
103                              PyErr_Format(PyExc_TypeError, "element %d in argument %d must be string", j, i);
104                              return -1;
105                        }
106                    }
107                else {
108                    PyMem_Del(*argv);
109                    PyErr_Format(PyExc_TypeError, "argument %d must be string or list of strings", i);
110                    return -1;
111                }
112     }
113
114     (*argv)[0] = command;
115     *argc = element_count + 1;
116
117     /* reset getopt state */
118     opterr = optind = 0;
119
120     return 0;
121 }
122
123 static void destroy_args(
124     char ***argv)
125 {
126     PyMem_Del(*argv);
127     *argv = NULL;
128 }
129
130 static char PyRRD_create__doc__[] =
131     "create(args..): Set up a new Round Robin Database\n\
132     create filename [--start|-b start time] \
133 [--step|-s step] [DS:ds-name:DST:heartbeat:min:max] \
134 [RRA:CF:xff:steps:rows]";
135
136 static PyObject *PyRRD_create(
137     PyObject UNUSED(*self),
138     PyObject * args)
139 {
140     PyObject *r;
141     char    **argv;
142     int       argc;
143
144     if (create_args("create", args, &argc, &argv) < 0)
145         return NULL;
146
147     if (rrd_create(argc, argv) == -1) {
148         PyErr_SetString(ErrorObject, rrd_get_error());
149         rrd_clear_error();
150         r = NULL;
151     } else {
152         Py_INCREF(Py_None);
153         r = Py_None;
154     }
155
156     destroy_args(&argv);
157     return r;
158 }
159
160 static char PyRRD_update__doc__[] =
161     "update(args..): Store a new set of values into the rrd\n"
162     "    update filename [--template|-t ds-name[:ds-name]...] "
163     "N|timestamp:value[:value...] [timestamp:value[:value...] ...]";
164
165 static PyObject *PyRRD_update(
166     PyObject UNUSED(*self),
167     PyObject * args)
168 {
169     PyObject *r;
170     char    **argv;
171     int       argc;
172
173     if (create_args("update", args, &argc, &argv) < 0)
174         return NULL;
175
176     if (rrd_update(argc, argv) == -1) {
177         PyErr_SetString(ErrorObject, rrd_get_error());
178         rrd_clear_error();
179         r = NULL;
180     } else {
181         Py_INCREF(Py_None);
182         r = Py_None;
183     }
184
185     destroy_args(&argv);
186     return r;
187 }
188
189 static char PyRRD_fetch__doc__[] =
190     "fetch(args..): fetch data from an rrd.\n"
191     "    fetch filename CF [--resolution|-r resolution] "
192     "[--start|-s start] [--end|-e end]";
193
194 static PyObject *PyRRD_fetch(
195     PyObject UNUSED(*self),
196     PyObject * args)
197 {
198     PyObject *r;
199     rrd_value_t *data, *datai;
200     unsigned long step, ds_cnt;
201     time_t    start, end;
202     int       argc;
203     char    **argv, **ds_namv;
204
205     if (create_args("fetch", args, &argc, &argv) < 0)
206         return NULL;
207
208     if (rrd_fetch(argc, argv, &start, &end, &step,
209                   &ds_cnt, &ds_namv, &data) == -1) {
210         PyErr_SetString(ErrorObject, rrd_get_error());
211         rrd_clear_error();
212         r = NULL;
213     } else {
214         /* Return :
215            ((start, end, step), (name1, name2, ...), [(data1, data2, ..), ...]) */
216         PyObject *range_tup, *dsnam_tup, *data_list, *t;
217         unsigned long i, j, row;
218         rrd_value_t dv;
219
220         row = (end - start) / step;
221
222         r = PyTuple_New(3);
223         range_tup = PyTuple_New(3);
224         dsnam_tup = PyTuple_New(ds_cnt);
225         data_list = PyList_New(row);
226         PyTuple_SET_ITEM(r, 0, range_tup);
227         PyTuple_SET_ITEM(r, 1, dsnam_tup);
228         PyTuple_SET_ITEM(r, 2, data_list);
229
230         datai = data;
231
232         PyTuple_SET_ITEM(range_tup, 0, PyInt_FromLong((long) start));
233         PyTuple_SET_ITEM(range_tup, 1, PyInt_FromLong((long) end));
234         PyTuple_SET_ITEM(range_tup, 2, PyInt_FromLong((long) step));
235
236         for (i = 0; i < ds_cnt; i++)
237             PyTuple_SET_ITEM(dsnam_tup, i, PyString_FromString(ds_namv[i]));
238
239         for (i = 0; i < row; i++) {
240             t = PyTuple_New(ds_cnt);
241             PyList_SET_ITEM(data_list, i, t);
242
243             for (j = 0; j < ds_cnt; j++) {
244                 dv = *(datai++);
245                 if (isnan(dv)) {
246                     PyTuple_SET_ITEM(t, j, Py_None);
247                     Py_INCREF(Py_None);
248                 } else {
249                     PyTuple_SET_ITEM(t, j, PyFloat_FromDouble((double) dv));
250                 }
251             }
252         }
253
254         for (i = 0; i < ds_cnt; i++)
255             rrd_freemem(ds_namv[i]);
256         rrd_freemem(ds_namv);   /* rrdtool don't use PyMem_Malloc :) */
257         rrd_freemem(data);
258     }
259
260     destroy_args(&argv);
261     return r;
262 }
263
264 static char PyRRD_graph__doc__[] =
265     "graph(args..): Create a graph based on data from one or several RRD\n"
266     "    graph filename [-s|--start seconds] "
267     "[-e|--end seconds] [-x|--x-grid x-axis grid and label] "
268     "[-y|--y-grid y-axis grid and label] [--alt-y-grid] [--alt-y-mrtg] "
269     "[--alt-autoscale] [--alt-autoscale-max] [--units-exponent] value "
270     "[-v|--vertical-label text] [-w|--width pixels] [-h|--height pixels] "
271     "[-i|--interlaced] "
272     "[-f|--imginfo formatstring] [-a|--imgformat GIF|PNG|GD] "
273     "[-B|--background value] [-O|--overlay value] "
274     "[-U|--unit value] [-z|--lazy] [-o|--logarithmic] "
275     "[-u|--upper-limit value] [-l|--lower-limit value] "
276     "[-g|--no-legend] [-r|--rigid] [--step value] "
277     "[-b|--base value] [-c|--color COLORTAG#rrggbb] "
278     "[-t|--title title] [DEF:vname=rrd:ds-name:CF] "
279     "[CDEF:vname=rpn-expression] [PRINT:vname:CF:format] "
280     "[GPRINT:vname:CF:format] [COMMENT:text] "
281     "[HRULE:value#rrggbb[:legend]] [VRULE:time#rrggbb[:legend]] "
282     "[LINE{1|2|3}:vname[#rrggbb[:legend]]] "
283     "[AREA:vname[#rrggbb[:legend]]] " "[STACK:vname[#rrggbb[:legend]]]";
284
285 static PyObject *PyRRD_graph(
286     PyObject UNUSED(*self),
287     PyObject * args)
288 {
289     PyObject *r;
290     char    **argv, **calcpr;
291     int       argc, xsize, ysize, i;
292     double    ymin, ymax;
293
294     if (create_args("graph", args, &argc, &argv) < 0)
295         return NULL;
296
297     if (rrd_graph(argc, argv, &calcpr, &xsize, &ysize, NULL, &ymin, &ymax) ==
298         -1) {
299         PyErr_SetString(ErrorObject, rrd_get_error());
300         rrd_clear_error();
301         r = NULL;
302     } else {
303         r = PyTuple_New(3);
304
305         PyTuple_SET_ITEM(r, 0, PyInt_FromLong((long) xsize));
306         PyTuple_SET_ITEM(r, 1, PyInt_FromLong((long) ysize));
307
308         if (calcpr) {
309             PyObject *e, *t;
310
311             e = PyList_New(0);
312             PyTuple_SET_ITEM(r, 2, e);
313
314             for (i = 0; calcpr[i]; i++) {
315                 t = PyString_FromString(calcpr[i]);
316                 PyList_Append(e, t);
317                 Py_DECREF(t);
318                 rrd_freemem(calcpr[i]);
319             }
320             rrd_freemem(calcpr);
321         } else {
322             Py_INCREF(Py_None);
323             PyTuple_SET_ITEM(r, 2, Py_None);
324         }
325     }
326
327     destroy_args(&argv);
328     return r;
329 }
330
331 static char PyRRD_tune__doc__[] =
332     "tune(args...): Modify some basic properties of a Round Robin Database\n"
333     "    tune filename [--heartbeat|-h ds-name:heartbeat] "
334     "[--minimum|-i ds-name:min] [--maximum|-a ds-name:max] "
335     "[--data-source-type|-d ds-name:DST] [--data-source-rename|-r old-name:new-name]";
336
337 static PyObject *PyRRD_tune(
338     PyObject UNUSED(*self),
339     PyObject * args)
340 {
341     PyObject *r;
342     char    **argv;
343     int       argc;
344
345     if (create_args("tune", args, &argc, &argv) < 0)
346         return NULL;
347
348     if (rrd_tune(argc, argv) == -1) {
349         PyErr_SetString(ErrorObject, rrd_get_error());
350         rrd_clear_error();
351         r = NULL;
352     } else {
353         Py_INCREF(Py_None);
354         r = Py_None;
355     }
356
357     destroy_args(&argv);
358     return r;
359 }
360
361 static char PyRRD_first__doc__[] =
362     "first(filename): Return the timestamp of the first data sample in an RRD";
363
364 static PyObject *PyRRD_first(
365     PyObject UNUSED(*self),
366     PyObject * args)
367 {
368     PyObject *r;
369     int       argc, ts;
370     char    **argv;
371
372     if (create_args("first", args, &argc, &argv) < 0)
373         return NULL;
374
375     if ((ts = rrd_first(argc, argv)) == -1) {
376         PyErr_SetString(ErrorObject, rrd_get_error());
377         rrd_clear_error();
378         r = NULL;
379     } else
380         r = PyInt_FromLong((long) ts);
381
382     destroy_args(&argv);
383     return r;
384 }
385
386 static char PyRRD_last__doc__[] =
387     "last(filename): Return the timestamp of the last data sample in an RRD";
388
389 static PyObject *PyRRD_last(
390     PyObject UNUSED(*self),
391     PyObject * args)
392 {
393     PyObject *r;
394     int       argc, ts;
395     char    **argv;
396
397     if (create_args("last", args, &argc, &argv) < 0)
398         return NULL;
399
400     if ((ts = rrd_last(argc, argv)) == -1) {
401         PyErr_SetString(ErrorObject, rrd_get_error());
402         rrd_clear_error();
403         r = NULL;
404     } else
405         r = PyInt_FromLong((long) ts);
406
407     destroy_args(&argv);
408     return r;
409 }
410
411 static char PyRRD_resize__doc__[] =
412     "resize(args...): alters the size of an RRA.\n"
413     "    resize filename rra-num GROW|SHRINK rows";
414
415 static PyObject *PyRRD_resize(
416     PyObject UNUSED(*self),
417     PyObject * args)
418 {
419     PyObject *r;
420     char    **argv;
421     int       argc, ts;
422
423     if (create_args("resize", args, &argc, &argv) < 0)
424         return NULL;
425
426     if ((ts = rrd_resize(argc, argv)) == -1) {
427         PyErr_SetString(ErrorObject, rrd_get_error());
428         rrd_clear_error();
429         r = NULL;
430     } else {
431         Py_INCREF(Py_None);
432         r = Py_None;
433     }
434
435     destroy_args(&argv);
436     return r;
437 }
438
439 static PyObject *PyDict_FromInfo(
440     rrd_info_t * data)
441 {
442     PyObject *r;
443
444     r = PyDict_New();
445     while (data) {
446         PyObject *val = NULL;
447
448         switch (data->type) {
449         case RD_I_VAL:
450             val = isnan(data->value.u_val)
451                 ? (Py_INCREF(Py_None), Py_None)
452                 : PyFloat_FromDouble(data->value.u_val);
453             break;
454         case RD_I_CNT:
455             val = PyLong_FromUnsignedLong(data->value.u_cnt);
456             break;
457         case RD_I_INT:
458             val = PyLong_FromLong(data->value.u_int);
459             break;
460         case RD_I_STR:
461             val = PyString_FromString(data->value.u_str);
462             break;
463         case RD_I_BLO:
464             val =
465                 PyString_FromStringAndSize((char *) data->value.u_blo.ptr,
466                                            data->value.u_blo.size);
467             break;
468         }
469         if (val) {
470             PyDict_SetItemString(r, data->key, val);
471             Py_DECREF(val);
472         }
473         data = data->next;
474     }
475     return r;
476 }
477
478 static char PyRRD_info__doc__[] =
479     "info(filename): extract header information from an rrd";
480
481 static PyObject *PyRRD_info(
482     PyObject UNUSED(*self),
483     PyObject * args)
484 {
485     PyObject *r;
486     int       argc;
487     char    **argv;
488     rrd_info_t *data;
489
490     if (create_args("info", args, &argc, &argv) < 0)
491         return NULL;
492
493     if ((data = rrd_info(argc, argv)) == NULL) {
494         PyErr_SetString(ErrorObject, rrd_get_error());
495         rrd_clear_error();
496         r = NULL;
497     } else {
498         r = PyDict_FromInfo(data);
499         rrd_info_free(data);
500     }
501
502     destroy_args(&argv);
503     return r;
504 }
505
506 static char PyRRD_graphv__doc__[] =
507     "graphv is called in the same manner as graph";
508
509 static PyObject *PyRRD_graphv(
510     PyObject UNUSED(*self),
511     PyObject * args)
512 {
513     PyObject *r;
514     int       argc;
515     char    **argv;
516     rrd_info_t *data;
517
518     if (create_args("graphv", args, &argc, &argv) < 0)
519         return NULL;
520
521     if ((data = rrd_graph_v(argc, argv)) == NULL) {
522         PyErr_SetString(ErrorObject, rrd_get_error());
523         rrd_clear_error();
524         r = NULL;
525     } else {
526         r = PyDict_FromInfo(data);
527         rrd_info_free(data);
528     }
529
530     destroy_args(&argv);
531     return r;
532 }
533
534 static char PyRRD_updatev__doc__[] =
535     "updatev is called in the same manner as update";
536
537 static PyObject *PyRRD_updatev(
538     PyObject UNUSED(*self),
539     PyObject * args)
540 {
541     PyObject *r;
542     int       argc;
543     char    **argv;
544     rrd_info_t *data;
545
546     if (create_args("updatev", args, &argc, &argv) < 0)
547         return NULL;
548
549     if ((data = rrd_update_v(argc, argv)) == NULL) {
550         PyErr_SetString(ErrorObject, rrd_get_error());
551         rrd_clear_error();
552         r = NULL;
553     } else {
554         r = PyDict_FromInfo(data);
555         rrd_info_free(data);
556     }
557
558     destroy_args(&argv);
559     return r;
560 }
561
562 static char PyRRD_flushcached__doc__[] =
563   "flush(args..): flush RRD files from memory\n"
564   "   flush [--daemon address] file [file ...]";
565
566 static PyObject *PyRRD_flushcached(
567     PyObject UNUSED(*self),
568     PyObject * args)
569 {
570     PyObject *r;
571     int       argc;
572     char    **argv;
573
574     if (create_args("flushcached", args, &argc, &argv) < 0)
575         return NULL;
576
577     if (rrd_flushcached(argc, argv) != 0) {
578         PyErr_SetString(ErrorObject, rrd_get_error());
579         rrd_clear_error();
580         r = NULL;
581     } else {
582         Py_INCREF(Py_None);
583         r = Py_None;
584     }
585
586     destroy_args(&argv);
587     return r;
588 }
589
590 static char PyRRD_xport__doc__[] =
591     "xport(args..): dictionary representation of data stored in RRDs\n"
592     "    [-s|--start seconds] [-e|--end seconds] [-m|--maxrows rows]"
593     "[--step value] [--daemon address] [DEF:vname=rrd:ds-name:CF]"
594     "[CDEF:vname=rpn-expression] [XPORT:vname[:legend]]";
595
596
597 static PyObject *PyRRD_xport(
598     PyObject UNUSED(*self),
599     PyObject * args)
600 {
601     PyObject *r;
602     int       argc, xsize;
603     char    **argv, **legend_v;
604     time_t    start, end;
605     unsigned long step, col_cnt;
606     rrd_value_t *data, *datai;
607
608     if (create_args("xport", args, &argc, &argv) < 0)
609         return NULL;
610
611     if (rrd_xport(argc, argv, &xsize, &start, &end,
612                   &step, &col_cnt, &legend_v, &data) == -1) {
613         PyErr_SetString(ErrorObject, rrd_get_error());
614         rrd_clear_error();
615         r = NULL;
616     } else {
617         PyObject *meta_dict, *data_list, *legend_list, *t;
618         unsigned long i, j;
619         rrd_value_t dv;
620
621         unsigned long row_cnt = ((end - start) / step) + 1;
622
623         r = PyDict_New();
624         meta_dict = PyDict_New();
625         legend_list = PyList_New(col_cnt);
626         data_list = PyList_New(row_cnt);
627         PyDict_SetItem(r, PyString_FromString("meta"), meta_dict);
628         PyDict_SetItem(r, PyString_FromString("data"), data_list);
629
630         datai = data;
631
632         PyDict_SetItem(meta_dict, PyString_FromString("start"), PyInt_FromLong((long) start));
633         PyDict_SetItem(meta_dict, PyString_FromString("end"), PyInt_FromLong((long) end));
634         PyDict_SetItem(meta_dict, PyString_FromString("step"), PyInt_FromLong((long) step));
635         PyDict_SetItem(meta_dict, PyString_FromString("rows"), PyInt_FromLong((long) row_cnt));
636         PyDict_SetItem(meta_dict, PyString_FromString("columns"), PyInt_FromLong((long) col_cnt));
637         PyDict_SetItem(meta_dict, PyString_FromString("legend"), legend_list);
638
639         for (i = 0; i < col_cnt; i++) {
640             PyList_SET_ITEM(legend_list, i, PyString_FromString(legend_v[i]));
641         }
642
643         for (i = 0; i < row_cnt; i++) {
644             t = PyTuple_New(col_cnt);
645             PyList_SET_ITEM(data_list, i, t);
646
647             for (j = 0; j < col_cnt; j++) {
648                 dv = *(datai++);
649                 if (isnan(dv)) {
650                     PyTuple_SET_ITEM(t, j, Py_None);
651                     Py_INCREF(Py_None);
652                 } else {
653                     PyTuple_SET_ITEM(t, j, PyFloat_FromDouble((double) dv));
654                 }
655             }
656         }
657
658         for (i = 0; i < col_cnt; i++) {
659             rrd_freemem(legend_v[i]);
660         }
661         rrd_freemem(legend_v);
662         rrd_freemem(data);
663     }
664     destroy_args(&argv);
665     return r;
666 }
667
668 /* List of methods defined in the module */
669 #define meth(name, func, doc) {name, (PyCFunction)func, METH_VARARGS, doc}
670
671 static PyMethodDef _rrdtool_methods[] = {
672     meth("create", PyRRD_create, PyRRD_create__doc__),
673     meth("update", PyRRD_update, PyRRD_update__doc__),
674     meth("fetch", PyRRD_fetch, PyRRD_fetch__doc__),
675     meth("graph", PyRRD_graph, PyRRD_graph__doc__),
676     meth("tune", PyRRD_tune, PyRRD_tune__doc__),
677     meth("first", PyRRD_first, PyRRD_first__doc__),
678     meth("last", PyRRD_last, PyRRD_last__doc__),
679     meth("resize", PyRRD_resize, PyRRD_resize__doc__),
680     meth("info", PyRRD_info, PyRRD_info__doc__),
681     meth("graphv", PyRRD_graphv, PyRRD_graphv__doc__),
682     meth("updatev", PyRRD_updatev, PyRRD_updatev__doc__),
683     meth("flushcached", PyRRD_flushcached, PyRRD_flushcached__doc__),
684     meth("xport", PyRRD_xport, PyRRD_xport__doc__),
685     {NULL, NULL, 0, NULL}
686 };
687
688 #define SET_INTCONSTANT(dict, value) \
689             t = PyInt_FromLong((long)value); \
690             PyDict_SetItemString(dict, #value, t); \
691             Py_DECREF(t);
692 #define SET_STRCONSTANT(dict, value) \
693             t = PyString_FromString(value); \
694             PyDict_SetItemString(dict, #value, t); \
695             Py_DECREF(t);
696
697 /* Initialization function for the module */
698 void initrrdtool(
699     void)
700 {
701     PyObject *m, *d, *t;
702
703     /* Create the module and add the functions */
704     m = Py_InitModule("rrdtool", _rrdtool_methods);
705
706     /* Add some symbolic constants to the module */
707     d = PyModule_GetDict(m);
708
709     SET_STRCONSTANT(d, __version__);
710     ErrorObject = PyErr_NewException("rrdtool.error", NULL, NULL);
711     PyDict_SetItemString(d, "error", ErrorObject);
712
713     /* Check for errors */
714     if (PyErr_Occurred())
715         Py_FatalError("can't initialize the rrdtool module");
716 }
717
718 /*
719  * $Id: _rrdtoolmodule.c,v 1.14 2003/02/22 07:41:19 perky Exp $
720  * ex: ts=8 sts=4 et
721  */