1 #include <Python.h>
2 #include <structmember.h>
4 #if HAVE_PTHREAD_H
5 # include <pthread.h>
6 #endif
8 #include "collectd.h"
9 #include "common.h"
11 #include "cpython.h"
13 typedef struct cpy_callback_s {
14 char *name;
15 PyObject *callback;
16 PyObject *data;
17 struct cpy_callback_s *next;
18 } cpy_callback_t;
20 static int do_interactive = 0;
22 /* This is our global thread state. Python saves some stuff in thread-local
23 * storage. So if we allow the interpreter to run in the background
24 * (the scriptwriters might have created some threads from python), we have
25 * to save the state so we can resume it later after shutdown. */
27 static PyThreadState *state;
29 static PyObject *cpy_format_exception;
31 static cpy_callback_t *cpy_config_callbacks;
32 static cpy_callback_t *cpy_init_callbacks;
33 static cpy_callback_t *cpy_shutdown_callbacks;
35 static void cpy_destroy_user_data(void *data) {
36 cpy_callback_t *c = data;
37 free(c->name);
38 Py_DECREF(c->callback);
39 Py_XDECREF(c->data);
40 free(c);
41 }
43 /* You must hold the GIL to call this function!
44 * But if you managed to extract the callback parameter then you probably already do. */
46 static void cpy_build_name(char *buf, size_t size, PyObject *callback, const char *name) {
47 const char *module;
48 PyObject *mod = NULL, *n = NULL;
50 if (name != NULL && strchr(name, '.') != NULL) {
51 snprintf(buf, size, "python.%s", name);
52 return;
53 }
55 mod = PyObject_GetAttrString(callback, "__module__"); /* New reference. */
56 if (mod != NULL)
57 module = PyString_AsString(mod);
58 else
59 module = "collectd";
60 if (name != NULL) {
61 snprintf(buf, size, "python.%s.%s", module, name);
62 Py_XDECREF(mod);
63 return;
64 }
66 n = PyObject_GetAttrString(callback, "__name__"); /* New reference. */
67 if (n != NULL)
68 name = PyString_AsString(n);
70 if (name != NULL)
71 snprintf(buf, size, "python.%s.%s", module, name);
72 else
73 snprintf(buf, size, "python.%s.%p", module, callback);
74 Py_XDECREF(mod);
75 Py_XDECREF(n);
76 }
78 static void cpy_log_exception(const char *context) {
79 int l = 0, i;
80 const char *typename = NULL, *message = NULL;
81 PyObject *type, *value, *traceback, *tn, *m, *list;
83 PyErr_Fetch(&type, &value, &traceback);
84 PyErr_NormalizeException(&type, &value, &traceback);
85 if (type == NULL) return;
86 tn = PyObject_GetAttrString(type, "__name__"); /* New reference. */
87 m = PyObject_GetAttrString(value, "message"); /* New reference. */
88 if (tn != NULL)
89 typename = PyString_AsString(tn);
90 if (m != NULL)
91 message = PyString_AsString(m);
92 if (typename == NULL)
93 typename = "NamelessException";
94 if (message == NULL)
95 message = "N/A";
96 ERROR("Unhandled python exception in %s: %s: %s", context, typename, message);
97 Py_XDECREF(tn);
98 Py_XDECREF(m);
99 if (!cpy_format_exception) {
100 PyErr_Clear();
101 Py_XDECREF(type);
102 Py_XDECREF(value);
103 Py_XDECREF(traceback);
104 return;
105 }
106 if (!traceback) {
107 PyErr_Clear();
108 return;
109 }
110 list = PyObject_CallFunction(cpy_format_exception, "NNN", type, value, traceback);
111 if (list)
112 l = PyObject_Length(list);
113 for (i = 0; i < l; ++i) {
114 char *s;
115 PyObject *line;
117 line = PyList_GET_ITEM(list, i);
118 s = strdup(PyString_AsString(line));
119 Py_DECREF(line);
120 if (s[strlen(s) - 1] == '\n')
121 s[strlen(s) - 1] = 0;
122 ERROR("%s", s);
123 free(s);
124 }
125 PyErr_Clear();
126 }
128 static int cpy_read_callback(user_data_t *data) {
129 cpy_callback_t *c = data->data;
130 PyObject *ret;
132 CPY_LOCK_THREADS
133 ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
134 if (ret == NULL) {
135 cpy_log_exception("read callback");
136 } else {
137 Py_DECREF(ret);
138 }
139 CPY_RELEASE_THREADS
140 return 0;
141 }
143 static int cpy_write_callback(const data_set_t *ds, const value_list_t *value_list, user_data_t *data) {
144 int i;
145 cpy_callback_t *c = data->data;
146 PyObject *ret, *v, *list;
148 CPY_LOCK_THREADS
149 list = PyList_New(value_list->values_len); /* New reference. */
150 if (list == NULL) {
151 cpy_log_exception("write callback");
152 CPY_RETURN_FROM_THREADS 0;
153 }
154 for (i = 0; i < value_list->values_len; ++i) {
155 if (ds->ds->type == DS_TYPE_COUNTER) {
156 if ((long) value_list->values[i].counter == value_list->values[i].counter)
157 PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].counter));
158 else
159 PyList_SetItem(list, i, PyLong_FromUnsignedLongLong(value_list->values[i].counter));
160 } else if (ds->ds->type == DS_TYPE_GAUGE) {
161 PyList_SetItem(list, i, PyFloat_FromDouble(value_list->values[i].gauge));
162 } else if (ds->ds->type == DS_TYPE_DERIVE) {
163 if ((long) value_list->values[i].derive == value_list->values[i].derive)
164 PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].derive));
165 else
166 PyList_SetItem(list, i, PyLong_FromLongLong(value_list->values[i].derive));
167 } else if (ds->ds->type == DS_TYPE_ABSOLUTE) {
168 if ((long) value_list->values[i].absolute == value_list->values[i].absolute)
169 PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].absolute));
170 else
171 PyList_SetItem(list, i, PyLong_FromUnsignedLongLong(value_list->values[i].absolute));
172 } else {
173 ERROR("cpy_write_callback: Unknown value type %d.", ds->ds->type);
174 Py_DECREF(list);
175 CPY_RETURN_FROM_THREADS 0;
176 }
177 if (PyErr_Occurred() != NULL) {
178 cpy_log_exception("value building for write callback");
179 CPY_RETURN_FROM_THREADS 0;
180 }
181 }
182 v = PyObject_CallFunction((PyObject *) &ValuesType, "sOssssdi", value_list->type, list,
183 value_list->plugin_instance, value_list->type_instance, value_list->plugin,
184 value_list->host, (double) value_list->time, value_list->interval);
185 Py_DECREF(list);
186 ret = PyObject_CallFunctionObjArgs(c->callback, v, c->data, (void *) 0); /* New reference. */
187 if (ret == NULL) {
188 cpy_log_exception("write callback");
189 } else {
190 Py_DECREF(ret);
191 }
192 CPY_RELEASE_THREADS
193 return 0;
194 }
196 static void cpy_log_callback(int severity, const char *message, user_data_t *data) {
197 cpy_callback_t * c = data->data;
198 PyObject *ret;
200 CPY_LOCK_THREADS
201 if (c->data == NULL)
202 ret = PyObject_CallFunction(c->callback, "is", severity, message); /* New reference. */
203 else
204 ret = PyObject_CallFunction(c->callback, "isO", severity, message, c->data); /* New reference. */
206 if (ret == NULL) {
207 /* FIXME */
208 /* Do we really want to trigger a log callback because a log callback failed?
209 * Probably not. */
210 PyErr_Print();
211 } else {
212 Py_DECREF(ret);
213 }
214 CPY_RELEASE_THREADS
215 }
217 static PyObject *cpy_register_generic(cpy_callback_t **list_head, PyObject *args, PyObject *kwds) {
218 cpy_callback_t *c;
219 const char *name = NULL;
220 PyObject *callback = NULL, *data = NULL, *mod = NULL;
221 static char *kwlist[] = {"callback", "data", "name", NULL};
223 if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
224 if (PyCallable_Check(callback) == 0) {
225 PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
226 return NULL;
227 }
228 if (name == NULL) {
229 mod = PyObject_GetAttrString(callback, "__module__"); /* New reference. */
230 if (mod != NULL) name = PyString_AsString(mod);
231 if (name == NULL) {
232 Py_XDECREF(mod);
233 PyErr_SetString(PyExc_ValueError, "No module name specified and "
234 "callback function does not have a \"__module__\" attribute.");
235 return NULL;
236 }
237 }
238 Py_INCREF(callback);
239 Py_XINCREF(data);
240 c = malloc(sizeof(*c));
241 c->name = strdup(name);
242 c->callback = callback;
243 c->data = data;
244 c->next = *list_head;
245 *list_head = c;
246 Py_XDECREF(mod);
247 Py_RETURN_NONE;
248 }
250 static PyObject *cpy_register_config(PyObject *self, PyObject *args, PyObject *kwds) {
251 return cpy_register_generic(&cpy_config_callbacks, args, kwds);
252 }
254 static PyObject *cpy_register_init(PyObject *self, PyObject *args, PyObject *kwds) {
255 return cpy_register_generic(&cpy_init_callbacks, args, kwds);
256 }
258 typedef int reg_function_t(const char *name, void *callback, void *data);
260 static PyObject *cpy_register_generic_userdata(void *reg, void *handler, PyObject *args, PyObject *kwds) {
261 char buf[512];
262 reg_function_t *register_function = (reg_function_t *) reg;
263 cpy_callback_t *c = NULL;
264 user_data_t *user_data = NULL;
265 const char *name = NULL;
266 PyObject *callback = NULL, *data = NULL;
267 static char *kwlist[] = {"callback", "data", "name", NULL};
269 if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
270 if (PyCallable_Check(callback) == 0) {
271 PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
272 return NULL;
273 }
274 cpy_build_name(buf, sizeof(buf), callback, name);
276 Py_INCREF(callback);
277 Py_XINCREF(data);
278 c = malloc(sizeof(*c));
279 c->name = strdup(buf);
280 c->callback = callback;
281 c->data = data;
282 c->next = NULL;
283 user_data = malloc(sizeof(*user_data));
284 user_data->free_func = cpy_destroy_user_data;
285 user_data->data = c;
286 register_function(buf, handler, user_data);
287 return PyString_FromString(buf);
288 }
290 static PyObject *cpy_register_read(PyObject *self, PyObject *args, PyObject *kwds) {
291 char buf[512];
292 cpy_callback_t *c = NULL;
293 user_data_t *user_data = NULL;
294 double interval = 0;
295 const char *name = NULL;
296 PyObject *callback = NULL, *data = NULL;
297 struct timespec ts;
298 static char *kwlist[] = {"callback", "interval", "data", "name", NULL};
300 if (PyArg_ParseTupleAndKeywords(args, kwds, "O|dOz", kwlist, &callback, &interval, &data, &name) == 0) return NULL;
301 if (PyCallable_Check(callback) == 0) {
302 PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
303 return NULL;
304 }
305 cpy_build_name(buf, sizeof(buf), callback, name);
307 Py_INCREF(callback);
308 Py_XINCREF(data);
309 c = malloc(sizeof(*c));
310 c->name = strdup(buf);
311 c->callback = callback;
312 c->data = data;
313 c->next = NULL;
314 user_data = malloc(sizeof(*user_data));
315 user_data->free_func = cpy_destroy_user_data;
316 user_data->data = c;
317 ts.tv_sec = interval;
318 ts.tv_nsec = (interval - ts.tv_sec) * 1000000000;
319 plugin_register_complex_read(buf, cpy_read_callback, &ts, user_data);
320 return PyString_FromString(buf);
321 }
323 static PyObject *cpy_register_log(PyObject *self, PyObject *args, PyObject *kwds) {
324 return cpy_register_generic_userdata(plugin_register_log, cpy_log_callback, args, kwds);
325 }
327 static PyObject *cpy_register_write(PyObject *self, PyObject *args, PyObject *kwds) {
328 return cpy_register_generic_userdata(plugin_register_write, cpy_write_callback, args, kwds);
329 }
331 static PyObject *cpy_register_shutdown(PyObject *self, PyObject *args, PyObject *kwds) {
332 return cpy_register_generic(&cpy_shutdown_callbacks, args, kwds);
333 }
335 static PyObject *cpy_Error(PyObject *self, PyObject *args) {
336 const char *text;
337 if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
338 Py_BEGIN_ALLOW_THREADS
339 plugin_log(LOG_ERR, "%s", text);
340 Py_END_ALLOW_THREADS
341 Py_RETURN_NONE;
342 }
344 static PyObject *cpy_Warning(PyObject *self, PyObject *args) {
345 const char *text;
346 if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
347 Py_BEGIN_ALLOW_THREADS
348 plugin_log(LOG_WARNING, "%s", text);
349 Py_END_ALLOW_THREADS
350 Py_RETURN_NONE;
351 }
353 static PyObject *cpy_Notice(PyObject *self, PyObject *args) {
354 const char *text;
355 if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
356 Py_BEGIN_ALLOW_THREADS
357 plugin_log(LOG_NOTICE, "%s", text);
358 Py_END_ALLOW_THREADS
359 Py_RETURN_NONE;
360 }
362 static PyObject *cpy_Info(PyObject *self, PyObject *args) {
363 const char *text;
364 if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
365 Py_BEGIN_ALLOW_THREADS
366 plugin_log(LOG_INFO, "%s", text);
367 Py_END_ALLOW_THREADS
368 Py_RETURN_NONE;
369 }
371 static PyObject *cpy_Debug(PyObject *self, PyObject *args) {
372 #ifdef COLLECT_DEBUG
373 const char *text;
374 if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
375 plugin_log(LOG_DEBUG, "%s", text);
376 #endif
377 Py_RETURN_NONE;
378 }
380 static PyMethodDef cpy_methods[] = {
381 {"debug", cpy_Debug, METH_VARARGS, "This is an unhelpful text."},
382 {"info", cpy_Info, METH_VARARGS, "This is an unhelpful text."},
383 {"notice", cpy_Notice, METH_VARARGS, "This is an unhelpful text."},
384 {"warning", cpy_Warning, METH_VARARGS, "This is an unhelpful text."},
385 {"error", cpy_Error, METH_VARARGS, "This is an unhelpful text."},
386 {"register_log", (PyCFunction) cpy_register_log, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
387 {"register_init", (PyCFunction) cpy_register_init, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
388 {"register_config", (PyCFunction) cpy_register_config, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
389 {"register_read", (PyCFunction) cpy_register_read, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
390 {"register_write", (PyCFunction) cpy_register_write, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
391 {"register_shutdown", (PyCFunction) cpy_register_shutdown, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
392 {0, 0, 0, 0}
393 };
395 static int cpy_shutdown(void) {
396 cpy_callback_t *c;
397 PyObject *ret;
399 /* This can happen if the module was loaded but not configured. */
400 if (state != NULL)
401 PyEval_RestoreThread(state);
403 for (c = cpy_shutdown_callbacks; c; c = c->next) {
404 ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
405 if (ret == NULL)
406 cpy_log_exception("shutdown callback");
407 else
408 Py_DECREF(ret);
409 }
410 Py_Finalize();
411 return 0;
412 }
414 static void *cpy_interactive(void *data) {
415 CPY_LOCK_THREADS
416 if (PyImport_ImportModule("readline") == NULL) {
417 /* This interactive session will suck. */
418 cpy_log_exception("interactive session init");
419 }
420 PyRun_InteractiveLoop(stdin, "<stdin>");
421 CPY_RELEASE_THREADS
422 NOTICE("python: Interactive interpreter exited, stopping collectd ...");
423 raise(SIGINT);
424 return NULL;
425 }
427 static int cpy_init(void) {
428 cpy_callback_t *c;
429 PyObject *ret;
430 static pthread_t thread;
432 PyEval_InitThreads();
433 /* Now it's finally OK to use python threads. */
434 for (c = cpy_init_callbacks; c; c = c->next) {
435 ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
436 if (ret == NULL)
437 cpy_log_exception("init callback");
438 else
439 Py_DECREF(ret);
440 }
441 state = PyEval_SaveThread();
442 if (do_interactive) {
443 if (pthread_create(&thread, NULL, cpy_interactive, NULL)) {
444 ERROR("python: Error creating thread for interactive interpreter.");
445 }
446 }
448 return 0;
449 }
451 static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
452 int i;
453 PyObject *item, *values, *children, *tmp;
455 if (parent == NULL)
456 parent = Py_None;
458 values = PyTuple_New(ci->values_num); /* New reference. */
459 for (i = 0; i < ci->values_num; ++i) {
460 if (ci->values[i].type == OCONFIG_TYPE_STRING) {
461 PyTuple_SET_ITEM(values, i, PyString_FromString(ci->values[i].value.string));
462 } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
463 PyTuple_SET_ITEM(values, i, PyFloat_FromDouble(ci->values[i].value.number));
464 } else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) {
465 PyTuple_SET_ITEM(values, i, PyBool_FromLong(ci->values[i].value.boolean));
466 }
467 }
469 item = PyObject_CallFunction((PyObject *) &ConfigType, "sONO", ci->key, parent, values, Py_None);
470 if (item == NULL)
471 return NULL;
472 children = PyTuple_New(ci->children_num); /* New reference. */
473 for (i = 0; i < ci->children_num; ++i) {
474 PyTuple_SET_ITEM(children, i, cpy_oconfig_to_pyconfig(ci->children + i, item));
475 }
476 tmp = ((Config *) item)->children;
477 ((Config *) item)->children = children;
478 Py_XDECREF(tmp);
479 return item;
480 }
482 static int cpy_config(oconfig_item_t *ci) {
483 int i;
484 PyObject *sys, *tb;
485 PyObject *sys_path;
486 PyObject *module;
488 /* Ok in theory we shouldn't do initialization at this point
489 * but we have to. In order to give python scripts a chance
490 * to register a config callback we need to be able to execute
491 * python code during the config callback so we have to start
492 * the interpreter here. */
493 /* Do *not* use the python "thread" module at this point! */
494 Py_Initialize();
496 PyType_Ready(&ConfigType);
497 PyType_Ready(&ValuesType);
498 sys = PyImport_ImportModule("sys"); /* New reference. */
499 if (sys == NULL) {
500 cpy_log_exception("python initialization");
501 return 1;
502 }
503 sys_path = PyObject_GetAttrString(sys, "path"); /* New reference. */
504 Py_DECREF(sys);
505 if (sys_path == NULL) {
506 cpy_log_exception("python initialization");
507 return 1;
508 }
509 module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */
510 PyModule_AddObject(module, "Config", (PyObject *) &ConfigType); /* Steals a reference. */
511 PyModule_AddObject(module, "Values", (PyObject *) &ValuesType); /* Steals a reference. */
512 PyModule_AddIntConstant(module, "LOG_DEBUG", LOG_DEBUG);
513 PyModule_AddIntConstant(module, "LOG_INFO", LOG_INFO);
514 PyModule_AddIntConstant(module, "LOG_NOTICE", LOG_NOTICE);
515 PyModule_AddIntConstant(module, "LOG_WARNING", LOG_WARNING);
516 PyModule_AddIntConstant(module, "LOG_ERROR", LOG_ERR);
517 for (i = 0; i < ci->children_num; ++i) {
518 oconfig_item_t *item = ci->children + i;
520 if (strcasecmp(item->key, "Interactive") == 0) {
521 if (item->values_num != 1 || item->values[0].type != OCONFIG_TYPE_BOOLEAN)
522 continue;
523 do_interactive = item->values[0].value.boolean;
524 } else if (strcasecmp(item->key, "LogTraces") == 0) {
525 if (item->values_num != 1 || item->values[0].type != OCONFIG_TYPE_BOOLEAN)
526 continue;
527 if (!item->values[0].value.boolean) {
528 Py_XDECREF(cpy_format_exception);
529 cpy_format_exception = NULL;
530 continue;
531 }
532 if (cpy_format_exception)
533 continue;
534 tb = PyImport_ImportModule("traceback"); /* New reference. */
535 if (tb == NULL) {
536 cpy_log_exception("python initialization");
537 continue;
538 }
539 cpy_format_exception = PyObject_GetAttrString(tb, "format_exception"); /* New reference. */
540 Py_DECREF(tb);
541 if (cpy_format_exception == NULL)
542 cpy_log_exception("python initialization");
543 } else if (strcasecmp(item->key, "ModulePath") == 0) {
544 char *dir = NULL;
545 PyObject *dir_object;
547 if (cf_util_get_string(item, &dir) != 0)
548 continue;
549 dir_object = PyString_FromString(dir); /* New reference. */
550 if (dir_object == NULL) {
551 ERROR("python plugin: Unable to convert \"%s\" to "
552 "a python object.", dir);
553 free(dir);
554 cpy_log_exception("python initialization");
555 continue;
556 }
557 if (PyList_Append(sys_path, dir_object) != 0) {
558 ERROR("python plugin: Unable to append \"%s\" to "
559 "python module path.", dir);
560 cpy_log_exception("python initialization");
561 }
562 Py_DECREF(dir_object);
563 free(dir);
564 } else if (strcasecmp(item->key, "Import") == 0) {
565 char *module_name = NULL;
566 PyObject *module;
568 if (cf_util_get_string(item, &module_name) != 0)
569 continue;
570 module = PyImport_ImportModule(module_name); /* New reference. */
571 if (module == NULL) {
572 ERROR("python plugin: Error importing module \"%s\".", module_name);
573 cpy_log_exception("python initialization");
574 PyErr_Print();
575 }
576 free(module_name);
577 Py_XDECREF(module);
578 } else if (strcasecmp(item->key, "Module") == 0) {
579 char *name = NULL;
580 cpy_callback_t *c;
581 PyObject *ret;
583 if (cf_util_get_string(item, &name) != 0)
584 continue;
585 for (c = cpy_config_callbacks; c; c = c->next) {
586 if (strcasecmp(c->name, name) == 0)
587 break;
588 }
589 if (c == NULL) {
590 WARNING("python plugin: Found a configuration for the \"%s\" plugin, "
591 "but the plugin isn't loaded or didn't register "
592 "a configuration callback.", name);
593 free(name);
594 continue;
595 }
596 free(name);
597 if (c->data == NULL)
598 ret = PyObject_CallFunction(c->callback, "N",
599 cpy_oconfig_to_pyconfig(item, NULL)); /* New reference. */
600 else
601 ret = PyObject_CallFunction(c->callback, "NO",
602 cpy_oconfig_to_pyconfig(item, NULL), c->data); /* New reference. */
603 if (ret == NULL)
604 cpy_log_exception("loading module");
605 else
606 Py_DECREF(ret);
607 } else {
608 WARNING("python plugin: Ignoring unknown config key \"%s\".", item->key);
609 }
610 }
611 Py_DECREF(sys_path);
612 return 0;
613 }
615 void module_register(void) {
616 plugin_register_complex_config("python", cpy_config);
617 plugin_register_init("python", cpy_init);
618 // plugin_register_read("python", cna_read);
619 plugin_register_shutdown("python", cpy_shutdown);
620 }