Added python plugin.
authorSven Trenkel <collectd@semidefinite.de>
Sun, 11 Oct 2009 01:28:07 +0000 (03:28 +0200)
committerSven Trenkel <collectd@semidefinite.de>
Sun, 11 Oct 2009 01:28:07 +0000 (03:28 +0200)
It adds a "collectd" module to the embedded python interpreter
which contains a "register_config" method to register config
callbacks and "Config" class which contains a config item.

src/python.c [new file with mode: 0644]

diff --git a/src/python.c b/src/python.c
new file mode 100644 (file)
index 0000000..6428a94
--- /dev/null
@@ -0,0 +1,336 @@
+#include <Python.h>
+#include <structmember.h>
+
+#include "collectd.h"
+#include "common.h"
+
+typedef struct cpy_callback_s {
+       char *name;
+       PyObject *callback;
+       struct cpy_callback_s *next;
+} cpy_callback_t;
+
+/* This is our global thread state. Python saves some stuff in thread-local
+ * storage. So if we allow the interpreter to run in the background
+ * (the scriptwriters might have created some threads from python), we have
+ * to save the state so we can resume it later from a different thread.
+
+ * Technically the Global Interpreter Lock (GIL) and thread states can be
+ * manipulated independently. But to keep stuff from getting too complex
+ * we'll just use PyEval_SaveTread and PyEval_RestoreThreas which takes
+ * care of the thread states as well as the GIL. */
+
+static PyThreadState *state;
+
+static cpy_callback_t *cpy_config_callbacks;
+
+typedef struct {
+       PyObject_HEAD      /* No semicolon! */
+       PyObject *parent;
+       PyObject *key;
+       PyObject *values;
+       PyObject *children;
+} Config;
+
+static void Config_dealloc(PyObject *s) {
+       Config *self = (Config *) s;
+       
+       Py_XDECREF(self->parent);
+       Py_XDECREF(self->key);
+       Py_XDECREF(self->values);
+       Py_XDECREF(self->children);
+       self->ob_type->tp_free(s);
+}
+
+static PyObject *Config_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
+       Config *self;
+       
+       self = (Config *) type->tp_alloc(type, 0);
+       if (self == NULL)
+               return NULL;
+       
+       self->parent = NULL;
+       self->key = NULL;
+       self->values = NULL;
+       self->children = NULL;
+       return (PyObject *) self;
+}
+
+static int Config_init(PyObject *s, PyObject *args, PyObject *kwds) {
+       PyObject *key = NULL, *parent = NULL, *values = NULL, *children = NULL, *tmp;
+       Config *self = (Config *) s;
+       static char *kwlist[] = {"key", "parent", "values", "children", NULL};
+       
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "S|OOO", kwlist,
+                       &key, &parent, &values, &children))
+               return -1;
+       
+       if (values == NULL) {
+               values = PyTuple_New(0);
+               PyErr_Clear();
+       }
+       if (children == NULL) {
+               children = PyTuple_New(0);
+               PyErr_Clear();
+       }
+       tmp = self->key;
+       Py_INCREF(key);
+       self->key = key;
+       Py_XDECREF(tmp);
+       if (parent != NULL) {
+               tmp = self->parent;
+               Py_INCREF(parent);
+               self->parent = parent;
+               Py_XDECREF(tmp);
+       }
+       if (values != NULL) {
+               tmp = self->values;
+               Py_INCREF(values);
+               self->values = values;
+               Py_XDECREF(tmp);
+       }
+       if (children != NULL) {
+               tmp = self->children;
+               Py_INCREF(children);
+               self->children = children;
+               Py_XDECREF(tmp);
+       }
+       return 0;
+}
+
+static PyMemberDef Config_members[] = {
+    {"Parent", T_OBJECT, offsetof(Config, parent), 0, "Parent node"},
+    {"Key", T_OBJECT_EX, offsetof(Config, key), 0, "Keyword of this node"},
+    {"Values", T_OBJECT_EX, offsetof(Config, values), 0, "Values after the key"},
+    {"Children", T_OBJECT_EX, offsetof(Config, children), 0, "Childnodes of this node"},
+    {NULL}
+};
+
+static PyTypeObject ConfigType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                         /* Always 0 */
+    "collectd.Config",         /* tp_name */
+    sizeof(Config),            /* tp_basicsize */
+    0,                         /* Will be filled in later */
+    Config_dealloc,            /* tp_dealloc */
+    0,                         /* tp_print */
+    0,                         /* tp_getattr */
+    0,                         /* tp_setattr */
+    0,                         /* tp_compare */
+    0,                         /* tp_repr */
+    0,                         /* tp_as_number */
+    0,                         /* tp_as_sequence */
+    0,                         /* tp_as_mapping */
+    0,                         /* tp_hash */
+    0,                         /* tp_call */
+    0,                         /* tp_str */
+    0,                         /* tp_getattro */
+    0,                         /* tp_setattro */
+    0,                         /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    "Cool help text later",    /* tp_doc */
+    0,                        /* tp_traverse */
+    0,                        /* tp_clear */
+    0,                        /* tp_richcompare */
+    0,                        /* tp_weaklistoffset */
+    0,                        /* tp_iter */
+    0,                        /* tp_iternext */
+    0,                         /* tp_methods */
+    Config_members,            /* tp_members */
+    0,                         /* tp_getset */
+    0,                         /* tp_base */
+    0,                         /* tp_dict */
+    0,                         /* tp_descr_get */
+    0,                         /* tp_descr_set */
+    0,                         /* tp_dictoffset */
+    Config_init,               /* tp_init */
+    0,                         /* tp_alloc */
+    Config_new                 /* tp_new */
+};
+
+static PyObject *cpy_register_config(PyObject *self, PyObject *args) {
+       cpy_callback_t *c;
+       const char *name = NULL;
+       PyObject *callback = NULL;
+       
+       if (PyArg_ParseTuple(args, "O|z", &callback, &name) == 0) return NULL;
+       if (PyCallable_Check(callback) == 0) {
+               PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
+               return 0;
+       }
+       if (name == NULL) {
+               PyObject *mod;
+               
+               mod = PyObject_GetAttrString(callback, "__module__");
+               if (mod != NULL) name = PyString_AsString(mod);
+               if (name == NULL) {
+                       PyErr_SetString(PyExc_ValueError, "No module name specified and "
+                               "callback function does not have a \"__module__\" attribute.");
+                       return 0;
+               }
+       }
+       c = malloc(sizeof(*c));
+       c->name = strdup(name);
+       c->callback = callback;
+       c->next = cpy_config_callbacks;
+       cpy_config_callbacks = c;
+       return Py_None;
+}
+
+static PyMethodDef cpy_methods[] = {
+       {"register_config", cpy_register_config, METH_VARARGS, "foo"},
+       {0, 0, 0, 0}
+};
+
+static int cpy_shutdown(void) {
+       /* This can happen if the module was loaded but not configured. */
+       if (state != NULL)
+               PyEval_RestoreThread(state);
+       Py_Finalize();
+       return 0;
+}
+
+static int cpy_init(void) {
+       PyEval_InitThreads();
+       /* Now it's finally OK to use python threads. */
+       return 0;
+}
+
+static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
+       int i;
+       PyObject *item, *values, *children, *tmp;
+       
+       if (parent == NULL)
+               parent = Py_None;
+       
+       values = PyTuple_New(ci->values_num); /* New reference. */
+       for (i = 0; i < ci->values_num; ++i) {
+               if (ci->values[i].type == OCONFIG_TYPE_STRING) {
+                       PyTuple_SET_ITEM(values, i, PyString_FromString(ci->values[i].value.string));
+               } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
+                       PyTuple_SET_ITEM(values, i, PyFloat_FromDouble(ci->values[i].value.number));
+               } else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) {
+                       PyTuple_SET_ITEM(values, i, PyBool_FromLong(ci->values[i].value.boolean));
+               }
+       }
+       
+       item = PyObject_CallFunction((PyObject *) &ConfigType, "sONO", ci->key, parent, values, Py_None);
+       if (item == NULL)
+               return NULL;
+       children = PyTuple_New(ci->children_num); /* New reference. */
+       for (i = 0; i < ci->children_num; ++i) {
+                       PyTuple_SET_ITEM(children, i, cpy_oconfig_to_pyconfig(ci->children + i, item));
+       }
+       tmp = ((Config *) item)->children;
+       ((Config *) item)->children = children;
+       Py_XDECREF(tmp);
+       return item;
+}
+
+static int cpy_config(oconfig_item_t *ci) {
+       int i;
+       PyObject *sys;
+       PyObject *sys_path;
+       PyObject *module;
+       
+       /* Ok in theory we shouldn't do initialization at this point
+        * but we have to. In order to give python scripts a chance
+        * to register a config callback we need to be able to execute
+        * python code during the config callback so we have to start
+        * the interpreter here. */
+       /* Do *not* use the python "thread" module at this point! */
+       Py_Initialize();
+       
+       PyType_Ready(&ConfigType);
+       sys = PyImport_ImportModule("sys"); /* New reference. */
+       if (sys == NULL) {
+               ERROR("python module: Unable to import \"sys\" module.");
+               /* Just print the default python exception text to stderr. */
+               PyErr_Print();
+               return 1;
+       }
+       sys_path = PyObject_GetAttrString(sys, "path"); /* New reference. */
+       Py_DECREF(sys);
+       if (sys_path == NULL) {
+               ERROR("python module: Unable to read \"sys.path\".");
+               PyErr_Print();
+               return 1;
+       }
+       module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */
+       PyModule_AddObject(module, "Config", (PyObject *) &ConfigType); /* Steals a reference. */
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *item = ci->children + i;
+               
+               if (strcasecmp(item->key, "ModulePath") == 0) {
+                       char *dir = NULL;
+                       PyObject *dir_object;
+                       
+                       if (cf_util_get_string(item, &dir) != 0) 
+                               continue;
+                       dir_object = PyString_FromString(dir); /* New reference. */
+                       if (dir_object == NULL) {
+                               ERROR("python plugin: Unable to convert \"%s\" to "
+                                     "a python object.", dir);
+                               free(dir);
+                               PyErr_Print();
+                               continue;
+                       }
+                       if (PyList_Append(sys_path, dir_object) != 0) {
+                               ERROR("python plugin: Unable to append \"%s\" to "
+                                     "python module path.", dir);
+                               PyErr_Print();
+                       }
+                       Py_DECREF(dir_object);
+                       free(dir);
+               } else if (strcasecmp(item->key, "Import") == 0) {
+                       char *module_name = NULL;
+                       PyObject *module;
+                       
+                       if (cf_util_get_string(item, &module_name) != 0) 
+                               continue;
+                       module = PyImport_ImportModule(module_name); /* New reference. */
+                       if (module == NULL) {
+                               ERROR("python plugin: Error importing module \"%s\".", module_name);
+                               PyErr_Print();
+                       }
+                       free(module_name);
+                       Py_XDECREF(module);
+               } else if (strcasecmp(item->key, "Module") == 0) {
+                       char *name = NULL;
+                       cpy_callback_t *c;
+                       PyObject *ret;
+                       
+                       if (cf_util_get_string(item, &name) != 0)
+                               continue;
+                       for (c = cpy_config_callbacks; c; c = c->next) {
+                               if (strcasecmp(c->name, name) == 0)
+                                       break;
+                       }
+                       if (c == NULL) {
+                               WARNING("python plugin: Found a configuration for the \"%s\" plugin, "
+                                       "but the plugin isn't loaded or didn't register "
+                                       "a configuration callback.", name);
+                               free(name);
+                               continue;
+                       }
+                       free(name);
+                       ret = PyObject_CallFunction(c->callback, "N",
+                                       cpy_oconfig_to_pyconfig(item, NULL)); /* New reference. */
+                       if (ret == NULL)
+                               PyErr_Print();
+                       else
+                               Py_DECREF(ret);
+               } else {
+                       WARNING("python plugin: Ignoring unknown config key \"%s\".", item->key);
+               }
+       }
+       Py_DECREF(sys_path);
+       return 0;
+}
+
+void module_register(void) {
+       plugin_register_complex_config("python", cpy_config);
+       plugin_register_init("python", cpy_init);
+//     plugin_register_read("netapp", cna_read);
+       plugin_register_shutdown("netapp", cpy_shutdown);
+}