Split python module. Added support for cyclic GC runs.
[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_log_callback(int severity, const char *message, user_data_t *data) {
36         cpy_callback_t * c = data->data;
37         PyObject *ret;
38
39         CPY_LOCK_THREADS
40         if (c->data == NULL)
41                 ret = PyObject_CallFunction(c->callback, "is", severity, message); /* New reference. */
42         else
43                 ret = PyObject_CallFunction(c->callback, "isO", severity, message, c->data); /* New reference. */
44
45         if (ret == NULL) {
46                 /* FIXME */
47                 PyErr_Print();
48         } else {
49                 Py_DECREF(ret);
50         }
51         CPY_RELEASE_THREADS
52 }
53
54 static PyObject *cpy_register_generic(cpy_callback_t **list_head, PyObject *args, PyObject *kwds) {
55         cpy_callback_t *c;
56         const char *name = NULL;
57         PyObject *callback = NULL, *data = NULL;
58         static char *kwlist[] = {"callback", "data", "name", NULL};
59         
60         if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
61         if (PyCallable_Check(callback) == 0) {
62                 PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
63                 return NULL;
64         }
65         if (name == NULL) {
66                 PyObject *mod;
67                 
68                 mod = PyObject_GetAttrString(callback, "__module__");
69                 if (mod != NULL) name = PyString_AsString(mod);
70                 if (name == NULL) {
71                         PyErr_SetString(PyExc_ValueError, "No module name specified and "
72                                 "callback function does not have a \"__module__\" attribute.");
73                         return NULL;
74                 }
75         }
76         Py_INCREF(callback);
77         Py_XINCREF(data);
78         c = malloc(sizeof(*c));
79         c->name = strdup(name);
80         c->callback = callback;
81         c->data = data;
82         c->next = *list_head;
83         *list_head = c;
84         Py_RETURN_NONE;
85 }
86
87 static PyObject *cpy_register_config(PyObject *self, PyObject *args, PyObject *kwds) {
88         return cpy_register_generic(&cpy_config_callbacks, args, kwds);
89 }
90
91 static PyObject *cpy_register_init(PyObject *self, PyObject *args, PyObject *kwds) {
92         return cpy_register_generic(&cpy_init_callbacks, args, kwds);
93 }
94
95 static PyObject *cpy_register_log(PyObject *self, PyObject *args, PyObject *kwds) {
96         cpy_callback_t *c = NULL;
97         user_data_t *user_data = NULL;
98         const char *name = NULL;
99         PyObject *callback = NULL, *data = NULL;
100         static char *kwlist[] = {"callback", "data", "name", NULL};
101         
102         if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
103         if (PyCallable_Check(callback) == 0) {
104                 PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
105                 return NULL;
106         }
107         if (name == NULL) {
108                 PyObject *mod;
109                 
110                 mod = PyObject_GetAttrString(callback, "__module__");
111                 if (mod != NULL) name = PyString_AsString(mod);
112                 if (name == NULL) {
113                         PyErr_SetString(PyExc_ValueError, "No module name specified and "
114                                 "callback function does not have a \"__module__\" attribute.");
115                         return NULL;
116                 }
117         }
118         Py_INCREF(callback);
119         Py_XINCREF(data);
120         c = malloc(sizeof(*c));
121         c->name = strdup(name);
122         c->callback = callback;
123         c->data = data;
124         c->next = NULL;
125         user_data = malloc(sizeof(*user_data));
126         user_data->free_func = cpy_destroy_user_data;
127         user_data->data = c;
128         plugin_register_log(name, cpy_log_callback, user_data);
129         Py_RETURN_NONE;
130 }
131
132 static PyObject *cpy_register_shutdown(PyObject *self, PyObject *args, PyObject *kwds) {
133         return cpy_register_generic(&cpy_shutdown_callbacks, args, kwds);
134 }
135
136 static PyObject *cpy_Error(PyObject *self, PyObject *args) {
137         const char *text;
138         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
139         Py_BEGIN_ALLOW_THREADS
140         plugin_log(LOG_ERR, "%s", text);
141         Py_END_ALLOW_THREADS
142         Py_RETURN_NONE;
143 }
144
145 static PyObject *cpy_Warning(PyObject *self, PyObject *args) {
146         const char *text;
147         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
148         Py_BEGIN_ALLOW_THREADS
149         plugin_log(LOG_WARNING, "%s", text);
150         Py_END_ALLOW_THREADS
151         Py_RETURN_NONE;
152 }
153
154 static PyObject *cpy_Notice(PyObject *self, PyObject *args) {
155         const char *text;
156         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
157         Py_BEGIN_ALLOW_THREADS
158         plugin_log(LOG_NOTICE, "%s", text);
159         Py_END_ALLOW_THREADS
160         Py_RETURN_NONE;
161 }
162
163 static PyObject *cpy_Info(PyObject *self, PyObject *args) {
164         const char *text;
165         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
166         Py_BEGIN_ALLOW_THREADS
167         plugin_log(LOG_INFO, "%s", text);
168         Py_END_ALLOW_THREADS
169         Py_RETURN_NONE;
170 }
171
172 static PyObject *cpy_Debug(PyObject *self, PyObject *args) {
173 #ifdef COLLECT_DEBUG
174         const char *text;
175         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
176         plugin_log(LOG_DEBUG, "%s", text);
177 #endif
178         Py_RETURN_NONE;
179 }
180
181 static PyMethodDef cpy_methods[] = {
182         {"Debug", cpy_Debug, METH_VARARGS, "This is an unhelpful text."},
183         {"Info", cpy_Info, METH_VARARGS, "This is an unhelpful text."},
184         {"Notice", cpy_Notice, METH_VARARGS, "This is an unhelpful text."},
185         {"Warning", cpy_Warning, METH_VARARGS, "This is an unhelpful text."},
186         {"Error", cpy_Error, METH_VARARGS, "This is an unhelpful text."},
187         {"register_log", (PyCFunction) cpy_register_log, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
188         {"register_init", (PyCFunction) cpy_register_init, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
189         {"register_config", (PyCFunction) cpy_register_config, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
190         {"register_shutdown", (PyCFunction) cpy_register_shutdown, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
191         {0, 0, 0, 0}
192 };
193
194 static int cpy_shutdown(void) {
195         cpy_callback_t *c;
196         PyObject *ret;
197         
198         /* This can happen if the module was loaded but not configured. */
199         if (state != NULL)
200                 PyEval_RestoreThread(state);
201
202         for (c = cpy_shutdown_callbacks; c; c = c->next) {
203                 if (c->data == NULL)
204                         ret = PyObject_CallObject(c->callback, NULL); /* New reference. */
205                 else
206                         ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
207                 if (ret == NULL)
208                         PyErr_Print(); /* FIXME */
209                 else
210                         Py_DECREF(ret);
211         }
212         Py_Finalize();
213         return 0;
214 }
215
216 static int cpy_init(void) {
217         cpy_callback_t *c;
218         PyObject *ret;
219         
220         PyEval_InitThreads();
221         /* Now it's finally OK to use python threads. */
222         for (c = cpy_init_callbacks; c; c = c->next) {
223                 if (c->data == NULL)
224                         ret = PyObject_CallObject(c->callback, NULL); /* New reference. */
225                 else
226                         ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
227                 if (ret == NULL)
228                         PyErr_Print(); /* FIXME */
229                 else
230                         Py_DECREF(ret);
231         }
232         state = PyEval_SaveThread();
233         return 0;
234 }
235
236 static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
237         int i;
238         PyObject *item, *values, *children, *tmp;
239         
240         if (parent == NULL)
241                 parent = Py_None;
242         
243         values = PyTuple_New(ci->values_num); /* New reference. */
244         for (i = 0; i < ci->values_num; ++i) {
245                 if (ci->values[i].type == OCONFIG_TYPE_STRING) {
246                         PyTuple_SET_ITEM(values, i, PyString_FromString(ci->values[i].value.string));
247                 } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
248                         PyTuple_SET_ITEM(values, i, PyFloat_FromDouble(ci->values[i].value.number));
249                 } else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) {
250                         PyTuple_SET_ITEM(values, i, PyBool_FromLong(ci->values[i].value.boolean));
251                 }
252         }
253         
254         item = PyObject_CallFunction((PyObject *) &ConfigType, "sONO", ci->key, parent, values, Py_None);
255         if (item == NULL)
256                 return NULL;
257         children = PyTuple_New(ci->children_num); /* New reference. */
258         for (i = 0; i < ci->children_num; ++i) {
259                         PyTuple_SET_ITEM(children, i, cpy_oconfig_to_pyconfig(ci->children + i, item));
260         }
261         tmp = ((Config *) item)->children;
262         ((Config *) item)->children = children;
263         Py_XDECREF(tmp);
264         return item;
265 }
266
267 static int cpy_config(oconfig_item_t *ci) {
268         int i;
269         PyObject *sys;
270         PyObject *sys_path;
271         PyObject *module;
272         
273         /* Ok in theory we shouldn't do initialization at this point
274          * but we have to. In order to give python scripts a chance
275          * to register a config callback we need to be able to execute
276          * python code during the config callback so we have to start
277          * the interpreter here. */
278         /* Do *not* use the python "thread" module at this point! */
279         Py_Initialize();
280         
281         PyType_Ready(&ConfigType);
282         sys = PyImport_ImportModule("sys"); /* New reference. */
283         if (sys == NULL) {
284                 ERROR("python module: Unable to import \"sys\" module.");
285                 /* Just print the default python exception text to stderr. */
286                 PyErr_Print();
287                 return 1;
288         }
289         sys_path = PyObject_GetAttrString(sys, "path"); /* New reference. */
290         Py_DECREF(sys);
291         if (sys_path == NULL) {
292                 ERROR("python module: Unable to read \"sys.path\".");
293                 PyErr_Print();
294                 return 1;
295         }
296         module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */
297         PyModule_AddObject(module, "Config", (PyObject *) &ConfigType); /* Steals a reference. */
298         PyModule_AddIntConstant(module, "LOG_DEBUG", LOG_DEBUG);
299         PyModule_AddIntConstant(module, "LOG_INFO", LOG_INFO);
300         PyModule_AddIntConstant(module, "LOG_NOTICE", LOG_NOTICE);
301         PyModule_AddIntConstant(module, "LOG_WARNING", LOG_WARNING);
302         PyModule_AddIntConstant(module, "LOG_ERROR", LOG_ERR);
303         for (i = 0; i < ci->children_num; ++i) {
304                 oconfig_item_t *item = ci->children + i;
305                 
306                 if (strcasecmp(item->key, "ModulePath") == 0) {
307                         char *dir = NULL;
308                         PyObject *dir_object;
309                         
310                         if (cf_util_get_string(item, &dir) != 0) 
311                                 continue;
312                         dir_object = PyString_FromString(dir); /* New reference. */
313                         if (dir_object == NULL) {
314                                 ERROR("python plugin: Unable to convert \"%s\" to "
315                                       "a python object.", dir);
316                                 free(dir);
317                                 PyErr_Print();
318                                 continue;
319                         }
320                         if (PyList_Append(sys_path, dir_object) != 0) {
321                                 ERROR("python plugin: Unable to append \"%s\" to "
322                                       "python module path.", dir);
323                                 PyErr_Print();
324                         }
325                         Py_DECREF(dir_object);
326                         free(dir);
327                 } else if (strcasecmp(item->key, "Import") == 0) {
328                         char *module_name = NULL;
329                         PyObject *module;
330                         
331                         if (cf_util_get_string(item, &module_name) != 0) 
332                                 continue;
333                         module = PyImport_ImportModule(module_name); /* New reference. */
334                         if (module == NULL) {
335                                 ERROR("python plugin: Error importing module \"%s\".", module_name);
336                                 PyErr_Print();
337                         }
338                         free(module_name);
339                         Py_XDECREF(module);
340                 } else if (strcasecmp(item->key, "Module") == 0) {
341                         char *name = NULL;
342                         cpy_callback_t *c;
343                         PyObject *ret;
344                         
345                         if (cf_util_get_string(item, &name) != 0)
346                                 continue;
347                         for (c = cpy_config_callbacks; c; c = c->next) {
348                                 if (strcasecmp(c->name, name) == 0)
349                                         break;
350                         }
351                         if (c == NULL) {
352                                 WARNING("python plugin: Found a configuration for the \"%s\" plugin, "
353                                         "but the plugin isn't loaded or didn't register "
354                                         "a configuration callback.", name);
355                                 free(name);
356                                 continue;
357                         }
358                         free(name);
359                         if (c->data == NULL)
360                                 ret = PyObject_CallFunction(c->callback, "N",
361                                         cpy_oconfig_to_pyconfig(item, NULL)); /* New reference. */
362                         else
363                                 ret = PyObject_CallFunction(c->callback, "NO",
364                                         cpy_oconfig_to_pyconfig(item, NULL), c->data); /* New reference. */
365                         if (ret == NULL)
366                                 PyErr_Print();
367                         else
368                                 Py_DECREF(ret);
369                 } else {
370                         WARNING("python plugin: Ignoring unknown config key \"%s\".", item->key);
371                 }
372         }
373         Py_DECREF(sys_path);
374         return 0;
375 }
376
377 void module_register(void) {
378         plugin_register_complex_config("python", cpy_config);
379         plugin_register_init("python", cpy_init);
380 //      plugin_register_read("python", cna_read);
381         plugin_register_shutdown("python", cpy_shutdown);
382 }