Added write callbacks.
[collectd.git] / src / python.c
1 #include <Python.h>
2 #include <structmember.h>
3
4 #include "collectd.h"
5 #include "common.h"
6
7 #include "cpython.h"
8
9 typedef struct cpy_callback_s {
10         char *name;
11         PyObject *callback;
12         PyObject *data;
13         struct cpy_callback_s *next;
14 } cpy_callback_t;
15
16 /* This is our global thread state. Python saves some stuff in thread-local
17  * storage. So if we allow the interpreter to run in the background
18  * (the scriptwriters might have created some threads from python), we have
19  * to save the state so we can resume it later after shutdown. */
20
21 static PyThreadState *state;
22
23 static cpy_callback_t *cpy_config_callbacks;
24 static cpy_callback_t *cpy_init_callbacks;
25 static cpy_callback_t *cpy_shutdown_callbacks;
26
27 static void cpy_destroy_user_data(void *data) {
28         cpy_callback_t *c = data;
29         free(c->name);
30         Py_DECREF(c->callback);
31         Py_XDECREF(c->data);
32         free(c);
33 }
34
35 static void cpy_build_name(char *buf, size_t size, PyObject *callback, const char *name) {
36         const char *module;
37         PyObject *mod = NULL, *n = NULL;
38         
39         if (name != NULL && strchr(name, '.') != NULL) {
40                 snprintf(buf, size, "python.%s", name);
41                 return;
42         }
43         
44         mod = PyObject_GetAttrString(callback, "__module__"); /* New reference. */
45         if (mod != NULL)
46                 module = PyString_AsString(mod);
47         else
48                 module = "collectd";
49         if (name != NULL) {
50                 snprintf(buf, size, "python.%s.%s", module, name);
51                 Py_XDECREF(mod);
52                 return;
53         }
54         
55         n = PyObject_GetAttrString(callback, "__name__"); /* New reference. */
56         if (n != NULL)
57                 name = PyString_AsString(n);
58         
59         if (name != NULL)
60                 snprintf(buf, size, "python.%s.%s", module, name);
61         else
62                 snprintf(buf, size, "python.%s.%p", module, callback);
63         Py_XDECREF(mod);
64         Py_XDECREF(n);
65 }
66
67 static int cpy_write_callback(const data_set_t *ds, const value_list_t *value_list, user_data_t *data) {
68         int i;
69         cpy_callback_t * c = data->data;
70         PyObject *ret, *v, *list;
71
72         CPY_LOCK_THREADS
73                 list = PyList_New(value_list->values_len); /* New reference. */
74                 if (list == NULL) {
75                         PyErr_Print();
76                         CPY_RETURN_FROM_THREADS 0;
77                 }
78                 for (i = 0; i < value_list->values_len; ++i) {
79                         if (ds->ds->type == DS_TYPE_COUNTER) {
80                                 if ((long) value_list->values[i].counter == value_list->values[i].counter)
81                                         PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].counter));
82                                 else
83                                         PyList_SetItem(list, i, PyLong_FromUnsignedLongLong(value_list->values[i].counter));
84                         } else if (ds->ds->type == DS_TYPE_GAUGE) {
85                                 PyList_SetItem(list, i, PyFloat_FromDouble(value_list->values[i].gauge));
86                         } else if (ds->ds->type == DS_TYPE_DERIVE) {
87                                 if ((long) value_list->values[i].derive == value_list->values[i].derive)
88                                         PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].derive));
89                                 else
90                                         PyList_SetItem(list, i, PyLong_FromLongLong(value_list->values[i].derive));
91                         } else if (ds->ds->type == DS_TYPE_ABSOLUTE) {
92                                 if ((long) value_list->values[i].absolute == value_list->values[i].absolute)
93                                         PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].absolute));
94                                 else
95                                         PyList_SetItem(list, i, PyLong_FromUnsignedLongLong(value_list->values[i].absolute));
96                         } else {
97                                 ERROR("cpy_write_callback: Unknown value type %d.", ds->ds->type);
98                                 Py_DECREF(list);
99                                 CPY_RETURN_FROM_THREADS 0;
100                         }
101                         if (PyErr_Occurred() != NULL) {
102                                 PyErr_Print();
103                                 CPY_RETURN_FROM_THREADS 0;
104                         }
105                 }
106                 v = PyObject_CallFunction((PyObject *) &ValuesType, "sOssssdi", value_list->type, list,
107                                 value_list->plugin_instance, value_list->type_instance, value_list->plugin,
108                                 value_list->host, (double) value_list->time, value_list->interval);
109                 Py_DECREF(list);
110                 ret = PyObject_CallFunctionObjArgs(c->callback, v, (void *) 0);
111                 if (ret == NULL) {
112                         /* FIXME */
113                         PyErr_Print();
114                 } else {
115                         Py_DECREF(ret);
116                 }
117         CPY_RELEASE_THREADS
118         return 0;
119 }
120
121 static void cpy_log_callback(int severity, const char *message, user_data_t *data) {
122         cpy_callback_t * c = data->data;
123         PyObject *ret;
124
125         CPY_LOCK_THREADS
126         if (c->data == NULL)
127                 ret = PyObject_CallFunction(c->callback, "is", severity, message); /* New reference. */
128         else
129                 ret = PyObject_CallFunction(c->callback, "isO", severity, message, c->data); /* New reference. */
130
131         if (ret == NULL) {
132                 /* FIXME */
133                 PyErr_Print();
134         } else {
135                 Py_DECREF(ret);
136         }
137         CPY_RELEASE_THREADS
138 }
139
140 static PyObject *cpy_register_generic(cpy_callback_t **list_head, PyObject *args, PyObject *kwds) {
141         cpy_callback_t *c;
142         const char *name = NULL;
143         PyObject *callback = NULL, *data = NULL, *mod = NULL;
144         static char *kwlist[] = {"callback", "data", "name", NULL};
145         
146         if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
147         if (PyCallable_Check(callback) == 0) {
148                 PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
149                 return NULL;
150         }
151         if (name == NULL) {
152                 mod = PyObject_GetAttrString(callback, "__module__"); /* New reference. */
153                 if (mod != NULL) name = PyString_AsString(mod);
154                 if (name == NULL) {
155                         Py_XDECREF(mod);
156                         PyErr_SetString(PyExc_ValueError, "No module name specified and "
157                                 "callback function does not have a \"__module__\" attribute.");
158                         return NULL;
159                 }
160         }
161         Py_INCREF(callback);
162         Py_XINCREF(data);
163         c = malloc(sizeof(*c));
164         c->name = strdup(name);
165         c->callback = callback;
166         c->data = data;
167         c->next = *list_head;
168         *list_head = c;
169         Py_XDECREF(mod);
170         Py_RETURN_NONE;
171 }
172
173 static PyObject *cpy_register_config(PyObject *self, PyObject *args, PyObject *kwds) {
174         return cpy_register_generic(&cpy_config_callbacks, args, kwds);
175 }
176
177 static PyObject *cpy_register_init(PyObject *self, PyObject *args, PyObject *kwds) {
178         return cpy_register_generic(&cpy_init_callbacks, args, kwds);
179 }
180
181 typedef int reg_function_t(const char *name, void *callback, void *data);
182
183 static PyObject *cpy_register_generic_userdata(void *reg, void *handler, PyObject *args, PyObject *kwds) {
184         char buf[512];
185         reg_function_t *register_function = (reg_function_t *) reg;
186         cpy_callback_t *c = NULL;
187         user_data_t *user_data = NULL;
188         const char *name = NULL;
189         PyObject *callback = NULL, *data = NULL;
190         static char *kwlist[] = {"callback", "data", "name", NULL};
191         
192         if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
193         if (PyCallable_Check(callback) == 0) {
194                 PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
195                 return NULL;
196         }
197         cpy_build_name(buf, sizeof(buf), callback, name);
198         
199         Py_INCREF(callback);
200         Py_XINCREF(data);
201         c = malloc(sizeof(*c));
202         c->name = strdup(buf);
203         c->callback = callback;
204         c->data = data;
205         c->next = NULL;
206         user_data = malloc(sizeof(*user_data));
207         user_data->free_func = cpy_destroy_user_data;
208         user_data->data = c;
209         register_function(buf, handler, user_data);
210         return PyString_FromString(buf);
211 }
212
213 static PyObject *cpy_register_log(PyObject *self, PyObject *args, PyObject *kwds) {
214         return cpy_register_generic_userdata(plugin_register_log, cpy_log_callback, args, kwds);
215 }
216
217 static PyObject *cpy_register_write(PyObject *self, PyObject *args, PyObject *kwds) {
218         return cpy_register_generic_userdata(plugin_register_write, cpy_write_callback, args, kwds);
219 }
220
221 static PyObject *cpy_register_shutdown(PyObject *self, PyObject *args, PyObject *kwds) {
222         return cpy_register_generic(&cpy_shutdown_callbacks, args, kwds);
223 }
224
225 static PyObject *cpy_Error(PyObject *self, PyObject *args) {
226         const char *text;
227         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
228         Py_BEGIN_ALLOW_THREADS
229         plugin_log(LOG_ERR, "%s", text);
230         Py_END_ALLOW_THREADS
231         Py_RETURN_NONE;
232 }
233
234 static PyObject *cpy_Warning(PyObject *self, PyObject *args) {
235         const char *text;
236         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
237         Py_BEGIN_ALLOW_THREADS
238         plugin_log(LOG_WARNING, "%s", text);
239         Py_END_ALLOW_THREADS
240         Py_RETURN_NONE;
241 }
242
243 static PyObject *cpy_Notice(PyObject *self, PyObject *args) {
244         const char *text;
245         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
246         Py_BEGIN_ALLOW_THREADS
247         plugin_log(LOG_NOTICE, "%s", text);
248         Py_END_ALLOW_THREADS
249         Py_RETURN_NONE;
250 }
251
252 static PyObject *cpy_Info(PyObject *self, PyObject *args) {
253         const char *text;
254         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
255         Py_BEGIN_ALLOW_THREADS
256         plugin_log(LOG_INFO, "%s", text);
257         Py_END_ALLOW_THREADS
258         Py_RETURN_NONE;
259 }
260
261 static PyObject *cpy_Debug(PyObject *self, PyObject *args) {
262 #ifdef COLLECT_DEBUG
263         const char *text;
264         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
265         plugin_log(LOG_DEBUG, "%s", text);
266 #endif
267         Py_RETURN_NONE;
268 }
269
270 static PyMethodDef cpy_methods[] = {
271         {"Debug", cpy_Debug, METH_VARARGS, "This is an unhelpful text."},
272         {"Info", cpy_Info, METH_VARARGS, "This is an unhelpful text."},
273         {"Notice", cpy_Notice, METH_VARARGS, "This is an unhelpful text."},
274         {"Warning", cpy_Warning, METH_VARARGS, "This is an unhelpful text."},
275         {"Error", cpy_Error, METH_VARARGS, "This is an unhelpful text."},
276         {"register_log", (PyCFunction) cpy_register_log, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
277         {"register_init", (PyCFunction) cpy_register_init, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
278         {"register_config", (PyCFunction) cpy_register_config, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
279         {"register_write", (PyCFunction) cpy_register_write, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
280         {"register_shutdown", (PyCFunction) cpy_register_shutdown, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
281         {0, 0, 0, 0}
282 };
283
284 static int cpy_shutdown(void) {
285         cpy_callback_t *c;
286         PyObject *ret;
287         
288         /* This can happen if the module was loaded but not configured. */
289         if (state != NULL)
290                 PyEval_RestoreThread(state);
291
292         for (c = cpy_shutdown_callbacks; c; c = c->next) {
293                 if (c->data == NULL)
294                         ret = PyObject_CallObject(c->callback, NULL); /* New reference. */
295                 else
296                         ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
297                 if (ret == NULL)
298                         PyErr_Print(); /* FIXME */
299                 else
300                         Py_DECREF(ret);
301         }
302         Py_Finalize();
303         return 0;
304 }
305
306 static int cpy_init(void) {
307         cpy_callback_t *c;
308         PyObject *ret;
309         
310         PyEval_InitThreads();
311         /* Now it's finally OK to use python threads. */
312         for (c = cpy_init_callbacks; c; c = c->next) {
313                 if (c->data == NULL)
314                         ret = PyObject_CallObject(c->callback, NULL); /* New reference. */
315                 else
316                         ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
317                 if (ret == NULL)
318                         PyErr_Print(); /* FIXME */
319                 else
320                         Py_DECREF(ret);
321         }
322         state = PyEval_SaveThread();
323         return 0;
324 }
325
326 static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
327         int i;
328         PyObject *item, *values, *children, *tmp;
329         
330         if (parent == NULL)
331                 parent = Py_None;
332         
333         values = PyTuple_New(ci->values_num); /* New reference. */
334         for (i = 0; i < ci->values_num; ++i) {
335                 if (ci->values[i].type == OCONFIG_TYPE_STRING) {
336                         PyTuple_SET_ITEM(values, i, PyString_FromString(ci->values[i].value.string));
337                 } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
338                         PyTuple_SET_ITEM(values, i, PyFloat_FromDouble(ci->values[i].value.number));
339                 } else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) {
340                         PyTuple_SET_ITEM(values, i, PyBool_FromLong(ci->values[i].value.boolean));
341                 }
342         }
343         
344         item = PyObject_CallFunction((PyObject *) &ConfigType, "sONO", ci->key, parent, values, Py_None);
345         if (item == NULL)
346                 return NULL;
347         children = PyTuple_New(ci->children_num); /* New reference. */
348         for (i = 0; i < ci->children_num; ++i) {
349                         PyTuple_SET_ITEM(children, i, cpy_oconfig_to_pyconfig(ci->children + i, item));
350         }
351         tmp = ((Config *) item)->children;
352         ((Config *) item)->children = children;
353         Py_XDECREF(tmp);
354         return item;
355 }
356
357 static int cpy_config(oconfig_item_t *ci) {
358         int i;
359         PyObject *sys;
360         PyObject *sys_path;
361         PyObject *module;
362         
363         /* Ok in theory we shouldn't do initialization at this point
364          * but we have to. In order to give python scripts a chance
365          * to register a config callback we need to be able to execute
366          * python code during the config callback so we have to start
367          * the interpreter here. */
368         /* Do *not* use the python "thread" module at this point! */
369         Py_Initialize();
370         
371         PyType_Ready(&ConfigType);
372         PyType_Ready(&ValuesType);
373         sys = PyImport_ImportModule("sys"); /* New reference. */
374         if (sys == NULL) {
375                 ERROR("python module: Unable to import \"sys\" module.");
376                 /* Just print the default python exception text to stderr. */
377                 PyErr_Print();
378                 return 1;
379         }
380         sys_path = PyObject_GetAttrString(sys, "path"); /* New reference. */
381         Py_DECREF(sys);
382         if (sys_path == NULL) {
383                 ERROR("python module: Unable to read \"sys.path\".");
384                 PyErr_Print();
385                 return 1;
386         }
387         module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */
388         PyModule_AddObject(module, "Config", (PyObject *) &ConfigType); /* Steals a reference. */
389         PyModule_AddObject(module, "Values", (PyObject *) &ValuesType); /* Steals a reference. */
390         PyModule_AddIntConstant(module, "LOG_DEBUG", LOG_DEBUG);
391         PyModule_AddIntConstant(module, "LOG_INFO", LOG_INFO);
392         PyModule_AddIntConstant(module, "LOG_NOTICE", LOG_NOTICE);
393         PyModule_AddIntConstant(module, "LOG_WARNING", LOG_WARNING);
394         PyModule_AddIntConstant(module, "LOG_ERROR", LOG_ERR);
395         for (i = 0; i < ci->children_num; ++i) {
396                 oconfig_item_t *item = ci->children + i;
397                 
398                 if (strcasecmp(item->key, "ModulePath") == 0) {
399                         char *dir = NULL;
400                         PyObject *dir_object;
401                         
402                         if (cf_util_get_string(item, &dir) != 0) 
403                                 continue;
404                         dir_object = PyString_FromString(dir); /* New reference. */
405                         if (dir_object == NULL) {
406                                 ERROR("python plugin: Unable to convert \"%s\" to "
407                                       "a python object.", dir);
408                                 free(dir);
409                                 PyErr_Print();
410                                 continue;
411                         }
412                         if (PyList_Append(sys_path, dir_object) != 0) {
413                                 ERROR("python plugin: Unable to append \"%s\" to "
414                                       "python module path.", dir);
415                                 PyErr_Print();
416                         }
417                         Py_DECREF(dir_object);
418                         free(dir);
419                 } else if (strcasecmp(item->key, "Import") == 0) {
420                         char *module_name = NULL;
421                         PyObject *module;
422                         
423                         if (cf_util_get_string(item, &module_name) != 0) 
424                                 continue;
425                         module = PyImport_ImportModule(module_name); /* New reference. */
426                         if (module == NULL) {
427                                 ERROR("python plugin: Error importing module \"%s\".", module_name);
428                                 PyErr_Print();
429                         }
430                         free(module_name);
431                         Py_XDECREF(module);
432                 } else if (strcasecmp(item->key, "Module") == 0) {
433                         char *name = NULL;
434                         cpy_callback_t *c;
435                         PyObject *ret;
436                         
437                         if (cf_util_get_string(item, &name) != 0)
438                                 continue;
439                         for (c = cpy_config_callbacks; c; c = c->next) {
440                                 if (strcasecmp(c->name, name) == 0)
441                                         break;
442                         }
443                         if (c == NULL) {
444                                 WARNING("python plugin: Found a configuration for the \"%s\" plugin, "
445                                         "but the plugin isn't loaded or didn't register "
446                                         "a configuration callback.", name);
447                                 free(name);
448                                 continue;
449                         }
450                         free(name);
451                         if (c->data == NULL)
452                                 ret = PyObject_CallFunction(c->callback, "N",
453                                         cpy_oconfig_to_pyconfig(item, NULL)); /* New reference. */
454                         else
455                                 ret = PyObject_CallFunction(c->callback, "NO",
456                                         cpy_oconfig_to_pyconfig(item, NULL), c->data); /* New reference. */
457                         if (ret == NULL)
458                                 PyErr_Print();
459                         else
460                                 Py_DECREF(ret);
461                 } else {
462                         WARNING("python plugin: Ignoring unknown config key \"%s\".", item->key);
463                 }
464         }
465         Py_DECREF(sys_path);
466         return 0;
467 }
468
469 void module_register(void) {
470         plugin_register_complex_config("python", cpy_config);
471         plugin_register_init("python", cpy_init);
472 //      plugin_register_read("python", cna_read);
473         plugin_register_shutdown("python", cpy_shutdown);
474 }