Skip to content
This repository was archived by the owner on Sep 29, 2023. It is now read-only.

Commit b4570f2

Browse files
author
Christian Jurk
committed
* Added initial code for high-level module for using rrdtool in an
object-oriented manner. * Updated README. * Updated setup script to include the high-level module, as well as building the low-level extension.
1 parent 06fe4e5 commit b4570f2

File tree

4 files changed

+213
-11
lines changed

4 files changed

+213
-11
lines changed

README.md

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,66 @@
11
rrdtool-py3k
22
============
33

4-
rrdtool bindings for Python 3
4+
Python 3 bindings for rrdtool with a native C extension and an object-oriented way to work with Round Robin Databases.
5+
6+
The bindings are based on the code of the original Python 2 bindings for rrdtool by Hye-Shik Chang.
7+
8+
Installation
9+
------------
10+
11+
In order to build the native C extension (which is an required step), you'll need librrd and its headers installed. Having rrdtool installed should be enough on most distributions.
12+
13+
**How to Install? **
14+
15+
1. Download a copy of the repository.
16+
2. Run `python setup.py install` to build an install the native C extension as well as the RRD module.
17+
18+
Usage
19+
-----
20+
21+
You can either use the low-level `rrdtool` module (which offers almost the same functions as the old Python 2 bindings for rrdtool provided), or the `RRDtool` module, which represents a object-oriented interface to rrdtool.
22+
23+
Unlike the Python 2 binding, this binding is able to create graphs entirely in-memory, which makes it ideal for generating a large amount of graphs without having high I/O. This feature is currently available on POSIX platforms only, because I wasn't able to find a portable way to redirect stdout to a memory allocated buffer (which is required for that). To use this feature, specify "-" as the output filename in graphs (low-level extension), or `None` in the high-level module.
24+
25+
### Using the low-level `rrdtool` module
26+
27+
```python
28+
import rrdtool
29+
30+
# Create Round Robin Database
31+
rrdtool.create('test.rrd', '--start', 'now', '--step', '300', 'RRA:AVERAGE:0.5:1:1200', 'DS:temp:GAUGE:600:-273:5000')
32+
33+
# Feed updates to the RRD
34+
rrdtool.update('test.rrd', 'N:32')
35+
```
36+
37+
### Using the high-level `RRDtool` module
38+
39+
```python
40+
import RRDtool
41+
42+
# Create a Round Robin Database
43+
rrd = RRDtool.create('test.rrd', '--start', 'now', '--step', '300', 'RRA:AVERAGE:0.5:1:1200', 'DS:temp:GAUGE:600:-273:5000')
44+
45+
# Update the RRD
46+
rrd.update([(None, 32)])
47+
48+
# Create a graph from it
49+
rrd.graph('test.png', '--end', 'now', '--start', 'end-5minutes', '--width', '400', 'DEF:ds0a=test.rrd:temp:AVERAGE', 'LINE1:ds0a#0000FF:"temperature\l"')
50+
51+
# Same, but keep data in memory.
52+
data = rrd.graph(None, '--end', 'now', '--start', 'end-5minutes', '--width', '400', 'DEF:ds0a=test.rrd:temp:AVERAGE', 'LINE1:ds0a#0000FF:"temperature\l"')
53+
54+
# You can also use file-like objects
55+
from io import BytesIO
56+
rrd.graph(io, ...)
57+
```
58+
59+
Author
60+
------
61+
62+
Christian Jurk <commx@commx.ws>
63+
64+
This binding was created because I am currently porting some existing Python 2 code to Python 3 and try to help the community by contributing a updated binding extension. Hope someone can benefit from it.
65+
66+
If you encounter any bugs (which I expected at time of writing this), please submit them in the issue tracker here on the project page on Github. Thank you.

