From: Christian Jurk Date: Tue, 18 Sep 2012 01:34:02 +0000 (+0200) Subject: * Added initial code for high-level module for using rrdtool in an X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b4570f2a26681efd027561660947b8a55a752fd2;p=people%2Fms%2Fpython-rrdtool.git * 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. --- diff --git a/README.md b/README.md index 36ce6c7..3492a0b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,66 @@ rrdtool-py3k ============ -rrdtool bindings for Python 3 \ No newline at end of file +Python 3 bindings for rrdtool with a native C extension and an object-oriented way to work with Round Robin Databases. + +The bindings are based on the code of the original Python 2 bindings for rrdtool by Hye-Shik Chang. + +Installation +------------ + +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. + +**How to Install? ** + +1. Download a copy of the repository. +2. Run `python setup.py install` to build an install the native C extension as well as the RRD module. + +Usage +----- + +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. + +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. + +### Using the low-level `rrdtool` module + +```python +import rrdtool + +# Create Round Robin Database +rrdtool.create('test.rrd', '--start', 'now', '--step', '300', 'RRA:AVERAGE:0.5:1:1200', 'DS:temp:GAUGE:600:-273:5000') + +# Feed updates to the RRD +rrdtool.update('test.rrd', 'N:32') +``` + +### Using the high-level `RRDtool` module + +```python +import RRDtool + +# Create a Round Robin Database +rrd = RRDtool.create('test.rrd', '--start', 'now', '--step', '300', 'RRA:AVERAGE:0.5:1:1200', 'DS:temp:GAUGE:600:-273:5000') + +# Update the RRD +rrd.update([(None, 32)]) + +# Create a graph from it +rrd.graph('test.png', '--end', 'now', '--start', 'end-5minutes', '--width', '400', 'DEF:ds0a=test.rrd:temp:AVERAGE', 'LINE1:ds0a#0000FF:"temperature\l"') + +# Same, but keep data in memory. +data = rrd.graph(None, '--end', 'now', '--start', 'end-5minutes', '--width', '400', 'DEF:ds0a=test.rrd:temp:AVERAGE', 'LINE1:ds0a#0000FF:"temperature\l"') + +# You can also use file-like objects +from io import BytesIO +rrd.graph(io, ...) +``` + +Author +------ + +Christian Jurk + +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. + +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. \ No newline at end of file diff --git a/RRDtool.py b/RRDtool.py new file mode 100644 index 0000000..ee46ab9 --- /dev/null +++ b/RRDtool.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +# +# rrdtool-py3k, rrdtool bindings for Python 3. +# Based on the rrdtool Python bindings for Python 2 from +# Hye-Shik Chang . +# +# Copyright 2012 Christian Jurk +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# + +from datetime import datetime +from io import BytesIO +import os +import rrdtool +from time import mktime + +def create(filename, *args): + "Create a Round Robin Database and return a RRD object on success." + rrdtool.create(filename, *args) + + if not os.access(filename, os.F_OK): + raise rrdtool.OperationalError('RRD file was not created') + + return RRD(filename) + +class RRD: + """An object-based interface to the rrdtool module.""" + + def __init__(self, filename, check_type=True): + "Initialize the class instance with a filename." + + if not os.access(filename, os.F_OK | os.R_OK): + raise rrdtool.OperationalError('RRD {!s} cannot be opened.' \ + .format(filename)) + + # Use rrdinfo to test whether the file is a valid RRD file + if check_type is True: + rrdtool.info(filename) + + self.readonly = not os.access(filename, os.W_OK) + self.filename = filename + + def graph(self, output_file, *args): + "Create a graph based on one or more RRDs." + buffered = True + outfile = '-' + + # write straigt into file using wrapper functions + if isinstance(output_file, str): + buffered = False + outfile = output_file + + gdata = rrdtool.graph(outfile, *args) + + if isinstance(gdata, tuple) and len(gdata) >= 4: + if output_file is None: + return gdata[3] + elif isinstance(output_file, BytesIO): + output_file.write(gdata[3]) + return output_file + + return None + + def info(self): + return rrdtool.info(self.filename) + + def update(self, values, *args): + vl = [] + + if self.readonly: + raise rrdtool.OperationalError('RRD file is read-only: {!s}' \ + .format(self.filename)) + elif not isinstance(values, (list, tuple)): + raise rrdtool.ProgrammingError('The values parameter must be a ' \ + 'list or tuple') + else: + for row in values: + if isinstance(row, str): + vl.append(row) + elif isinstance(row, (list, tuple)): + if len(row) < 2: + raise rrdtool.ProgrammingError('Value {!r} has too ' \ + 'few elements in sequence object'.format(row)) + else: + ts = row[0] + if ts is None: + ts = 'N' + elif isinstance(ts, datetime): + ts = int(mktime(ts.timetuple())) + elif isinstance(ts, str): + ts = int(ts) + elif not isinstance(ts, int): + raise ValueError('Unsupported type') + + v = '{}:{}'.format(ts, ':'.join([str(x) for x in row[1:]])) + vl.append(v) + + arglist = tuple(vl + list(args)) + return rrdtool.update(self.filename, *arglist) + + def __repr__(self): + return ''.format(self.filename) diff --git a/rrdtool-py3k.c b/rrdtool-py3k.c index 3507cf5..22f1d7b 100644 --- a/rrdtool-py3k.c +++ b/rrdtool-py3k.c @@ -113,11 +113,13 @@ convert_args(char *command, PyObject *args) static PyObject * _rrdtool_util_info2dict(const rrd_info_t *data) { - PyObject *dict, *val = NULL; + PyObject *dict, *val; dict = PyDict_New(); while (data) { + val = NULL; + switch (data->type) { case RD_I_VAL: if (isnan(data->value.u_val)) { @@ -144,11 +146,10 @@ _rrdtool_util_info2dict(const rrd_info_t *data) (char *)data->value.u_blo.ptr, data->value.u_blo.size); break; default: - val = NULL; break; } - if (val) { + if (val != NULL) { PyDict_SetItemString(dict, data->key, val); Py_DECREF(val); } @@ -675,6 +676,15 @@ _rrdtool_info(PyObject *self, PyObject *args) return ret; } +static char _rrdtool_lib_version__doc__[] = "Get the version this binding "\ + "was compiled against."; + +static PyObject * +_rrdtool_lib_version(PyObject *self, PyObject *args) +{ + return PyUnicode_FromString(rrd_strversion()); +} + static PyMethodDef rrdtool_methods[] = { {"create", (PyCFunction)_rrdtool_create, METH_VARARGS, _rrdtool_create__doc__}, @@ -700,6 +710,8 @@ static PyMethodDef rrdtool_methods[] = { METH_VARARGS, _rrdtool_resize__doc__}, {"info", (PyCFunction)_rrdtool_info, METH_VARARGS, _rrdtool_info__doc__}, + {"lib_version", (PyCFunction)_rrdtool_lib_version, + METH_VARARGS, _rrdtool_lib_version__doc__}, {NULL, NULL, 0, NULL} }; diff --git a/setup.py b/setup.py index b8fbe1d..be48251 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,21 @@ from distutils.core import setup, Extension -from glob import glob -sources = glob('*.c') -module = Extension('rrdtool', sources=sources, libraries=['rrd']) +def main(): + module = Extension('rrdtool', sources=['rrdtool-py3k.c'], + libraries=['rrd']) -setup(name='rrdtool', - version='1.0', - description='rrdtool bindings for Python 3', - ext_modules=[module]) + kwargs = dict( + name='python-rrdtool', + version='0.1.0', + description='rrdtool bindings for Python 3', + keywords=['rrdtool'], + author='Christian Jurk, Hye-Shik Chang', + author_email='commx@commx.ws', + ext_modules=[module], + py_modules=['RRDtool'] + ) + setup(**kwargs) + +if __name__ == '__main__': + main()