Merge remote-tracking branch 'github/pr/2492'
authorFlorian Forster <octo@collectd.org>
Mon, 23 Oct 2017 13:20:40 +0000 (15:20 +0200)
committerFlorian Forster <octo@collectd.org>
Mon, 23 Oct 2017 13:20:40 +0000 (15:20 +0200)
20 files changed:
Makefile.am
src/ceph.c
src/ceph_test.c
src/collectd-python.pod
src/collectd.conf.pod
src/cpython.h
src/daemon/collectd.c
src/daemon/collectd.h
src/daemon/configfile.c
src/daemon/globals.c [new file with mode: 0644]
src/daemon/globals.h [new file with mode: 0644]
src/daemon/plugin.h
src/daemon/plugin_mock.c
src/ipmi.c
src/lvm.c
src/perl.c
src/postgresql.c
src/python.c
src/pyvalues.c
src/write_prometheus.c

index 04636b3..ae027a3 100644 (file)
@@ -199,6 +199,8 @@ collectd_SOURCES = \
        src/daemon/configfile.h \
        src/daemon/filter_chain.c \
        src/daemon/filter_chain.h \
+       src/daemon/globals.c \
+       src/daemon/globals.h \
        src/daemon/meta_data.c \
        src/daemon/meta_data.h \
        src/daemon/plugin.c \
