=head2 Plugin C<dbi>
-This plugin uses the "B<dbi>" library (L<http://libdbi.sourceforge.net/>) to
-connect to various databases, execute SQL statements and read back the results.
-You can configure how each column is to be interpreted and the plugin will
-generate one data set from each row returned according to these rules.
+This plugin uses the B<dbi> library (L<http://libdbi.sourceforge.net/>) to
+connect to various databases, execute I<SQL> statements and read back the
+results. I<dbi> is an acronym for "database interface" in case you were
+wondering about the name. You can configure how each column is to be
+interpreted and the plugin will generate one or more data sets from each row
+returned according to these rules.
Because the plugin is very generic, the configuration is a little more complex
than those of other plugins. It usually looks something like this:
<Plugin dbi>
<Query "out_of_stock">
Statement "SELECT category, COUNT(*) AS value FROM products WHERE in_stock = 0 GROUP BY category"
- Type "gauge"
- InstancesFrom "category"
- ValuesFrom "value"
+ <Result>
+ Type "gauge"
+ InstancesFrom "category"
+ ValuesFrom "value"
+ </Result>
</Query>
<Database "product_information">
Driver "mysql"
</Database>
</Plugin>
-The configuration above defines one query and one database. The query is then
-linked to the database with the B<Query> option I<within> the
+The configuration above defines one query with one result and one database. The
+query is then linked to the database with the B<Query> option I<within> the
B<E<lt>DatabaseE<gt>> block. You can have any number of queries and databases
and you can also use the B<Include> statement to split up the configuration
file in multiple, smaller files. However, the B<E<lt>QueryE<gt>> block I<must>
=head3 B<Query> blocks
-Query blocks define SQL statements and how the returned data should be
+Query blocks define I<SQL> statements and how the returned data should be
interpreted. They are identified by the name that is given in the opening line
of the block. Thus the name needs to be unique. Other than that, the name is
-not used in collectd.
+not used in collectd.
+
+In each B<Query> block, there is one or more B<Result> blocks. B<Result> blocks
+define which column holds which value or instance information. You can use
+multiple B<Result> blocks to create multiple values from one returned row. This
+is especially useful, when queries take a long time and sending almost the same
+query again and again is not desirable.
+
+Example:
+
+ <Query "environment">
+ Statement "select station, temperature, humidity from environment"
+ <Result>
+ Type "temperature"
+ InstancesFrom "station"
+ ValuesFrom "temperature"
+ </Result>
+ <Result>
+ Type "humidity"
+ InstancesFrom "station"
+ ValuesFrom "humidity"
+ </Result>
+ </Query>
+
+The following options are accepted:
=over 4
specify "if_octets", you will need two counter columns. See the B<ValuesFrom>
setting below.
+There must be exactly one B<Type> option inside each B<Result> block.
+
=item B<InstancesFrom> I<column0> [I<column1> ...]
Specifies the columns whose values will be used to create the "TypeInstance"
The plugin itself does not check whether or not all built instances are
different. It's your responsibility to assure that each is unique.
+There must be at least one B<InstancesFrom> option inside each B<Result> block.
+
=item B<ValuesFrom> I<column0> [I<column1> ...]
Names the columns whose content is used as the actual data for the data sets
it should be able to handle integer an floating point types, as well as strings
(if they include a number at the beginning).
+There must be at least one B<ValuesFrom> option inside each B<Result> block.
+
=back
=head3 B<Database> blocks
/**
* collectd - src/dbi.c
- * Copyright (C) 2008 Florian octo Forster
+ * Copyright (C) 2008,2009 Florian octo 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
};
typedef struct cdbi_driver_option_s cdbi_driver_option_t;
-struct cdbi_query_s
+struct cdbi_result_s;
+typedef struct cdbi_result_s cdbi_result_t;
+struct cdbi_result_s
{
- char *name;
- char *statement;
char *type;
char **instances;
size_t instances_num;
char **values;
size_t values_num;
+
+ cdbi_result_t *next;
+};
+
+struct cdbi_query_s
+{
+ char *name;
+ char *statement;
+
+ cdbi_result_t *results;
};
typedef struct cdbi_query_s cdbi_query_t;
* Global variables
*/
static cdbi_query_t **queries = NULL;
-static size_t queries_num = 0;
+static size_t queries_num = 0;
static cdbi_database_t **databases = NULL;
-static size_t databases_num = 0;
+static size_t databases_num = 0;
/*
* Functions
return (0);
} /* }}} int cdbi_result_get_field */
-static void cdbi_query_free (cdbi_query_t *q) /* {{{ */
+static void cdbi_result_free (cdbi_result_t *r) /* {{{ */
{
size_t i;
+ if (r == NULL)
+ return;
+
+ sfree (r->type);
+
+ for (i = 0; i < r->instances_num; i++)
+ sfree (r->instances[i]);
+ sfree (r->instances);
+
+ for (i = 0; i < r->values_num; i++)
+ sfree (r->values[i]);
+ sfree (r->values);
+
+ cdbi_result_free (r->next);
+
+ sfree (r);
+} /* }}} void cdbi_result_free */
+
+static void cdbi_query_free (cdbi_query_t *q) /* {{{ */
+{
if (q == NULL)
return;
sfree (q->name);
sfree (q->statement);
- sfree (q->type);
- for (i = 0; i < q->instances_num; i++)
- sfree (q->instances[i]);
- sfree (q->instances);
-
- for (i = 0; i < q->values_num; i++)
- sfree (q->values[i]);
- sfree (q->values);
+ cdbi_result_free (q->results);
sfree (q);
} /* }}} void cdbi_query_free */
sfree (db);
} /* }}} void cdbi_database_free */
-static void cdbi_submit (cdbi_database_t *db, cdbi_query_t *q, /* {{{ */
+static void cdbi_submit (cdbi_database_t *db, cdbi_result_t *r, /* {{{ */
char **instances, value_t *values)
{
value_list_t vl = VALUE_LIST_INIT;
vl.values = values;
- vl.values_len = (int) q->values_num;
+ vl.values_len = (int) r->values_num;
vl.time = time (NULL);
sstrncpy (vl.host, hostname_g, sizeof (vl.host));
sstrncpy (vl.plugin, "dbi", sizeof (vl.plugin));
sstrncpy (vl.plugin_instance, db->name, sizeof (vl.type_instance));
- sstrncpy (vl.type, q->type, sizeof (vl.type));
+ sstrncpy (vl.type, r->type, sizeof (vl.type));
strjoin (vl.type_instance, sizeof (vl.type_instance),
- instances, q->instances_num, "-");
+ instances, r->instances_num, "-");
vl.type_instance[sizeof (vl.type_instance) - 1] = 0;
plugin_dispatch_values (&vl);
* <Plugin dbi>
* <Query "plugin_instance0">
* Statement "SELECT name, value FROM table"
- * Type "gauge"
- * InstancesFrom "name"
- * ValuesFrom "value"
+ * <Result>
+ * Type "gauge"
+ * InstancesFrom "name"
+ * ValuesFrom "value"
+ * </Result>
+ * ...
* </Query>
*
* <Database "plugin_instance1">
return (0);
} /* }}} int cdbi_config_add_string */
+static int cdbi_config_add_query_result (cdbi_query_t *q, /* {{{ */
+ oconfig_item_t *ci)
+{
+ cdbi_result_t *r;
+ int status;
+ int i;
+
+ if (ci->values_num != 0)
+ {
+ WARNING ("dbi plugin: The `Result' block doesn't accept any arguments. "
+ "Ignoring %i argument%s.",
+ ci->values_num, (ci->values_num == 1) ? "" : "s");
+ }
+
+ r = (cdbi_result_t *) malloc (sizeof (*r));
+ if (r == NULL)
+ {
+ ERROR ("dbi plugin: malloc failed.");
+ return (-1);
+ }
+ memset (r, 0, sizeof (*r));
+ r->type = NULL;
+ r->instances = NULL;
+ r->values = NULL;
+ r->next = NULL;
+
+ /* Fill the `cdbi_result_t' structure.. */
+ for (i = 0; i < ci->children_num; i++)
+ {
+ oconfig_item_t *child = ci->children + i;
+
+ if (strcasecmp ("Type", child->key) == 0)
+ status = cdbi_config_set_string (&r->type, child);
+ else if (strcasecmp ("InstancesFrom", child->key) == 0)
+ status = cdbi_config_add_string (&r->instances, &r->instances_num, child);
+ else if (strcasecmp ("ValuesFrom", child->key) == 0)
+ status = cdbi_config_add_string (&r->values, &r->values_num, child);
+ else
+ {
+ WARNING ("dbi plugin: Option `%s' not allowed here.", child->key);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ }
+
+ /* Check that all necessary options have been given. */
+ while (status == 0)
+ {
+ if (r->type == NULL)
+ {
+ WARNING ("dbi plugin: `Type' not given for "
+ "result in query `%s'", q->name);
+ status = -1;
+ }
+ if (r->instances == NULL)
+ {
+ WARNING ("dbi plugin: `InstancesFrom' not given for "
+ "result in query `%s'", q->name);
+ status = -1;
+ }
+ if (r->values == NULL)
+ {
+ WARNING ("dbi plugin: `ValuesFrom' not given for "
+ "result in query `%s'", q->name);
+ status = -1;
+ }
+
+ break;
+ } /* while (status == 0) */
+
+ /* If all went well, add this result to the list of results within the
+ * query structure. */
+ if (status == 0)
+ {
+ if (q->results == NULL)
+ {
+ q->results = r;
+ }
+ else
+ {
+ cdbi_result_t *last;
+
+ last = q->results;
+ while (last->next != NULL)
+ last = last->next;
+
+ last->next = r;
+ }
+ }
+
+ if (status != 0)
+ {
+ cdbi_result_free (r);
+ return (-1);
+ }
+
+ return (0);
+} /* }}} int cdbi_config_add_query_result */
+
static int cdbi_config_add_query (oconfig_item_t *ci) /* {{{ */
{
cdbi_query_t *q;
if (strcasecmp ("Statement", child->key) == 0)
status = cdbi_config_set_string (&q->statement, child);
- else if (strcasecmp ("Type", child->key) == 0)
- status = cdbi_config_set_string (&q->type, child);
- else if (strcasecmp ("InstancesFrom", child->key) == 0)
- status = cdbi_config_add_string (&q->instances, &q->instances_num, child);
- else if (strcasecmp ("ValuesFrom", child->key) == 0)
- status = cdbi_config_add_string (&q->values, &q->values_num, child);
+ else if (strcasecmp ("Result", child->key) == 0)
+ status = cdbi_config_add_query_result (q, child);
else
{
WARNING ("dbi plugin: Option `%s' not allowed here.", child->key);
WARNING ("dbi plugin: `Statement' not given for query `%s'", q->name);
status = -1;
}
- if (q->type == NULL)
- {
- WARNING ("dbi plugin: `Type' not given for query `%s'", q->name);
- status = -1;
- }
- if (q->instances == NULL)
- {
- WARNING ("dbi plugin: `InstancesFrom' not given for query `%s'", q->name);
- status = -1;
- }
- if (q->values == NULL)
+ if (q->results == NULL)
{
- WARNING ("dbi plugin: `ValuesFrom' not given for query `%s'", q->name);
+ WARNING ("dbi plugin: No (valid) `Result' block given for query `%s'",
+ q->name);
status = -1;
}
cdbi_query_t *q)
{
dbi_result res;
- char **instances;
- value_t *values;
- const data_set_t *ds;
- size_t i;
int status;
- res = NULL;
- instances = NULL;
- values = NULL;
-
/* Macro that cleans up dynamically allocated memory and returns the
* specified status. */
#define BAIL_OUT(status) \
if (res != NULL) { dbi_result_free (res); res = NULL; } \
- if (instances != NULL) { sfree (instances[0]); sfree (instances); } \
- sfree (values); \
return (status)
- ds = plugin_get_ds (q->type);
- if (ds == NULL)
- {
- ERROR ("dbi plugin: cdbi_read_database_query: Query `%s': Type `%s' is not "
- "known by the daemon. See types.db(5) for details.",
- q->name, q->type);
- BAIL_OUT (-1);
- }
-
- if (((size_t) ds->ds_num) != q->values_num)
- {
- ERROR ("dbi plugin: cdbi_read_database_query: Query `%s': The type `%s' "
- "requires exactly %i value%s, but the configuration specifies %zu.",
- q->name, q->type,
- ds->ds_num, (ds->ds_num == 1) ? "" : "s",
- q->values_num);
- BAIL_OUT (-1);
- }
-
- /* Allocate `instances' and `values' {{{ */
- instances = (char **) malloc (sizeof (*instances) * q->instances_num);
- if (instances == NULL)
- {
- ERROR ("dbi plugin: malloc failed.");
- BAIL_OUT (-1);
- }
-
- instances[0] = (char *) malloc (q->instances_num * DATA_MAX_NAME_LEN);
- if (instances[0] == NULL)
- {
- ERROR ("dbi plugin: malloc failed.");
- BAIL_OUT (-1);
- }
- for (i = 1; i < q->instances_num; i++)
- instances[i] = instances[i - 1] + DATA_MAX_NAME_LEN;
-
- values = (value_t *) malloc (sizeof (*values) * q->values_num);
- if (values == NULL)
- {
- ERROR ("dbi plugin: malloc failed.");
- BAIL_OUT (-1);
- }
- /* }}} */
-
res = dbi_conn_query (db->connection, q->statement);
if (res == NULL)
{
BAIL_OUT (-1);
}
+ /* 0 = error; 1 = success; */
status = dbi_result_first_row (res);
if (status != 1)
{
BAIL_OUT (-1);
}
- while (42)
+ /* Iterate over all rows and use every result with each row. */
+ while (42) /* {{{ */
{
- /* Get instance names and values from the result: */
- for (i = 0; i < q->instances_num; i++) /* {{{ */
- {
- const char *inst;
+ cdbi_result_t *r;
- inst = dbi_result_get_string (res, q->instances[i]);
- if (dbi_conn_error (db->connection, NULL) != 0)
+ /* Iterate over all results, get the appropriate data_set, allocate memory
+ * for the instance(s) and value(s), copy the values and finally call
+ * `cdbi_submit' to create and dispatch a value_list. */
+ for (r = q->results; r != NULL; r = r->next) /* {{{ */
+ {
+ const data_set_t *ds;
+ char **instances;
+ value_t *values;
+ size_t i;
+
+ instances = NULL;
+ values = NULL;
+
+ /* Macro to clean up dynamically allocated memory and continue with the
+ * next iteration of the containing loop, i. e. the `for' loop iterating
+ * over all `Result' sets. */
+#define BAIL_OUT_CONTINUE \
+ if (instances != NULL) { sfree (instances[0]); sfree (instances); } \
+ sfree (values); \
+ continue
+
+ /* Read `ds' and check number of values {{{ */
+ ds = plugin_get_ds (r->type);
+ if (ds == NULL)
{
- char errbuf[1024];
- ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
- "dbi_result_get_string (%s) failed: %s",
- db->name, q->name, q->instances[i],
- cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
- BAIL_OUT (-1);
+ ERROR ("dbi plugin: cdbi_read_database_query: Query `%s': Type `%s' is not "
+ "known by the daemon. See types.db(5) for details.",
+ q->name, r->type);
+ BAIL_OUT_CONTINUE;
}
- sstrncpy (instances[i], (inst == NULL) ? "" : inst, DATA_MAX_NAME_LEN);
- DEBUG ("dbi plugin: cdbi_read_database_query (%s, %s): "
- "instances[%zu] = %s;",
- db->name, q->name, i, instances[i]);
- } /* }}} for (i = 0; i < q->instances_num; i++) */
+ if (((size_t) ds->ds_num) != r->values_num)
+ {
+ ERROR ("dbi plugin: cdbi_read_database_query: Query `%s': The type `%s' "
+ "requires exactly %i value%s, but the configuration specifies %zu.",
+ q->name, r->type,
+ ds->ds_num, (ds->ds_num == 1) ? "" : "s",
+ r->values_num);
+ BAIL_OUT_CONTINUE;
+ }
+ /* }}} */
- for (i = 0; i < q->values_num; i++) /* {{{ */
- {
- status = cdbi_result_get_field (res, q->values[i], ds->ds[i].type,
- values + i);
- if (status != 0)
+ /* Allocate `instances' and `values' {{{ */
+ instances = (char **) malloc (sizeof (*instances) * r->instances_num);
+ if (instances == NULL)
{
- BAIL_OUT (-1);
+ ERROR ("dbi plugin: malloc failed.");
+ BAIL_OUT_CONTINUE;
}
- if (ds->ds[i].type == DS_TYPE_COUNTER)
+ instances[0] = (char *) malloc (r->instances_num * DATA_MAX_NAME_LEN);
+ if (instances[0] == NULL)
{
- DEBUG ("dbi plugin: cdbi_read_database_query (%s, %s): values[%zu] = %llu;",
- db->name, q->name, i, values[i].counter);
+ ERROR ("dbi plugin: malloc failed.");
+ BAIL_OUT_CONTINUE;
}
- else
+ for (i = 1; i < r->instances_num; i++)
+ instances[i] = instances[i - 1] + DATA_MAX_NAME_LEN;
+
+ values = (value_t *) malloc (sizeof (*values) * r->values_num);
+ if (values == NULL)
{
- DEBUG ("dbi plugin: cdbi_read_database_query (%s, %s): values[%zu] = %g;",
- db->name, q->name, i, values[i].gauge);
+ ERROR ("dbi plugin: malloc failed.");
+ BAIL_OUT_CONTINUE;
}
- } /* }}} for (i = 0; i < q->values_num; i++) */
+ /* }}} */
- /* Dispatch this row to the daemon. */
- cdbi_submit (db, q, instances, values);
+ /* Get instance names and values from the result: */
+ for (i = 0; i < r->instances_num; i++) /* {{{ */
+ {
+ const char *inst;
+
+ inst = dbi_result_get_string (res, r->instances[i]);
+ if (dbi_conn_error (db->connection, NULL) != 0)
+ {
+ char errbuf[1024];
+ ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+ "dbi_result_get_string (%s) failed: %s",
+ db->name, q->name, r->instances[i],
+ cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
+ BAIL_OUT_CONTINUE;
+ }
+
+ sstrncpy (instances[i], (inst == NULL) ? "" : inst, DATA_MAX_NAME_LEN);
+ DEBUG ("dbi plugin: cdbi_read_database_query (%s, %s): "
+ "instances[%zu] = %s;",
+ db->name, q->name, i, instances[i]);
+ } /* }}} for (i = 0; i < q->instances_num; i++) */
+
+ for (i = 0; i < r->values_num; i++) /* {{{ */
+ {
+ status = cdbi_result_get_field (res, r->values[i], ds->ds[i].type,
+ values + i);
+ if (status != 0)
+ {
+ BAIL_OUT_CONTINUE;
+ }
+
+ if (ds->ds[i].type == DS_TYPE_COUNTER)
+ {
+ DEBUG ("dbi plugin: cdbi_read_database_query (%s, %s): values[%zu] = %llu;",
+ db->name, q->name, i, values[i].counter);
+ }
+ else
+ {
+ DEBUG ("dbi plugin: cdbi_read_database_query (%s, %s): values[%zu] = %g;",
+ db->name, q->name, i, values[i].gauge);
+ }
+ } /* }}} for (i = 0; i < q->values_num; i++) */
+
+ /* Dispatch this row to the daemon. */
+ cdbi_submit (db, r, instances, values);
+
+ BAIL_OUT_CONTINUE;
+#undef BAIL_OUT_CONTINUE
+ } /* }}} for (r = q->results; r != NULL; r = r->next) */
/* Get the next row from the database. */
status = dbi_result_next_row (res);
}
break;
}
- } /* while (42) */
+ } /* }}} while (42) */
+ /* Clean up and return `status = 0' (success) */
BAIL_OUT (0);
#undef BAIL_OUT
} /* }}} int cdbi_read_database_query */