]> git.ipfire.org Git - people/ms/python-rrdtool.git/commitdiff
* Added initial code for high-level module for using rrdtool in an
authorChristian Jurk <commx@commx.ws>
Tue, 18 Sep 2012 01:34:02 +0000 (03:34 +0200)
committerChristian Jurk <commx@commx.ws>
Tue, 18 Sep 2012 01:34:02 +0000 (03:34 +0200)
  object-oriented manner.
* Updated README.
* Updated setup script to include the high-level module, as well as
  building the low-level extension.

README.md
RRDtool.py [new file with mode: 0644]
rrdtool-py3k.c
setup.py

index 36ce6c757c849584ee46ec065fde5de89ef205c5..3492a0bd17772430c6da11fcd3ee7608f732a807 100644 (file)
--- 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 <commx@commx.ws>
+
+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 (file)
index 0000000..ee46ab9
--- /dev/null
@@ -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 <perky@fallin.lv>.
+#
+#  Copyright 2012 Christian Jurk <commx@commx.ws>
+#
+#  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 '<RRD {!r}>'.format(self.filename)
index 3507cf51df027cb27ee3af66f429eff1c10b6325..22f1d7bc9d96414cb0fa5d8f20264d275e6b4433 100644 (file)
@@ -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}
 };
 
index b8fbe1d49d493444161279084bf393217396c7dd..be48251db86b77617292458765123cea72b87c65 100644 (file)
--- 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()