Merge branch 'master' into ff/java
authorFlorian Forster <octo@leeloo.lan.home.verplant.org>
Sun, 22 Feb 2009 18:50:14 +0000 (19:50 +0100)
committerFlorian Forster <octo@leeloo.lan.home.verplant.org>
Sun, 22 Feb 2009 18:50:14 +0000 (19:50 +0100)
19 files changed:
ChangeLog
bindings/perl/Collectd/Unixsock.pm
configure.in
contrib/cussh.pl
src/Makefile.am
src/bind.c
src/collectd-perl.pod
src/collectd.conf.pod
src/common.c
src/common.h
src/ipmi.c
src/perl.c
src/powerdns.c
src/table.c [new file with mode: 0644]
src/types.db
src/types.db.pod
src/utils_cmd_putval.c
src/utils_db_query.c
version-gen.sh

index 6445cdc..10a4cc1 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2009-02-22, Version 4.6.1
+       * collectd: Many documentation fixes.
+       * Collectd::Unixsock: Error handling has been improved.
+       * regex match: Don't link with the PCRE library.
+       * bind plugin: Various bugs have been fixed. Thanks to Bruno PrĂ©mont
+         for finding and fixing most of them.
+       * ipmi plugin: Fix an off-by-one error which could cause segmentation
+         faults. Thanks to Peter Holik for his patch.
+
 2009-02-16, Version 4.6.0
        * collectd: Added the `filter chain' infrastructure, which allows the
          user to use `matches' and `targets' to control value processing.
        * swap plugin: Code for OpenBSD (and possibly other *BSDs) has been
          added.
 
+2009-02-22, Version 4.5.3
+       * build system: The check for libupsclient even when `pkg-config' is
+         not available.
+       * collectd: Fix error handling in the global cache.
+       * Collectd::Unixsock: Error handling has been improved.
+       * ascent plugin: Fix a memory leak. Thanks to Bruno PrĂ©mont for his
+         patch.
+       * ipmi plugin: Fix an off-by-one error which could cause segmentation
+         faults. Thanks to Peter Holik for his patch.
+       * tcpconns plugin: An endianness problem has been fixed in the *BSD
+         code. Thanks to "thated" for reporting this.
+
 2009-01-02, Version 4.5.2
        * build system: Check for `mysql.h' and `mysql/mysql.h', since the
          file may be in both locations, especially when the database was
index eb6e389..4403178 100644 (file)
@@ -287,7 +287,21 @@ sub putval
        }
        else
        {
-               my $time = $args{'time'} ? $args{'time'} : time ();
+               my $time;
+
+               if ("ARRAY" ne ref ($args{'values'}))
+               {
+                       cluck ("Invalid `values' argument (expected an array ref)");
+                       return;
+               }
+
+               if (! scalar @{$args{'values'}})
+               {
+                       cluck ("Empty `values' array");
+                       return;
+               }
+
+               $time = $args{'time'} ? $args{'time'} : time ();
                $values = join (':', $time, map { defined ($_) ? $_ : 'U' } (@{$args{'values'}}));
        }
 
index cb303ca..401604f 100644 (file)
@@ -3187,6 +3187,7 @@ AC_PLUGIN([serial],      [$plugin_serial],     [serial port traffic])
 AC_PLUGIN([snmp],        [$with_libnetsnmp],   [SNMP querying plugin])
 AC_PLUGIN([swap],        [$plugin_swap],       [Swap usage statistics])
 AC_PLUGIN([syslog],      [$have_syslog],       [Syslog logging plugin])
+AC_PLUGIN([table],       [yes],                [Parsing of tabular data])
 AC_PLUGIN([tail],        [yes],                [Parsing of logfiles])
 AC_PLUGIN([tape],        [$plugin_tape],       [Tape drive statistics])
 AC_PLUGIN([target_notification], [yes],        [The notification target])
@@ -3388,6 +3389,7 @@ Configuration:
     snmp  . . . . . . . . $enable_snmp
     swap  . . . . . . . . $enable_swap
     syslog  . . . . . . . $enable_syslog
+    table . . . . . . . . $enable_table
     tail  . . . . . . . . $enable_tail
     tape  . . . . . . . . $enable_tape
     target_notification . $enable_target_notification
index ee4c893..c143aea 100755 (executable)
@@ -110,13 +110,42 @@ use Collectd::Unixsock();
        exit 0;
 }
 
