]> git.ipfire.org Git - people/ms/python-rrdtool.git/blame - rrdtool-py3k.c
* Added fetch and graph function.
[people/ms/python-rrdtool.git] / rrdtool-py3k.c
CommitLineData
ce6564c2 1/*
618986c4
CJ
2 * rrdtool-py3k, rrdtool bindings for Python 3.
3 * Based on the rrdtool Python bindings for Python 2 from
4 * Hye-Shik Chang <perky@fallin.lv>.
ce6564c2
CJ
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 General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (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 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 *
ce6564c2
CJ
23 */
24
25#include <Python.h>
26#include <rrd.h>
27
28/* Exception types */
29static PyObject *rrdtool_OperationalError;
30static PyObject *rrdtool_ProgrammingError;
31
618986c4
CJ
32static char **rrdtool_argv = NULL;
33static int rrdtool_argc = 0;
34
35static void
36destroy_args(void)
37{
38 PyMem_Del(rrdtool_argv);
39 rrdtool_argv = NULL;
40}
41
42/* Helper function to convert Python objects into a representation that the
43 * rrdtool functions can work with.
44 */
45static int
46convert_args(char *command, PyObject *args)
47{
48 PyObject *o, *lo;
49 int i, j, args_count, argv_count, element_count;
50
51 args_count = PyTuple_Size(args);
52 element_count = 0;
53 for (i = 0; i < args_count; i++)
54 {
55 o = PyTuple_GET_ITEM(args, i);
56 if (PyUnicode_Check(o) || PyBytes_Check(o))
57 element_count++;
58 else if (PyList_CheckExact(o))
59 element_count += PyList_Size(o);
60 else {
61 PyErr_Format(rrdtool_ProgrammingError,
62 "Argument %d must be string, bytes or list of " \
63 "string/bytes", i);
64 return -1;
65 }
66 }
67
68 rrdtool_argv = PyMem_New(char *, element_count + 1);
69
70 if (rrdtool_argv == NULL)
71 return -1;
72
73 argv_count = 0;
74 for (i = 0; i < args_count; i++) {
75 o = PyTuple_GET_ITEM(args, i);
76
77 if (PyUnicode_Check(o))
78 rrdtool_argv[++argv_count] = PyBytes_AsString(
79 PyUnicode_AsUTF8String(o));
80 else if (PyBytes_Check(o))
81 rrdtool_argv[++argv_count] = PyBytes_AS_STRING(o);
82 else if (PyList_CheckExact(o)) {
83 for (j = 0; j < PyList_Size(o); j++) {
84 lo = PyList_GetItem(o, j);
85 if (PyUnicode_Check(lo))
86 rrdtool_argv[++argv_count] = PyBytes_AS_STRING(
87 PyUnicode_AsUTF8String(lo));
88 else if (PyBytes_Check(lo))
89 rrdtool_argv[++argv_count] = PyBytes_AS_STRING(lo);
90 else {
91 PyMem_Del(rrdtool_argv);
92 PyErr_Format(rrdtool_ProgrammingError,
93 "Element %d in argument %d must be string", j, i);
94 return -1;
95 }
96 }
97 } else {
98 PyMem_Del(rrdtool_argv);
99 PyErr_Format(rrdtool_ProgrammingError,
100 "Argument %d must be string or list of strings", i);
101 return -1;
102 }
103 }
104
105 rrdtool_argv[0] = command;
106 rrdtool_argc = element_count + 1;
107
108 return 0;
109}
110
111static char _rrdtool_create__doc__[] = "Create a new Round Robin Database.\n\n\
112 Usage: create(args...)\n\
113 Arguments:\n\n\
114 filename\n\
115 [--start|-b start time]\n\
116 [--step|-s step]\n\
117 [DS:ds-name:DST:heartbeat:min:max]\n\
118 [RRA:CF:xff:steps:rows]\n\n\
119 Full documentation can be found at:\n\
120 http://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html";
121
ce6564c2
CJ
122static PyObject *
123_rrdtool_create(PyObject *self, PyObject *args)
124{
618986c4 125 PyObject *ret;
ce6564c2 126
618986c4
CJ
127 if (convert_args("create", args) == -1)
128 return NULL;
ce6564c2 129
618986c4 130 if (rrd_create(rrdtool_argc, rrdtool_argv) == -1) {
ce6564c2
CJ
131 PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
132 rrd_clear_error();
618986c4
CJ
133 ret = NULL;
134 } else {
135 Py_INCREF(Py_None);
136 ret = Py_None;
137 }
138
139 destroy_args();
140 return ret;
141}
142
143static char _rrdtool_update__doc__[] = "Store a new set of values into\
144 the RRD.\n\n\
145 Usage: update(args..)\n\
146 Arguments:\n\n\
147 filename\n\
148 [--template|-t ds-name[:ds-name]...]\n\
149 N|timestamp:value[:value...]\n\
150 [timestamp:value[:value...] ...]\n\n\
151 Full documentation can be found at:\n\
152 http://oss.oetiker.ch/rrdtool/doc/rrdupdate.en.html";
153
154static PyObject *
155_rrdtool_update(PyObject *self, PyObject *args)
156{
157 PyObject *ret;
158
159 if (convert_args("update", args) == -1)
160 return NULL;
161
162 if (rrd_update(rrdtool_argc, rrdtool_argv) == -1) {
163 PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
164 rrd_clear_error();
165 ret = NULL;
166 } else {
167 Py_INCREF(Py_None);
168 ret = Py_None;
169 }
170
171 destroy_args();
172 return ret;
173}
174
175static char _rrdtool_fetch__doc__[] = "Fetch data from an RRD.\n\n\
176 Usage: fetch(args..)\n\
177 Arguments:\n\n\
178 filename\n\
179 CF\n\
180 [--resolution|-r resolution]\n\
181 [--start|-s start]\n\
182 [--end|-e end]\n\n\
183 Full documentation can be found at:\n\
184 http://oss.oetiker.ch/rrdtool/doc/rrdfetch.en.html";
185
186static PyObject *
187_rrdtool_fetch(PyObject *self, PyObject *args)
188{
189 PyObject *ret, *range_tup, *dsnam_tup, *data_list, *t;
190 rrd_value_t *data, *datai, dv;
191 unsigned long step, ds_cnt, i, j, row;
192 time_t start, end;
193 char **ds_namv;
194
195 if (convert_args("fetch", args) == -1)
196 return NULL;
197
198 if (rrd_fetch(rrdtool_argc, rrdtool_argv, &start, &end, &step, &ds_cnt,
199 &ds_namv, &data) == -1) {
200 PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
201 rrd_clear_error();
202 ret = NULL;
203 } else {
204 row = (end - start) / step;
205 ret = PyTuple_New(3);
206 range_tup = PyTuple_New(3);
207 dsnam_tup = PyTuple_New(ds_cnt);
208 data_list = PyList_New(row);
209
210 PyTuple_SET_ITEM(ret, 0, range_tup);
211 PyTuple_SET_ITEM(ret, 1, dsnam_tup);
212 PyTuple_SET_ITEM(ret, 2, data_list);
213
214 datai = data;
215
216 PyTuple_SET_ITEM(range_tup, 0, PyLong_FromLong((long)start));
217 PyTuple_SET_ITEM(range_tup, 1, PyLong_FromLong((long)end));
218 PyTuple_SET_ITEM(range_tup, 2, PyLong_FromLong((long)step));
219
220 for (i = 0; i < ds_cnt; i++)
221 PyTuple_SET_ITEM(dsnam_tup, i, PyUnicode_FromString(ds_namv[i]));
222
223 for (i = 0; i < row; i++) {
224 t = PyTuple_New(ds_cnt);
225 PyList_SET_ITEM(data_list, i, t);
226
227 for (j = 0; j < ds_cnt; j++) {
228 dv = *(datai++);
229 if (isnan(dv)) {
230 PyTuple_SET_ITEM(t, j, Py_None);
231 Py_INCREF(Py_None);
232 } else
233 PyTuple_SET_ITEM(t, j, PyFloat_FromDouble((double)dv));
234 }
235 }
236
237 for (i = 0; i < ds_cnt; i++)
238 rrd_freemem(ds_namv[i]);
239
240 rrd_freemem(ds_namv);
241 rrd_freemem(data);
242 }
243
244 destroy_args();
245 return ret;
246}
247
248static char _rrdtool_graph__doc__[] = "Create a graph based on one or more " \
249 "RRDs.\n\n\
250 Usage: graph(args..)\n\
251 Arguments:\n\n\
252 filename | -\n\
253 [-s|--start start]\n\
254 [-e|--end end]\n\
255 [-S|--step step]\n\
256 [-t|--title string]\n\
257 [-v|--vertical-label string]\n\
258 [-w|--width pixels]\n\
259 [-h|--height pixels]\n\
260 [-j|--only-graph]\n\
261 [-D|--full-size-mode]\n\
262 [-u|--upper-limit value]\n\
263 [-l|--lower-limit value]\n\
264 [-r|--rigid]\n\
265 [-A|--alt-autoscale]\n\
266 [-J|--alt-autoscale-min]\n\
267 [-M|--alt-autoscale-max]\n\
268 [-N|--no-gridfit]\n\
269 [-x|--x-grid (GTM:GST:MTM:MST:LTM:LST:LPR:LFM|none)]\n\
270 [-y|--y-grid (grid step:label factor|none)]\n\
271 [-Y|--alt-y-grid]\n\
272 [-o|--logarithmic]\n\
273 [-X|--units-exponent value]\n\
274 [-L|--units-length value]\n\
275 [--units=si]\n\
276 [--right-axis scale:shift]\n\
277 [--right-axis-label label]\n\
278 [--right-axis-format format-string]\n\
279 [-g|--no-legend]\n\
280 [-F|--force-rules-legend]\n\
281 [--legend-position=(north|south|west|east)]\n\
282 [--legend-direction=(topdown|bottomup)]\n\
283 [-z|--lazy]\n\
284 [--daemon address]\n\
285 [-f|--imginfo printfstr]\n\
286 [-c|--color COLORTAG#rrggbb[aa]]\n\
287 [--grid-dash on:off]\n\
288 [--border width]\n\
289 [--dynamic-labels]\n\
290 [-m|--zoom factor]\n\
291 [-n|--font FONTTAG:size:[font]]\n\
292 [-R|--font-render-mode {normal,light,mono}]\n\
293 [-B|--font-smoothing-threshold size]\n\
294 [-P|--pango-markup]\n\
295 [-G|--graph-render-mode {normal,mono}]\n\
296 [-E|--slope-mode]\n\
297 [-a|--imgformat {PNG,SVG,EPS,PDF}]\n\
298 [-T|--tabwidth value]\n\
299 [-b|--base value]\n\
300 [-W|--watermark string]\n\
301 DEF:vname=rrdfile:ds-name:CF[:step=step][:start=time][:end=time]\n\
302 CDEF:vname=RPN expression\n\
303 VDEF=vname:RPN expression\n\n\
304 Full documentation can be found at:\n\
305 http://oss.oetiker.ch/rrdtool/doc/rrdgraph.en.html";
306
307static PyObject *
308_rrdtool_graph(PyObject *self, PyObject *args, PyObject *kwargs)
309{
310 PyObject *ret;
311 int orig_stdout, op[2], xsize, ysize, i;
312 int keep_in_mem = 0;
313 double ymin, ymax;
314 char **calcpr;
315
316 if (convert_args("graph", args) == -1)
317 return NULL;
318
319 if (rrdtool_argc >= 2 && strcmp(rrdtool_argv[1], "-") == 0)
320 keep_in_mem = 1;
321
322 if (keep_in_mem) {
323 orig_stdout = dup(STDOUT_FILENO);
324 if (pipe(op) != 0) {
325 PyErr_Format(rrdtool_OperationalError,
326 "Cannot create pipe for stdout: %s", strerror(errno));
327 ret = NULL;
328 } else {
329 dup2(op[1], STDOUT_FILENO);
330 close(op[1]);
331
332 // do something on stdout
333 // read(op[0], buffer, MAX_LEN);
334 }
335 }
336
337 if (rrd_graph(rrdtool_argc, rrdtool_argv, &calcpr, &xsize, &ysize, NULL,
338 &ymin, &ymax) == -1) {
339 PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
340 rrd_clear_error();
341 ret = NULL;
342 } else {
343 ret = PyTuple_New(keep_in_mem ? 4 : 3);
344
345 PyTuple_SET_ITEM(ret, 0, PyLong_FromLong((long)xsize));
346 PyTuple_SET_ITEM(ret, 1, PyLong_FromLong((long)ysize));
347
348 if (calcpr) {
349 PyObject *e, *t;
350
351 e = PyList_New(0);
352 PyTuple_SET_ITEM(ret, 2, e);
353
354 for (i = 0; calcpr[i]; i++) {
355 t = PyUnicode_FromString(calcpr[i]);
356 PyList_Append(e, t);
357 Py_DECREF(t);
358 rrd_freemem(calcpr[i]);
359 }
360 } else {
361 Py_INCREF(Py_None);
362 PyTuple_SET_ITEM(ret, 2, Py_None);
363 }
364
365 /* feed buffered contents into a PyBytes object */
366 if (keep_in_mem) {
367 PyObject *pb;
368 ssize_t rs;
369 char buffer[4096];
370
371 pb = PyBytes_FromStringAndSize("", 0);
372 for (;;) {
373 if ((rs = read(op[0], buffer, 4096)) <= 0)
374 break;
375 else
376 PyBytes_Concat(&pb, PyBytes_FromStringAndSize(buffer, rs));
377 }
378
379 PyTuple_SET_ITEM(ret, 3, pb);
380 }
381 }
382
383 if (keep_in_mem)
384 dup2(orig_stdout, STDOUT_FILENO);
ce6564c2 385
618986c4
CJ
386 destroy_args();
387 return ret;
ce6564c2
CJ
388}
389
390static PyMethodDef rrdtool_methods[] = {
618986c4
CJ
391 {"create", (PyCFunction)_rrdtool_create,
392 METH_VARARGS, _rrdtool_create__doc__},
393 {"update", (PyCFunction)_rrdtool_update,
394 METH_VARARGS, _rrdtool_update__doc__},
395 {"fetch", (PyCFunction)_rrdtool_fetch,
396 METH_VARARGS, _rrdtool_fetch__doc__},
397 {"graph", (PyCFunction)_rrdtool_graph,
398 METH_VARARGS | METH_KEYWORDS, _rrdtool_graph__doc__},
ce6564c2
CJ
399 {NULL, NULL, 0, NULL}
400};
401
402static struct PyModuleDef rrdtoolmodule = {
403 PyModuleDef_HEAD_INIT,
404 "rrdtool",
405 "rrdtool bindings for Python 3",
406 -1,
407 rrdtool_methods
408};
409
410PyMODINIT_FUNC
411PyInit_rrdtool(void)
412{
413 PyObject *m;
414
415 m = PyModule_Create(&rrdtoolmodule);
416 if (m == NULL)
417 return NULL;
418
419 rrdtool_ProgrammingError = PyErr_NewException("rrdtool.ProgrammingError",
420 NULL, NULL);
421 Py_INCREF(rrdtool_ProgrammingError);
422 PyModule_AddObject(m, "ProgrammingError", rrdtool_ProgrammingError);
423
424 rrdtool_OperationalError = PyErr_NewException("rrdtool.OperationalError",
425 NULL, NULL);
426 Py_INCREF(rrdtool_OperationalError);
427 PyModule_AddObject(m, "OperationalError", rrdtool_OperationalError);
428
429 return m;
430}