index 8bee8e8..3accbd3 100644 (file)
@@ -176,8 +176,6 @@ struct values_tmp {
   uint64_t avgcount;
   /** current index of counters - used to get type of counter */
   int index;
-  /** do we already have an avgcount for latency pair */
-  int avgcount_exists;
   /**
    * similar to index, but current index of latency type counters -
    * used to get last poll data of counter
@@ -261,7 +259,6 @@ static int ceph_cb_number(void *ctx, const char *number_val,
   yajl_struct *state = (yajl_struct *)ctx;
   char buffer[number_len + 1];
   char key[2 * DATA_MAX_NAME_LEN] = {0};
-  _Bool latency_type = 0;
   int status;
 
   memcpy(buffer, number_val, number_len);
@@ -276,44 +273,25 @@ static int ceph_cb_number(void *ctx, const char *number_val,
     BUFFER_ADD(key, state->stack[i]);
   }
 
-  /* Special case for latency metrics. */
-  if ((strcmp("avgcount", state->key) == 0) ||
-      (strcmp("sum", state->key) == 0)) {
-    latency_type = 1;
-
-    /* depth >= 2  =>  (stack[-1] != NULL && stack[-2] != NULL) */
-    assert((state->depth < 2) || ((state->stack[state->depth - 1] != NULL) &&
-                                  (state->stack[state->depth - 2] != NULL)));
-
-    /* Super-special case for filestore.journal_wr_bytes.avgcount: For
-     * some reason, Ceph schema encodes this as a count/sum pair while all
-     * other "Bytes" data (excluding used/capacity bytes for OSD space) uses
-     * a single "Derive" type. To spare further confusion, keep this KPI as
-     * the same type of other "Bytes". Instead of keeping an "average" or
-     * "rate", use the "sum" in the pair and assign that to the derive
-     * value. */
-    if (convert_special_metrics && (state->depth >= 2) &&
-        (strcmp("filestore", state->stack[state->depth - 2]) == 0) &&
-        (strcmp("journal_wr_bytes", state->stack[state->depth - 1]) == 0) &&
-        (strcmp("avgcount", state->key) == 0)) {
-      DEBUG("ceph plugin: Skipping avgcount for filestore.JournalWrBytes");
-      return CEPH_CB_CONTINUE;
-    }
-  } else /* not a latency type */
-  {
-    BUFFER_ADD(key, ".");
-    BUFFER_ADD(key, state->key);
+  /* Super-special case for filestore.journal_wr_bytes.avgcount: For
+   * some reason, Ceph schema encodes this as a count/sum pair while all
+   * other "Bytes" data (excluding used/capacity bytes for OSD space) uses
+   * a single "Derive" type. To spare further confusion, keep this KPI as
+   * the same type of other "Bytes". Instead of keeping an "average" or
+   * "rate", use the "sum" in the pair and assign that to the derive
+   * value. */
+  if (convert_special_metrics && (state->depth >= 2) &&
+      (strcmp("filestore", state->stack[state->depth - 2]) == 0) &&
+      (strcmp("journal_wr_bytes", state->stack[state->depth - 1]) == 0) &&
+      (strcmp("avgcount", state->key) == 0)) {
+    DEBUG("ceph plugin: Skipping avgcount for filestore.JournalWrBytes");
+    return CEPH_CB_CONTINUE;
   }
 
-  status = state->handler(state->handler_arg, buffer, key);
-  if ((status == RETRY_AVGCOUNT) && latency_type) {
-    /* Add previously skipped part of the key, either "avgcount" or "sum",
-     * and try again. */
-    BUFFER_ADD(key, ".");
-    BUFFER_ADD(key, state->key);
+  BUFFER_ADD(key, ".");
+  BUFFER_ADD(key, state->key);
 
-    status = state->handler(state->handler_arg, buffer, key);
-  }
+  status = state->handler(state->handler_arg, buffer, key);
 
   if (status != 0) {
     ERROR("ceph plugin: JSON handler failed with status %d.", status);
@@ -507,6 +485,21 @@ static _Bool has_suffix(char const *str, char const *suffix) {
   return 0;
 }
 
+static void cut_suffix(char *buffer, size_t buffer_size, char const *str,
+                       char const *suffix) {
+
+  size_t str_len = strlen(str);
+  size_t suffix_len = strlen(suffix);
+
+  size_t offset = str_len - suffix_len + 1;
+
+  if (offset > buffer_size) {
+    offset = buffer_size;
+  }
+
+  sstrncpy(buffer, str, offset);
+}
+
 /* count_parts returns the number of elements a "foo.bar.baz" style key has. */
 static size_t count_parts(char const *key) {
   size_t parts_num = 0;
@@ -522,20 +515,23 @@ static size_t count_parts(char const *key) {
  */
 static int parse_keys(char *buffer, size_t buffer_size, const char *key_str) {
   char tmp[2 * buffer_size];
+  size_t tmp_size = sizeof(tmp);
+  const char *cut_suffixes[] = {".type", ".avgcount", ".sum", ".avgtime"};
 
   if (buffer == NULL || buffer_size == 0 || key_str == NULL ||
       strlen(key_str) == 0)
     return EINVAL;
 
-  if ((count_parts(key_str) > 2) && has_suffix(key_str, ".type")) {
-    /* strip ".type" suffix iff the key has more than two parts. */
-    size_t sz = strlen(key_str) - strlen(".type") + 1;
+  sstrncpy(tmp, key_str, tmp_size);
 
-    if (sz > sizeof(tmp))
-      sz = sizeof(tmp);
-    sstrncpy(tmp, key_str, sz);
-  } else {
-    sstrncpy(tmp, key_str, sizeof(tmp));
+  /* Strip suffix if it is ".type" or one of latency metric suffix. */
+  if (count_parts(key_str) > 2) {
+    for (size_t i = 0; i < STATIC_ARRAY_SIZE(cut_suffixes); i++) {
+      if (has_suffix(key_str, cut_suffixes[i])) {
+        cut_suffix(tmp, tmp_size, key_str, cut_suffixes[i]);
+        break;
+      }
+    }
   }
 
   return compact_ds_name(buffer, buffer_size, tmp);
@@ -907,34 +903,47 @@ static int node_handler_fetch_data(void *arg, const char *val,
 
   switch (type) {
   case DSET_LATENCY:
-    if (vtmp->avgcount_exists == -1) {
+    if (has_suffix(key, ".avgcount")) {
       sscanf(val, "%" PRIu64, &vtmp->avgcount);
-      vtmp->avgcount_exists = 0;
       // return after saving avgcount - don't dispatch value
       // until latency calculation
       return 0;
-    } else {
-      double sum, result;
-      sscanf(val, "%lf", &sum);
-
+    } else if (has_suffix(key, ".sum")) {
       if (vtmp->avgcount == 0) {
         vtmp->avgcount = 1;
       }
-
-      /** User wants latency values as long run avg */
+      // user wants latency values as long run avg
+      // skip this step
       if (long_run_latency_avg) {
-        result = (sum / vtmp->avgcount);
-      } else {
-        result = get_last_avg(vtmp->d, ds_name, vtmp->latency_index, sum,
-                              vtmp->avgcount);
-        if (result == -ENOMEM) {
-          return -ENOMEM;
-        }
+        return 0;
       }
+      double sum, result;
+      sscanf(val, "%lf", &sum);
+      result = get_last_avg(vtmp->d, ds_name, vtmp->latency_index, sum,
+                            vtmp->avgcount);
+      if (result == -ENOMEM) {
+        return -ENOMEM;
+      }
+      uv.gauge = result;
+      vtmp->latency_index = (vtmp->latency_index + 1);
+    } else if (has_suffix(key, ".avgtime")) {
 
+      /* The "avgtime" metric reports ("sum" / "avgcount"), i.e. the average
+       * time per request since the start of the Ceph daemon. Report this only
+       * when the user has configured "long running average". Otherwise, use the
+       * rate of "sum" and "avgcount" to calculate the current latency.
+       */
+
+      if (!long_run_latency_avg) {
+        return 0;
+      }
+      double result;
+      sscanf(val, "%lf", &result);
       uv.gauge = result;
-      vtmp->avgcount_exists = -1;
       vtmp->latency_index = (vtmp->latency_index + 1);
+    } else {
+      WARNING("ceph plugin: ignoring unknown latency metric: %s", key);
+      return 0;
     }
     break;
   case DSET_BYTES:
@@ -1032,7 +1041,6 @@ static int cconn_process_data(struct cconn *io, yajl_struct *yajl,
            sizeof(vtmp->vlist.plugin_instance));
 
   vtmp->d = io->d;
-  vtmp->avgcount_exists = -1;
   vtmp->latency_index = 0;
   vtmp->index = 0;
   yajl->handler_arg = vtmp;
index 2f65b50..4546773 100644 (file)
@@ -123,7 +123,7 @@ DEF_TEST(traverse_json) {
       {"WBThrottle.ios_wb.type", "2"},
       {"WBThrottle.inodes_dirtied.type", "2"},
       {"WBThrottle.inodes_wb.type", "10"},
-      {"filestore.journal_wr_bytes", "3117"},
+      {"filestore.journal_wr_bytes.sum", "3117"},
       {"filestore.example_latency.avgcount", "42"},
       {"filestore.example_latency.sum", "4711"},
   };
index 7a19f45..1f46f6f 100644 (file)
@@ -68,8 +68,10 @@ use multiple B<ModulePath> lines to add more than one directory.
 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 @@ 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
index 4c4c261..cf76e63 100644 (file)
@@ -7674,6 +7674,8 @@ Calculate and dispatch various values out of I<Timer> metrics received during
 an interval. If set to B<False>, the default, these values aren't calculated /
 dispatched.
 
+Please note what reported timer values less than 0.001 are ignored in all B<Timer*> reports.
+
 =back
 
 =head2 Plugin C<swap>
index 04fad0c..38951c0 100644 (file)
@@ -144,17 +144,21 @@ void cpy_log_exception(const char *context);
 /* 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];
index af8fb56..dd9b12f 100644 (file)
 #define COLLECTD_LOCALE "C"
 #endif
 
-/*
- * Global variables
- */
-char hostname_g[DATA_MAX_NAME_LEN];
-cdtime_t interval_g;
-int timeout_g;
-#if HAVE_LIBKSTAT
-kstat_ctl_t *kc;
-#endif /* HAVE_LIBKSTAT */
-
 static int loop = 0;
 
 static void *do_flush(void __attribute__((unused)) * arg) {
@@ -91,13 +81,19 @@ static int init_hostname(void) {
   struct addrinfo *ai_list;
   int status;
 
+  long hostname_len = sysconf(_SC_HOST_NAME_MAX);
+  if (hostname_len == -1) {
+    hostname_len = NI_MAXHOST;
+  }
+  char hostname[hostname_len];
+
   str = global_option_get("Hostname");
   if ((str != NULL) && (str[0] != 0)) {
-    sstrncpy(hostname_g, str, sizeof(hostname_g));
+    hostname_set(str);
     return 0;
   }
 
-  if (gethostname(hostname_g, sizeof(hostname_g)) != 0) {
+  if (gethostname(hostname, hostname_len) != 0) {
     fprintf(stderr, "`gethostname' failed and no "
                     "hostname was configured.\n");
     return -1;
@@ -109,14 +105,14 @@ static int init_hostname(void) {
 
   struct addrinfo ai_hints = {.ai_flags = AI_CANONNAME};
 
-  status = getaddrinfo(hostname_g, NULL, &ai_hints, &ai_list);
+  status = getaddrinfo(hostname, NULL, &ai_hints, &ai_list);
   if (status != 0) {
     ERROR("Looking up \"%s\" failed. You have set the "
           "\"FQDNLookup\" option, but I cannot resolve "
           "my hostname to a fully qualified domain "
           "name. Please fix the network "
           "configuration.",
-          hostname_g);
+          hostname);
     return -1;
   }
 
@@ -125,7 +121,7 @@ static int init_hostname(void) {
     if (ai_ptr->ai_canonname == NULL)
       continue;
 
-    sstrncpy(hostname_g, ai_ptr->ai_canonname, sizeof(hostname_g));
+    hostname_set(ai_ptr->ai_canonname);
     break;
   }
 
@@ -455,23 +451,19 @@ static int notify_systemd(void) {
 }
 #endif /* KERNEL_LINUX */
 
-int main(int argc, char **argv) {
-  const char *configfile = CONFIGFILE;
-  int test_config = 0;
-  int test_readall = 0;
-  const char *basedir;
-  _Bool opt_create_basedir = 1;
-#if COLLECT_DAEMON
-  pid_t pid;
-  int daemonize = 1;
-#endif
-  int exit_status = 0;
+struct cmdline_config {
+  _Bool test_config;
+  _Bool test_readall;
+  _Bool create_basedir;
+  const char *configfile;
+  _Bool daemonize;
+};
 
+void read_cmdline(int argc, char **argv, struct cmdline_config *config) {
   /* read options */
   while (1) {
     int c;
-
-    c = getopt(argc, argv, "BhtTC:"
+    c = getopt(argc, argv, "htTC:"
 #if COLLECT_DAEMON
                            "fP:"
 #endif
@@ -482,19 +474,19 @@ int main(int argc, char **argv) {
 
     switch (c) {
     case 'B':
-      opt_create_basedir = 0;
+      config->create_basedir = 0;
       break;
     case 'C':
-      configfile = optarg;
+      config->configfile = optarg;
       break;
     case 't':
-      test_config = 1;
+      config->test_config = 1;
       break;
     case 'T':
-      test_readall = 1;
+      config->test_readall = 1;
       global_option_set("ReadThreads", "-1", 1);
 #if COLLECT_DAEMON
-      daemonize = 0;
+      config->daemonize = 0;
 #endif /* COLLECT_DAEMON */
       break;
 #if COLLECT_DAEMON
@@ -502,7 +494,7 @@ int main(int argc, char **argv) {
       global_option_set("PIDFile", optarg, 1);
       break;
     case 'f':
-      daemonize = 0;
+      config->daemonize = 0;
       break;
 #endif /* COLLECT_DAEMON */
     case 'h':
@@ -512,19 +504,17 @@ int main(int argc, char **argv) {
       exit_usage(1);
     } /* switch (c) */
   }   /* while (1) */
+}
 
-  if (optind < argc)
-    exit_usage(1);
-
-  plugin_init_ctx();
-
+int configure_collectd(struct cmdline_config *config) {
+  const char *basedir;
   /*
    * Read options from the config file, the environment and the command
    * line (in that order, with later options overwriting previous ones in
    * general).
    * Also, this will automatically load modules.
    */
-  if (cf_read(configfile)) {
+  if (cf_read(config->configfile)) {
     fprintf(stderr, "Error: Reading the config file failed!\n"
                     "Read the logs for details.\n");
     return 1;
@@ -538,23 +528,47 @@ int main(int argc, char **argv) {
     fprintf(stderr,
             "Don't have a basedir to use. This should not happen. Ever.");
     return 1;
-  } else if (change_basedir(basedir, opt_create_basedir)) {
+  } else if (change_basedir(basedir, config->create_basedir)) {
     fprintf(stderr, "Error: Unable to change to directory `%s'.\n", basedir);
     return 1;
   }
 
   /*
-   * Set global variables or, if that failes, exit. We cannot run with
+   * Set global variables or, if that fails, exit. We cannot run with
    * them being uninitialized. If nothing is configured, then defaults
    * are being used. So this means that the user has actually done
    * something wrong.
    */
   if (init_global_variables() != 0)
-    exit(EXIT_FAILURE);
+    return 1;
+
+  return 0;
+}
+
+int main(int argc, char **argv) {
+#if COLLECT_DAEMON
+  pid_t pid;
+#endif
+  int exit_status = 0;
 
-  if (test_config)
+  struct cmdline_config config = {
+      .daemonize = 1, .create_basedir = 1, .configfile = CONFIGFILE,
+  };
+
+  read_cmdline(argc, argv, &config);
+
+  if (config.test_config)
     return 0;
 
+  if (optind < argc)
+    exit_usage(1);
+
+  plugin_init_ctx();
+
+  int status;
+  if ((status = configure_collectd(&config)) != 0)
+    exit(EXIT_FAILURE);
+
 #if COLLECT_DAEMON
   /*
    * fork off child
@@ -567,7 +581,7 @@ int main(int argc, char **argv) {
    * Only daemonize if we're not being supervised
    * by upstart or systemd (when using Linux).
    */
-  if (daemonize
+  if (config.daemonize
 #ifdef KERNEL_LINUX
       && notify_upstart() == 0 && notify_systemd() == 0
 #endif
@@ -617,7 +631,7 @@ int main(int argc, char **argv) {
             status);
       return 1;
     }
-  }    /* if (daemonize) */
+  }    /* if (config.daemonize) */
 #endif /* COLLECT_DAEMON */
 
   struct sigaction sig_pipe_action = {.sa_handler = SIG_IGN};
@@ -662,7 +676,7 @@ int main(int argc, char **argv) {
     exit_status = 1;
   }
 
-  if (test_readall) {
+  if (config.test_readall) {
     if (plugin_read_all_once() != 0) {
       ERROR("Error: one or more plugin read callbacks failed.");
       exit_status = 1;
@@ -681,7 +695,7 @@ int main(int argc, char **argv) {
   }
 
 #if COLLECT_DAEMON
-  if (daemonize)
+  if (config.daemonize)
     pidfile_remove();
 #endif /* COLLECT_DAEMON */
 
index 01d484e..0558aa4 100644 (file)
 #include <sys/param.h>
 #endif
 
-#if HAVE_KSTAT_H
-#include <kstat.h>
-#endif
-
 #ifndef PACKAGE_NAME
 #define PACKAGE_NAME "collectd"
 #endif
 #define GAUGE_FORMAT "%.15g"
 #endif
 
-/* Type for time as used by "utils_time.h" */
-typedef uint64_t cdtime_t;
-
-extern char hostname_g[];
-extern cdtime_t interval_g;
-extern int timeout_g;
+#include "globals.h"
 
 #endif /* COLLECTD_H */
index 0d295c1..f5086ae 100644 (file)
@@ -461,9 +461,9 @@ static int cf_ci_replace_child(oconfig_item_t *dst, oconfig_item_t *src,
     return 0;
   }
 
-  temp =
-      realloc(dst->children, sizeof(oconfig_item_t) *
-                                 (dst->children_num + src->children_num - 1));
+  temp = realloc(dst->children,
+                 sizeof(oconfig_item_t) *
+                     (dst->children_num + src->children_num - 1));
   if (temp == NULL) {
     ERROR("configfile: realloc failed.");
     return -1;
@@ -502,8 +502,9 @@ static int cf_ci_append_children(oconfig_item_t *dst, oconfig_item_t *src) {
   if ((src == NULL) || (src->children_num == 0))
     return 0;
 
-  temp = realloc(dst->children, sizeof(oconfig_item_t) *
-                                    (dst->children_num + src->children_num));
+  temp =
+      realloc(dst->children,
+              sizeof(oconfig_item_t) * (dst->children_num + src->children_num));
   if (temp == NULL) {
     ERROR("configfile: realloc failed.");
     return -1;
@@ -874,7 +875,8 @@ const char *global_option_get(const char *option) {
     return NULL;
   }
 
-  return (cf_global_options[i].value != NULL) ? cf_global_options[i].value : cf_global_options[i].def;
+  return (cf_global_options[i].value != NULL) ? cf_global_options[i].value
+                                              : cf_global_options[i].def;
 } /* char *global_option_get */
 
 long global_option_get_long(const char *option, long default_value) {
@@ -1031,6 +1033,7 @@ int cf_read(const char *filename) {
   }
 
   return ret;
+
 } /* int cf_read */
 
 /* Assures the config option is a string, duplicates it and returns the copy in
diff --git a/src/daemon/globals.c b/src/daemon/globals.c
new file mode 100644 (file)
index 0000000..5c6749f
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * collectd - src/globals.c
+ * Copyright (C) 2017  Google LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ **/
+
+#include "common.h"
+#include "globals.h"
+
+#if HAVE_KSTAT_H
+#include <kstat.h>
+#endif
+
+/*
+ * Global variables
+ */
+char *hostname_g;
+cdtime_t interval_g;
+int timeout_g;
+#if HAVE_KSTAT_H
+kstat_ctl_t *kc;
+#endif
+
+void hostname_set(char const *hostname) {
+  char *h = strdup(hostname);
+  if (h == NULL)
+    return;
+
+  sfree(hostname_g);
+  hostname_g = h;
+}
diff --git a/src/daemon/globals.h b/src/daemon/globals.h
new file mode 100644 (file)
index 0000000..bc11d6b
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * collectd - src/globals.h
+ * Copyright (C) 2017  Google LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ **/
+
+#ifndef GLOBALS_H
+#define GLOBALS_H
+
+#include <inttypes.h>
+
+#ifndef DATA_MAX_NAME_LEN
+#define DATA_MAX_NAME_LEN 128
+#endif
+
+/* Type for time as used by "utils_time.h" */
+typedef uint64_t cdtime_t;
+
+/* hostname_set updates hostname_g */
+void hostname_set(char const *hostname);
+
+extern char *hostname_g;
+extern cdtime_t interval_g;
+extern int pidfile_from_cli;
+extern int timeout_g;
+#endif /* GLOBALS_H */
index 4f877e0..a9ee72d 100644 (file)
 
 #include <pthread.h>
 
-#ifndef DATA_MAX_NAME_LEN
-#define DATA_MAX_NAME_LEN 128
-#endif
-
 #define DS_TYPE_COUNTER 0
 #define DS_TYPE_GAUGE 1
 #define DS_TYPE_DERIVE 2
 #define DS_TYPE_ABSOLUTE 3
 
 #define DS_TYPE_TO_STRING(t)                                                   \
-  (t == DS_TYPE_COUNTER) ? "counter" : (t == DS_TYPE_GAUGE)                    \
-                                           ? "gauge"                           \
-                                           : (t == DS_TYPE_DERIVE)             \
-                                                 ? "derive"                    \
-                                                 : (t == DS_TYPE_ABSOLUTE)     \
-                                                       ? "absolute"            \
-                                                       : "unknown"
+  (t == DS_TYPE_COUNTER)                                                       \
+      ? "counter"                                                              \
+      : (t == DS_TYPE_GAUGE)                                                   \
+            ? "gauge"                                                          \
+            : (t == DS_TYPE_DERIVE)                                            \
+                  ? "derive"                                                   \
+                  : (t == DS_TYPE_ABSOLUTE) ? "absolute" : "unknown"
 
 #ifndef LOG_ERR
 #define LOG_ERR 3
index ca98539..6df4c15 100644 (file)
@@ -30,7 +30,7 @@
 kstat_ctl_t *kc = NULL;
 #endif /* HAVE_LIBKSTAT */
 
-char hostname_g[] = "example.com";
+char *hostname_g = "example.com";
 
 void plugin_set_dir(const char *dir) { /* nop */
 }
index a206521..f28ac52 100644 (file)
@@ -79,12 +79,19 @@ struct c_ipmi_sensor_list_s {
   ipmi_sensor_id_t sensor_id;
   char sensor_name[DATA_MAX_NAME_LEN];
   char sensor_type[DATA_MAX_NAME_LEN];
+  char type_instance[DATA_MAX_NAME_LEN];
   int sensor_not_present;
   c_ipmi_sensor_list_t *next;
   c_ipmi_instance_t *instance;
   unsigned int use;
 };
 
+struct c_ipmi_db_type_map_s {
+  enum ipmi_unit_type_e type;
+  const char *type_name;
+};
+typedef struct c_ipmi_db_type_map_s c_ipmi_db_type_map_t;
+
 /*
  * Module global variables
  */
@@ -190,7 +197,7 @@ static void sensor_read_handler(ipmi_sensor_t *sensor, int err,
         if (st->notify_notpresent) {
           notification_t n = c_ipmi_notification_init(st, NOTIF_WARNING);
 
-          sstrncpy(n.type_instance, list_item->sensor_name,
+          sstrncpy(n.type_instance, list_item->type_instance,
                    sizeof(n.type_instance));
           sstrncpy(n.type, list_item->sensor_type, sizeof(n.type));
           snprintf(n.message, sizeof(n.message), "sensor %s not present",
@@ -243,7 +250,7 @@ static void sensor_read_handler(ipmi_sensor_t *sensor, int err,
     if (st->notify_notpresent) {
       notification_t n = c_ipmi_notification_init(st, NOTIF_OKAY);
 
-      sstrncpy(n.type_instance, list_item->sensor_name,
+      sstrncpy(n.type_instance, list_item->type_instance,
                sizeof(n.type_instance));
       sstrncpy(n.type, list_item->sensor_type, sizeof(n.type));
       snprintf(n.message, sizeof(n.message), "sensor %s present",
@@ -285,7 +292,8 @@ static void sensor_read_handler(ipmi_sensor_t *sensor, int err,
     sstrncpy(vl.host, st->host, sizeof(vl.host));
   sstrncpy(vl.plugin, "ipmi", sizeof(vl.plugin));
   sstrncpy(vl.type, list_item->sensor_type, sizeof(vl.type));
-  sstrncpy(vl.type_instance, list_item->sensor_name, sizeof(vl.type_instance));
+  sstrncpy(vl.type_instance, list_item->type_instance,
+           sizeof(vl.type_instance));
 
   plugin_dispatch_values(&vl);
 } /* void sensor_read_handler */
@@ -338,6 +346,24 @@ static void sensor_get_name(ipmi_sensor_t *sensor, char *buffer, int buf_len) {
   sstrncpy(buffer, sensor_name, buf_len);
 }
 
+static const char *sensor_unit_to_type(ipmi_sensor_t *sensor) {
+  static const c_ipmi_db_type_map_t ipmi_db_type_map[] = {
+      {IPMI_UNIT_TYPE_WATTS, "power"}, {IPMI_UNIT_TYPE_CFM, "flow"}};
+
+  /* check the modifier and rate of the sensor value */
+  if ((ipmi_sensor_get_modifier_unit_use(sensor) != IPMI_MODIFIER_UNIT_NONE) ||
+      (ipmi_sensor_get_rate_unit(sensor) != IPMI_RATE_UNIT_NONE))
+    return NULL;
+
+  /* find the db type by using sensor base unit type */
+  enum ipmi_unit_type_e ipmi_type = ipmi_sensor_get_base_unit(sensor);
+  for (int i = 0; i < STATIC_ARRAY_SIZE(ipmi_db_type_map); i++)
+    if (ipmi_db_type_map[i].type == ipmi_type)
+      return ipmi_db_type_map[i].type_name;
+
+  return NULL;
+} /* const char* sensor_unit_to_type */
+
 static int sensor_list_add(c_ipmi_instance_t *st, ipmi_sensor_t *sensor) {
   ipmi_sensor_id_t sensor_id;
   c_ipmi_sensor_list_t *list_item;
@@ -377,10 +403,10 @@ static int sensor_list_add(c_ipmi_instance_t *st, ipmi_sensor_t *sensor) {
    *
    * ipmi_sensor_id_get_reading() supports only 'Threshold' sensors.
    * See lib/sensor.c:4842, stand_ipmi_sensor_get_reading() for details.
-  */
+   */
   if (!ipmi_sensor_get_is_readable(sensor)) {
     INFO("ipmi plugin: sensor_list_add: Ignore sensor `%s` of `%s`, "
-         "because it don't readable! Its type: (%#x, %s). ",
+         "because it isn't readable! Its type: (%#x, %s). ",
          sensor_name_ptr, st->name, sensor_type,
          ipmi_sensor_get_sensor_type_string(sensor));
     return -1;
@@ -413,11 +439,22 @@ static int sensor_list_add(c_ipmi_instance_t *st, ipmi_sensor_t *sensor) {
     type = "fanspeed";
     break;
 
+  case IPMI_SENSOR_TYPE_MEMORY:
+    type = "memory";
+    break;
+
   default: {
+    /* try to get collectd DB type based on sensor base unit type */
+    if ((type = sensor_unit_to_type(sensor)) != NULL)
+      break;
+
     INFO("ipmi plugin: sensor_list_add: Ignore sensor `%s` of `%s`, "
-         "because I don't know how to handle its type (%#x, %s). "
-         "If you need this sensor, please file a bug report.",
-         sensor_name_ptr, st->name, sensor_type,
+         "because I don't know how to handle its units (%#x, %#x, %#x). "
+         "Sensor type: (%#x, %s). If you need this sensor, please file "
+         "a bug report at http://collectd.org/.",
+         sensor_name_ptr, st->name, ipmi_sensor_get_base_unit(sensor),
+         ipmi_sensor_get_modifier_unit(sensor),
+         ipmi_sensor_get_rate_unit(sensor), sensor_type,
          ipmi_sensor_get_sensor_type_string(sensor));
     return -1;
   }
@@ -452,6 +489,18 @@ static int sensor_list_add(c_ipmi_instance_t *st, ipmi_sensor_t *sensor) {
   else
     st->sensor_list = list_item;
 
+  /* if sensor provides the percentage value, use "percent" collectd type
+     and add the `percent` to the type instance of the reported value */
+  if (ipmi_sensor_get_percentage(sensor)) {
+    snprintf(list_item->type_instance, sizeof(list_item->type_instance),
+             "percent-%s", sensor_name_ptr);
+    type = "percent";
+  } else {
+    /* use type instance as a name of the sensor */
+    sstrncpy(list_item->type_instance, sensor_name_ptr,
+             sizeof(list_item->type_instance));
+  }
+
   sstrncpy(list_item->sensor_name, sensor_name_ptr,
            sizeof(list_item->sensor_name));
   sstrncpy(list_item->sensor_type, type, sizeof(list_item->sensor_type));
@@ -461,7 +510,8 @@ static int sensor_list_add(c_ipmi_instance_t *st, ipmi_sensor_t *sensor) {
   if (st->notify_add && (st->init_in_progress == 0)) {
     notification_t n = c_ipmi_notification_init(st, NOTIF_OKAY);
 
-    sstrncpy(n.type_instance, list_item->sensor_name, sizeof(n.type_instance));
+    sstrncpy(n.type_instance, list_item->type_instance,
+             sizeof(n.type_instance));
     sstrncpy(n.type, list_item->sensor_type, sizeof(n.type));
     snprintf(n.message, sizeof(n.message), "sensor %s added",
              list_item->sensor_name);
@@ -507,7 +557,8 @@ static int sensor_list_remove(c_ipmi_instance_t *st, ipmi_sensor_t *sensor) {
   if (st->notify_remove && st->active) {
     notification_t n = c_ipmi_notification_init(st, NOTIF_WARNING);
 
-    sstrncpy(n.type_instance, list_item->sensor_name, sizeof(n.type_instance));
+    sstrncpy(n.type_instance, list_item->type_instance,
+             sizeof(n.type_instance));
     sstrncpy(n.type, list_item->sensor_type, sizeof(n.type));
     snprintf(n.message, sizeof(n.message), "sensor %s removed",
              list_item->sensor_name);
index 6310727..4bb1c34 100644 (file)
--- a/src/lvm.c
+++ b/src/lvm.c
  *   Benjamin Gilbert <bgilbert at backtick.net>
  **/
 
-#include <lvm2app.h>
-
 #include "collectd.h"
 
 #include "common.h"
 #include "plugin.h"
 
+#include <lvm2app.h>
+
+#ifdef HAVE_SYS_CAPABILITY_H
+#include <sys/capability.h>
+#endif /* HAVE_SYS_CAPABILITY_H */
+
 #define NO_VALUE UINT64_MAX
 #define PERCENT_SCALE_FACTOR 1e-8
 
@@ -183,6 +187,24 @@ static int lvm_read(void) {
   return 0;
 } /*lvm_read */
 
+static int lvm_init(void) {
+#if defined(HAVE_SYS_CAPABILITY_H) && defined(CAP_SYS_ADMIN)
+  if (check_capability(CAP_SYS_ADMIN) != 0) {
+    if (getuid() == 0)
+      WARNING("smart plugin: Running collectd as root, but the "
+              "CAP_SYS_ADMIN capability is missing. The plugin's read "
+              "function will probably fail. Is your init system dropping "
+              "capabilities?");
+    else
+      WARNING("smart plugin: collectd doesn't have the CAP_SYS_ADMIN "
+              "capability. If you don't want to run collectd as root, try "
+              "running \"setcap cap_sys_admin=ep\" on the collectd binary.");
+  }
+#endif
+  return 0;
+}
+
 void module_register(void) {
+  plugin_register_init("lvm", lvm_init);
   plugin_register_read("lvm", lvm_read);
 } /* void module_register */
index d2a00bf..671d1f3 100644 (file)
@@ -261,12 +261,6 @@ struct {
                  {"Collectd::NOTIF_WARNING", NOTIF_WARNING},
                  {"Collectd::NOTIF_OKAY", NOTIF_OKAY},
                  {"", 0}};
-
-struct {
-  char name[64];
-  char *var;
-} g_strings[] = {{"Collectd::hostname_g", hostname_g}, {"", NULL}};
-
 /*
  * Helper functions for data type conversion.
  */
@@ -2099,7 +2093,7 @@ static int perl_init(void) {
   /* Lock the base thread to avoid race conditions with c_ithread_create().
    * See https://github.com/collectd/collectd/issues/9 and
    *     https://github.com/collectd/collectd/issues/1706 for details.
-  */
+   */
   assert(aTHX == perl_threads->head->interp);
   pthread_mutex_lock(&perl_threads->mutex);
 
@@ -2190,7 +2184,7 @@ static void perl_log(int level, const char *msg, user_data_t *user_data) {
   /* Lock the base thread if this is not called from one of the read threads
    * to avoid race conditions with c_ithread_create(). See
    * https://github.com/collectd/collectd/issues/9 for details.
-  */
+   */
 
   if (aTHX == perl_threads->head->interp)
     pthread_mutex_lock(&perl_threads->mutex);
@@ -2349,14 +2343,25 @@ static int g_interval_set(pTHX_ SV *var, MAGIC *mg) {
   return 0;
 } /* static int g_interval_set (pTHX_ SV *, MAGIC *) */
 
-static MGVTBL g_pv_vtbl = {g_pv_get, g_pv_set, NULL, NULL, NULL, NULL, NULL
+static MGVTBL g_pv_vtbl = {g_pv_get,
+                           g_pv_set,
+                           NULL,
+                           NULL,
+                           NULL,
+                           NULL,
+                           NULL
 #if HAVE_PERL_STRUCT_MGVTBL_SVT_LOCAL
                            ,
                            NULL
 #endif
 };
-static MGVTBL g_interval_vtbl = {g_interval_get, g_interval_set, NULL, NULL,
-                                 NULL, NULL, NULL
+static MGVTBL g_interval_vtbl = {g_interval_get,
+                                 g_interval_set,
+                                 NULL,
+                                 NULL,
+                                 NULL,
+                                 NULL,
+                                 NULL
 #if HAVE_PERL_STRUCT_MGVTBL_SVT_LOCAL
                                  ,
                                  NULL
@@ -2390,6 +2395,11 @@ static void xs_init(pTHX) {
    * accessing any such variable (this is basically the same as using
    * tie() in Perl) */
   /* global strings */
+  struct {
+    char name[64];
+    char *var;
+  } g_strings[] = {{"Collectd::hostname_g", hostname_g}, {"", NULL}};
+
   for (int i = 0; '\0' != g_strings[i].name[0]; ++i) {
     tmp = get_sv(g_strings[i].name, 1);
     sv_magicext(tmp, NULL, PERL_MAGIC_ext, &g_pv_vtbl, g_strings[i].var, 0);
index 3b702ae..25bedf8 100644 (file)
@@ -340,6 +340,7 @@ static int c_psql_connect(c_psql_database_t *db) {
   C_PSQL_PAR_APPEND(buf, buf_len, "sslmode", db->sslmode);
   C_PSQL_PAR_APPEND(buf, buf_len, "krbsrvname", db->krbsrvname);
   C_PSQL_PAR_APPEND(buf, buf_len, "service", db->service);
+  C_PSQL_PAR_APPEND(buf, buf_len, "application_name", "collectd_postgresql");
 
   db->conn = PQconnectdb(conninfo);
   db->proto_version = PQprotocolVersion(db->conn);
@@ -546,8 +547,8 @@ static int c_psql_exec_query(c_psql_database_t *db, udb_query_t *q,
 
   status = udb_query_prepare_result(
       q, prep_area, host,
-      (db->plugin_name != NULL) ? db->plugin_name : "postgresql",
-      db->instance, column_names, (size_t)column_num, db->interval);
+      (db->plugin_name != NULL) ? db->plugin_name : "postgresql", db->instance,
+      column_names, (size_t)column_num, db->interval);
 
   if (0 != status) {
     log_err("udb_query_prepare_result failed with status %i.", status);
@@ -1146,8 +1147,8 @@ static int c_psql_config_database(oconfig_item_t *ci) {
       cf_util_get_string(c, &db->password);
     else if (0 == strcasecmp(c->key, "Instance"))
       cf_util_get_string(c, &db->instance);
-    else if (0 == strcasecmp (c->key, "Plugin"))
-      cf_util_get_string (c, &db->plugin_name);
+    else if (0 == strcasecmp(c->key, "Plugin"))
+      cf_util_get_string(c, &db->plugin_name);
     else if (0 == strcasecmp(c->key, "SSLMode"))
       cf_util_get_string(c, &db->sslmode);
     else if (0 == strcasecmp(c->key, "KRBSrvName"))
index d27e958..e60ba45 100644 (file)
@@ -233,6 +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;
@@ -244,7 +250,7 @@ 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 @@ static void cpy_build_name(char *buf, size_t size, PyObject *callback,
 }
 
 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;
 
@@ -308,6 +314,7 @@ void cpy_log_exception(const char *context) {
   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)
@@ -318,11 +325,17 @@ void cpy_log_exception(const char *context) {
     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);
@@ -356,10 +369,11 @@ void cpy_log_exception(const char *context) {
     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 @@ static int cpy_write_callback(const data_set_t *ds,
       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) {
@@ -706,8 +721,10 @@ static PyObject *cpy_flush(PyObject *self, PyObject *args, PyObject *kwds) {
   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;
 }
@@ -843,8 +860,10 @@ static PyObject *cpy_error(PyObject *self, PyObject *args) {
   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 +871,10 @@ static PyObject *cpy_warning(PyObject *self, PyObject *args) {
   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 +882,10 @@ static PyObject *cpy_notice(PyObject *self, PyObject *args) {
   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 +893,10 @@ static PyObject *cpy_info(PyObject *self, PyObject *args) {
   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 +905,10 @@ static PyObject *cpy_debug(PyObject *self, PyObject *args) {
   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 +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;
   }
@@ -1212,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
@@ -1239,6 +1267,11 @@ static int cpy_init_python(void) {
   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");
@@ -1268,6 +1301,9 @@ static int cpy_init_python(void) {
                      (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);
index d1bdedd..15c1848 100644 (file)
@@ -1280,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 +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 */
 };
index a005ffb..9e9ed2e 100644 (file)
@@ -731,6 +731,15 @@ metric_family_get(data_set_t const *ds, value_list_t const *vl, size_t ds_index,
 }
 /* }}} */
 
+static void prom_logger(__attribute__((unused)) void *arg, char const *fmt,
+                        va_list ap) {
+  /* {{{ */
+  char errbuf[1024];
+  vsnprintf(errbuf, sizeof(errbuf), fmt, ap);
+
+  ERROR("write_prometheus plugin: %s", errbuf);
+} /* }}} prom_logger */
+
 #if MHD_VERSION >= 0x00090000
 static int prom_open_socket(int addrfamily) {
   /* {{{ */
@@ -785,11 +794,12 @@ static struct MHD_Daemon *prom_start_daemon() {
     return NULL;
   }
 
-  struct MHD_Daemon *d =
-      MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, 0,
-                       /* MHD_AcceptPolicyCallback = */ NULL,
-                       /* MHD_AcceptPolicyCallback arg = */ NULL, http_handler,
-                       NULL, MHD_OPTION_LISTEN_SOCKET, fd, MHD_OPTION_END);
+  struct MHD_Daemon *d = MHD_start_daemon(
+      MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, httpd_port,
+      /* MHD_AcceptPolicyCallback = */ NULL,
+      /* MHD_AcceptPolicyCallback arg = */ NULL, http_handler, NULL,
+      MHD_OPTION_LISTEN_SOCKET, fd, MHD_OPTION_EXTERNAL_LOGGER, prom_logger,
+      NULL, MHD_OPTION_END);
   if (d == NULL) {
     ERROR("write_prometheus plugin: MHD_start_daemon() failed.");
     close(fd);
@@ -801,11 +811,11 @@ static struct MHD_Daemon *prom_start_daemon() {
 #else /* if MHD_VERSION < 0x00090000 */
 static struct MHD_Daemon *prom_start_daemon() {
   /* {{{ */
-  struct MHD_Daemon *d =
-      MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, 0,
-                       /* MHD_AcceptPolicyCallback = */ NULL,
-                       /* MHD_AcceptPolicyCallback arg = */ NULL, http_handler,
-                       NULL, MHD_OPTION_END);
+  struct MHD_Daemon *d = MHD_start_daemon(
+      MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, httpd_port,
+      /* MHD_AcceptPolicyCallback = */ NULL,
+      /* MHD_AcceptPolicyCallback arg = */ NULL, http_handler, NULL,
+      MHD_OPTION_EXTERNAL_LOGGER, prom_logger, NULL, MHD_OPTION_END);
   if (d == NULL) {
     ERROR("write_prometheus plugin: MHD_start_daemon() failed.");
     return NULL;