Merge pull request #2346 from trenkel/master
authorPavel Rochnyak <pavel2000@ngs.ru>
Fri, 20 Oct 2017 13:15:57 +0000 (20:15 +0700)
committerGitHub <noreply@github.com>
Fri, 20 Oct 2017 13:15:57 +0000 (20:15 +0700)
Add CollectdError exception which can be thrown without causing a stacktrace to be logged.

1  2 
src/collectd-python.pod
src/cpython.h
src/python.c
src/pyvalues.c

diff --combined src/collectd-python.pod
@@@ -68,8 -68,10 +68,10 @@@ use multiple B<ModulePath> lines to ad
  If a Python script throws an exception it will be logged by collectd with the
  name of the exception and the message. If you set this option to true it will
  also log the full stacktrace just like the default output of an interactive
- Python interpreter. This should probably be set to false most of the time but
- is very useful for development and debugging of new modules.
+ Python interpreter. This does not apply to the CollectError exception, which
+ will never log a stacktrace.
+ This should probably be set to false most of the time but is very useful for
+ development and debugging of new modules.
  
  =item B<Interactive> I<bool>
  
@@@ -248,6 -250,18 +250,18 @@@ collectd you're done
  The following complex types are used to pass values between the Python plugin
  and collectd:
  
+ =head2 CollectdError
+ This is an exception. If any Python script raises this exception it will
+ still be treated like an error by collectd but it will be logged as a
+ warning instead of an error and it will never generate a stacktrace.
+  class CollectdError(Exception)
+ Basic exception for collectd Python scripts.
+ Throwing this exception will not cause a stacktrace to be logged, even if
+ LogTraces is enabled in the config.
  =head2 Signed
  
  The Signed class is just a long. It has all its methods and behaves exactly
@@@ -475,7 -489,7 +489,7 @@@ Methods defined here
  
  =over 4
  
 -=item B<dispatch>([type][, values][, plugin_instance][, type_instance][, plugin][, host][, time][, interval]) -> None.  Dispatch a value list.
 +=item B<dispatch>([type][, message][, plugin_instance][, type_instance][, plugin][, host][, time][, severity][, meta]) -> None.  Dispatch a notification.
  
  Dispatch this instance to the collectd process. The object has members for each
  of the possible arguments for this method. For a detailed explanation of these
@@@ -501,16 -515,6 +515,16 @@@ generated
  The severity of this notification. Assign or compare to I<NOTIF_FAILURE>,
  I<NOTIF_WARNING> or I<NOTIF_OKAY>.
  
 +=item meta
 +
 +These are the meta data for the Notification object.
 +It has to be a dictionary of numbers, strings or bools. All keys must be
 +strings. I<int> and I<long> objects will be dispatched as signed integers unless
 +they are between 2**63 and 2**64-1, which will result in a unsigned integer.
 +One of these storage classes can be forced by using the classes
 +B<collectd.Signed> and B<collectd.Unsigned>. A meta object received by a
 +notification callback will always contain B<Signed> or B<Unsigned> objects.
 +
  =back
  
  =head1 FUNCTIONS
