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