+sub tokenize {
+       my $line     = shift || return;
+       my $line_ptr = $line;
+       my @line     = ();
+
+       my $token_pattern = qr/[^"\s]+|"[^"]+"/;
+
+       while (my ($token) = $line_ptr =~ m/^($token_pattern)\s+/) {
+               $line_ptr = $';
+               push @line, $token;
+       }
+
+       if ($line_ptr =~ m/^$token_pattern$/) {
+               push @line, $line_ptr;
+       }
+       else {
+               my ($token) = split m/ /, $line_ptr, 1;
+               print STDERR "Failed to parse line: $line\n";
+               print STDERR "Parse error near token \"$token\".\n";
+               return;
+       }
+
+       foreach my $l (@line) {
+               if ($l =~ m/^"(.*)"$/) {
+                       $l = $1;
+               }
+       }
+       return @line;
+}
+
 sub getid {
        my $string = shift || return;
 
-       print $$string . $/;
        my ($h, $p, $pi, $t, $ti) =
-               $$string =~ m#^([^/]+)/([^/-]+)(?:-([^/]+))?/([^/-]+)(?:-([^/]+))?\s*#;
-       $$string = $';
+               $string =~ m#^([^/]+)/([^/-]+)(?:-([^/]+))?/([^/-]+)(?:-([^/]+))?\s*#;
+       $string = $';
 
        return if ((! $h) || (! $p) || (! $t));
 
@@ -172,7 +201,7 @@ HELP
        return 1;
 } # cmd_help
 
-=item B<GETVAL> I<Identifier>
+=item B<PUTVAL> I<Identifier> I<Valuelist>
 
 =cut
 
@@ -180,18 +209,37 @@ sub putval {
        my $sock = shift || return;
        my $line = shift || return;
 
-       my $id = getid(\$line);
+       my @line = tokenize($line);
+
+       my $id;
+       my $ret;
+
+       if (! @line) {
+               return;
+       }
+
+       if (scalar(@line) < 2) {
+               print STDERR "Synopsis: PUTVAL <id> <value0> [<value1> ...]" . $/;
+               return;
+       }
+
+       $id = getid($line[0]);
 
        if (! $id) {
-               print STDERR $sock->{'error'} . $/;
+               print STDERR "Invalid id \"$line[0]\"." . $/;
                return;
        }
 
        my ($time, @values) = split m/:/, $line;
-       return $sock->putval(%$id, time => $time, values => \@values);
+       $ret = $sock->putval(%$id, time => $time, values => \@values);
+
+       if (! $ret) {
+               print STDERR "socket error: " . $sock->{'error'} . $/;
+       }
+       return $ret;
 }
 
-=item B<PUTVAL> I<Identifier> I<Valuelist>
+=item B<GETVAL> I<Identifier>
 
 =cut
 
@@ -199,17 +247,31 @@ sub getval {
        my $sock = shift || return;
        my $line = shift || return;
 
-       my $id = getid(\$line);
+       my @line = tokenize($line);
+
+       my $id;
+       my $vals;
+
+       if (! @line) {
+               return;
+       }
+
+       if (scalar(@line) < 1) {
+               print STDERR "Synopsis: GETVAL <id>" . $/;
+               return;
+       }
+
+       $id = getid($line[0]);
 
        if (! $id) {
-               print STDERR $sock->{'error'} . $/;
+               print STDERR "Invalid id \"$line[0]\"." . $/;
                return;
        }
 
-       my $vals = $sock->getval(%$id);
+       $vals = $sock->getval(%$id);
 
        if (! $vals) {
-               print STDERR $sock->{'error'} . $/;
+               print STDERR "socket error: " . $sock->{'error'} . $/;
                return;
        }
 
@@ -227,6 +289,8 @@ sub flush {
        my $sock = shift || return;
        my $line = shift;
 
+       my @line = tokenize($line);
+
        my $res;
 
        if (! $line) {
@@ -235,7 +299,7 @@ sub flush {
        else {
                my %args = ();
 
-               foreach my $i (split m/ /, $line) {
+               foreach my $i (@line) {
                        my ($option, $value) = $i =~ m/^([^=]+)=(.+)$/;
                        next if (! ($option && $value));
 
@@ -264,10 +328,9 @@ sub flush {
        }
 
        if (! $res) {
-               print STDERR $sock->{'error'} . $/;
-               return;
+               print STDERR "socket error: " . $sock->{'error'} . $/;
        }
-       return 1;
+       return $res;
 }
 
 =item B<LISTVAL>
@@ -276,13 +339,19 @@ sub flush {
 
 sub listval {
        my $sock = shift || return;
+       my $line = shift;
 
        my @res;
 
+       if ($line ne "") {
+               print STDERR "Synopsis: LISTVAL" . $/;
+               return;
+       }
+
        @res = $sock->listval();
 
        if (! @res) {
-               print STDERR $sock->{'error'} . $/;
+               print STDERR "socket error: " . $sock->{'error'} . $/;
                return;
        }
 
@@ -300,19 +369,29 @@ sub putnotif {
        my $sock = shift || return;
        my $line = shift || return;
 
+       my @line = tokenize($line);
+
+       my $ret;
+
        my (%values) = ();
-       foreach my $i (split m/ /, $line) {
-               my($key,$val) = split m/=/, $i, 2;
+       foreach my $i (@line) {
+               my ($key, $val) = split m/=/, $i, 2;
                if ($key && $val) {
                        $values{$key} = $val;
                }
                else {
-                       $values{'message'} .= ' '.$key;
+                       $values{'message'} = defined($values{'message'})
+                               ? ($values{'message'} . ' ' . $key)
+                               : $key;
                }
        }
        $values{'time'} ||= time();
-       my(@tmp) = %values;
-       return $sock->putnotif(%values);
+
+       $ret = $sock->putnotif(%values);
+       if (! $ret) {
+               print STDERR "socket error: " . $sock->{'error'} . $/;
+       }
+       return $ret;
 }
 
 =back
index e159026..f4f2be9 100644 (file)
@@ -777,6 +777,14 @@ collectd_LDADD += "-dlopen" syslog.la
 collectd_DEPENDENCIES += syslog.la
 endif
 
+if BUILD_PLUGIN_TABLE
+pkglib_LTLIBRARIES += table.la
+table_la_SOURCES = table.c
+table_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" table.la
+collectd_DEPENDENCIES += table.la
+endif
+
 if BUILD_PLUGIN_TAIL
 pkglib_LTLIBRARIES += tail.la
 tail_la_SOURCES = tail.c
index 8e785d9..4b3662f 100644 (file)
@@ -59,7 +59,7 @@
  * `list_info_ptr_t' are passed to the callbacks in the `void *user_data'
  * pointer.
  */
-typedef int (*list_callback_t) (const char *name, counter_t value,
+typedef int (*list_callback_t) (const char *name, value_t value,
     time_t current_time, void *user_data);
 
 struct cb_view_s
@@ -131,7 +131,7 @@ static const translation_info_t nsstats_translation_table[] = /* {{{ */
   /* Rejects */
   { "AuthQryRej",      "dns_reject",   "authorative" },
   { "RecQryRej",       "dns_reject",   "recursive"   },
-  { "XfrRej",          "dns_reject",   "transer"     },
+  { "XfrRej",          "dns_reject",   "transfer"    },
   { "UpdateRej",       "dns_reject",   "update"      },
   /* Responses */
   { "Response",        "dns_response", "normal"      },
@@ -178,7 +178,7 @@ static const translation_info_t zonestats_translation_table[] = /* {{{ */
   { "NotifyRej",       "dns_notify",   "rejected"    },
   /* SOA/AXFS/IXFS requests */
   { "SOAOutv4",        "dns_opcode",   "SOA-IPv4"    },
-  { "SOAOutv6",        "dns_opcode",   "SOA-IPv4"    },
+  { "SOAOutv6",        "dns_opcode",   "SOA-IPv6"    },
   { "AXFRReqv4",       "dns_opcode",   "AXFR-IPv4"   },
   { "AXFRReqv6",       "dns_opcode",   "AXFR-IPv6"   },
   { "IXFRReqv4",       "dns_opcode",   "IXFR-IPv4"   },
@@ -239,26 +239,13 @@ static int memsummary_translation_table_length =
   STATIC_ARRAY_SIZE (memsummary_translation_table);
 /* }}} */
 
-static void remove_special (char *buffer, size_t buffer_size) /* {{{ */
-{
-  size_t i;
-
-  for (i = 0; i < buffer_size; i++)
-  {
-    if (buffer[i] == 0)
-      return;
-    if ((!isalnum ((int) buffer[i])) && (buffer[i] != '-'))
-      buffer[i] = '_';
-  }
-} /* }}} void remove_special */
-
-static void submit_counter(time_t ts, const char *plugin_instance, /* {{{ */
-    const char *type, const char *type_instance, counter_t value)
+static void submit (time_t ts, const char *plugin_instance, /* {{{ */
+    const char *type, const char *type_instance, value_t value)
 {
   value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0].counter = value;
+  values[0] = value;
 
   vl.values = values;
   vl.values_len = 1;
@@ -268,16 +255,16 @@ static void submit_counter(time_t ts, const char *plugin_instance, /* {{{ */
   if (plugin_instance) {
     sstrncpy(vl.plugin_instance, plugin_instance,
         sizeof(vl.plugin_instance));
-    remove_special (vl.plugin_instance, sizeof (vl.plugin_instance));
+    replace_special (vl.plugin_instance, sizeof (vl.plugin_instance));
   }
   sstrncpy(vl.type, type, sizeof(vl.type));
   if (type_instance) {
     sstrncpy(vl.type_instance, type_instance,
         sizeof(vl.type_instance));
-    remove_special (vl.plugin_instance, sizeof (vl.plugin_instance));
+    replace_special (vl.plugin_instance, sizeof (vl.plugin_instance));
   }
   plugin_dispatch_values(&vl);
-} /* }}} void submit_counter */
+} /* }}} void submit */
 
 static size_t bind_curl_callback (void *buf, size_t size, /* {{{ */
     size_t nmemb, void __attribute__((unused)) *stream)
@@ -312,7 +299,7 @@ static size_t bind_curl_callback (void *buf, size_t size, /* {{{ */
  * Callback, that's called with a translation table.
  * (Plugin instance is fixed, type and type instance come from lookup table.)
  */
-static int bind_xml_table_callback (const char *name, counter_t value, /* {{{ */
+static int bind_xml_table_callback (const char *name, value_t value, /* {{{ */
     time_t current_time, void *user_data)
 {
   translation_table_ptr_t *table = (translation_table_ptr_t *) user_data;
@@ -326,7 +313,7 @@ static int bind_xml_table_callback (const char *name, counter_t value, /* {{{ */
     if (strcmp (table->table[i].xml_name, name) != 0)
       continue;
 
-    submit_counter (current_time,
+    submit (current_time,
         table->plugin_instance,
         table->table[i].type,
         table->table[i].type_instance,
@@ -342,14 +329,14 @@ static int bind_xml_table_callback (const char *name, counter_t value, /* {{{ */
  * (Plugin instance and type are fixed, xml name is used as type instance.)
  */
 static int bind_xml_list_callback (const char *name, /* {{{ */
-    counter_t value, time_t current_time, void *user_data)
+    value_t value, time_t current_time, void *user_data)
 {
   list_info_ptr_t *list_info = (list_info_ptr_t *) user_data;
 
   if (list_info == NULL)
     return (-1);
 
-  submit_counter (current_time,
+  submit (current_time,
       list_info->plugin_instance,
       list_info->type,
       /* type instance = */ name,
@@ -389,6 +376,37 @@ static int bind_xml_read_counter (xmlDoc *doc, xmlNode *node, /* {{{ */
   return (0);
 } /* }}} int bind_xml_read_counter */
 
+static int bind_xml_read_gauge (xmlDoc *doc, xmlNode *node, /* {{{ */
+    gauge_t *ret_value)
+{
+  char *str_ptr, *end_ptr;
+  double value;
+
+  str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+  if (str_ptr == NULL)
+  {
+    ERROR ("bind plugin: bind_xml_read_gauge: xmlNodeListGetString failed.");
+    return (-1);
+  }
+
+  errno = 0;
+  value = strtod (str_ptr, &end_ptr);
+  xmlFree(str_ptr);
+  if (str_ptr == end_ptr || errno)
+  {
+    if (errno && (value < 0))
+      ERROR ("bind plugin: bind_xml_read_gauge: strtod failed with underflow.");
+    else if (errno && (value > 0))
+      ERROR ("bind plugin: bind_xml_read_gauge: strtod failed with overflow.");
+    else
+      ERROR ("bind plugin: bind_xml_read_gauge: strtod failed.");
+    return (-1);
+  }
+
+  *ret_value = (gauge_t) value;
+  return (0);
+} /* }}} int bind_xml_read_gauge */
+
 static int bind_xml_read_timestamp (const char *xpath_expression, /* {{{ */
     xmlDoc *doc, xmlXPathContext *xpathCtx, time_t *ret_value)
 {
@@ -459,7 +477,7 @@ static int bind_xml_read_timestamp (const char *xpath_expression, /* {{{ */
  * Reads statistics in the form:
  * <foo>
  *   <name>QUERY</name>
- *   <value>123</name>
+ *   <counter>123</counter>
  * </foo>
  */
 static int bind_parse_generic_name_value (const char *xpath_expression, /* {{{ */
@@ -486,10 +504,15 @@ static int bind_parse_generic_name_value (const char *xpath_expression, /* {{{ *
   {
     xmlNode *name_node = NULL;
     xmlNode *counter = NULL;
+    xmlNode *parent;
     xmlNode *child;
 
+    parent = xpathObj->nodesetval->nodeTab[i];
+    DEBUG ("bind plugin: bind_parse_generic_name_value: parent->name = %s;",
+        (char *) parent->name);
+
     /* Iterate over all child nodes. */
-    for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
+    for (child = parent->xmlChildrenNode;
         child != NULL;
         child = child->next)
     {
@@ -506,10 +529,10 @@ static int bind_parse_generic_name_value (const char *xpath_expression, /* {{{ *
     {
       char *name = (char *) xmlNodeListGetString (doc,
           name_node->xmlChildrenNode, 1);
-      counter_t value;
+      value_t value;
       int status;
 
-      status = bind_xml_read_counter (doc, counter, &value);
+      status = bind_xml_read_counter (doc, counter, &value.counter);
       if (status != 0)
         continue;
 
@@ -545,7 +568,7 @@ static int bind_parse_generic_value_list (const char *xpath_expression, /* {{{ *
     list_callback_t list_callback,
     void *user_data,
     xmlDoc *doc, xmlXPathContext *xpathCtx,
-    time_t current_time)
+    time_t current_time, int ds_type)
 {
   xmlXPathObject *xpathObj = NULL;
   int num_entries;
@@ -571,14 +594,18 @@ static int bind_parse_generic_value_list (const char *xpath_expression, /* {{{ *
         child = child->next)
     {
       char *node_name;
-      counter_t value;
+      value_t value;
       int status;
 
       if (child->type != XML_ELEMENT_NODE)
         continue;
 
       node_name = (char *) child->name;
-      status = bind_xml_read_counter (doc, child, &value);
+
+      if (ds_type == DS_TYPE_GAUGE)
+        status = bind_xml_read_gauge (doc, child, &value.gauge);
+      else
+        status = bind_xml_read_counter (doc, child, &value.counter);
       if (status != 0)
         continue;
 
@@ -663,7 +690,7 @@ static int bind_xml_stats_handle_zone (int version, xmlDoc *doc, /* {{{ */
     bind_parse_generic_value_list (/* xpath = */ "counters",
         /* callback = */ bind_xml_table_callback,
         /* user_data = */ &table_ptr,
-        doc, path_ctx, current_time);
+        doc, path_ctx, current_time, DS_TYPE_COUNTER);
   } /* }}} */
 
   xmlXPathFreeObject (path_obj);
@@ -972,7 +999,7 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
       bind_parse_generic_value_list ("server/nsstats",
           /* callback = */ bind_xml_table_callback,
           /* user_data = */ &table_ptr,
-          doc, xpathCtx, current_time);
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
     }
     else
     {
@@ -1018,7 +1045,7 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
       bind_parse_generic_value_list ("server/zonestats",
           /* callback = */ bind_xml_table_callback,
           /* user_data = */ &table_ptr,
-          doc, xpathCtx, current_time);
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
     }
     else
     {
@@ -1065,7 +1092,7 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
       bind_parse_generic_value_list ("server/resstats",
           /* callback = */ bind_xml_table_callback,
           /* user_data = */ &table_ptr,
-          doc, xpathCtx, current_time);
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
     }
     else
     {
@@ -1099,7 +1126,7 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
     bind_parse_generic_value_list ("memory/summary",
           /* callback = */ bind_xml_table_callback,
           /* user_data = */ &table_ptr,
-          doc, xpathCtx, current_time);
+          doc, xpathCtx, current_time, DS_TYPE_GAUGE);
   }
 
   if (views_num > 0)
@@ -1315,7 +1342,16 @@ static int bind_config (oconfig_item_t *ci) /* {{{ */
   {
     oconfig_item_t *child = ci->children + i;
 
-    if (strcasecmp ("OpCodes", child->key) == 0)
+    if (strcasecmp ("Url", child->key) == 0) {
+      if ((child->values_num != 1) || (child->values[0].type != OCONFIG_TYPE_STRING))
+      {
+        WARNING ("bind plugin: The `Url' option needs "
+                 "exactly one string argument.");
+        return (-1);
+      }
+
+      url = strdup (child->values[0].value.string);
+    } else if (strcasecmp ("OpCodes", child->key) == 0)
       bind_config_set_bool ("OpCodes", &global_opcodes, child);
     else if (strcasecmp ("QTypes", child->key) == 0)
       bind_config_set_bool ("QTypes", &global_qtypes, child);
@@ -1326,7 +1362,7 @@ static int bind_config (oconfig_item_t *ci) /* {{{ */
     else if (strcasecmp ("ResolverStats", child->key) == 0)
       bind_config_set_bool ("ResolverStats", &global_resolver_stats, child);
     else if (strcasecmp ("MemoryStats", child->key) == 0)
-      bind_config_set_bool ("MemoryStats", &global_resolver_stats, child);
+      bind_config_set_bool ("MemoryStats", &global_memory_stats, child);
     else if (strcasecmp ("View", child->key) == 0)
       bind_config_add_view (child);
     else
index acb8abd..0f48ba5 100644 (file)
@@ -297,6 +297,10 @@ there is a large number of predefined data-sets available in the B<types.db>
 file which are automatically registered with collectd - see L<types.db(5)> for
 a description of the format of this file.
 
+B<Note>: Using B<plugin_register> to register a data-set is deprecated. Add
+the new type to a custom L<types.db(5)> file instead. This functionality might
+be removed in a future version of collectd.
+
 If the I<type> argument is any of the other types (B<TYPE_INIT>, B<TYPE_READ>,
 ...) then I<data> is expected to be a function name. If the name is not
 prefixed with the plugin's package name collectd will add it automatically.
index ea583e3..48b75bc 100644 (file)
@@ -662,7 +662,7 @@ separated by dashes I<("-")>.
 
 Specifies the columns whose values will be used to create the "type-instance"
 for each row. If you specify more than one column, the value of all columns
-will be join together with the dashes I<("-")> as separation character.
+will be joined together with dashes I<("-")> as separation characters.
 
 The plugin itself does not check whether or not all built instances are
 different. It's your responsibility to assure that each is unique. This is
@@ -2495,9 +2495,109 @@ debugging support.
 
 =back
 
+=head2 Plugin C<table>
+
+The C<table plugin> provides generic means to parse tabular data and dispatch
+user specified values. Values are selected based on column numbers. For
+example, this plugin may be used to get values from the Linux L<proc(5)>
+filesystem or CSV (comma separated values) files.
+
+  <Plugin table>
+    <Table "/proc/slabinfo">
+      Instance "slabinfo"
+      Separator " "
+      <Result>
+        Type gauge
+        InstancePrefix "active_objs"
+        InstancesFrom 0
+        ValuesFrom 1
+      </Result>
+      <Result>
+        Type gauge
+        InstancePrefix "objperslab"
+        InstancesFrom 0
+        ValuesFrom 4
+      </Result>
+    </Table>
+  </Plugin>
+
+The configuration consists of one or more B<Table> blocks, each of which
+configures one file to parse. Within each B<Table> block, there are one or
+more B<Result> blocks, which configure which data to select and how to
+interpret it.
+
+The following options are available inside a B<Table> block:
+
+=over 4
+
+=item B<Instance> I<instance>
+
+If specified, I<instance> is used as the plugin instance. So, in the above
+example, the plugin name C<table-slabinfo> would be used. If omitted, the
+filename of the table is used instead, with all special characters replaced
+with an underscore (C<_>).
+
+=item B<Separator> I<string>
+
+Any character of I<string> is interpreted as a delimiter between the different
+columns of the table. A sequence of two or more contiguous delimiters in the
+table is considered to be a single delimiter, i.E<nbsp>e. there cannot be any
+empty columns. The plugin uses the L<strtok_r(3)> function to parse the lines
+of a table - see its documentation for more details. This option is mandatory.
+
+A horizontal tab, newline and carriage return may be specified by C<\\t>,
+C<\\n> and C<\\r> respectively. Please note that the double backslashes are
+required because of collectd's config parsing.
+
+=back
+
+The following options are available inside a B<Result> block:
+
+=over 4
+
+=item B<Type> I<type>
+
+Sets the type used to dispatch the values to the daemon. Detailed information
+about types and their configuration can be found in L<types.db(5)>. This
+option is mandatory.
+
+=item B<InstancePrefix> I<prefix>
+
+If specified, prepend I<prefix> to the type instance. If omitted, only the
+B<InstancesFrom> option is considered for the type instance.
+
+=item B<InstancesFrom> I<column0> [I<column1> ...]
+
+If specified, the content of the given columns (identified by the column
+number starting at zero) will be used to create the type instance for each
+row. Multiple values (and the instance prefix) will be joined together with
+dashes (I<->) as separation character. If omitted, only the B<InstancePrefix>
+option is considered for the type instance.
+
+The plugin itself does not check whether or not all built instances are
+different. It’s your responsibility to assure that each is unique. This is
+especially true, if you do not specify B<InstancesFrom>: B<You> have to make
+sure that the table only contains one row.
+
+If neither B<InstancePrefix> nor B<InstancesFrom> is given, the type instance
+will be empty.
+
+=item B<ValuesFrom> I<column0> [I<column1> ...]
+
+Specifies the columns (identified by the column numbers starting at zero)
+whose content is used as the actual data for the data sets that are dispatched
+to the daemon. How many such columns you need is determined by the B<Type>
+setting above. If you specify too many or not enough columns, the plugin will
+complain about that and no data will be submitted to the daemon. The plugin
+uses L<strtoll(3)> and L<strtod(3)> to parse counter and gauge values
+respectively, so anything supported by those functions is supported by the
+plugin as well. This option is mandatory.
+
+=back
+
 =head2 Plugin C<tail>
 
-The C<tail plugin> plugins follows logfiles, just like L<tail(1)> does, parses
+The C<tail plugin> follows logfiles, just like L<tail(1)> does, parses
 each line and dispatches found values. What is matched can be configured by the
 user using (extended) regular expressions, as described in L<regex(7)>.
 
index 28f16da..2286088 100644 (file)
@@ -315,6 +315,40 @@ int strsubstitute (char *str, char c_from, char c_to)
        return (ret);
 } /* int strsubstitute */
 
+int strunescape (char *buf, size_t buf_len)
+{
+       size_t i;
+
+       for (i = 0; (i < buf_len) && (buf[i] != '\0'); ++i)
+       {
+               if (buf[i] != '\\')
+                       continue;
+
+               if ((i >= buf_len) || (buf[i + 1] == '\0')) {
+                       ERROR ("string unescape: backslash found at end of string.");
+                       return (-1);
+               }
+
+               switch (buf[i + 1]) {
+                       case 't':
+                               buf[i] = '\t';
+                               break;
+                       case 'n':
+                               buf[i] = '\n';
+                               break;
+                       case 'r':
+                               buf[i] = '\r';
+                               break;
+                       default:
+                               buf[i] = buf[i + 1];
+                               break;
+               }
+
+               memmove (buf + i + 1, buf + i + 2, buf_len - i - 2);
+       }
+       return (0);
+} /* int strunescape */
+
 int escape_slashes (char *buf, int buf_len)
 {
        int i;
@@ -347,6 +381,19 @@ int escape_slashes (char *buf, int buf_len)
        return (0);
 } /* int escape_slashes */
 
+void replace_special (char *buffer, size_t buffer_size)
+{
+       size_t i;
+
+       for (i = 0; i < buffer_size; i++)
+       {
+               if (buffer[i] == 0)
+                       return;
+               if ((!isalnum ((int) buffer[i])) && (buffer[i] != '-'))
+                       buffer[i] = '_';
+       }
+} /* void replace_special */
+
 int timeval_cmp (struct timeval tv0, struct timeval tv1, struct timeval *delta)
 {
        struct timeval *larger;
@@ -777,6 +824,30 @@ int parse_identifier (char *str, char **ret_host,
        return (0);
 } /* int parse_identifier */
 
+int parse_value (const char *value, value_t *ret_value, const data_source_t ds)
+{
+       char *endptr = NULL;
+
+       if (DS_TYPE_COUNTER == ds.type)
+               ret_value->counter = (counter_t)strtoll (value, &endptr, 0);
+       else if (DS_TYPE_GAUGE == ds.type)
+               ret_value->gauge = (gauge_t)strtod (value, &endptr);
+       else {
+               ERROR ("parse_value: Invalid data source \"%s\" "
+                               "(type = %i).", ds.name, ds.type);
+               return -1;
+       }
+
+       if (value == endptr) {
+               ERROR ("parse_value: Failed to parse string as number: %s.", value);
+               return -1;
+       }
+       else if ((NULL != endptr) && ('\0' != *endptr))
+               WARNING ("parse_value: Ignoring trailing garbage after number: %s.",
+                               endptr);
+       return 0;
+} /* int parse_value */
+
 int parse_values (char *buffer, value_list_t *vl, const data_set_t *ds)
 {
        int i;
@@ -803,12 +874,10 @@ int parse_values (char *buffer, value_list_t *vl, const data_set_t *ds)
                }
                else
                {
-                       if (strcmp ("U", ptr) == 0)
+                       if ((strcmp ("U", ptr) == 0) && (ds->ds[i].type == DS_TYPE_GAUGE))
                                vl->values[i].gauge = NAN;
-                       else if (ds->ds[i].type == DS_TYPE_COUNTER)
-                               vl->values[i].counter = atoll (ptr);
-                       else if (ds->ds[i].type == DS_TYPE_GAUGE)
-                               vl->values[i].gauge = atof (ptr);
+                       else if (0 != parse_value (ptr, &vl->values[i], ds->ds[i]))
+                               return -1;
                }
 
                i++;
index 85db3ad..7209808 100644 (file)
@@ -158,10 +158,50 @@ int strjoin (char *dst, size_t dst_len, char **fields, size_t fields_num, const
  */
 int escape_slashes (char *buf, int buf_len);
 
+/*
+ * NAME
+ *   replace_special
+ *
+ * DESCRIPTION
+ *   Replaces any special characters (anything that's not alpha-numeric or a
+ *   dash) with an underscore.
+ *
+ *   E.g. "foo$bar&" would become "foo_bar_".
+ *
+ * PARAMETERS
+ *   `buffer'      String to be handled.
+ *   `buffer_size' Length of the string. The function returns after
+ *                 encountering a null-byte or reading this many bytes.
+ */
+void replace_special (char *buffer, size_t buffer_size);
+
 int strsubstitute (char *str, char c_from, char c_to);
 
 /*
  * NAME
+ *   strunescape
+ *
+ * DESCRIPTION
+ *   Replaces any escaped characters in a string with the appropriate special
+ *   characters. The following escaped characters are recognized:
+ *
+ *     \t -> <tab>
+ *     \n -> <newline>
+ *     \r -> <carriage return>
+ *
+ *   For all other escacped characters only the backslash will be removed.
+ *
+ * PARAMETERS
+ *   `buf'         String to be unescaped.
+ *   `buf_len'     Length of the string, including the terminating null-byte.
+ *
+ * RETURN VALUE
+ *   Returns zero upon success, a value less than zero else.
+ */
+int strunescape (char *buf, size_t buf_len);
+
+/*
+ * NAME
  *   timeval_cmp
  *
  * DESCRIPTION
@@ -213,6 +253,7 @@ int format_name (char *ret, int ret_len,
 int parse_identifier (char *str, char **ret_host,
                char **ret_plugin, char **ret_plugin_instance,
                char **ret_type, char **ret_type_instance);
+int parse_value (const char *value, value_t *ret_value, const data_source_t ds);
 int parse_values (char *buffer, value_list_t *vl, const data_set_t *ds);
 
 #if !HAVE_GETPWNAM_R
index 0b79823..44fae35 100644 (file)
@@ -234,35 +234,54 @@ static int sensor_list_add (ipmi_sensor_t *sensor)
   c_ipmi_sensor_list_t *list_item;
   c_ipmi_sensor_list_t *list_prev;
 
+  char buffer[DATA_MAX_NAME_LEN];
+  const char *entity_id_string;
   char sensor_name[DATA_MAX_NAME_LEN];
   char *sensor_name_ptr;
-  int sensor_type, len;
+  int sensor_type;
   const char *type;
   ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor);
 
   sensor_id = ipmi_sensor_convert_to_id (sensor);
 
-  memset (sensor_name, 0, sizeof (sensor_name));
-  ipmi_sensor_get_name (sensor, sensor_name, sizeof (sensor_name));
-  sensor_name[sizeof (sensor_name) - 1] = 0;
+  memset (buffer, 0, sizeof (buffer));
+  ipmi_sensor_get_name (sensor, buffer, sizeof (buffer));
+  buffer[sizeof (buffer) - 1] = 0;
 
-  len = DATA_MAX_NAME_LEN - strlen(sensor_name);
-  strncat(sensor_name, " ", len--);
-  strncat(sensor_name, ipmi_entity_get_entity_id_string(ent), len);
+  entity_id_string = ipmi_entity_get_entity_id_string (ent);
 
-  sensor_name_ptr = strstr (sensor_name, ").");
-  if (sensor_name_ptr == NULL)
-    sensor_name_ptr = sensor_name;
+  if (entity_id_string == NULL)
+    sstrncpy (sensor_name, buffer, sizeof (sensor_name));
   else
-  {
-    char *sensor_name_ptr_id = strstr (sensor_name, "(");
-
+    ssnprintf (sensor_name, sizeof (sensor_name),
+        "%s %s", buffer, entity_id_string);
+
+  sstrncpy (buffer, sensor_name, sizeof (buffer));
+  sensor_name_ptr = strstr (buffer, ").");
+  if (sensor_name_ptr != NULL)
+  {
+    /* If name is something like "foo (123).bar",
+     * change that to "bar (123)".
+     * Both, sensor_name_ptr and sensor_id_ptr point to memory within the
+     * `buffer' array, which holds a copy of the current `sensor_name'. */
+    char *sensor_id_ptr;
+
+    /* `sensor_name_ptr' points to ").bar". */
+    sensor_name_ptr[1] = 0;
+    /* `buffer' holds "foo (123)\0bar\0". */
     sensor_name_ptr += 2;
-    len = DATA_MAX_NAME_LEN - strlen(sensor_name);
-    strncat(sensor_name, " ", len--);
-    strncat(sensor_name, sensor_name_ptr_id, 
-      MIN(sensor_name_ptr - sensor_name_ptr_id - 1, len));
+    /* `sensor_name_ptr' now points to "bar". */
+
+    sensor_id_ptr = strstr (buffer, "(");
+    if (sensor_id_ptr != NULL)
+    {
+      /* `sensor_id_ptr' now points to "(123)". */
+      ssnprintf (sensor_name, sizeof (sensor_name),
+          "%s %s", sensor_name_ptr, sensor_id_ptr); 
+    }
+    /* else: don't touch sensor_name. */
   }
+  sensor_name_ptr = sensor_name;
 
   /* Both `ignorelist' and `plugin_instance' may be NULL. */
   if (ignorelist_match (ignorelist, sensor_name_ptr) != 0)
index 9ef9cfc..0091266 100644 (file)
@@ -1527,6 +1527,9 @@ static XS (Collectd_plugin_register_ds)
 
        dXSARGS;
 
+       log_warn ("Using plugin_register() to register new data-sets is "
+                       "deprecated - add new entries to a custom types.db instead.");
+
        if (2 != items) {
                log_err ("Usage: Collectd::plugin_register_data_set(type, dataset)");
                XSRETURN_EMPTY;
index 164137b..beb49fb 100644 (file)
@@ -289,30 +289,11 @@ static void submit (const char *plugin_instance, /* {{{ */
     return;
   }
 
-  if (ds->ds[0].type == DS_TYPE_GAUGE)
+  if (0 != parse_value (value, &values[0], ds->ds[0]))
   {
-    char *endptr = NULL;
-
-    values[0].gauge = strtod (value, &endptr);
-
-    if (endptr == value)
-    {
-      ERROR ("powerdns plugin: Cannot convert `%s' "
-          "to a floating point number.", value);
-      return;
-    }
-  }
-  else
-  {
-    char *endptr = NULL;
-
-    values[0].counter = strtoll (value, &endptr, 0);
-    if (endptr == value)
-    {
-      ERROR ("powerdns plugin: Cannot convert `%s' "
-          "to an integer number.", value);
-      return;
-    }
+    ERROR ("powerdns plugin: Cannot convert `%s' "
+        "to a number.", value);
+    return;
   }
 
   vl.values = values;
diff --git a/src/table.c b/src/table.c
new file mode 100644 (file)
index 0000000..2911bf0
--- /dev/null
@@ -0,0 +1,560 @@
+/**
+ * collectd - src/table.c
+ * Copyright (C) 2009  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This module provides generic means to parse and dispatch tabular data.
+ */
+
+#include "collectd.h"
+#include "common.h"
+
+#include "configfile.h"
+#include "plugin.h"
+
+#define log_err(...) ERROR ("table plugin: " __VA_ARGS__)
+#define log_warn(...) WARNING ("table plugin: " __VA_ARGS__)
+
+/*
+ * private data types
+ */
+
+typedef struct {
+       char  *type;
+       char  *instance_prefix;
+       int   *instances;
+       size_t instances_num;
+       int   *values;
+       size_t values_num;
+
+       const data_set_t *ds;
+} tbl_result_t;
+
+typedef struct {
+       char *file;
+       char *sep;
+       char *instance;
+
+       tbl_result_t *results;
+       size_t        results_num;
+
+       size_t max_colnum;
+} tbl_t;
+
+static void tbl_result_setup (tbl_result_t *res)
+{
+       res->type            = NULL;
+
+       res->instance_prefix = NULL;
+       res->instances       = NULL;
+       res->instances_num   = 0;
+
+       res->values          = NULL;
+       res->values_num      = 0;
+
+       res->ds              = NULL;
+} /* tbl_result_setup */
+
+static void tbl_result_clear (tbl_result_t *res)
+{
+       sfree (res->type);
+
+       sfree (res->instance_prefix);
+       sfree (res->instances);
+       res->instances_num = 0;
+
+       sfree (res->values);
+       res->values_num = 0;
+
+       res->ds = NULL;
+} /* tbl_result_clear */
+
+static void tbl_setup (tbl_t *tbl, char *file)
+{
+       tbl->file        = sstrdup (file);
+       tbl->sep         = NULL;
+       tbl->instance    = NULL;
+
+       tbl->results     = NULL;
+       tbl->results_num = 0;
+
+       tbl->max_colnum  = 0;
+} /* tbl_setup */
+
+static void tbl_clear (tbl_t *tbl)
+{
+       size_t i;
+
+       sfree (tbl->file);
+       sfree (tbl->sep);
+       sfree (tbl->instance);
+
+       for (i = 0; i < tbl->results_num; ++i)
+               tbl_result_clear (tbl->results + i);
+       sfree (tbl->results);
+       tbl->results_num = 0;
+
+       tbl->max_colnum  = 0;
+} /* tbl_clear */
+
+static tbl_t *tables;
+static size_t tables_num;
+
+/*
+ * configuration handling
+ */
+
+static int tbl_config_set_s (char *name, char **var, oconfig_item_t *ci)
+{
+       if ((1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("\"%s\" expects a single string argument.", name);
+               return 1;
+       }
+
+       sfree (*var);
+       *var = sstrdup (ci->values[0].value.string);
+       return 0;
+} /* tbl_config_set_separator */
+
+static int tbl_config_append_array_i (char *name, int **var, size_t *len,
+               oconfig_item_t *ci)
+{
+       int *tmp;
+
+       size_t i;
+
+       if (1 > ci->values_num) {
+               log_err ("\"%s\" expects at least one argument.", name);
+               return 1;
+       }
+
+       for (i = 0; i < ci->values_num; ++i) {
+               if (OCONFIG_TYPE_NUMBER != ci->values[i].type) {
+                       log_err ("\"%s\" expects numerical arguments only.", name);
+                       return 1;
+               }
+       }
+
+       *len += ci->values_num;
+       tmp = (int *)realloc (*var, *len * sizeof (**var));
+       if (NULL == tmp) {
+               char errbuf[1024];
+               log_err ("realloc failed: %s.",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return -1;
+       }
+
+       *var = tmp;
+
+       for (i = *len - ci->values_num; i < *len; ++i)
+               (*var)[i] = (int)ci->values[i].value.number;
+       return 0;
+} /* tbl_config_append_array_s */
+
+static int tbl_config_result (tbl_t *tbl, oconfig_item_t *ci)
+{
+       tbl_result_t *res;
+
+       int status = 0;
+       size_t i;
+
+       if (0 != ci->values_num) {
+               log_err ("<Result> does not expect any arguments.");
+               return 1;
+       }
+
+       res = (tbl_result_t *)realloc (tbl->results,
+                       (tbl->results_num + 1) * sizeof (*tbl->results));
+       if (NULL == tbl) {
+               char errbuf[1024];
+               log_err ("realloc failed: %s.",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return -1;
+       }
+
+       tbl->results = res;
+       ++tbl->results_num;
+
+       res = tbl->results + tbl->results_num - 1;
+       tbl_result_setup (res);
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (0 == strcasecmp (c->key, "Type"))
+                       tbl_config_set_s (c->key, &res->type, c);
+               else if (0 == strcasecmp (c->key, "InstancePrefix"))
+                       tbl_config_set_s (c->key, &res->instance_prefix, c);
+               else if (0 == strcasecmp (c->key, "InstancesFrom"))
+                       tbl_config_append_array_i (c->key,
+                                       &res->instances, &res->instances_num, c);
+               else if (0 == strcasecmp (c->key, "ValuesFrom"))
+                       tbl_config_append_array_i (c->key,
+                                       &res->values, &res->values_num, c);
+               else
+                       log_warn ("Ignoring unknown config key \"%s\" "
+                                       " in <Result>.", c->key);
+       }
+
+       if (NULL == res->type) {
+               log_err ("No \"Type\" option specified for <Result> "
+                               "in table \"%s\".", tbl->file);
+               status = 1;
+       }
+
+       if (NULL == res->values) {
+               log_err ("No \"ValuesFrom\" option specified for <Result> "
+                               "in table \"%s\".", tbl->file);
+               status = 1;
+       }
+
+       if (0 != status) {
+               tbl_result_clear (res);
+               --tbl->results_num;
+               return status;
+       }
+       return 0;
+} /* tbl_config_result */
+
+static int tbl_config_table (oconfig_item_t *ci)
+{
+       tbl_t *tbl;
+
+       int status = 0;
+       size_t i;
+
+       if ((1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("<Table> expects a single string argument.");
+               return 1;
+       }
+
+       tbl = (tbl_t *)realloc (tables, (tables_num + 1) * sizeof (*tables));
+       if (NULL == tbl) {
+               char errbuf[1024];
+               log_err ("realloc failed: %s.",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return -1;
+       }
+
+       tables = tbl;
+       ++tables_num;
+
+       tbl = tables + tables_num - 1;
+       tbl_setup (tbl, ci->values[0].value.string);
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (0 == strcasecmp (c->key, "Separator"))
+                       tbl_config_set_s (c->key, &tbl->sep, c);
+               else if (0 == strcasecmp (c->key, "Instance"))
+                       tbl_config_set_s (c->key, &tbl->instance, c);
+               else if (0 == strcasecmp (c->key, "Result"))
+                       tbl_config_result (tbl, c);
+               else
+                       log_warn ("Ignoring unknown config key \"%s\" "
+                                       "in <Table %s>.", c->key, tbl->file);
+       }
+
+       if (NULL == tbl->sep) {
+               log_err ("Table \"%s\" does not specify any separator.", tbl->file);
+               status = 1;
+       }
+       strunescape (tbl->sep, strlen (tbl->sep) + 1);
+
+       if (NULL == tbl->instance) {
+               tbl->instance = sstrdup (tbl->file);
+               replace_special (tbl->instance, strlen (tbl->instance));
+       }
+
+       if (NULL == tbl->results) {
+               log_err ("Table \"%s\" does not specify any (valid) results.",
+                               tbl->file);
+               status = 1;
+       }
+
+       if (0 != status) {
+               tbl_clear (tbl);
+               --tables_num;
+               return status;
+       }
+
+       for (i = 0; i < tbl->results_num; ++i) {
+               tbl_result_t *res = tbl->results + i;
+               size_t j;
+
+               for (j = 0; j < res->instances_num; ++j)
+                       if (res->instances[j] > tbl->max_colnum)
+                               tbl->max_colnum = res->instances[j];
+
+               for (j = 0; j < res->values_num; ++j)
+                       if (res->values[j] > tbl->max_colnum)
+                               tbl->max_colnum = res->values[j];
+       }
+       return 0;
+} /* tbl_config_table */
+
+static int tbl_config (oconfig_item_t *ci)
+{
+       size_t i;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (0 == strcasecmp (c->key, "Table"))
+                       tbl_config_table (c);
+               else
+                       log_warn ("Ignoring unknown config key \"%s\".", c->key);
+       }
+       return 0;
+} /* tbl_config */
+
+/*
+ * result handling
+ */
+
+static int tbl_prepare (tbl_t *tbl)
+{
+       size_t i;
+
+       for (i = 0; i < tbl->results_num; ++i) {
+               tbl_result_t *res = tbl->results + i;
+
+               res->ds = plugin_get_ds (res->type);
+               if (NULL == res->ds) {
+                       log_err ("Unknown type \"%s\". See types.db(5) for details.",
+                                       res->type);
+                       return -1;
+               }
+
+               if (res->values_num != (size_t)res->ds->ds_num) {
+                       log_err ("Invalid type \"%s\". Expected %zu data source%s, "
+                                       "got %i.", res->type, res->values_num,
+                                       (1 == res->values_num) ? "" : "s",
+                                       res->ds->ds_num);
+                       return -1;
+               }
+       }
+       return 0;
+} /* tbl_prepare */
+
+static int tbl_finish (tbl_t *tbl)
+{
+       size_t i;
+
+       for (i = 0; i < tbl->results_num; ++i)
+               tbl->results[i].ds = NULL;
+       return 0;
+} /* tbl_finish */
+
+static int tbl_result_dispatch (tbl_t *tbl, tbl_result_t *res,
+               char **fields, size_t fields_num)
+{
+       value_list_t vl = VALUE_LIST_INIT;
+       value_t values[res->values_num];
+
+       size_t i;
+
+       assert (NULL != res->ds);
+       assert (res->values_num == res->ds->ds_num);
+
+       for (i = 0; i < res->values_num; ++i) {
+               char *value;
+
+               assert (res->values[i] < fields_num);
+               value = fields[res->values[i]];
+
+               if (0 != parse_value (value, &values[i], res->ds->ds[i]))
+                       return -1;
+       }
+
+       vl.values     = values;
+       vl.values_len = STATIC_ARRAY_SIZE (values);
+
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "table", sizeof (vl.plugin));
+       sstrncpy (vl.plugin_instance, tbl->instance, sizeof (vl.plugin_instance));
+       sstrncpy (vl.type, res->type, sizeof (vl.type));
+
+       if (0 == res->instances_num) {
+               if (NULL != res->instance_prefix)
+                       sstrncpy (vl.type_instance, res->instance_prefix,
+                                       sizeof (vl.type_instance));
+       }
+       else {
+               char *instances[res->instances_num];
+               char  instances_str[DATA_MAX_NAME_LEN];
+
+               for (i = 0; i < res->instances_num; ++i) {
+                       assert (res->instances[i] < fields_num);
+                       instances[i] = fields[res->instances[i]];
+               }
+
+               strjoin (instances_str, sizeof (instances_str),
+                               instances, STATIC_ARRAY_SIZE (instances), "-");
+               instances_str[sizeof (instances_str) - 1] = '\0';
+
+               vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
+               if (NULL == res->instance_prefix)
+                       strncpy (vl.type_instance, instances_str,
+                                       sizeof (vl.type_instance));
+               else
+                       snprintf (vl.type_instance, sizeof (vl.type_instance),
+                                       "%s-%s", res->instance_prefix, instances_str);
+
+               if ('\0' != vl.type_instance[sizeof (vl.type_instance) - 1]) {
+                       vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
+                       log_warn ("Truncated type instance: %s.", vl.type_instance);
+               }
+       }
+
+       plugin_dispatch_values (&vl);
+       return 0;
+} /* tbl_result_dispatch */
+
+static int tbl_parse_line (tbl_t *tbl, char *line, size_t len)
+{
+       char *fields[tbl->max_colnum + 1];
+       char *ptr, *saveptr;
+
+       size_t i;
+
+       i = 0;
+       ptr = line;
+       saveptr = NULL;
+       while (NULL != (fields[i] = strtok_r (ptr, tbl->sep, &saveptr))) {
+               ptr = NULL;
+               ++i;
+
+               if (i > tbl->max_colnum)
+                       break;
+       }
+
+       if (i <= tbl->max_colnum) {
+               log_err ("Not enough columns in line "
+                               "(expected at least %zu, got %zu).",
+                               tbl->max_colnum + 1, i);
+               return -1;
+       }
+
+       for (i = 0; i < tbl->results_num; ++i)
+               if (0 != tbl_result_dispatch (tbl, tbl->results + i,
+                                       fields, STATIC_ARRAY_SIZE (fields))) {
+                       log_err ("Failed to dispatch result.");
+                       continue;
+               }
+       return 0;
+} /* tbl_parse_line */
+
+static int tbl_read_table (tbl_t *tbl)
+{
+       FILE *fh;
+       char  buf[4096];
+
+       fh = fopen (tbl->file, "r");
+       if (NULL == fh) {
+               char errbuf[1024];
+               log_err ("Failed to open file \"%s\": %s.", tbl->file,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return -1;
+       }
+
+       buf[sizeof (buf) - 1] = '\0';
+       while (NULL != fgets (buf, sizeof (buf), fh)) {
+               if ('\0' != buf[sizeof (buf) - 1]) {
+                       buf[sizeof (buf) - 1] = '\0';
+                       log_err ("Table %s: Truncated line: %s", tbl->file, buf);
+               }
+
+               if (0 != tbl_parse_line (tbl, buf, sizeof (buf))) {
+                       log_err ("Table %s: Failed to parse line: %s", tbl->file, buf);
+                       continue;
+               }
+       }
+
+       if (0 != ferror (fh)) {
+               char errbuf[1024];
+               log_err ("Failed to read from file \"%s\": %s.", tbl->file,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               fclose (fh);
+               return -1;
+       }
+
+       fclose (fh);
+       return 0;
+} /* tbl_read_table */
+
+/*
+ * collectd callbacks
+ */
+
+static int tbl_read (void)
+{
+       int status = -1;
+       size_t i;
+
+       if (0 == tables_num)
+               return 0;
+
+       for (i = 0; i < tables_num; ++i) {
+               tbl_t *tbl = tables + i;
+
+               if (0 != tbl_prepare (tbl)) {
+                       log_err ("Failed to prepare and parse table \"%s\".", tbl->file);
+                       continue;
+               }
+
+               if (0 == tbl_read_table (tbl))
+                       status = 0;
+
+               tbl_finish (tbl);
+       }
+       return status;
+} /* tbl_read */
+
+static int tbl_shutdown (void)
+{
+       size_t i;
+
+       for (i = 0; i < tables_num; ++i)
+               tbl_clear (&tables[i]);
+       sfree (tables);
+       return 0;
+} /* tbl_shutdown */
+
+static int tbl_init (void)
+{
+       if (0 == tables_num)
+               return 0;
+
+       plugin_register_read ("table", tbl_read);
+       plugin_register_shutdown ("table", tbl_shutdown);
+       return 0;
+} /* tbl_init */
+
+void module_register (void)
+{
+       plugin_register_complex_config ("table", tbl_config);
+       plugin_register_init ("table", tbl_init);
+} /* module_register */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
index 6d01532..82b5b4f 100644 (file)
@@ -33,6 +33,7 @@ dns_notify            value:COUNTER:0:65535
 dns_transfer           value:COUNTER:0:65535
 dns_query              value:COUNTER:0:65535
 dns_response           value:COUNTER:0:65535
+dns_reject             value:COUNTER:0:65535
 email_check            value:GAUGE:0:U
 email_count            value:GAUGE:0:U
 email_size             value:GAUGE:0:U
index 4a1bfa4..a46eb41 100644 (file)
@@ -44,6 +44,9 @@ For example:
  TypesDB "/opt/collectd/share/collectd/types.db"
  TypesDB "/opt/collectd/etc/types.db.custom"
 
+B<Note>: Make sure to make this file available on all systems if you're
+sending values over the network.
+
 =head1 SEE ALSO
 
 L<collectd(1)>,
index 5bd6ec7..be09185 100644 (file)
@@ -33,7 +33,7 @@
                return -1; \
        }
 
-static int parse_value (const data_set_t *ds, value_list_t *vl,
+static int dispatch_values (const data_set_t *ds, value_list_t *vl,
                FILE *fh, char *buffer)
 {
        char *dummy;
@@ -65,12 +65,13 @@ static int parse_value (const data_set_t *ds, value_list_t *vl,
                        break;
                }
 
-               if (strcmp (ptr, "U") == 0)
+               if ((strcmp (ptr, "U") == 0) && (ds->ds[i].type == DS_TYPE_GAUGE))
                        vl->values[i].gauge = NAN;
-               else if (ds->ds[i].type == DS_TYPE_COUNTER)
-                       vl->values[i].counter = atoll (ptr);
-               else if (ds->ds[i].type == DS_TYPE_GAUGE)
-                       vl->values[i].gauge = atof (ptr);
+               else if (0 != parse_value (ptr, &vl->values[i], ds->ds[i]))
+               {
+                       print_to_socket (fh, "-1 Failed to parse value `%s'.", ptr);
+                       return (-1);
+               }
 
                i++;
        } /* while (strtok_r) */
@@ -79,7 +80,7 @@ static int parse_value (const data_set_t *ds, value_list_t *vl,
        {
                char identifier[128];
                FORMAT_VL (identifier, sizeof (identifier), vl, ds);
-               ERROR ("cmd putval: parse_value: "
+               ERROR ("cmd putval: dispatch_values: "
                                "Number of values incorrect: "
                                "Got %i, expected %i. Identifier is `%s'.",
                                i, vl->values_len, identifier);
@@ -91,7 +92,7 @@ static int parse_value (const data_set_t *ds, value_list_t *vl,
 
        plugin_dispatch_values (vl);
        return (0);
-} /* int parse_value */
+} /* int dispatch_values */
 
 static int set_option (value_list_t *vl, const char *key, const char *value)
 {
@@ -252,7 +253,7 @@ int handle_putval (FILE *fh, char *buffer)
                }
                assert (string != NULL);
 
-               status = parse_value (ds, &vl, fh, string);
+               status = dispatch_values (ds, &vl, fh, string);
                if (status != 0)
                {
                        /* An error has already been printed. */
index c2897c7..5531b25 100644 (file)
@@ -197,7 +197,7 @@ static int udb_legacy_result_handle_result (udb_result_t *r, /* {{{ */
 {
   value_list_t vl = VALUE_LIST_INIT;
   value_t value;
-  char *endptr;
+  char *value_str;
 
   assert (r->legacy_mode == 1);
   assert (r->ds != NULL);
@@ -206,23 +206,14 @@ static int udb_legacy_result_handle_result (udb_result_t *r, /* {{{ */
   vl.values = &value;
   vl.values_len = 1;
 
-  endptr = NULL;
-  errno = 0;
-  if (r->ds->ds[0].type == DS_TYPE_COUNTER)
-    vl.values[0].counter = (counter_t) strtoll (column_values[r->legacy_position],
-        &endptr, /* base = */ 0);
-  else if (r->ds->ds[0].type == DS_TYPE_GAUGE)
-    vl.values[0].gauge = (gauge_t) strtod (column_values[r->legacy_position],
-        &endptr);
-  else
-    errno = EINVAL;
-
-  if ((endptr == column_values[r->legacy_position]) || (errno != 0))
+  value_str = column_values[r->legacy_position];
+  if (0 != parse_value (value_str, &vl.values[0], r->ds->ds[0]))
   {
-    WARNING ("db query utils: udb_result_submit: Parsing `%s' as %s failed.",
-        column_values[r->legacy_position],
+    ERROR ("db query utils: udb_legacy_result_handle_result: "
+        "Parsing `%s' as %s failed.", value_str,
         (r->ds->ds[0].type == DS_TYPE_COUNTER) ? "counter" : "gauge");
-    vl.values[0].gauge = NAN;
+    errno = EINVAL;
+    return (-1);
   }
 
   sstrncpy (vl.host, q->host, sizeof (vl.host));
@@ -351,7 +342,7 @@ static int udb_legacy_result_create (const char *query_name, /* {{{ */
 /*
  * Result private functions
  */
-static void udb_result_submit (udb_result_t *r, udb_query_t *q) /* {{{ */
+static int udb_result_submit (udb_result_t *r, udb_query_t *q) /* {{{ */
 {
   value_list_t vl = VALUE_LIST_INIT;
   size_t i;
@@ -365,30 +356,21 @@ static void udb_result_submit (udb_result_t *r, udb_query_t *q) /* {{{ */
   if (vl.values == NULL)
   {
     ERROR ("db query utils: malloc failed.");
-    return;
+    return (-1);
   }
   vl.values_len = r->ds->ds_num;
 
   for (i = 0; i < r->values_num; i++)
   {
-    char *endptr;
-
-    endptr = NULL;
-    errno = 0;
-    if (r->ds->ds[i].type == DS_TYPE_COUNTER)
-      vl.values[i].counter = (counter_t) strtoll (r->values_buffer[i],
-          &endptr, /* base = */ 0);
-    else if (r->ds->ds[i].type == DS_TYPE_GAUGE)
-      vl.values[i].gauge = (gauge_t) strtod (r->values_buffer[i], &endptr);
-    else
-      errno = EINVAL;
+    char *value_str = r->values_buffer[i];
 
-    if ((endptr == r->values_buffer[i]) || (errno != 0))
+    if (0 != parse_value (value_str, &vl.values[i], r->ds->ds[i]))
     {
-      WARNING ("db query utils: udb_result_submit: Parsing `%s' as %s failed.",
-          r->values_buffer[i],
+      ERROR ("db query utils: udb_result_submit: Parsing `%s' as %s failed.",
+          value_str,
           (r->ds->ds[i].type == DS_TYPE_COUNTER) ? "counter" : "gauge");
-      vl.values[i].gauge = NAN;
+      errno = EINVAL;
+      return (-1);
     }
   }
 
@@ -430,6 +412,7 @@ static void udb_result_submit (udb_result_t *r, udb_query_t *q) /* {{{ */
   plugin_dispatch_values (&vl);
 
   sfree (vl.values);
+  return (0);
 } /* }}} void udb_result_submit */
 
 static void udb_result_finish_result (udb_result_t *r) /* {{{ */
@@ -468,9 +451,7 @@ static int udb_result_handle_result (udb_result_t *r, /* {{{ */
   for (i = 0; i < r->values_num; i++)
     r->values_buffer[i] = column_values[r->values_pos[i]];
 
-  udb_result_submit (r, q);
-
-  return (0);
+  return udb_result_submit (r, q);
 } /* }}} int udb_result_handle_result */
 
 static int udb_result_prepare_result (udb_result_t *r, /* {{{ */
index c310059..69e4f9a 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-DEFAULT_VERSION="4.6.0.git"
+DEFAULT_VERSION="4.6.1.git"
 
 VERSION="$( git describe 2> /dev/null | sed -e 's/^collectd-//' )"