Code

Added python plugin.
[collectd.git] / src / python.c
1 #include <Python.h>
2 #include <structmember.h>
4 #include "collectd.h"
5 #include "common.h"
7 typedef struct cpy_callback_s {
8         char *name;
9         PyObject *callback;
10         struct cpy_callback_s *next;
11 } cpy_callback_t;
13 /* This is our global thread state. Python saves some stuff in thread-local
14  * storage. So if we allow the interpreter to run in the background
15  * (the scriptwriters might have created some threads from python), we have
16  * to save the state so we can resume it later from a different thread.
18  * Technically the Global Interpreter Lock (GIL) and thread states can be
19  * manipulated independently. But to keep stuff from getting too complex
20  * we'll just use PyEval_SaveTread and PyEval_RestoreThreas which takes
21  * care of the thread states as well as the GIL. */
23 static PyThreadState *state;
25 static cpy_callback_t *cpy_config_callbacks;
27 typedef struct {
28         PyObject_HEAD      /* No semicolon! */
29         PyObject *parent;
30         PyObject *key;
31         PyObject *values;
32         PyObject *children;
33 } Config;
35 static void Config_dealloc(PyObject *s) {
36         Config *self = (Config *) s;
37         
38         Py_XDECREF(self->parent);
39         Py_XDECREF(self->key);
40         Py_XDECREF(self->values);
41         Py_XDECREF(self->children);
42         self->ob_type->tp_free(s);
43 }
45 static PyObject *Config_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
46         Config *self;
47         
48         self = (Config *) type->tp_alloc(type, 0);
49         if (self == NULL)
50                 return NULL;
51         
52         self->parent = NULL;
53         self->key = NULL;
54         self->values = NULL;
55         self->children = NULL;
56         return (PyObject *) self;
57 }
59 static int Config_init(PyObject *s, PyObject *args, PyObject *kwds) {
60         PyObject *key = NULL, *parent = NULL, *values = NULL, *children = NULL, *tmp;
61         Config *self = (Config *) s;
62         static char *kwlist[] = {"key", "parent", "values", "children", NULL};
63         
64         if (!PyArg_ParseTupleAndKeywords(args, kwds, "S|OOO", kwlist,
65                         &key, &parent, &values, &children))
66                 return -1;
67         
68         if (values == NULL) {
69                 values = PyTuple_New(0);
70                 PyErr_Clear();
71         }
72         if (children == NULL) {
73                 children = PyTuple_New(0);
74                 PyErr_Clear();
75         }
76         tmp = self->key;
77         Py_INCREF(key);
78         self->key = key;
79         Py_XDECREF(tmp);
80         if (parent != NULL) {
81                 tmp = self->parent;
82                 Py_INCREF(parent);
83                 self->parent = parent;
84                 Py_XDECREF(tmp);
85         }
86         if (values != NULL) {
87                 tmp = self->values;
88                 Py_INCREF(values);
89                 self->values = values;
90                 Py_XDECREF(tmp);
91         }
92         if (children != NULL) {
93                 tmp = self->children;
94                 Py_INCREF(children);
95                 self->children = children;
96                 Py_XDECREF(tmp);
97         }
98         return 0;
99 }
101 static PyMemberDef Config_members[] = {
102     {"Parent", T_OBJECT, offsetof(Config, parent), 0, "Parent node"},
103     {"Key", T_OBJECT_EX, offsetof(Config, key), 0, "Keyword of this node"},
104     {"Values", T_OBJECT_EX, offsetof(Config, values), 0, "Values after the key"},
105     {"Children", T_OBJECT_EX, offsetof(Config, children), 0, "Childnodes of this node"},
106     {NULL}
107 };
109 static PyTypeObject ConfigType = {
110     PyObject_HEAD_INIT(NULL)
111     0,                         /* Always 0 */
112     "collectd.Config",         /* tp_name */
113     sizeof(Config),            /* tp_basicsize */
114     0,                         /* Will be filled in later */
115     Config_dealloc,            /* tp_dealloc */
116     0,                         /* tp_print */
117     0,                         /* tp_getattr */
118     0,                         /* tp_setattr */
119     0,                         /* tp_compare */
120     0,                         /* tp_repr */
121     0,                         /* tp_as_number */
122     0,                         /* tp_as_sequence */
123     0,                         /* tp_as_mapping */
124     0,                         /* tp_hash */
125     0,                         /* tp_call */
126     0,                         /* tp_str */
127     0,                         /* tp_getattro */
128     0,                         /* tp_setattro */
129     0,                         /* tp_as_buffer */
130     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
131     "Cool help text later",    /* tp_doc */
132     0,                         /* tp_traverse */
133     0,                         /* tp_clear */
134     0,                         /* tp_richcompare */
135     0,                         /* tp_weaklistoffset */
136     0,                         /* tp_iter */
137     0,                         /* tp_iternext */
138     0,                         /* tp_methods */
139     Config_members,            /* tp_members */
140     0,                         /* tp_getset */
141     0,                         /* tp_base */
142     0,                         /* tp_dict */
143     0,                         /* tp_descr_get */
144     0,                         /* tp_descr_set */
145     0,                         /* tp_dictoffset */
146     Config_init,               /* tp_init */
147     0,                         /* tp_alloc */
148     Config_new                 /* tp_new */
149 };
151 static PyObject *cpy_register_config(PyObject *self, PyObject *args) {
152         cpy_callback_t *c;
153         const char *name = NULL;
154         PyObject *callback = NULL;
155         
156         if (PyArg_ParseTuple(args, "O|z", &callback, &name) == 0) return NULL;
157         if (PyCallable_Check(callback) == 0) {
158                 PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
159                 return 0;
160         }
161         if (name == NULL) {
162                 PyObject *mod;
163                 
164                 mod = PyObject_GetAttrString(callback, "__module__");
165                 if (mod != NULL) name = PyString_AsString(mod);
166                 if (name == NULL) {
167                         PyErr_SetString(PyExc_ValueError, "No module name specified and "
168                                 "callback function does not have a \"__module__\" attribute.");
169                         return 0;
170                 }
171         }
172         c = malloc(sizeof(*c));
173         c->name = strdup(name);
174         c->callback = callback;
175         c->next = cpy_config_callbacks;
176         cpy_config_callbacks = c;
177         return Py_None;
180 static PyMethodDef cpy_methods[] = {
181         {"register_config", cpy_register_config, METH_VARARGS, "foo"},
182         {0, 0, 0, 0}
183 };
185 static int cpy_shutdown(void) {
186         /* This can happen if the module was loaded but not configured. */
187         if (state != NULL)
188                 PyEval_RestoreThread(state);
189         Py_Finalize();
190         return 0;
193 static int cpy_init(void) {
194         PyEval_InitThreads();
195         /* Now it's finally OK to use python threads. */
196         return 0;
199 static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
200         int i;
201         PyObject *item, *values, *children, *tmp;
202         
203         if (parent == NULL)
204                 parent = Py_None;
205         
206         values = PyTuple_New(ci->values_num); /* New reference. */
207         for (i = 0; i < ci->values_num; ++i) {
208                 if (ci->values[i].type == OCONFIG_TYPE_STRING) {
209                         PyTuple_SET_ITEM(values, i, PyString_FromString(ci->values[i].value.string));
210                 } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
211                         PyTuple_SET_ITEM(values, i, PyFloat_FromDouble(ci->values[i].value.number));
212                 } else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) {
213                         PyTuple_SET_ITEM(values, i, PyBool_FromLong(ci->values[i].value.boolean));
214                 }
215         }
216         
217         item = PyObject_CallFunction((PyObject *) &ConfigType, "sONO", ci->key, parent, values, Py_None);
218         if (item == NULL)
219                 return NULL;
220         children = PyTuple_New(ci->children_num); /* New reference. */
221         for (i = 0; i < ci->children_num; ++i) {
222                         PyTuple_SET_ITEM(children, i, cpy_oconfig_to_pyconfig(ci->children + i, item));
223         }
224         tmp = ((Config *) item)->children;
225         ((Config *) item)->children = children;
226         Py_XDECREF(tmp);
227         return item;
230 static int cpy_config(oconfig_item_t *ci) {
231         int i;
232         PyObject *sys;
233         PyObject *sys_path;
234         PyObject *module;
235         
236         /* Ok in theory we shouldn't do initialization at this point
237          * but we have to. In order to give python scripts a chance
238          * to register a config callback we need to be able to execute
239          * python code during the config callback so we have to start
240          * the interpreter here. */
241         /* Do *not* use the python "thread" module at this point! */
242         Py_Initialize();
243         
244         PyType_Ready(&ConfigType);
245         sys = PyImport_ImportModule("sys"); /* New reference. */
246         if (sys == NULL) {
247                 ERROR("python module: Unable to import \"sys\" module.");
248                 /* Just print the default python exception text to stderr. */
249                 PyErr_Print();
250                 return 1;
251         }
252         sys_path = PyObject_GetAttrString(sys, "path"); /* New reference. */
253         Py_DECREF(sys);
254         if (sys_path == NULL) {
255                 ERROR("python module: Unable to read \"sys.path\".");
256                 PyErr_Print();
257                 return 1;
258         }
259         module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */
260         PyModule_AddObject(module, "Config", (PyObject *) &ConfigType); /* Steals a reference. */
261         for (i = 0; i < ci->children_num; ++i) {
262                 oconfig_item_t *item = ci->children + i;
263                 
264                 if (strcasecmp(item->key, "ModulePath") == 0) {
265                         char *dir = NULL;
266                         PyObject *dir_object;
267                         
268                         if (cf_util_get_string(item, &dir) != 0) 
269                                 continue;
270                         dir_object = PyString_FromString(dir); /* New reference. */
271                         if (dir_object == NULL) {
272                                 ERROR("python plugin: Unable to convert \"%s\" to "
273                                       "a python object.", dir);
274                                 free(dir);
275                                 PyErr_Print();
276                                 continue;
277                         }
278                         if (PyList_Append(sys_path, dir_object) != 0) {
279                                 ERROR("python plugin: Unable to append \"%s\" to "
280                                       "python module path.", dir);
281                                 PyErr_Print();
282                         }
283                         Py_DECREF(dir_object);
284                         free(dir);
285                 } else if (strcasecmp(item->key, "Import") == 0) {
286                         char *module_name = NULL;
287                         PyObject *module;
288                         
289                         if (cf_util_get_string(item, &module_name) != 0) 
290                                 continue;
291                         module = PyImport_ImportModule(module_name); /* New reference. */
292                         if (module == NULL) {
293                                 ERROR("python plugin: Error importing module \"%s\".", module_name);
294                                 PyErr_Print();
295                         }
296                         free(module_name);
297                         Py_XDECREF(module);
298                 } else if (strcasecmp(item->key, "Module") == 0) {
299                         char *name = NULL;
300                         cpy_callback_t *c;
301                         PyObject *ret;
302                         
303                         if (cf_util_get_string(item, &name) != 0)
304                                 continue;
305                         for (c = cpy_config_callbacks; c; c = c->next) {
306                                 if (strcasecmp(c->name, name) == 0)
307                                         break;
308                         }
309                         if (c == NULL) {
310                                 WARNING("python plugin: Found a configuration for the \"%s\" plugin, "
311                                         "but the plugin isn't loaded or didn't register "
312                                         "a configuration callback.", name);
313                                 free(name);
314                                 continue;
315                         }
316                         free(name);
317                         ret = PyObject_CallFunction(c->callback, "N",
318                                         cpy_oconfig_to_pyconfig(item, NULL)); /* New reference. */
319                         if (ret == NULL)
320                                 PyErr_Print();
321                         else
322                                 Py_DECREF(ret);
323                 } else {
324                         WARNING("python plugin: Ignoring unknown config key \"%s\".", item->key);
325                 }
326         }
327         Py_DECREF(sys_path);
328         return 0;
331 void module_register(void) {
332         plugin_register_complex_config("python", cpy_config);
333         plugin_register_init("python", cpy_init);
334 //      plugin_register_read("netapp", cna_read);
335         plugin_register_shutdown("netapp", cpy_shutdown);