Code

Added logging.
[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         PyObject *data;
11         struct cpy_callback_s *next;
12 } cpy_callback_t;
14 /* This is our global thread state. Python saves some stuff in thread-local
15  * storage. So if we allow the interpreter to run in the background
16  * (the scriptwriters might have created some threads from python), we have
17  * to save the state so we can resume it later from a different thread.
19  * Technically the Global Interpreter Lock (GIL) and thread states can be
20  * manipulated independently. But to keep stuff from getting too complex
21  * we'll just use PyEval_SaveTread and PyEval_RestoreThreas which takes
22  * care of the thread states as well as the GIL. */
24 static PyThreadState *state;
26 static cpy_callback_t *cpy_config_callbacks;
27 static cpy_callback_t *cpy_init_callbacks;
28 static cpy_callback_t *cpy_shutdown_callbacks;
30 typedef struct {
31         PyObject_HEAD      /* No semicolon! */
32         PyObject *parent;
33         PyObject *key;
34         PyObject *values;
35         PyObject *children;
36 } Config;
38 static void Config_dealloc(PyObject *s) {
39         Config *self = (Config *) s;
40         
41         Py_XDECREF(self->parent);
42         Py_XDECREF(self->key);
43         Py_XDECREF(self->values);
44         Py_XDECREF(self->children);
45         self->ob_type->tp_free(s);
46 }
48 static PyObject *Config_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
49         Config *self;
50         
51         self = (Config *) type->tp_alloc(type, 0);
52         if (self == NULL)
53                 return NULL;
54         
55         self->parent = NULL;
56         self->key = NULL;
57         self->values = NULL;
58         self->children = NULL;
59         return (PyObject *) self;
60 }
62 static int Config_init(PyObject *s, PyObject *args, PyObject *kwds) {
63         PyObject *key = NULL, *parent = NULL, *values = NULL, *children = NULL, *tmp;
64         Config *self = (Config *) s;
65         static char *kwlist[] = {"key", "parent", "values", "children", NULL};
66         
67         if (!PyArg_ParseTupleAndKeywords(args, kwds, "S|OOO", kwlist,
68                         &key, &parent, &values, &children))
69                 return -1;
70         
71         if (values == NULL) {
72                 values = PyTuple_New(0);
73                 PyErr_Clear();
74         }
75         if (children == NULL) {
76                 children = PyTuple_New(0);
77                 PyErr_Clear();
78         }
79         tmp = self->key;
80         Py_INCREF(key);
81         self->key = key;
82         Py_XDECREF(tmp);
83         if (parent != NULL) {
84                 tmp = self->parent;
85                 Py_INCREF(parent);
86                 self->parent = parent;
87                 Py_XDECREF(tmp);
88         }
89         if (values != NULL) {
90                 tmp = self->values;
91                 Py_INCREF(values);
92                 self->values = values;
93                 Py_XDECREF(tmp);
94         }
95         if (children != NULL) {
96                 tmp = self->children;
97                 Py_INCREF(children);
98                 self->children = children;
99                 Py_XDECREF(tmp);
100         }
101         return 0;
104 static PyMemberDef Config_members[] = {
105     {"Parent", T_OBJECT, offsetof(Config, parent), 0, "Parent node"},
106     {"Key", T_OBJECT_EX, offsetof(Config, key), 0, "Keyword of this node"},
107     {"Values", T_OBJECT_EX, offsetof(Config, values), 0, "Values after the key"},
108     {"Children", T_OBJECT_EX, offsetof(Config, children), 0, "Childnodes of this node"},
109     {NULL}
110 };
112 static PyTypeObject ConfigType = {
113     PyObject_HEAD_INIT(NULL)
114     0,                         /* Always 0 */
115     "collectd.Config",         /* tp_name */
116     sizeof(Config),            /* tp_basicsize */
117     0,                         /* Will be filled in later */
118     Config_dealloc,            /* tp_dealloc */
119     0,                         /* tp_print */
120     0,                         /* tp_getattr */
121     0,                         /* tp_setattr */
122     0,                         /* tp_compare */
123     0,                         /* tp_repr */
124     0,                         /* tp_as_number */
125     0,                         /* tp_as_sequence */
126     0,                         /* tp_as_mapping */
127     0,                         /* tp_hash */
128     0,                         /* tp_call */
129     0,                         /* tp_str */
130     0,                         /* tp_getattro */
131     0,                         /* tp_setattro */
132     0,                         /* tp_as_buffer */
133     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
134     "Cool help text later",    /* tp_doc */
135     0,                         /* tp_traverse */
136     0,                         /* tp_clear */
137     0,                         /* tp_richcompare */
138     0,                         /* tp_weaklistoffset */
139     0,                         /* tp_iter */
140     0,                         /* tp_iternext */
141     0,                         /* tp_methods */
142     Config_members,            /* tp_members */
143     0,                         /* tp_getset */
144     0,                         /* tp_base */
145     0,                         /* tp_dict */
146     0,                         /* tp_descr_get */
147     0,                         /* tp_descr_set */
148     0,                         /* tp_dictoffset */
149     Config_init,               /* tp_init */
150     0,                         /* tp_alloc */
151     Config_new                 /* tp_new */
152 };
154 static PyObject *cpy_register_generic(cpy_callback_t **list_head, PyObject *args, PyObject *kwds) {
155         cpy_callback_t *c;
156         const char *name = NULL;
157         PyObject *callback = NULL, *data = NULL;
158         static char *kwlist[] = {"callback", "data", "name", NULL};
159         
160         if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
161         if (PyCallable_Check(callback) == 0) {
162                 PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
163                 return NULL;
164         }
165         if (name == NULL) {
166                 PyObject *mod;
167                 
168                 mod = PyObject_GetAttrString(callback, "__module__");
169                 if (mod != NULL) name = PyString_AsString(mod);
170                 if (name == NULL) {
171                         PyErr_SetString(PyExc_ValueError, "No module name specified and "
172                                 "callback function does not have a \"__module__\" attribute.");
173                         return NULL;
174                 }
175         }
176         Py_INCREF(callback);
177         Py_XINCREF(data);
178         c = malloc(sizeof(*c));
179         c->name = strdup(name);
180         c->callback = callback;
181         c->data = data;
182         c->next = *list_head;
183         *list_head = c;
184         Py_RETURN_NONE;
187 static PyObject *cpy_register_config(PyObject *self, PyObject *args, PyObject *kwds) {
188         return cpy_register_generic(&cpy_config_callbacks, args, kwds);
191 static PyObject *cpy_register_init(PyObject *self, PyObject *args, PyObject *kwds) {
192         return cpy_register_generic(&cpy_init_callbacks, args, kwds);
195 static PyObject *cpy_register_shutdown(PyObject *self, PyObject *args, PyObject *kwds) {
196         return cpy_register_generic(&cpy_shutdown_callbacks, args, kwds);
199 static PyObject *cpy_Error(PyObject *self, PyObject *args) {
200         const char *text;
201         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
202         plugin_log(LOG_ERR, "%s", text);
203         Py_RETURN_NONE;
206 static PyObject *cpy_Warning(PyObject *self, PyObject *args) {
207         const char *text;
208         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
209         plugin_log(LOG_WARNING, "%s", text);
210         Py_RETURN_NONE;
213 static PyObject *cpy_Notice(PyObject *self, PyObject *args) {
214         const char *text;
215         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
216         plugin_log(LOG_NOTICE, "%s", text);
217         Py_RETURN_NONE;
220 static PyObject *cpy_Info(PyObject *self, PyObject *args) {
221         const char *text;
222         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
223         plugin_log(LOG_INFO, "%s", text);
224         Py_RETURN_NONE;
227 static PyObject *cpy_Debug(PyObject *self, PyObject *args) {
228 #ifdef COLLECT_DEBUG
229         const char *text;
230         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
231         plugin_log(LOG_DEBUG, "%s", text);
232 #endif
233         Py_RETURN_NONE;
236 static PyMethodDef cpy_methods[] = {
237         {"Debug", cpy_Debug, METH_VARARGS, "This is an unhelpful text."},
238         {"Info", cpy_Info, METH_VARARGS, "This is an unhelpful text."},
239         {"Notice", cpy_Notice, METH_VARARGS, "This is an unhelpful text."},
240         {"Warning", cpy_Warning, METH_VARARGS, "This is an unhelpful text."},
241         {"Error", cpy_Error, METH_VARARGS, "This is an unhelpful text."},
242         {"register_init", (PyCFunction) cpy_register_init, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
243         {"register_config", (PyCFunction) cpy_register_config, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
244         {"register_shutdown", (PyCFunction) cpy_register_shutdown, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
245         {0, 0, 0, 0}
246 };
248 static int cpy_shutdown(void) {
249         cpy_callback_t *c;
250         PyObject *ret;
251         
252         /* This can happen if the module was loaded but not configured. */
253         if (state != NULL)
254                 PyEval_RestoreThread(state);
256         for (c = cpy_shutdown_callbacks; c; c = c->next) {
257                 if (c->data == NULL)
258                         ret = PyObject_CallObject(c->callback, NULL); /* New reference. */
259                 else
260                         ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
261                 if (ret == NULL)
262                         PyErr_Print(); /* FIXME */
263                 else
264                         Py_DECREF(ret);
265         }
266         Py_Finalize();
267         return 0;
270 static int cpy_init(void) {
271         cpy_callback_t *c;
272         PyObject *ret;
273         
274         PyEval_InitThreads();
275         /* Now it's finally OK to use python threads. */
276         for (c = cpy_init_callbacks; c; c = c->next) {
277                 if (c->data == NULL)
278                         ret = PyObject_CallObject(c->callback, NULL); /* New reference. */
279                 else
280                         ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
281                 if (ret == NULL)
282                         PyErr_Print(); /* FIXME */
283                 else
284                         Py_DECREF(ret);
285         }
286         state = PyEval_SaveThread();
287         return 0;
290 static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
291         int i;
292         PyObject *item, *values, *children, *tmp;
293         
294         if (parent == NULL)
295                 parent = Py_None;
296         
297         values = PyTuple_New(ci->values_num); /* New reference. */
298         for (i = 0; i < ci->values_num; ++i) {
299                 if (ci->values[i].type == OCONFIG_TYPE_STRING) {
300                         PyTuple_SET_ITEM(values, i, PyString_FromString(ci->values[i].value.string));
301                 } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
302                         PyTuple_SET_ITEM(values, i, PyFloat_FromDouble(ci->values[i].value.number));
303                 } else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) {
304                         PyTuple_SET_ITEM(values, i, PyBool_FromLong(ci->values[i].value.boolean));
305                 }
306         }
307         
308         item = PyObject_CallFunction((PyObject *) &ConfigType, "sONO", ci->key, parent, values, Py_None);
309         if (item == NULL)
310                 return NULL;
311         children = PyTuple_New(ci->children_num); /* New reference. */
312         for (i = 0; i < ci->children_num; ++i) {
313                         PyTuple_SET_ITEM(children, i, cpy_oconfig_to_pyconfig(ci->children + i, item));
314         }
315         tmp = ((Config *) item)->children;
316         ((Config *) item)->children = children;
317         Py_XDECREF(tmp);
318         return item;
321 static int cpy_config(oconfig_item_t *ci) {
322         int i;
323         PyObject *sys;
324         PyObject *sys_path;
325         PyObject *module;
326         
327         /* Ok in theory we shouldn't do initialization at this point
328          * but we have to. In order to give python scripts a chance
329          * to register a config callback we need to be able to execute
330          * python code during the config callback so we have to start
331          * the interpreter here. */
332         /* Do *not* use the python "thread" module at this point! */
333         Py_Initialize();
334         
335         PyType_Ready(&ConfigType);
336         sys = PyImport_ImportModule("sys"); /* New reference. */
337         if (sys == NULL) {
338                 ERROR("python module: Unable to import \"sys\" module.");
339                 /* Just print the default python exception text to stderr. */
340                 PyErr_Print();
341                 return 1;
342         }
343         sys_path = PyObject_GetAttrString(sys, "path"); /* New reference. */
344         Py_DECREF(sys);
345         if (sys_path == NULL) {
346                 ERROR("python module: Unable to read \"sys.path\".");
347                 PyErr_Print();
348                 return 1;
349         }
350         module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */
351         PyModule_AddObject(module, "Config", (PyObject *) &ConfigType); /* Steals a reference. */
352         for (i = 0; i < ci->children_num; ++i) {
353                 oconfig_item_t *item = ci->children + i;
354                 
355                 if (strcasecmp(item->key, "ModulePath") == 0) {
356                         char *dir = NULL;
357                         PyObject *dir_object;
358                         
359                         if (cf_util_get_string(item, &dir) != 0) 
360                                 continue;
361                         dir_object = PyString_FromString(dir); /* New reference. */
362                         if (dir_object == NULL) {
363                                 ERROR("python plugin: Unable to convert \"%s\" to "
364                                       "a python object.", dir);
365                                 free(dir);
366                                 PyErr_Print();
367                                 continue;
368                         }
369                         if (PyList_Append(sys_path, dir_object) != 0) {
370                                 ERROR("python plugin: Unable to append \"%s\" to "
371                                       "python module path.", dir);
372                                 PyErr_Print();
373                         }
374                         Py_DECREF(dir_object);
375                         free(dir);
376                 } else if (strcasecmp(item->key, "Import") == 0) {
377                         char *module_name = NULL;
378                         PyObject *module;
379                         
380                         if (cf_util_get_string(item, &module_name) != 0) 
381                                 continue;
382                         module = PyImport_ImportModule(module_name); /* New reference. */
383                         if (module == NULL) {
384                                 ERROR("python plugin: Error importing module \"%s\".", module_name);
385                                 PyErr_Print();
386                         }
387                         free(module_name);
388                         Py_XDECREF(module);
389                 } else if (strcasecmp(item->key, "Module") == 0) {
390                         char *name = NULL;
391                         cpy_callback_t *c;
392                         PyObject *ret;
393                         
394                         if (cf_util_get_string(item, &name) != 0)
395                                 continue;
396                         for (c = cpy_config_callbacks; c; c = c->next) {
397                                 if (strcasecmp(c->name, name) == 0)
398                                         break;
399                         }
400                         if (c == NULL) {
401                                 WARNING("python plugin: Found a configuration for the \"%s\" plugin, "
402                                         "but the plugin isn't loaded or didn't register "
403                                         "a configuration callback.", name);
404                                 free(name);
405                                 continue;
406                         }
407                         free(name);
408                         if (c->data == NULL)
409                                 ret = PyObject_CallFunction(c->callback, "N",
410                                         cpy_oconfig_to_pyconfig(item, NULL)); /* New reference. */
411                         else
412                                 ret = PyObject_CallFunction(c->callback, "NO",
413                                         cpy_oconfig_to_pyconfig(item, NULL), c->data); /* New reference. */
414                         if (ret == NULL)
415                                 PyErr_Print();
416                         else
417                                 Py_DECREF(ret);
418                 } else {
419                         WARNING("python plugin: Ignoring unknown config key \"%s\".", item->key);
420                 }
421         }
422         Py_DECREF(sys_path);
423         return 0;
426 void module_register(void) {
427         plugin_register_complex_config("python", cpy_config);
428         plugin_register_init("python", cpy_init);
429 //      plugin_register_read("python", cna_read);
430         plugin_register_shutdown("python", cpy_shutdown);