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;
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;
51 self = (Config *) type->tp_alloc(type, 0);
52 if (self == NULL)
53 return NULL;
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};
67 if (!PyArg_ParseTupleAndKeywords(args, kwds, "S|OOO", kwlist,
68 &key, &parent, &values, &children))
69 return -1;
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;
102 }
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};
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;
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;
185 }
187 static PyObject *cpy_register_config(PyObject *self, PyObject *args, PyObject *kwds) {
188 return cpy_register_generic(&cpy_config_callbacks, args, kwds);
189 }
191 static PyObject *cpy_register_init(PyObject *self, PyObject *args, PyObject *kwds) {
192 return cpy_register_generic(&cpy_init_callbacks, args, kwds);
193 }
195 static PyObject *cpy_register_shutdown(PyObject *self, PyObject *args, PyObject *kwds) {
196 return cpy_register_generic(&cpy_shutdown_callbacks, args, kwds);
197 }
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;
204 }
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;
211 }
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;
218 }
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;
225 }
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;
234 }
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;
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;
268 }
270 static int cpy_init(void) {
271 cpy_callback_t *c;
272 PyObject *ret;
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;
288 }
290 static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
291 int i;
292 PyObject *item, *values, *children, *tmp;
294 if (parent == NULL)
295 parent = Py_None;
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 }
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;
319 }
321 static int cpy_config(oconfig_item_t *ci) {
322 int i;
323 PyObject *sys;
324 PyObject *sys_path;
325 PyObject *module;
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();
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;
355 if (strcasecmp(item->key, "ModulePath") == 0) {
356 char *dir = NULL;
357 PyObject *dir_object;
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;
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;
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;
424 }
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);
431 }