Code

tail_csv plugin: Rename the "snort" plugin to "tail_csv".
authorFlorian Forster <octo@collectd.org>
Fri, 22 Mar 2013 21:21:57 +0000 (22:21 +0100)
committerFlorian Forster <octo@collectd.org>
Fri, 22 Mar 2013 21:35:16 +0000 (22:35 +0100)
It's actually much more generic than just Snort, so give it an
appropriately generic name.

AUTHORS
README
configure.in
src/Makefile.am
src/collectd.conf.in
src/collectd.conf.pod
src/snort.c [deleted file]
src/tail_csv.c [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
index db480d50debc941872a44de315f0219e663e3826..45645d1a26e52700130ed68c6dc7042307f27895 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -92,7 +92,7 @@ Jérôme Renard <jerome.renard at gmail.com>
  - varnish plugin.
 
 Kris Nielander <nielander at fox-it.com>
- - snort plugin.
+ - tail_csv plugin.
 
 Luboš Staněk <kolektor at atlas.cz>
  - sensors plugin improvements.
diff --git a/README b/README
index ee09ddd05387ec2352ce59a1ae08d06331524bbf..8fdc791434976acef6a0b3e433af83169248f212 100644 (file)
--- a/README
+++ b/README
@@ -274,10 +274,6 @@ Features
       network devices such as switches, routers, thermometers, rack monitoring
       servers, etc. See collectd-snmp(5).
 
-    - snort
-      Reads the statistics file written by Snort, a network intrution detection
-      system (IDS) and dispatches the values according to the configuration.
-
     - swap
       Pages swapped out onto harddisk or whatever is called `swap' by the OS..
 
@@ -288,6 +284,10 @@ Features
       Follows (tails) logfiles, parses them by lines and submits matched
       values.
 
+    - tail_csv
+      Follows (tails) files in CSV format, parses each line and submits
+      extracted values.
+
     - tape
       Bytes and operations read and written on tape devices. Solaris only.
 
index b07712e0b4bc1a28fbc3ea0336263d02a99710c9..f0ced82e7ce2a593911f3c8cbfc6eba4b007122a 100644 (file)
@@ -327,15 +327,6 @@ else
        have_linux_raid_md_u_h="no"
 fi
 
-# For the snort module
-have_mman_h="no"
-AC_CHECK_HEADERS(sys/mman.h,
-    [have_mman_h="yes"],
-    [have_mman_h="no"],
-[
-#include <sys/stat.h>
-])
-
 # For the swap module
 have_linux_wireless_h="no"
 if test "x$ac_system" = "xLinux"
@@ -4961,11 +4952,11 @@ AC_PLUGIN([rrdtool],     [$with_librrd],       [RRDTool output plugin])
 AC_PLUGIN([sensors],     [$with_libsensors],   [lm_sensors statistics])
 AC_PLUGIN([serial],      [$plugin_serial],     [serial port traffic])
 AC_PLUGIN([snmp],        [$with_libnetsnmp],   [SNMP querying plugin])
-AC_PLUGIN([snort],       [$have_mman_h],       [Snort perfmon 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([tail_csv],    [yes],                [Parsing of CSV files])
 AC_PLUGIN([tape],        [$plugin_tape],       [Tape drive statistics])
 AC_PLUGIN([target_notification], [yes],        [The notification target])
 AC_PLUGIN([target_replace], [yes],             [The replace target])
@@ -5296,11 +5287,11 @@ Configuration:
     sensors . . . . . . . $enable_sensors
     serial  . . . . . . . $enable_serial
     snmp  . . . . . . . . $enable_snmp
-    snort . . . . . . . . $enable_snort
     swap  . . . . . . . . $enable_swap
     syslog  . . . . . . . $enable_syslog
     table . . . . . . . . $enable_table
     tail  . . . . . . . . $enable_tail
+    tail_csv  . . . . . . $enable_tail_csv
     tape  . . . . . . . . $enable_tape
     target_notification . $enable_target_notification
     target_replace  . . . $enable_target_replace
index e0d5b9dfd97e4a31901bb37a67777f6f8c2dd319..f2d418c65135830e8b51faffb63c48caab0bced6 100644 (file)
@@ -1049,14 +1049,6 @@ collectd_LDADD += "-dlopen" snmp.la
 collectd_DEPENDENCIES += snmp.la
 endif
 
-if BUILD_PLUGIN_SNORT
-pkglib_LTLIBRARIES += snort.la
-snort_la_SOURCES = snort.c
-snort_la_LDFLAGS = -module -avoid-version
-collectd_LDADD += "-dlopen" snort.la
-collectd_DEPENDENCIES += snort.la
-endif
-
 if BUILD_PLUGIN_SWAP
 pkglib_LTLIBRARIES += swap.la
 swap_la_SOURCES = swap.c
@@ -1108,6 +1100,14 @@ collectd_LDADD += "-dlopen" tail.la
 collectd_DEPENDENCIES += tail.la
 endif
 
+if BUILD_PLUGIN_TAIL_CSV
+pkglib_LTLIBRARIES += tail_csv.la
+tail_csv_la_SOURCES = tail_csv.c
+tail_csv_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" tail_csv.la
+collectd_DEPENDENCIES += tail_csv.la
+endif
+
 if BUILD_PLUGIN_TAPE
 pkglib_LTLIBRARIES += tape.la
 tape_la_SOURCES = tape.c
index 8065b0304f24b08fcaa0cdd06277d75c10f5ef8a..fa4cccfea127de2cda2e6f5f4a3727d2aa413f95 100644 (file)
 #@BUILD_PLUGIN_SENSORS_TRUE@LoadPlugin sensors
 #@BUILD_PLUGIN_SERIAL_TRUE@LoadPlugin serial
 #@BUILD_PLUGIN_SNMP_TRUE@LoadPlugin snmp
-#@BUILD_PLUGIN_SNORT_TRUE@LoadPlugin snort
 #@BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap
 #@BUILD_PLUGIN_TABLE_TRUE@LoadPlugin table
 #@BUILD_PLUGIN_TAIL_TRUE@LoadPlugin tail
+#@BUILD_PLUGIN_TAIL_CSV_TRUE@LoadPlugin tail_csv
 #@BUILD_PLUGIN_TAPE_TRUE@LoadPlugin tape
 #@BUILD_PLUGIN_TCPCONNS_TRUE@LoadPlugin tcpconns
 #@BUILD_PLUGIN_TEAMSPEAK2_TRUE@LoadPlugin teamspeak2
 #   </Host>
 #</Plugin>
 
-#<Plugin "snort">
-#   <Metric "dropped">
-#       Type "percent"
-#       Instance "dropped"
-#       Index 1
-#   </Metric>
-#   <Metric "mbps">
-#       Type "bytes"
-#       Instance "wire-realtime"
-#       Index 2
-#   </Metric>
-#   <Metric "alerts">
-#       Type "alerts_per_second"
-#       Index 3
-#   </Metric>
-#   <Metric "kpps">
-#       Type "kpackets_wire_per_sec.realtime"
-#       Index 4
-#   </Metric>
-#   <Instance "eth0">
-#       Path "/var/log/snort/snort.stats"
-#       Interval 600
-#       Collect "dropped" "mbps" "alerts" "kpps"
-#   </Instance>
-#</Plugin>
-
 #<Plugin "swap">
 #      ReportByDevice false
 #      ReportBytes true
 #  </File>
 #</Plugin>
 
+#<Plugin "tail_csv">
+#   <Metric "dropped">
+#       Type "percent"
+#       Instance "dropped"
+#       Index 1
+#   </Metric>
+#   <Metric "mbps">
+#       Type "bytes"
+#       Instance "wire-realtime"
+#       Index 2
+#   </Metric>
+#   <Metric "alerts">
+#       Type "alerts_per_second"
+#       Index 3
+#   </Metric>
+#   <Metric "kpps">
+#       Type "kpackets_wire_per_sec.realtime"
+#       Index 4
+#   </Metric>
+#   <Instance "snort-eth0">
+#       Path "/var/log/snort/snort.stats"
+#       Interval 600
+#       Collect "dropped" "mbps" "alerts" "kpps"
+#   </Instance>
+#</Plugin>
+
 #<Plugin tcpconns>
 #      ListeningPorts false
 #      LocalPort "25"
index b1b9bd222446a6c451907222a66548240b723830..ee1481520a970fcf617a39f20eb3107f4cfce110 100644 (file)
@@ -4845,98 +4845,6 @@ Since the configuration of the C<snmp plugin> is a little more complicated than
 other plugins, its documentation has been moved to an own manpage,
 L<collectd-snmp(5)>. Please see there for details.
 
-=head2 Plugin C<snort>
-
-The I<snort plugin> reads the statistics file written by I<Snort>, a free
-network intrusion detection system (IDS). The file is written by Snort's
-I<Performance Monitor> preprocessor and needs to be configured to write to a
-file.
-
-B<Synopsis:>
-
- <Plugin "snort">
-   <Metric "dropped">
-       Type "percent"
-       Instance "dropped"
-       Index 1
-   </Metric>
-   <Instance "eth0">
-       Path "/var/log/snort/snort.stats"
-       Interval 600
-       Collect "dropped"
-   </Instance>
- </Plugin>
-
-The configuration consists of one or more B<Metric> blocks that define an index
-into the line written by I<Snort> and how this value is mapped to I<collectd's>
-internal representation. These are followed by one or more B<Instance> blocks
-which configure which file to read, in which interval and which metrics to
-extract.
-
-=over 4
-
-=item E<lt>B<Metric> I<Name>E<gt>
-
-The B<Metric> block configures a new metric to be extracted from the statistics
-file and how it is mapped on I<collectd's> data model. The string I<Name> is
-only used inside the B<Instance> blocks to refer to this block, so you can use
-one B<Metric> block for multiple I<Snort> instances.
-
-=over 4
-
-=item B<Type> I<Type>
-
-Configures which I<Type> to use when dispatching this metric. Types are defined
-in the L<types.db(5)> file, see the appropriate manual page for more
-information on specifying types. Only types with a single I<data source> are
-supported by the I<snort plugin>. The information whether the value is an
-absolute value (i.e. a C<GAUGE>) or a rate (i.e. a C<DERIVE>) is taken from the
-I<Type's> definition.
-
-=item B<Instance> I<TypeInstance>
-
-If set, I<TypeInstance> is used to populate the type instance field of the
-created value lists. Otherwise, no type instance is used.
-
-=item B<Index> I<Index>
-
-Each line in the statistics file is broken into many fields with the first
-field, the timestamp of the line, is index with zero. This option configures to
-read the value from the field with index I<Index>. Since the first field is
-always the timestamp, I<Index> must be greater than zero.
-
-=back
-
-=item E<lt>B<Instance> I<Name>E<gt>
-
-Configures an instance of I<Snort> to extract values for. The block's I<Name>
-will be used as I<plugin instance> when dispatching the values and is used to
-support multiple separate I<Snort> instances. There must be at least one
-I<Instance> block but there can be multiple if you have multiple instances of
-I<Snort> running.
-
-=over 4
-
-=item B<Path> I<Path>
-
-Configures the I<Path> to the statistics file to read.
-
-=item B<Collect> I<Metric>
-
-Specifies which I<Metric> to collect. This option must be specified at least
-once, and you can use this option multiple times to specify more than one
-metric to be extracted from this statistic file.
-
-=item B<Interval> I<Seconds>
-
-Configures the interval in which to read values from this instance / file. This
-should match the interval in which I<Snort's> performance monitor is writing to
-the file. Defaults to the plugin's default interval.
-
-=back
-
-=back
-
 =head2 Plugin C<swap>
 
 The I<Swap plugin> collects information about used and available swap space. On
@@ -5211,6 +5119,94 @@ This optional setting sets the type instance to use.
 
 =back
 
+=head2 Plugin C<tail_csv>
+
+The I<tail_csv plugin> reads files in the CSV format, e.g. the statistics file
+written by I<Snort>.
+
+B<Synopsis:>
+
+ <Plugin "tail_csv">
+   <Metric "dropped">
+       Type "percent"
+       Instance "dropped"
+       Index 1
+   </Metric>
+   <Instance "snort-eth0">
+       Path "/var/log/snort/snort.stats"
+       Interval 600
+       Collect "dropped"
+   </Instance>
+ </Plugin>
+
+The configuration consists of one or more B<Metric> blocks that define an index
+into the line of the CSV file and how this value is mapped to I<collectd's>
+internal representation. These are followed by one or more B<Instance> blocks
+which configure which file to read, in which interval and which metrics to
+extract.
+
+=over 4
+
+=item E<lt>B<Metric> I<Name>E<gt>
+
+The B<Metric> block configures a new metric to be extracted from the statistics
+file and how it is mapped on I<collectd's> data model. The string I<Name> is
+only used inside the B<Instance> blocks to refer to this block, so you can use
+one B<Metric> block for multiple CSV files.
+
+=over 4
+
+=item B<Type> I<Type>
+
+Configures which I<Type> to use when dispatching this metric. Types are defined
+in the L<types.db(5)> file, see the appropriate manual page for more
+information on specifying types. Only types with a single I<data source> are
+supported by the I<tail_csv plugin>. The information whether the value is an
+absolute value (i.e. a C<GAUGE>) or a rate (i.e. a C<DERIVE>) is taken from the
+I<Type's> definition.
+
+=item B<Instance> I<TypeInstance>
+
+If set, I<TypeInstance> is used to populate the type instance field of the
+created value lists. Otherwise, no type instance is used.
+
+=item B<Index> I<Index>
+
+Each line in the statistics file is broken into many fields with the first
+field, the timestamp of the line, is index with zero. This option configures to
+read the value from the field with index I<Index>. Since the first field is
+always the timestamp, I<Index> must be greater than zero.
+
+=back
+
+=item E<lt>B<Instance> I<Name>E<gt>
+
+Each B<Instance> block represents on CSV file to read. The block's I<Name>
+will be used as I<plugin instance> when dispatching the values and is used to
+support multiple separate CSV files. There must be at least one I<Instance>
+block but there can be multiple if you have multiple CSV files.
+
+=over 4
+
+=item B<Path> I<Path>
+
+Configures the I<Path> to the statistics file to read.
+
+=item B<Collect> I<Metric>
+
+Specifies which I<Metric> to collect. This option must be specified at least
+once, and you can use this option multiple times to specify more than one
+metric to be extracted from this statistic file.
+
+=item B<Interval> I<Seconds>
+
+Configures the interval in which to read values from this instance / file.
+Defaults to the plugin's default interval.
+
+=back
+
+=back
+
 =head2 Plugin C<teamspeak2>
 
 The C<teamspeak2 plugin> connects to the query port of a teamspeak2 server and
diff --git a/src/snort.c b/src/snort.c
deleted file mode 100644 (file)
index e8a1500..0000000
+++ /dev/null
@@ -1,543 +0,0 @@
-/**
- * collectd - src/snort.c
- * Copyright (C) 2013 Kris Nielander
- * Copyright (C) 2013 Florian Forster
- *
- * 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:
- *   Kris Nielander <nielander at fox-it.com>
- *   Florian Forster <octo at collectd.org>
- **/
-
-#include "collectd.h"
-#include "plugin.h" /* plugin_register_*, plugin_dispatch_values */
-#include "common.h" /* auxiliary functions */
-#include "utils_tail.h"
-
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-
-struct metric_definition_s {
-    char *name;
-    char *type;
-    char *instance;
-    int data_source_type;
-    int index;
-    struct metric_definition_s *next;
-};
-typedef struct metric_definition_s metric_definition_t;
-
-struct instance_definition_s {
-    char *name;
-    char *path;
-    cu_tail_t *tail;
-    metric_definition_t **metric_list;
-    size_t metric_list_len;
-    cdtime_t interval;
-    struct instance_definition_s *next;
-};
-typedef struct instance_definition_s instance_definition_t;
-
-/* Private */
-static metric_definition_t *metric_head = NULL;
-
-static int snort_submit (instance_definition_t *id,
-        metric_definition_t *md,
-        value_t v, cdtime_t t)
-{
-    /* Registration variables */
-    value_list_t vl = VALUE_LIST_INIT;
-
-    /* Register */
-    vl.values_len = 1;
-    vl.values = &v;
-
-    sstrncpy(vl.host, hostname_g, sizeof (vl.host));
-    sstrncpy(vl.plugin, "snort", sizeof(vl.plugin));
-    sstrncpy(vl.plugin_instance, id->name, sizeof(vl.plugin_instance));
-    sstrncpy(vl.type, md->type, sizeof(vl.type));
-    if (md->instance != NULL)
-        sstrncpy(vl.type_instance, md->instance, sizeof(vl.type_instance));
-
-    vl.time = t;
-    vl.interval = id->interval;
-
-    DEBUG("snort plugin: -> plugin_dispatch_values (&vl);");
-    plugin_dispatch_values(&vl);
-
-    return (0);
-}
-
-static cdtime_t parse_time (char const *tbuf)
-{
-    double t;
-    char *endptr = 0;
-
-    errno = 0;
-    t = strtod (tbuf, &endptr);
-    if ((errno != 0) || (endptr == NULL) || (endptr[0] != 0))
-        return (cdtime ());
-
-    return (DOUBLE_TO_CDTIME_T (t));
-}
-
-static int snort_read_metric (instance_definition_t *id,
-        metric_definition_t *md,
-        char **fields, size_t fields_num)
-{
-    value_t v;
-    cdtime_t t;
-    int status;
-
-    if (md->index >= fields_num)
-        return (EINVAL);
-
-    t = parse_time (fields[0]);
-
-    status = parse_value (fields[md->index], &v, md->data_source_type);
-    if (status != 0)
-        return (status);
-
-    return (snort_submit (id, md, v, t));
-}
-
-static int snort_read_buffer (instance_definition_t *id,
-        char *buffer, size_t buffer_size)
-{
-    char **metrics;
-    size_t metrics_num;
-
-    char *ptr;
-    size_t i;
-
-    /* Remove newlines at the end of line. */
-    while (buffer_size > 0) {
-        if ((buffer[buffer_size - 1] == '\n')
-                || (buffer[buffer_size - 1] == '\r')) {
-            buffer[buffer_size - 1] = 0;
-            buffer_size--;
-        } else {
-            break;
-        }
-    }
-
-    /* Ignore empty lines. */
-    if ((buffer_size == 0) || (buffer[0] == '#'))
-        return (0);
-
-    /* Count the number of fields. */
-    metrics_num = 1;
-    for (i = 0; i < buffer_size; i++) {
-        if (buffer[i] == ',')
-            metrics_num++;
-    }
-
-    if (metrics_num == 1) {
-        ERROR("snort plugin: last line of `%s' does not contain "
-                "enough values.", id->path);
-        return (-1);
-    }
-
-    /* Create a list of all values */
-    metrics = calloc (metrics_num, sizeof (*metrics));
-    if (metrics == NULL) {
-        ERROR ("snort plugin: calloc failed.");
-        return (ENOMEM);
-    }
-
-    ptr = buffer;
-    metrics[0] = ptr;
-    i = 1;
-    for (ptr = buffer; *ptr != 0; ptr++) {
-        if (*ptr != ',')
-            continue;
-
-        *ptr = 0;
-        metrics[i] = ptr + 1;
-        i++;
-    }
-    assert (i == metrics_num);
-
-    /* Register values */
-    for (i = 0; i < id->metric_list_len; ++i){
-        metric_definition_t *md = id->metric_list[i];
-
-        if (((size_t) md->index) >= metrics_num) {
-            ERROR ("snort plugin: Metric \"%s\": Request for index %i when "
-                    "only %zu fields are available.",
-                    md->name, md->index, metrics_num);
-            continue;
-        }
-
-        snort_read_metric (id, md, metrics, metrics_num);
-    }
-
-    /* Free up resources */
-    sfree (metrics);
-    return (0);
-}
-
-static int snort_read (user_data_t *ud) {
-    instance_definition_t *id;
-
-    id = ud->data;
-    DEBUG("snort plugin: snort_read (instance = %s)", id->name);
-
-    if (id->tail == NULL)
-    {
-        id->tail = cu_tail_create (id->path);
-        if (id->tail == NULL)
-        {
-            ERROR ("snort plugin: cu_tail_create (\"%s\") failed.",
-                    id->path);
-            return (-1);
-        }
-    }
-
-    while (42)
-    {
-        char buffer[1024];
-        size_t buffer_len;
-        int status;
-
-        status = cu_tail_readline (id->tail, buffer, (int) sizeof (buffer));
-        if (status != 0)
-        {
-            ERROR ("snort plugin: Instance \"%s\": cu_tail_readline failed "
-                    "with status %i.", id->name, status);
-            return (-1);
-        }
-
-        buffer_len = strlen (buffer);
-        if (buffer_len == 0)
-            break;
-
-        snort_read_buffer (id, buffer, buffer_len);
-    }
-
-    return (0);
-}
-
-static void snort_metric_definition_destroy(void *arg){
-    metric_definition_t *md;
-
-    md = arg;
-    if (md == NULL)
-        return;
-
-    if (md->name != NULL)
-        DEBUG("snort plugin: Destroying metric definition `%s'.", md->name);
-
-    sfree(md->name);
-    sfree(md->type);
-    sfree(md->instance);
-    sfree(md);
-}
-
-static int snort_config_add_metric_index(metric_definition_t *md, oconfig_item_t *ci){
-    if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)){
-        WARNING("snort plugin: `Index' needs exactly one integer argument.");
-        return (-1);
-    }
-
-    md->index = (int)ci->values[0].value.number;
-    if (md->index <= 0){
-        WARNING("snort plugin: `Index' must be higher than 0.");
-        return (-1);
-    }
-
-    return (0);
-}
-
-/* Parse metric  */
-static int snort_config_add_metric(oconfig_item_t *ci){
-    metric_definition_t *md;
-    const data_set_t *ds;
-    int status = 0;
-    int i;
-
-    md = (metric_definition_t *)malloc(sizeof(*md));
-    if (md == NULL)
-        return (-1);
-    memset(md, 0, sizeof(*md));
-    md->name = NULL;
-    md->type = NULL;
-    md->instance = NULL;
-    md->next = NULL;
-
-    status = cf_util_get_string (ci, &md->name);
-    if (status != 0) {
-        sfree (md);
-        return (-1);
-    }
-
-    for (i = 0; i < ci->children_num; ++i){
-        oconfig_item_t *option = ci->children + i;
-        status = 0;
-
-        if (strcasecmp("Type", option->key) == 0)
-            status = cf_util_get_string(option, &md->type);
-        else if (strcasecmp("Instance", option->key) == 0)
-            status = cf_util_get_string(option, &md->instance);
-        else if (strcasecmp("Index", option->key) == 0)
-            status = snort_config_add_metric_index(md, option);
-        else {
-            WARNING("snort plugin: Option `%s' not allowed here.", option->key);
-            status = -1;
-        }
-
-        if (status != 0)
-            break;
-    }
-
-    if (status != 0){
-        snort_metric_definition_destroy(md);
-        return (-1);
-    }
-
-    /* Verify all necessary options have been set. */
-    if (md->type == NULL){
-        WARNING("snort plugin: Option `Type' must be set.");
-        status = -1;
-    } else if (md->index == 0){
-        WARNING("snort plugin: Option `Index' must be set.");
-        status = -1;
-    }
-
-    if (status != 0){
-        snort_metric_definition_destroy(md);
-        return (-1);
-    }
-
-    /* Retrieve the data source type from the types db. */
-    ds = plugin_get_ds(md->type);
-    if (ds == NULL){
-        ERROR ("snort plugin: Failed to look up type \"%s\". "
-                "It may not be defined in the types.db file. "
-                "Please read the types.db(5) manual page for more details.",
-                md->type);
-        snort_metric_definition_destroy(md);
-        return (-1);
-    } else if (ds->ds_num != 1) {
-        ERROR ("snort plugin: The type \"%s\" has %i data sources. "
-                "Only types with a single data soure are supported.",
-                ds->type, ds->ds_num);
-        return (-1);
-    } else {
-        md->data_source_type = ds->ds->type;
-    }
-
-    DEBUG("snort plugin: md = { name = %s, type = %s, data_source_type = %d, index = %d }",
-        md->name, md->type, md->data_source_type, md->index);
-
-    if (metric_head == NULL)
-        metric_head = md;
-    else {
-        metric_definition_t *last;
-        last = metric_head;
-        while (last->next != NULL)
-            last = last->next;
-        last->next = md;
-    }
-
-    return (0);
-}
-
-static void snort_instance_definition_destroy(void *arg){
-    instance_definition_t *id;
-
-    id = arg;
-    if (id == NULL)
-        return;
-
-    if (id->name != NULL)
-        DEBUG("snort plugin: Destroying instance definition `%s'.", id->name);
-
-    cu_tail_destroy (id->tail);
-    id->tail = NULL;
-
-    sfree(id->name);
-    sfree(id->path);
-    sfree(id->metric_list);
-    sfree(id);
-}
-
-static int snort_config_add_instance_collect(instance_definition_t *id, oconfig_item_t *ci){
-    metric_definition_t *metric;
-    int i;
-
-    if (ci->values_num < 1){
-        WARNING("snort plugin: The `Collect' config option needs at least one argument.");
-        return (-1);
-    }
-
-    /* Verify string arguments */
-    for (i = 0; i < ci->values_num; ++i)
-        if (ci->values[i].type != OCONFIG_TYPE_STRING){
-            WARNING("snort plugin: All arguments to `Collect' must be strings.");
-            return (-1);
-        }
-
-    id->metric_list = (metric_definition_t **)malloc(sizeof(metric_definition_t *) * ci->values_num);
-    if (id->metric_list == NULL)
-        return (-1);
-
-    for (i = 0; i < ci->values_num; ++i){
-        for (metric = metric_head; metric != NULL; metric = metric->next)
-            if (strcasecmp(ci->values[i].value.string, metric->name) == 0)
-                break;
-
-        if (metric == NULL){
-            WARNING("snort plugin: `Collect' argument not found `%s'.", ci->values[i].value.string);
-            return (-1);
-        }
-
-        DEBUG("snort plugin: id { name=%s md->name=%s }", id->name, metric->name);
-
-        id->metric_list[i] = metric;
-        id->metric_list_len++;
-    }
-
-    return (0);
-}
-
-/* Parse instance  */
-static int snort_config_add_instance(oconfig_item_t *ci){
-
-    instance_definition_t* id;
-    int status = 0;
-    int i;
-
-    /* Registration variables */
-    char cb_name[DATA_MAX_NAME_LEN];
-    user_data_t cb_data;
-    struct timespec cb_interval;
-
-    if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)){
-        WARNING("snort plugin: The `Instance' config option needs exactly one string argument.");
-        return (-1);
-    }
-
-    id = (instance_definition_t *)malloc(sizeof(*id));
-    if (id == NULL)
-        return (-1);
-    memset(id, 0, sizeof(*id));
-
-    id->name = strdup(ci->values[0].value.string);
-    if (id->name == NULL){
-        free(id);
-        return (-1);
-    }
-
-    /* Use default interval. */
-    id->interval = plugin_get_interval();
-
-    for (i = 0; i < ci->children_num; ++i){
-        oconfig_item_t *option = ci->children + i;
-        status = 0;
-
-        if (strcasecmp("Path", option->key) == 0)
-            status = cf_util_get_string(option, &id->path);
-        else if (strcasecmp("Collect", option->key) == 0)
-            status = snort_config_add_instance_collect(id, option);
-        else if (strcasecmp("Interval", option->key) == 0)
-            cf_util_get_cdtime(option, &id->interval);
-        else {
-            WARNING("snort plugin: Option `%s' not allowed here.", option->key);
-            status = -1;
-        }
-
-        if (status != 0)
-            break;
-    }
-
-    if (status != 0){
-        snort_instance_definition_destroy(id);
-        return (-1);
-    }
-
-    /* Verify all necessary options have been set. */
-    if (id->path == NULL){
-        WARNING("snort plugin: Option `Path' must be set.");
-        status = -1;
-    } else if (id->metric_list == NULL){
-        WARNING("snort plugin: Option `Collect' must be set.");
-        status = -1;
-   }
-
-    if (status != 0){
-        snort_instance_definition_destroy(id);
-        return (-1);
-    }
-
-    DEBUG("snort plugin: id = { name = %s, path = %s }", id->name, id->path);
-
-    ssnprintf (cb_name, sizeof (cb_name), "snort-%s", id->name);
-    memset(&cb_data, 0, sizeof(cb_data));
-    cb_data.data = id;
-    cb_data.free_func = snort_instance_definition_destroy;
-    CDTIME_T_TO_TIMESPEC(id->interval, &cb_interval);
-    status = plugin_register_complex_read(NULL, cb_name, snort_read, &cb_interval, &cb_data);
-
-    if (status != 0){
-        ERROR("snort plugin: Registering complex read function failed.");
-        snort_instance_definition_destroy(id);
-        return (-1);
-    }
-
-    return (0);
-}
-
-/* Parse blocks */
-static int snort_config(oconfig_item_t *ci){
-    int i;
-    for (i = 0; i < ci->children_num; ++i){
-        oconfig_item_t *child = ci->children + i;
-        if (strcasecmp("Metric", child->key) == 0)
-            snort_config_add_metric(child);
-        else if (strcasecmp("Instance", child->key) == 0)
-            snort_config_add_instance(child);
-        else
-            WARNING("snort plugin: Ignore unknown config option `%s'.", child->key);
-    }
-
-    return (0);
-} /* int snort_config */
-
-static int snort_shutdown(void){
-    metric_definition_t *metric_this;
-    metric_definition_t *metric_next;
-
-    metric_this = metric_head;
-    metric_head = NULL;
-
-    while (metric_this != NULL){
-        metric_next = metric_this->next;
-        snort_metric_definition_destroy(metric_this);
-        metric_this = metric_next;
-    }
-
-    return (0);
-}
-
-void module_register(void){
-    plugin_register_complex_config("snort", snort_config);
-    plugin_register_shutdown("snort", snort_shutdown);
-}
-
-/* vim: set sw=4 sts=4 et : */
diff --git a/src/tail_csv.c b/src/tail_csv.c
new file mode 100644 (file)
index 0000000..cab2641
--- /dev/null
@@ -0,0 +1,543 @@
+/**
+ * collectd - src/tail_csv.c
+ * Copyright (C) 2013 Kris Nielander
+ * Copyright (C) 2013 Florian Forster
+ *
+ * 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:
+ *   Kris Nielander <nielander at fox-it.com>
+ *   Florian Forster <octo at collectd.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h" /* plugin_register_*, plugin_dispatch_values */
+#include "common.h" /* auxiliary functions */
+#include "utils_tail.h"
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct metric_definition_s {
+    char *name;
+    char *type;
+    char *instance;
+    int data_source_type;
+    int index;
+    struct metric_definition_s *next;
+};
+typedef struct metric_definition_s metric_definition_t;
+
+struct instance_definition_s {
+    char *name;
+    char *path;
+    cu_tail_t *tail;
+    metric_definition_t **metric_list;
+    size_t metric_list_len;
+    cdtime_t interval;
+    struct instance_definition_s *next;
+};
+typedef struct instance_definition_s instance_definition_t;
+
+/* Private */
+static metric_definition_t *metric_head = NULL;
+
+static int tcsv_submit (instance_definition_t *id,
+        metric_definition_t *md,
+        value_t v, cdtime_t t)
+{
+    /* Registration variables */
+    value_list_t vl = VALUE_LIST_INIT;
+
+    /* Register */
+    vl.values_len = 1;
+    vl.values = &v;
+
+    sstrncpy(vl.host, hostname_g, sizeof (vl.host));
+    sstrncpy(vl.plugin, "tail_csv", sizeof(vl.plugin));
+    sstrncpy(vl.plugin_instance, id->name, sizeof(vl.plugin_instance));
+    sstrncpy(vl.type, md->type, sizeof(vl.type));
+    if (md->instance != NULL)
+        sstrncpy(vl.type_instance, md->instance, sizeof(vl.type_instance));
+
+    vl.time = t;
+    vl.interval = id->interval;
+
+    DEBUG("tail_csv plugin: -> plugin_dispatch_values (&vl);");
+    plugin_dispatch_values(&vl);
+
+    return (0);
+}
+
+static cdtime_t parse_time (char const *tbuf)
+{
+    double t;
+    char *endptr = 0;
+
+    errno = 0;
+    t = strtod (tbuf, &endptr);
+    if ((errno != 0) || (endptr == NULL) || (endptr[0] != 0))
+        return (cdtime ());
+
+    return (DOUBLE_TO_CDTIME_T (t));
+}
+
+static int tcsv_read_metric (instance_definition_t *id,
+        metric_definition_t *md,
+        char **fields, size_t fields_num)
+{
+    value_t v;
+    cdtime_t t;
+    int status;
+
+    if (md->index >= fields_num)
+        return (EINVAL);
+
+    t = parse_time (fields[0]);
+
+    status = parse_value (fields[md->index], &v, md->data_source_type);
+    if (status != 0)
+        return (status);
+
+    return (tcsv_submit (id, md, v, t));
+}
+
+static int tcsv_read_buffer (instance_definition_t *id,
+        char *buffer, size_t buffer_size)
+{
+    char **metrics;
+    size_t metrics_num;
+
+    char *ptr;
+    size_t i;
+
+    /* Remove newlines at the end of line. */
+    while (buffer_size > 0) {
+        if ((buffer[buffer_size - 1] == '\n')
+                || (buffer[buffer_size - 1] == '\r')) {
+            buffer[buffer_size - 1] = 0;
+            buffer_size--;
+        } else {
+            break;
+        }
+    }
+
+    /* Ignore empty lines. */
+    if ((buffer_size == 0) || (buffer[0] == '#'))
+        return (0);
+
+    /* Count the number of fields. */
+    metrics_num = 1;
+    for (i = 0; i < buffer_size; i++) {
+        if (buffer[i] == ',')
+            metrics_num++;
+    }
+
+    if (metrics_num == 1) {
+        ERROR("tail_csv plugin: last line of `%s' does not contain "
+                "enough values.", id->path);
+        return (-1);
+    }
+
+    /* Create a list of all values */
+    metrics = calloc (metrics_num, sizeof (*metrics));
+    if (metrics == NULL) {
+        ERROR ("tail_csv plugin: calloc failed.");
+        return (ENOMEM);
+    }
+
+    ptr = buffer;
+    metrics[0] = ptr;
+    i = 1;
+    for (ptr = buffer; *ptr != 0; ptr++) {
+        if (*ptr != ',')
+            continue;
+
+        *ptr = 0;
+        metrics[i] = ptr + 1;
+        i++;
+    }
+    assert (i == metrics_num);
+
+    /* Register values */
+    for (i = 0; i < id->metric_list_len; ++i){
+        metric_definition_t *md = id->metric_list[i];
+
+        if (((size_t) md->index) >= metrics_num) {
+            ERROR ("tail_csv plugin: Metric \"%s\": Request for index %i when "
+                    "only %zu fields are available.",
+                    md->name, md->index, metrics_num);
+            continue;
+        }
+
+        tcsv_read_metric (id, md, metrics, metrics_num);
+    }
+
+    /* Free up resources */
+    sfree (metrics);
+    return (0);
+}
+
+static int tcsv_read (user_data_t *ud) {
+    instance_definition_t *id;
+
+    id = ud->data;
+    DEBUG("tail_csv plugin: tcsv_read (instance = %s)", id->name);
+
+    if (id->tail == NULL)
+    {
+        id->tail = cu_tail_create (id->path);
+        if (id->tail == NULL)
+        {
+            ERROR ("tail_csv plugin: cu_tail_create (\"%s\") failed.",
+                    id->path);
+            return (-1);
+        }
+    }
+
+    while (42)
+    {
+        char buffer[1024];
+        size_t buffer_len;
+        int status;
+
+        status = cu_tail_readline (id->tail, buffer, (int) sizeof (buffer));
+        if (status != 0)
+        {
+            ERROR ("tail_csv plugin: Instance \"%s\": cu_tail_readline failed "
+                    "with status %i.", id->name, status);
+            return (-1);
+        }
+
+        buffer_len = strlen (buffer);
+        if (buffer_len == 0)
+            break;
+
+        tcsv_read_buffer (id, buffer, buffer_len);
+    }
+
+    return (0);
+}
+
+static void tcsv_metric_definition_destroy(void *arg){
+    metric_definition_t *md;
+
+    md = arg;
+    if (md == NULL)
+        return;
+
+    if (md->name != NULL)
+        DEBUG("tail_csv plugin: Destroying metric definition `%s'.", md->name);
+
+    sfree(md->name);
+    sfree(md->type);
+    sfree(md->instance);
+    sfree(md);
+}
+
+static int tcsv_config_add_metric_index(metric_definition_t *md, oconfig_item_t *ci){
+    if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)){
+        WARNING("tail_csv plugin: `Index' needs exactly one integer argument.");
+        return (-1);
+    }
+
+    md->index = (int)ci->values[0].value.number;
+    if (md->index <= 0){
+        WARNING("tail_csv plugin: `Index' must be higher than 0.");
+        return (-1);
+    }
+
+    return (0);
+}
+
+/* Parse metric  */
+static int tcsv_config_add_metric(oconfig_item_t *ci){
+    metric_definition_t *md;
+    const data_set_t *ds;
+    int status = 0;
+    int i;
+
+    md = (metric_definition_t *)malloc(sizeof(*md));
+    if (md == NULL)
+        return (-1);
+    memset(md, 0, sizeof(*md));
+    md->name = NULL;
+    md->type = NULL;
+    md->instance = NULL;
+    md->next = NULL;
+
+    status = cf_util_get_string (ci, &md->name);
+    if (status != 0) {
+        sfree (md);
+        return (-1);
+    }
+
+    for (i = 0; i < ci->children_num; ++i){
+        oconfig_item_t *option = ci->children + i;
+        status = 0;
+
+        if (strcasecmp("Type", option->key) == 0)
+            status = cf_util_get_string(option, &md->type);
+        else if (strcasecmp("Instance", option->key) == 0)
+            status = cf_util_get_string(option, &md->instance);
+        else if (strcasecmp("Index", option->key) == 0)
+            status = tcsv_config_add_metric_index(md, option);
+        else {
+            WARNING("tail_csv plugin: Option `%s' not allowed here.", option->key);
+            status = -1;
+        }
+
+        if (status != 0)
+            break;
+    }
+
+    if (status != 0){
+        tcsv_metric_definition_destroy(md);
+        return (-1);
+    }
+
+    /* Verify all necessary options have been set. */
+    if (md->type == NULL){
+        WARNING("tail_csv plugin: Option `Type' must be set.");
+        status = -1;
+    } else if (md->index == 0){
+        WARNING("tail_csv plugin: Option `Index' must be set.");
+        status = -1;
+    }
+
+    if (status != 0){
+        tcsv_metric_definition_destroy(md);
+        return (-1);
+    }
+
+    /* Retrieve the data source type from the types db. */
+    ds = plugin_get_ds(md->type);
+    if (ds == NULL){
+        ERROR ("tail_csv plugin: Failed to look up type \"%s\". "
+                "It may not be defined in the types.db file. "
+                "Please read the types.db(5) manual page for more details.",
+                md->type);
+        tcsv_metric_definition_destroy(md);
+        return (-1);
+    } else if (ds->ds_num != 1) {
+        ERROR ("tail_csv plugin: The type \"%s\" has %i data sources. "
+                "Only types with a single data soure are supported.",
+                ds->type, ds->ds_num);
+        return (-1);
+    } else {
+        md->data_source_type = ds->ds->type;
+    }
+
+    DEBUG("tail_csv plugin: md = { name = %s, type = %s, data_source_type = %d, index = %d }",
+        md->name, md->type, md->data_source_type, md->index);
+
+    if (metric_head == NULL)
+        metric_head = md;
+    else {
+        metric_definition_t *last;
+        last = metric_head;
+        while (last->next != NULL)
+            last = last->next;
+        last->next = md;
+    }
+
+    return (0);
+}
+
+static void tcsv_instance_definition_destroy(void *arg){
+    instance_definition_t *id;
+
+    id = arg;
+    if (id == NULL)
+        return;
+
+    if (id->name != NULL)
+        DEBUG("tail_csv plugin: Destroying instance definition `%s'.", id->name);
+
+    cu_tail_destroy (id->tail);
+    id->tail = NULL;
+
+    sfree(id->name);
+    sfree(id->path);
+    sfree(id->metric_list);
+    sfree(id);
+}
+
+static int tcsv_config_add_instance_collect(instance_definition_t *id, oconfig_item_t *ci){
+    metric_definition_t *metric;
+    int i;
+
+    if (ci->values_num < 1){
+        WARNING("tail_csv plugin: The `Collect' config option needs at least one argument.");
+        return (-1);
+    }
+
+    /* Verify string arguments */
+    for (i = 0; i < ci->values_num; ++i)
+        if (ci->values[i].type != OCONFIG_TYPE_STRING){
+            WARNING("tail_csv plugin: All arguments to `Collect' must be strings.");
+            return (-1);
+        }
+
+    id->metric_list = (metric_definition_t **)malloc(sizeof(metric_definition_t *) * ci->values_num);
+    if (id->metric_list == NULL)
+        return (-1);
+
+    for (i = 0; i < ci->values_num; ++i){
+        for (metric = metric_head; metric != NULL; metric = metric->next)
+            if (strcasecmp(ci->values[i].value.string, metric->name) == 0)
+                break;
+
+        if (metric == NULL){
+            WARNING("tail_csv plugin: `Collect' argument not found `%s'.", ci->values[i].value.string);
+            return (-1);
+        }
+
+        DEBUG("tail_csv plugin: id { name=%s md->name=%s }", id->name, metric->name);
+
+        id->metric_list[i] = metric;
+        id->metric_list_len++;
+    }
+
+    return (0);
+}
+
+/* Parse instance  */
+static int tcsv_config_add_instance(oconfig_item_t *ci){
+
+    instance_definition_t* id;
+    int status = 0;
+    int i;
+
+    /* Registration variables */
+    char cb_name[DATA_MAX_NAME_LEN];
+    user_data_t cb_data;
+    struct timespec cb_interval;
+
+    if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)){
+        WARNING("tail_csv plugin: The `Instance' config option needs exactly one string argument.");
+        return (-1);
+    }
+
+    id = (instance_definition_t *)malloc(sizeof(*id));
+    if (id == NULL)
+        return (-1);
+    memset(id, 0, sizeof(*id));
+
+    id->name = strdup(ci->values[0].value.string);
+    if (id->name == NULL){
+        free(id);
+        return (-1);
+    }
+
+    /* Use default interval. */
+    id->interval = plugin_get_interval();
+
+    for (i = 0; i < ci->children_num; ++i){
+        oconfig_item_t *option = ci->children + i;
+        status = 0;
+
+        if (strcasecmp("Path", option->key) == 0)
+            status = cf_util_get_string(option, &id->path);
+        else if (strcasecmp("Collect", option->key) == 0)
+            status = tcsv_config_add_instance_collect(id, option);
+        else if (strcasecmp("Interval", option->key) == 0)
+            cf_util_get_cdtime(option, &id->interval);
+        else {
+            WARNING("tail_csv plugin: Option `%s' not allowed here.", option->key);
+            status = -1;
+        }
+
+        if (status != 0)
+            break;
+    }
+
+    if (status != 0){
+        tcsv_instance_definition_destroy(id);
+        return (-1);
+    }
+
+    /* Verify all necessary options have been set. */
+    if (id->path == NULL){
+        WARNING("tail_csv plugin: Option `Path' must be set.");
+        status = -1;
+    } else if (id->metric_list == NULL){
+        WARNING("tail_csv plugin: Option `Collect' must be set.");
+        status = -1;
+   }
+
+    if (status != 0){
+        tcsv_instance_definition_destroy(id);
+        return (-1);
+    }
+
+    DEBUG("tail_csv plugin: id = { name = %s, path = %s }", id->name, id->path);
+
+    ssnprintf (cb_name, sizeof (cb_name), "tail_csv/%s", id->name);
+    memset(&cb_data, 0, sizeof(cb_data));
+    cb_data.data = id;
+    cb_data.free_func = tcsv_instance_definition_destroy;
+    CDTIME_T_TO_TIMESPEC(id->interval, &cb_interval);
+    status = plugin_register_complex_read(NULL, cb_name, tcsv_read, &cb_interval, &cb_data);
+
+    if (status != 0){
+        ERROR("tail_csv plugin: Registering complex read function failed.");
+        tcsv_instance_definition_destroy(id);
+        return (-1);
+    }
+
+    return (0);
+}
+
+/* Parse blocks */
+static int tcsv_config(oconfig_item_t *ci){
+    int i;
+    for (i = 0; i < ci->children_num; ++i){
+        oconfig_item_t *child = ci->children + i;
+        if (strcasecmp("Metric", child->key) == 0)
+            tcsv_config_add_metric(child);
+        else if (strcasecmp("Instance", child->key) == 0)
+            tcsv_config_add_instance(child);
+        else
+            WARNING("tail_csv plugin: Ignore unknown config option `%s'.", child->key);
+    }
+
+    return (0);
+} /* int tcsv_config */
+
+static int tcsv_shutdown(void){
+    metric_definition_t *metric_this;
+    metric_definition_t *metric_next;
+
+    metric_this = metric_head;
+    metric_head = NULL;
+
+    while (metric_this != NULL){
+        metric_next = metric_this->next;
+        tcsv_metric_definition_destroy(metric_this);
+        metric_this = metric_next;
+    }
+
+    return (0);
+}
+
+void module_register(void){
+    plugin_register_complex_config("tail_csv", tcsv_config);
+    plugin_register_shutdown("tail_csv", tcsv_shutdown);
+}
+
+/* vim: set sw=4 sts=4 et : */