RRDtool.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/usr/bin/env python2
2+
# -*- coding: utf-8 -*-
3+
#
4+
# rrdtool-py3k, rrdtool bindings for Python 3.
5+
# Based on the rrdtool Python bindings for Python 2 from
6+
# Hye-Shik Chang <perky@fallin.lv>.
7+
#
8+
# Copyright 2012 Christian Jurk <commx@commx.ws>
9+
#
10+
# This program is free software; you can redistribute it and/or modify
11+
# it under the terms of the GNU General Public License as published by
12+
# the Free Software Foundation; either version 2 of the License, or
13+
# (at your option) any later version.
14+
#
15+
# This program is distributed in the hope that it will be useful,
16+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
# GNU General Public License for more details.
19+
#
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program; if not, write to the Free Software
22+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
23+
# MA 02110-1301, USA.
24+
#
25+
#
26+
27+
from datetime import datetime
28+
from io import BytesIO
29+
import os
30+
import rrdtool
31+
from time import mktime
32+
33+
def create(filename, *args):
34+
"Create a Round Robin Database and return a RRD object on success."
35+
rrdtool.create(filename, *args)
36+
37+
if not os.access(filename, os.F_OK):
38+
raise rrdtool.OperationalError('RRD file was not created')
39+
40+
return RRD(filename)
41+
42+
class RRD:
43+
"""An object-based interface to the rrdtool module."""
44+
45+
def __init__(self, filename, check_type=True):
46+
"Initialize the class instance with a filename."
47+
48+
if not os.access(filename, os.F_OK | os.R_OK):
49+
raise rrdtool.OperationalError('RRD {!s} cannot be opened.' \
50+
.format(filename))
51+
52+
# Use rrdinfo to test whether the file is a valid RRD file
53+
if check_type is True:
54+
rrdtool.info(filename)
55+
56+
self.readonly = not os.access(filename, os.W_OK)
57+
self.filename = filename
58+
59+
def graph(self, output_file, *args):
60+
"Create a graph based on one or more RRDs."
61+
buffered = True
62+
outfile = '-'
63+
64+
# write straigt into file using wrapper functions
65+
if isinstance(output_file, str):
66+
buffered = False
67+
outfile = output_file
68+
69+
gdata = rrdtool.graph(outfile, *args)
70+
71+
if isinstance(gdata, tuple) and len(gdata) >= 4:
72+
if output_file is None:
73+
return gdata[3]
74+
elif isinstance(output_file, BytesIO):
75+
output_file.write(gdata[3])
76+
return output_file
77+
78+
return None
79+
80+
def info(self):
81+
return rrdtool.info(self.filename)
82+
83+
def update(self, values, *args):
84+
vl = []
85+
86+
if self.readonly:
87+
raise rrdtool.OperationalError('RRD file is read-only: {!s}' \
88+
.format(self.filename))
89+
elif not isinstance(values, (list, tuple)):
90+
raise rrdtool.ProgrammingError('The values parameter must be a ' \
91+
'list or tuple')
92+
else:
93+
for row in values:
94+
if isinstance(row, str):
95+
vl.append(row)
96+
elif isinstance(row, (list, tuple)):
97+
if len(row) < 2:
98+
raise rrdtool.ProgrammingError('Value {!r} has too ' \
99+
'few elements in sequence object'.format(row))
100+
else:
101+
ts = row[0]
102+
if ts is None:
103+
ts = 'N'
104+
elif isinstance(ts, datetime):
105+
ts = int(mktime(ts.timetuple()))
106+
elif isinstance(ts, str):
107+
ts = int(ts)
108+
elif not isinstance(ts, int):
109+
raise ValueError('Unsupported type')
110+
111+
v = '{}:{}'.format(ts, ':'.join([str(x) for x in row[1:]]))
112+
vl.append(v)
113+
114+
arglist = tuple(vl + list(args))
115+
return rrdtool.update(self.filename, *arglist)
116+
117+
def __repr__(self):
118+
return '<RRD {!r}>'.format(self.filename)

rrdtool-py3k.c

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,13 @@ convert_args(char *command, PyObject *args)
113113
static PyObject *
114114
_rrdtool_util_info2dict(const rrd_info_t *data)
115115
{
116-
PyObject *dict, *val = NULL;
116+
PyObject *dict, *val;
117117

118118
dict = PyDict_New();
119119

120120
while (data) {
121+
val = NULL;
122+
121123
switch (data->type) {
122124
case RD_I_VAL:
123125
if (isnan(data->value.u_val)) {
@@ -144,11 +146,10 @@ _rrdtool_util_info2dict(const rrd_info_t *data)
144146
(char *)data->value.u_blo.ptr, data->value.u_blo.size);
145147
break;
146148
default:
147-
val = NULL;
148149
break;
149150
}
150151

151-
if (val) {
152+
if (val != NULL) {
152153
PyDict_SetItemString(dict, data->key, val);
153154
Py_DECREF(val);
154155
}
@@ -675,6 +676,15 @@ _rrdtool_info(PyObject *self, PyObject *args)
675676
return ret;
676677
}
677678

679+
static char _rrdtool_lib_version__doc__[] = "Get the version this binding "\
680+
"was compiled against.";
681+
682+
static PyObject *
683+
_rrdtool_lib_version(PyObject *self, PyObject *args)
684+
{
685+
return PyUnicode_FromString(rrd_strversion());
686+
}
687+
678688
static PyMethodDef rrdtool_methods[] = {
679689
{"create", (PyCFunction)_rrdtool_create,
680690
METH_VARARGS, _rrdtool_create__doc__},
@@ -700,6 +710,8 @@ static PyMethodDef rrdtool_methods[] = {
700710
METH_VARARGS, _rrdtool_resize__doc__},
701711
{"info", (PyCFunction)_rrdtool_info,
702712
METH_VARARGS, _rrdtool_info__doc__},
713+
{"lib_version", (PyCFunction)_rrdtool_lib_version,
714+
METH_VARARGS, _rrdtool_lib_version__doc__},
703715
{NULL, NULL, 0, NULL}
704716
};
705717

setup.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
from distutils.core import setup, Extension
2-
from glob import glob
32

4-
sources = glob('*.c')
5-
module = Extension('rrdtool', sources=sources, libraries=['rrd'])
3+
def main():
4+
module = Extension('rrdtool', sources=['rrdtool-py3k.c'],
5+
libraries=['rrd'])
66

7-
setup(name='rrdtool',
8-
version='1.0',
9-
description='rrdtool bindings for Python 3',
10-
ext_modules=[module])
7+
kwargs = dict(
8+
name='python-rrdtool',
9+
version='0.1.0',
10+
description='rrdtool bindings for Python 3',
11+
keywords=['rrdtool'],
12+
author='Christian Jurk, Hye-Shik Chang',
13+
author_email='commx@commx.ws',
14+
ext_modules=[module],
15+
py_modules=['RRDtool']
16+
)
1117

18+
setup(**kwargs)
19+
20+
if __name__ == '__main__':
21+
main()

0 commit comments

Comments
 (0)