diff --combined src/cpython.h
@@@ -144,17 -144,21 +144,21 @@@ void cpy_log_exception(const char *cont
  /* Python object declarations. */
  
  typedef struct {
+   // clang-format off
    PyObject_HEAD         /* No semicolon! */
-       PyObject *parent; /* Config */
+   PyObject *parent;     /* Config */
    PyObject *key;        /* String */
    PyObject *values;     /* Sequence */
    PyObject *children;   /* Sequence */
+   // clang-format on
  } Config;
  extern PyTypeObject ConfigType;
  
  typedef struct {
+   // clang-format off
    PyObject_HEAD /* No semicolon! */
-       double time;
+   double time;
+   // clang-format on
    char host[DATA_MAX_NAME_LEN];
    char plugin[DATA_MAX_NAME_LEN];
    char plugin_instance[DATA_MAX_NAME_LEN];
@@@ -177,7 -181,6 +181,7 @@@ extern PyTypeObject ValuesType
  
  typedef struct {
    PluginData data;
 +  PyObject *meta;   /* dict */
    int severity;
    char message[NOTIF_MAX_MSG_LEN];
  } Notification;
diff --combined src/python.c
@@@ -233,6 -233,12 +233,12 @@@ static char reg_shutdown_doc[] 
      "The callback function will be called with no parameters except for\n"
      "    data if it was supplied.";
  
+ static char CollectdError_doc[] =
+     "Basic exception for collectd Python scripts.\n"
+     "\n"
+     "Throwing this exception will not cause a stacktrace to be logged, \n"
+     "even if LogTraces is enabled in the config.";
  static pthread_t main_thread;
  static PyOS_sighandler_t python_sigint_handler;
  static _Bool do_interactive = 0;
  
  static PyThreadState *state;
  
- static PyObject *sys_path, *cpy_format_exception;
+ static PyObject *sys_path, *cpy_format_exception, *CollectdError;
  
  static cpy_callback_t *cpy_config_callbacks;
  static cpy_callback_t *cpy_init_callbacks;
@@@ -300,7 -306,7 +306,7 @@@ static void cpy_build_name(char *buf, s
  }
  
  void cpy_log_exception(const char *context) {
-   int l = 0;
+   int l = 0, collectd_error;
    const char *typename = NULL, *message = NULL;
    PyObject *type, *value, *traceback, *tn, *m, *list;
  
    PyErr_NormalizeException(&type, &value, &traceback);
    if (type == NULL)
      return;
+   collectd_error = PyErr_GivenExceptionMatches(value, CollectdError);
    tn = PyObject_GetAttrString(type, "__name__"); /* New reference. */
    m = PyObject_Str(value);                       /* New reference. */
    if (tn != NULL)
      typename = "NamelessException";
    if (message == NULL)
      message = "N/A";
-   Py_BEGIN_ALLOW_THREADS ERROR("Unhandled python exception in %s: %s: %s",
-                                context, typename, message);
-   Py_END_ALLOW_THREADS Py_XDECREF(tn);
+   Py_BEGIN_ALLOW_THREADS;
+   if (collectd_error) {
+     WARNING("%s in %s: %s", typename, context, message);
+   } else {
+     ERROR("Unhandled python exception in %s: %s: %s", context, typename,
+           message);
+   }
+   Py_END_ALLOW_THREADS;
+   Py_XDECREF(tn);
    Py_XDECREF(m);
-   if (!cpy_format_exception || !traceback) {
+   if (!cpy_format_exception || !traceback || collectd_error) {
      PyErr_Clear();
      Py_DECREF(type);
      Py_XDECREF(value);
      if (cpy[strlen(cpy) - 1] == '\n')
        cpy[strlen(cpy) - 1] = 0;
  
-     Py_BEGIN_ALLOW_THREADS ERROR("%s", cpy);
-     Py_END_ALLOW_THREADS
+     Py_BEGIN_ALLOW_THREADS;
+     ERROR("%s", cpy);
+     Py_END_ALLOW_THREADS;
  
-         free(cpy);
+     free(cpy);
    }
  
    Py_XDECREF(list);
@@@ -410,9 -424,10 +424,10 @@@ static int cpy_write_callback(const dat
        PyList_SetItem(
            list, i, PyLong_FromUnsignedLongLong(value_list->values[i].absolute));
      } else {
-       Py_BEGIN_ALLOW_THREADS ERROR("cpy_write_callback: Unknown value type %d.",
-                                    ds->ds[i].type);
-       Py_END_ALLOW_THREADS Py_DECREF(list);
+       Py_BEGIN_ALLOW_THREADS;
+       ERROR("cpy_write_callback: Unknown value type %d.", ds->ds[i].type);
+       Py_END_ALLOW_THREADS;
+       Py_DECREF(list);
        CPY_RETURN_FROM_THREADS 0;
      }
      if (PyErr_Occurred() != NULL) {
    }
    dict = PyDict_New(); /* New reference. */
    if (value_list->meta) {
 -    char **table;
 +    char **table = NULL;
      meta_data_t *meta = value_list->meta;
  
      int num = meta_data_toc(meta, &table);
        } else if (type == MD_TYPE_SIGNED_INT) {
          if (meta_data_get_signed_int(meta, table[i], &si))
            continue;
 -        temp = PyObject_CallFunctionObjArgs((void *)&SignedType,
 -                                            PyLong_FromLongLong(si),
 +        PyObject *sival = PyLong_FromLongLong(si); /* New reference */
 +        temp = PyObject_CallFunctionObjArgs((void *)&SignedType, sival,
                                              (void *)0); /* New reference. */
          PyDict_SetItemString(dict, table[i], temp);
          Py_XDECREF(temp);
 +        Py_XDECREF(sival);
        } else if (type == MD_TYPE_UNSIGNED_INT) {
          if (meta_data_get_unsigned_int(meta, table[i], &ui))
            continue;
 -        temp = PyObject_CallFunctionObjArgs((void *)&UnsignedType,
 -                                            PyLong_FromUnsignedLongLong(ui),
 +        PyObject *uval = PyLong_FromUnsignedLongLong(ui); /* New reference */
 +        temp = PyObject_CallFunctionObjArgs((void *)&UnsignedType, uval,
                                              (void *)0); /* New reference. */
          PyDict_SetItemString(dict, table[i], temp);
          Py_XDECREF(temp);
 +        Py_XDECREF(uval);
        } else if (type == MD_TYPE_DOUBLE) {
          if (meta_data_get_double(meta, table[i], &d))
            continue;
@@@ -512,39 -525,6 +527,39 @@@ static int cpy_notification_callback(co
    Notification *n;
  
    CPY_LOCK_THREADS
 +  PyObject *dict = PyDict_New(); /* New reference. */
 +  for (notification_meta_t *meta = notification->meta; meta != NULL;
 +       meta = meta->next) {
 +    PyObject *temp = NULL;
 +    if (meta->type == NM_TYPE_STRING) {
 +      temp = cpy_string_to_unicode_or_bytes(
 +          meta->nm_value.nm_string); /* New reference. */
 +      PyDict_SetItemString(dict, meta->name, temp);
 +      Py_XDECREF(temp);
 +    } else if (meta->type == NM_TYPE_SIGNED_INT) {
 +      PyObject *sival = PyLong_FromLongLong(meta->nm_value.nm_signed_int);
 +      temp = PyObject_CallFunctionObjArgs((void *)&SignedType, sival,
 +                                          (void *)0); /* New reference. */
 +      PyDict_SetItemString(dict, meta->name, temp);
 +      Py_XDECREF(temp);
 +      Py_XDECREF(sival);
 +    } else if (meta->type == NM_TYPE_UNSIGNED_INT) {
 +      PyObject *uval =
 +          PyLong_FromUnsignedLongLong(meta->nm_value.nm_unsigned_int);
 +      temp = PyObject_CallFunctionObjArgs((void *)&UnsignedType, uval,
 +                                          (void *)0); /* New reference. */
 +      PyDict_SetItemString(dict, meta->name, temp);
 +      Py_XDECREF(temp);
 +      Py_XDECREF(uval);
 +    } else if (meta->type == NM_TYPE_DOUBLE) {
 +      temp = PyFloat_FromDouble(meta->nm_value.nm_double); /* New reference. */
 +      PyDict_SetItemString(dict, meta->name, temp);
 +      Py_XDECREF(temp);
 +    } else if (meta->type == NM_TYPE_BOOLEAN) {
 +      PyDict_SetItemString(dict, meta->name,
 +                           meta->nm_value.nm_boolean ? Py_True : Py_False);
 +    }
 +  }
    notify = Notification_New(); /* New reference. */
    n = (Notification *)notify;
    sstrncpy(n->data.host, notification->host, sizeof(n->data.host));
    n->data.time = CDTIME_T_TO_DOUBLE(notification->time);
    sstrncpy(n->message, notification->message, sizeof(n->message));
    n->severity = notification->severity;
 +  Py_CLEAR(n->meta);
 +  n->meta = dict; /* Steals a reference. */
    ret = PyObject_CallFunctionObjArgs(c->callback, n, c->data,
                                       (void *)0); /* New reference. */
    Py_XDECREF(notify);
@@@ -688,9 -666,8 +703,9 @@@ static PyObject *cpy_get_dataset(PyObje
    for (size_t i = 0; i < ds->ds_num; ++i) {
      tuple = PyTuple_New(4);
      PyTuple_SET_ITEM(tuple, 0, cpy_string_to_unicode_or_bytes(ds->ds[i].name));
 -    PyTuple_SET_ITEM(tuple, 1, cpy_string_to_unicode_or_bytes(
 -                                   DS_TYPE_TO_STRING(ds->ds[i].type)));
 +    PyTuple_SET_ITEM(
 +        tuple, 1,
 +        cpy_string_to_unicode_or_bytes(DS_TYPE_TO_STRING(ds->ds[i].type)));
      PyTuple_SET_ITEM(tuple, 2, float_or_none(ds->ds[i].min));
      PyTuple_SET_ITEM(tuple, 3, float_or_none(ds->ds[i].max));
      PyList_SET_ITEM(list, i, tuple);
@@@ -706,8 -683,10 +721,10 @@@ static PyObject *cpy_flush(PyObject *se
    if (PyArg_ParseTupleAndKeywords(args, kwds, "|etiet", kwlist, NULL, &plugin,
                                    &timeout, NULL, &identifier) == 0)
      return NULL;
-   Py_BEGIN_ALLOW_THREADS plugin_flush(plugin, timeout, identifier);
-   Py_END_ALLOW_THREADS PyMem_Free(plugin);
+   Py_BEGIN_ALLOW_THREADS;
+   plugin_flush(plugin, timeout, identifier);
+   Py_END_ALLOW_THREADS;
+   PyMem_Free(plugin);
    PyMem_Free(identifier);
    Py_RETURN_NONE;
  }
@@@ -758,8 -737,7 +775,8 @@@ static PyObject *cpy_register_generic_u
  
    register_function(buf, handler,
                      &(user_data_t){
 -                        .data = c, .free_func = cpy_destroy_user_data,
 +                        .data = c,
 +                        .free_func = cpy_destroy_user_data,
                      });
  
    ++cpy_num_callbacks;
@@@ -802,8 -780,7 +819,8 @@@ static PyObject *cpy_register_read(PyOb
        /* group = */ "python", buf, cpy_read_callback,
        DOUBLE_TO_CDTIME_T(interval),
        &(user_data_t){
 -          .data = c, .free_func = cpy_destroy_user_data,
 +          .data = c,
 +          .free_func = cpy_destroy_user_data,
        });
    ++cpy_num_callbacks;
    return cpy_string_to_unicode_or_bytes(buf);
@@@ -843,8 -820,10 +860,10 @@@ static PyObject *cpy_error(PyObject *se
    char *text;
    if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
      return NULL;
-   Py_BEGIN_ALLOW_THREADS plugin_log(LOG_ERR, "%s", text);
-   Py_END_ALLOW_THREADS PyMem_Free(text);
+   Py_BEGIN_ALLOW_THREADS;
+   plugin_log(LOG_ERR, "%s", text);
+   Py_END_ALLOW_THREADS;
+   PyMem_Free(text);
    Py_RETURN_NONE;
  }
  
@@@ -852,8 -831,10 +871,10 @@@ static PyObject *cpy_warning(PyObject *
    char *text;
    if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
      return NULL;
-   Py_BEGIN_ALLOW_THREADS plugin_log(LOG_WARNING, "%s", text);
-   Py_END_ALLOW_THREADS PyMem_Free(text);
+   Py_BEGIN_ALLOW_THREADS;
+   plugin_log(LOG_WARNING, "%s", text);
+   Py_END_ALLOW_THREADS;
+   PyMem_Free(text);
    Py_RETURN_NONE;
  }
  
@@@ -861,8 -842,10 +882,10 @@@ static PyObject *cpy_notice(PyObject *s
    char *text;
    if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
      return NULL;
-   Py_BEGIN_ALLOW_THREADS plugin_log(LOG_NOTICE, "%s", text);
-   Py_END_ALLOW_THREADS PyMem_Free(text);
+   Py_BEGIN_ALLOW_THREADS;
+   plugin_log(LOG_NOTICE, "%s", text);
+   Py_END_ALLOW_THREADS;
+   PyMem_Free(text);
    Py_RETURN_NONE;
  }
  
@@@ -870,8 -853,10 +893,10 @@@ static PyObject *cpy_info(PyObject *sel
    char *text;
    if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
      return NULL;
-   Py_BEGIN_ALLOW_THREADS plugin_log(LOG_INFO, "%s", text);
-   Py_END_ALLOW_THREADS PyMem_Free(text);
+   Py_BEGIN_ALLOW_THREADS;
+   plugin_log(LOG_INFO, "%s", text);
+   Py_END_ALLOW_THREADS;
+   PyMem_Free(text);
    Py_RETURN_NONE;
  }
  
@@@ -880,8 -865,10 +905,10 @@@ static PyObject *cpy_debug(PyObject *se
    char *text;
    if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
      return NULL;
-   Py_BEGIN_ALLOW_THREADS plugin_log(LOG_DEBUG, "%s", text);
-   Py_END_ALLOW_THREADS PyMem_Free(text);
+   Py_BEGIN_ALLOW_THREADS;
+   plugin_log(LOG_DEBUG, "%s", text);
+   Py_END_ALLOW_THREADS;
+   PyMem_Free(text);
  #endif
    Py_RETURN_NONE;
  }
@@@ -1063,13 -1050,14 +1090,14 @@@ static int cpy_shutdown(void) 
    }
    PyErr_Print();
  
-   Py_BEGIN_ALLOW_THREADS cpy_unregister_list(&cpy_config_callbacks);
+   Py_BEGIN_ALLOW_THREADS;
+   cpy_unregister_list(&cpy_config_callbacks);
    cpy_unregister_list(&cpy_init_callbacks);
    cpy_unregister_list(&cpy_shutdown_callbacks);
    cpy_shutdown_triggered = 1;
-   Py_END_ALLOW_THREADS
+   Py_END_ALLOW_THREADS;
  
-       if (!cpy_num_callbacks) {
+   if (!cpy_num_callbacks) {
      Py_Finalize();
      return 0;
    }
@@@ -1172,9 -1160,8 +1200,9 @@@ static PyObject *cpy_oconfig_to_pyconfi
    values = PyTuple_New(ci->values_num); /* New reference. */
    for (int i = 0; i < ci->values_num; ++i) {
      if (ci->values[i].type == OCONFIG_TYPE_STRING) {
 -      PyTuple_SET_ITEM(values, i, cpy_string_to_unicode_or_bytes(
 -                                      ci->values[i].value.string));
 +      PyTuple_SET_ITEM(
 +          values, i,
 +          cpy_string_to_unicode_or_bytes(ci->values[i].value.string));
      } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
        PyTuple_SET_ITEM(values, i,
                         PyFloat_FromDouble(ci->values[i].value.number));
@@@ -1212,7 -1199,7 +1240,7 @@@ PyMODINIT_FUNC PyInit_collectd(void) 
  
  static int cpy_init_python(void) {
    PyOS_sighandler_t cur_sig;
-   PyObject *sys;
+   PyObject *sys, *errordict;
    PyObject *module;
  
  #ifdef IS_PY3K
    PyType_Ready(&SignedType);
    UnsignedType.tp_base = &PyLong_Type;
    PyType_Ready(&UnsignedType);
+   errordict = PyDict_New();
+   PyDict_SetItemString(
+       errordict, "__doc__",
+       cpy_string_to_unicode_or_bytes(CollectdError_doc)); /* New reference. */
+   CollectdError = PyErr_NewException("collectd.CollectdError", NULL, errordict);
    sys = PyImport_ImportModule("sys"); /* New reference. */
    if (sys == NULL) {
      cpy_log_exception("python initialization");
                       (void *)&SignedType); /* Steals a reference. */
    PyModule_AddObject(module, "Unsigned",
                       (void *)&UnsignedType); /* Steals a reference. */
+   Py_XINCREF(CollectdError);
+   PyModule_AddObject(module, "CollectdError",
+                      CollectdError); /* Steals a reference. */
    PyModule_AddIntConstant(module, "LOG_DEBUG", LOG_DEBUG);
    PyModule_AddIntConstant(module, "LOG_INFO", LOG_INFO);
    PyModule_AddIntConstant(module, "LOG_NOTICE", LOG_NOTICE);
diff --combined src/pyvalues.c
  
  #include "cpython.h"
  
 +typedef struct {
 +  int (*add_string)(void *, const char *, const char *);
 +  int (*add_signed_int)(void *, const char *, int64_t);
 +  int (*add_unsigned_int)(void *, const char *, uint64_t);
 +  int (*add_double)(void *, const char *, double);
 +  int (*add_boolean)(void *, const char *, _Bool);
 +} cpy_build_meta_handler_t;
 +
  #define FreeAll()                                                              \
    do {                                                                         \
      PyMem_Free(type);                                                          \
@@@ -465,26 -457,26 +465,26 @@@ static int Values_init(PyObject *s, PyO
    return 0;
  }
  
 -static meta_data_t *cpy_build_meta(PyObject *meta) {
 +static int cpy_build_meta_generic(PyObject *meta,
 +                                  cpy_build_meta_handler_t *meta_func,
 +                                  void *m) {
    int s;
 -  meta_data_t *m = NULL;
    PyObject *l;
  
    if ((meta == NULL) || (meta == Py_None))
 -    return NULL;
 +    return -1;
  
    l = PyDict_Items(meta); /* New reference. */
    if (!l) {
      cpy_log_exception("building meta data");
 -    return NULL;
 +    return -1;
    }
    s = PyList_Size(l);
    if (s <= 0) {
      Py_XDECREF(l);
 -    return NULL;
 +    return -1;
    }
  
 -  m = meta_data_create();
    for (int i = 0; i < s; ++i) {
      const char *string, *keystring;
      PyObject *key, *value, *item, *tmp;
      value = PyTuple_GET_ITEM(item, 1);
      Py_INCREF(value);
      if (value == Py_True) {
 -      meta_data_add_boolean(m, keystring, 1);
 +      meta_func->add_boolean(m, keystring, 1);
      } else if (value == Py_False) {
 -      meta_data_add_boolean(m, keystring, 0);
 +      meta_func->add_boolean(m, keystring, 0);
      } else if (PyFloat_Check(value)) {
 -      meta_data_add_double(m, keystring, PyFloat_AsDouble(value));
 +      meta_func->add_double(m, keystring, PyFloat_AsDouble(value));
      } else if (PyObject_TypeCheck(value, &SignedType)) {
        long long int lli;
        lli = PyLong_AsLongLong(value);
        if (!PyErr_Occurred() && (lli == (int64_t)lli))
 -        meta_data_add_signed_int(m, keystring, lli);
 +        meta_func->add_signed_int(m, keystring, lli);
      } else if (PyObject_TypeCheck(value, &UnsignedType)) {
        long long unsigned llu;
        llu = PyLong_AsUnsignedLongLong(value);
        if (!PyErr_Occurred() && (llu == (uint64_t)llu))
 -        meta_data_add_unsigned_int(m, keystring, llu);
 +        meta_func->add_unsigned_int(m, keystring, llu);
      } else if (PyNumber_Check(value)) {
        long long int lli;
        long long unsigned llu;
        tmp = PyNumber_Long(value);
        lli = PyLong_AsLongLong(tmp);
        if (!PyErr_Occurred() && (lli == (int64_t)lli)) {
 -        meta_data_add_signed_int(m, keystring, lli);
 +        meta_func->add_signed_int(m, keystring, lli);
        } else {
          PyErr_Clear();
          llu = PyLong_AsUnsignedLongLong(tmp);
          if (!PyErr_Occurred() && (llu == (uint64_t)llu))
 -          meta_data_add_unsigned_int(m, keystring, llu);
 +          meta_func->add_unsigned_int(m, keystring, llu);
        }
        Py_XDECREF(tmp);
      } else {
        string = cpy_unicode_or_bytes_to_string(&value);
        if (string) {
 -        meta_data_add_string(m, keystring, string);
 +        meta_func->add_string(m, keystring, string);
        } else {
          PyErr_Clear();
          tmp = PyObject_Str(value);
          string = cpy_unicode_or_bytes_to_string(&tmp);
          if (string)
 -          meta_data_add_string(m, keystring, string);
 +          meta_func->add_string(m, keystring, string);
          Py_XDECREF(tmp);
        }
      }
      Py_DECREF(key);
    }
    Py_XDECREF(l);
 +  return 0;
 +}
 +
 +#define CPY_BUILD_META_FUNC(meta_type, func, val_type)                         \
 +  static int cpy_##func(void *meta, const char *key, val_type val) {           \
 +    return func((meta_type *)meta, key, val);                                  \
 +  }
 +
 +#define CPY_BUILD_META_HANDLER(func_prefix, meta_type)                         \
 +  CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_string, const char *)       \
 +  CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_signed_int, int64_t)        \
 +  CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_unsigned_int, uint64_t)     \
 +  CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_double, double)             \
 +  CPY_BUILD_META_FUNC(meta_type, func_prefix##_add_boolean, _Bool)             \
 +                                                                               \
 +  static cpy_build_meta_handler_t cpy_##func_prefix = {                        \
 +      .add_string = cpy_##func_prefix##_add_string,                            \
 +      .add_signed_int = cpy_##func_prefix##_add_signed_int,                    \
 +      .add_unsigned_int = cpy_##func_prefix##_add_unsigned_int,                \
 +      .add_double = cpy_##func_prefix##_add_double,                            \
 +      .add_boolean = cpy_##func_prefix##_add_boolean}
 +
 +CPY_BUILD_META_HANDLER(meta_data, meta_data_t);
 +CPY_BUILD_META_HANDLER(plugin_notification_meta, notification_t);
 +
 +static meta_data_t *cpy_build_meta(PyObject *meta) {
 +  meta_data_t *m = meta_data_create();
 +  if (cpy_build_meta_generic(meta, &cpy_meta_data, (void *)m) < 0) {
 +    meta_data_destroy(m);
 +    return NULL;
 +  }
    return m;
  }
  
 +static void cpy_build_notification_meta(notification_t *n, PyObject *meta) {
 +  cpy_build_meta_generic(meta, &cpy_plugin_notification_meta, (void *)n);
 +}
 +
  static PyObject *Values_dispatch(Values *self, PyObject *args, PyObject *kwds) {
    int ret;
    const data_set_t *ds;
@@@ -953,17 -910,6 +953,17 @@@ PyTypeObject ValuesType = 
      Values_new       /* tp_new */
  };
  
 +static char notification_meta_doc[] =
 +    "These are the meta data for the Notification object.\n"
 +    "It has to be a dictionary of numbers, strings or bools. All keys must be\n"
 +    "strings. int and long objects will be dispatched as signed integers "
 +    "unless\n"
 +    "they are between 2**63 and 2**64-1, which will result in an unsigned "
 +    "integer.\n"
 +    "One of these storage classes can be forced by using the classes\n"
 +    "collectd.Signed and collectd.Unsigned. A meta object received by a\n"
 +    "notification callback will always contain Signed or Unsigned objects.";
 +
  static char severity_doc[] =
      "The severity of this notification. Assign or compare to\n"
      "NOTIF_FAILURE, NOTIF_WARNING or NOTIF_OKAY.";
@@@ -985,17 -931,16 +985,17 @@@ static int Notification_init(PyObject *
    int severity = 0;
    double time = 0;
    char *message = NULL;
 +  PyObject *meta = NULL;
    char *type = NULL, *plugin_instance = NULL, *type_instance = NULL,
         *plugin = NULL, *host = NULL;
 -  static char *kwlist[] = {"type",          "message",  "plugin_instance",
 -                           "type_instance", "plugin",   "host",
 -                           "time",          "severity", NULL};
 +  static char *kwlist[] = {
 +      "type", "message", "plugin_instance", "type_instance", "plugin",
 +      "host", "time",    "severity",        "meta",          NULL};
  
 -  if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdi", kwlist, NULL,
 -                                   &type, NULL, &message, NULL,
 -                                   &plugin_instance, NULL, &type_instance, NULL,
 -                                   &plugin, NULL, &host, &time, &severity))
 +  if (!PyArg_ParseTupleAndKeywords(
 +          args, kwds, "|etetetetetetdiO", kwlist, NULL, &type, NULL, &message,
 +          NULL, &plugin_instance, NULL, &type_instance, NULL, &plugin, NULL,
 +          &host, &time, &severity, &meta))
      return -1;
  
    if (type && plugin_get_ds(type) == NULL) {
  
    FreeAll();
    PyMem_Free(message);
 +
 +  if (meta == NULL) {
 +    meta = PyDict_New();
 +    PyErr_Clear();
 +  } else {
 +    Py_INCREF(meta);
 +  }
 +
 +  PyObject *tmp = self->meta;
 +  self->meta = meta;
 +  Py_XDECREF(tmp);
 +
    return 0;
  }
  
@@@ -1039,19 -972,18 +1039,19 @@@ static PyObject *Notification_dispatch(
    const data_set_t *ds;
    notification_t notification;
    double t = self->data.time;
 +  PyObject *meta = self->meta;
    int severity = self->severity;
    char *host = NULL, *plugin = NULL, *plugin_instance = NULL, *type = NULL,
         *type_instance = NULL;
    char *message = NULL;
  
 -  static char *kwlist[] = {"type",          "message",  "plugin_instance",
 -                           "type_instance", "plugin",   "host",
 -                           "time",          "severity", NULL};
 -  if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdi", kwlist, NULL,
 +  static char *kwlist[] = {
 +      "type", "message", "plugin_instance", "type_instance", "plugin",
 +      "host", "time",    "severity",        "meta",          NULL};
 +  if (!PyArg_ParseTupleAndKeywords(args, kwds, "|etetetetetetdiO", kwlist, NULL,
                                     &type, NULL, &message, NULL,
                                     &plugin_instance, NULL, &type_instance, NULL,
 -                                   &plugin, NULL, &host, &t, &severity))
 +                                   &plugin, NULL, &host, &t, &severity, &meta))
      return NULL;
  
    notification.time = DOUBLE_TO_CDTIME_T(t);
      PyErr_Format(PyExc_TypeError, "Dataset %s not found", notification.type);
      return NULL;
    }
 +  if (meta != NULL && meta != Py_None && !PyDict_Check(meta)) {
 +    PyErr_Format(PyExc_TypeError, "meta must be a dict");
 +    return NULL;
 +  }
 +  cpy_build_notification_meta(&notification, meta);
  
    if (notification.time == 0)
      notification.time = cdtime();
      sstrncpy(notification.plugin, "python", sizeof(notification.plugin));
    Py_BEGIN_ALLOW_THREADS;
    ret = plugin_dispatch_notification(&notification);
 +  if (notification.meta)
 +    plugin_notification_meta_free(notification.meta);
    Py_END_ALLOW_THREADS;
    if (ret != 0) {
      PyErr_SetString(PyExc_RuntimeError,
@@@ -1116,7 -1041,6 +1116,7 @@@ static PyObject *Notification_new(PyTyp
    if (self == NULL)
      return NULL;
  
 +  self->meta = PyDict_New();
    self->message[0] = 0;
    self->severity = 0;
    return (PyObject *)self;
@@@ -1144,21 -1068,17 +1144,21 @@@ static int Notification_setstring(PyObj
  
  static PyObject *Notification_repr(PyObject *s) {
    PyObject *ret, *tmp;
 -  static PyObject *l_severity = NULL, *l_message = NULL, *l_closing = NULL;
 +  static PyObject *l_severity = NULL, *l_message = NULL, *l_meta = NULL,
 +                  *l_closing = NULL;
    Notification *self = (Notification *)s;
  
    if (l_severity == NULL)
      l_severity = cpy_string_to_unicode_or_bytes(",severity=");
    if (l_message == NULL)
      l_message = cpy_string_to_unicode_or_bytes(",message=");
 +  if (l_meta == NULL)
 +    l_meta = cpy_string_to_unicode_or_bytes(",meta=");
    if (l_closing == NULL)
      l_closing = cpy_string_to_unicode_or_bytes(")");
  
 -  if (l_severity == NULL || l_message == NULL || l_closing == NULL)
 +  if (l_severity == NULL || l_message == NULL || l_meta == NULL ||
 +      l_closing == NULL)
      return NULL;
  
    ret = cpy_common_repr(s);
      CPY_SUBSTITUTE(PyObject_Repr, tmp, tmp);
      CPY_STRCAT_AND_DEL(&ret, tmp);
    }
 +  if (self->meta &&
 +      (!PyDict_Check(self->meta) || PyDict_Size(self->meta) > 0)) {
 +    CPY_STRCAT(&ret, l_meta);
 +    tmp = PyObject_Repr(self->meta);
 +    CPY_STRCAT_AND_DEL(&ret, tmp);
 +  }
    CPY_STRCAT(&ret, l_closing);
    return ret;
  }
  
 +static int Notification_traverse(PyObject *self, visitproc visit, void *arg) {
 +  Notification *n = (Notification *)self;
 +  Py_VISIT(n->meta);
 +  return 0;
 +}
 +
 +static int Notification_clear(PyObject *self) {
 +  Notification *n = (Notification *)self;
 +  Py_CLEAR(n->meta);
 +  return 0;
 +}
 +
 +static void Notification_dealloc(PyObject *self) {
 +  Notification_clear(self);
 +  self->ob_type->tp_free(self);
 +}
 +
  static PyMethodDef Notification_methods[] = {
      {"dispatch", (PyCFunction)Notification_dispatch,
       METH_VARARGS | METH_KEYWORDS, dispatch_doc},
  
  static PyMemberDef Notification_members[] = {
      {"severity", T_INT, offsetof(Notification, severity), 0, severity_doc},
 +    {"meta", T_OBJECT_EX, offsetof(Notification, meta), 0,
 +     notification_meta_doc},
      {NULL}};
  
  static PyGetSetDef Notification_getseters[] = {
      {NULL}};
  
  PyTypeObject NotificationType = {
 -    CPY_INIT_TYPE "collectd.Notification",    /* tp_name */
 -    sizeof(Notification),                     /* tp_basicsize */
 -    0,                                        /* Will be filled in later */
 -    0,                                        /* tp_dealloc */
 -    0,                                        /* tp_print */
 -    0,                                        /* tp_getattr */
 -    0,                                        /* tp_setattr */
 -    0,                                        /* tp_compare */
 -    Notification_repr,                        /* tp_repr */
 -    0,                                        /* tp_as_number */
 -    0,                                        /* tp_as_sequence */
 -    0,                                        /* tp_as_mapping */
 -    0,                                        /* tp_hash */
 -    0,                                        /* tp_call */
 -    0,                                        /* tp_str */
 -    0,                                        /* tp_getattro */
 -    0,                                        /* tp_setattro */
 -    0,                                        /* tp_as_buffer */
 -    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
 -    Notification_doc,                         /* tp_doc */
 -    0,                                        /* tp_traverse */
 -    0,                                        /* tp_clear */
 -    0,                                        /* tp_richcompare */
 -    0,                                        /* tp_weaklistoffset */
 -    0,                                        /* tp_iter */
 -    0,                                        /* tp_iternext */
 -    Notification_methods,                     /* tp_methods */
 -    Notification_members,                     /* tp_members */
 -    Notification_getseters,                   /* tp_getset */
 -    0,                                        /* tp_base */
 -    0,                                        /* tp_dict */
 -    0,                                        /* tp_descr_get */
 -    0,                                        /* tp_descr_set */
 -    0,                                        /* tp_dictoffset */
 -    Notification_init,                        /* tp_init */
 -    0,                                        /* tp_alloc */
 -    Notification_new                          /* tp_new */
 +    CPY_INIT_TYPE "collectd.Notification", /* tp_name */
 +    sizeof(Notification),                  /* tp_basicsize */
 +    0,                                     /* Will be filled in later */
 +    Notification_dealloc,                  /* tp_dealloc */
 +    0,                                     /* tp_print */
 +    0,                                     /* tp_getattr */
 +    0,                                     /* tp_setattr */
 +    0,                                     /* tp_compare */
 +    Notification_repr,                     /* tp_repr */
 +    0,                                     /* tp_as_number */
 +    0,                                     /* tp_as_sequence */
 +    0,                                     /* tp_as_mapping */
 +    0,                                     /* tp_hash */
 +    0,                                     /* tp_call */
 +    0,                                     /* tp_str */
 +    0,                                     /* tp_getattro */
 +    0,                                     /* tp_setattro */
 +    0,                                     /* tp_as_buffer */
 +    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
 +    Notification_doc,                                              /* tp_doc */
 +    Notification_traverse,  /* tp_traverse */
 +    Notification_clear,     /* tp_clear */
 +    0,                      /* tp_richcompare */
 +    0,                      /* tp_weaklistoffset */
 +    0,                      /* tp_iter */
 +    0,                      /* tp_iternext */
 +    Notification_methods,   /* tp_methods */
 +    Notification_members,   /* tp_members */
 +    Notification_getseters, /* tp_getset */
 +    0,                      /* tp_base */
 +    0,                      /* tp_dict */
 +    0,                      /* tp_descr_get */
 +    0,                      /* tp_descr_set */
 +    0,                      /* tp_dictoffset */
 +    Notification_init,      /* tp_init */
 +    0,                      /* tp_alloc */
 +    Notification_new        /* tp_new */
  };
  
  static char Signed_doc[] =
@@@ -1280,7 -1175,7 +1280,7 @@@ PyTypeObject SignedType = 
      0,                                        /* tp_getattro */
      0,                                        /* tp_setattro */
      0,                                        /* tp_as_buffer */
-     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
      Signed_doc                                /* tp_doc */
  };
  
@@@ -1307,6 -1202,6 +1307,6 @@@ PyTypeObject UnsignedType = 
      0,                                        /* tp_getattro */
      0,                                        /* tp_setattro */
      0,                                        /* tp_as_buffer */
-     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
      Unsigned_doc                              /* tp_doc */
  };