3 * Copyright (C) 2008 Florian octo Forster
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; only version 2 of the License is applicable.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 * Florian octo Forster <octo at verplant.org>
25 #include "configfile.h"
32 struct cdbi_driver_option_s
37 typedef struct cdbi_driver_option_s cdbi_driver_option_t;
49 typedef struct cdbi_query_s cdbi_query_t;
51 struct cdbi_database_s
57 cdbi_driver_option_t *driver_options;
58 size_t driver_options_num;
60 cdbi_query_t **queries;
65 typedef struct cdbi_database_s cdbi_database_t;
70 static cdbi_query_t **queries = NULL;
71 static size_t queries_num = 0;
72 static cdbi_database_t **databases = NULL;
73 static size_t databases_num = 0;
78 static const char *cdbi_strerror (dbi_conn conn, /* {{{ */
79 char *buffer, size_t buffer_size)
86 sstrncpy (buffer, "connection is NULL", buffer_size);
91 status = dbi_conn_error (conn, &msg);
92 if ((status >= 0) && (msg != NULL))
93 ssnprintf (buffer, buffer_size, "%s (status %i)", msg, status);
95 ssnprintf (buffer, buffer_size, "dbi_conn_error failed with status %i",
99 } /* }}} const char *cdbi_conn_error */
101 static int cdbi_result_get_field (dbi_result res, /* {{{ */
102 const char *name, int dst_type, value_t *ret_value)
106 unsigned short src_type;
109 index = dbi_result_get_field_idx (res, name);
112 ERROR ("dbi plugin: cdbi_result_get: No such column: %s.", name);
116 src_type = dbi_result_get_field_type_idx (res, index);
117 if (src_type == DBI_TYPE_ERROR)
119 ERROR ("dbi plugin: cdbi_result_get: "
120 "dbi_result_get_field_type_idx failed.");
124 if ((dst_type != DS_TYPE_COUNTER) && (dst_type != DS_TYPE_GAUGE))
126 ERROR ("dbi plugin: cdbi_result_get: Don't know how to handle "
127 "destination type %i.", dst_type);
131 if (src_type == DBI_TYPE_INTEGER)
133 if (dst_type == DS_TYPE_COUNTER)
134 value.counter = dbi_result_get_ulonglong_idx (res, index);
136 value.gauge = (gauge_t) dbi_result_get_longlong_idx (res, index);
138 else if (src_type == DBI_TYPE_DECIMAL)
140 value.gauge = dbi_result_get_double_idx (res, index);
141 if (dst_type == DS_TYPE_COUNTER)
142 value.counter = (counter_t) round (value.gauge);
144 else if (src_type == DBI_TYPE_STRING)
146 const char *string = dbi_result_get_string_idx (res, index);
151 else if (dst_type == DS_TYPE_COUNTER)
152 value.counter = (counter_t) strtoll (string, &endptr, 0);
154 value.gauge = (gauge_t) strtod (string, &endptr);
156 if (string == endptr)
158 ERROR ("dbi plugin: cdbi_result_get: Can't parse string as number: %s.",
165 ERROR ("dbi plugin: cdbi_result_get: Don't know how to handle "
166 "source type %hu.", src_type);
170 connection = dbi_result_get_conn (res);
171 if (dbi_conn_error (connection, NULL) != 0)
174 ERROR ("dbi plugin: cdbi_result_get: dbi_result_get_*_idx failed: %s.",
175 cdbi_strerror (connection, errbuf, sizeof (errbuf)));
181 } /* }}} int cdbi_result_get_field */
183 static void cdbi_query_free (cdbi_query_t *q) /* {{{ */
191 sfree (q->statement);
194 for (i = 0; i < q->instances_num; i++)
195 sfree (q->instances[i]);
196 sfree (q->instances);
198 for (i = 0; i < q->values_num; i++)
199 sfree (q->values[i]);
203 } /* }}} void cdbi_query_free */
205 static void cdbi_database_free (cdbi_database_t *db) /* {{{ */
215 for (i = 0; i < db->driver_options_num; i++)
217 sfree (db->driver_options[i].key);
218 sfree (db->driver_options[i].value);
220 sfree (db->driver_options);
223 } /* }}} void cdbi_database_free */
225 static void cdbi_submit (cdbi_database_t *db, cdbi_query_t *q, /* {{{ */
226 char **instances, value_t *values)
228 value_list_t vl = VALUE_LIST_INIT;
231 vl.values_len = (int) q->values_num;
232 vl.time = time (NULL);
233 sstrncpy (vl.host, hostname_g, sizeof (vl.host));
234 sstrncpy (vl.plugin, "dbi", sizeof (vl.plugin));
235 sstrncpy (vl.plugin_instance, db->name, sizeof (vl.type_instance));
236 sstrncpy (vl.type, q->type, sizeof (vl.type));
237 strjoin (vl.type_instance, sizeof (vl.type_instance),
238 instances, q->instances_num, "-");
239 vl.type_instance[sizeof (vl.type_instance) - 1] = 0;
241 plugin_dispatch_values (&vl);
242 } /* }}} void cdbi_submit */
244 /* Configuration handling functions {{{
247 * <Query "plugin_instance0">
248 * Statement "SELECT name, value FROM table"
250 * InstancesFrom "name"
254 * <Database "plugin_instance1">
256 * DriverOption "hostname" "localhost"
258 * Query "plugin_instance0"
263 static int cdbi_config_set_string (char **ret_string, /* {{{ */
268 if ((ci->values_num != 1)
269 || (ci->values[0].type != OCONFIG_TYPE_STRING))
271 WARNING ("dbi plugin: The `%s' config option "
272 "needs exactly one string argument.", ci->key);
276 string = strdup (ci->values[0].value.string);
279 ERROR ("dbi plugin: strdup failed.");
283 if (*ret_string != NULL)
285 *ret_string = string;
288 } /* }}} int cdbi_config_set_string */
290 static int cdbi_config_add_string (char ***ret_array, /* {{{ */
291 size_t *ret_array_len, oconfig_item_t *ci)
297 if (ci->values_num < 1)
299 WARNING ("dbi plugin: The `%s' config option "
300 "needs at least one argument.", ci->key);
304 for (i = 0; i < ci->values_num; i++)
306 if (ci->values[i].type != OCONFIG_TYPE_STRING)
308 WARNING ("dbi plugin: Argument %i to the `%s' option "
309 "is not a string.", i + 1, ci->key);
314 array_len = *ret_array_len;
315 array = (char **) realloc (*ret_array,
316 sizeof (char *) * (array_len + ci->values_num));
319 ERROR ("dbi plugin: realloc failed.");
324 for (i = 0; i < ci->values_num; i++)
326 array[array_len] = strdup (ci->values[i].value.string);
327 if (array[array_len] == NULL)
329 ERROR ("dbi plugin: strdup failed.");
330 *ret_array_len = array_len;
336 *ret_array_len = array_len;
338 } /* }}} int cdbi_config_add_string */
340 static int cdbi_config_add_query (oconfig_item_t *ci) /* {{{ */
346 if ((ci->values_num != 1)
347 || (ci->values[0].type != OCONFIG_TYPE_STRING))
349 WARNING ("dbi plugin: The `Query' block "
350 "needs exactly one string argument.");
354 q = (cdbi_query_t *) malloc (sizeof (*q));
357 ERROR ("dbi plugin: malloc failed.");
360 memset (q, 0, sizeof (*q));
362 status = cdbi_config_set_string (&q->name, ci);
369 /* Fill the `cdbi_query_t' structure.. */
370 for (i = 0; i < ci->children_num; i++)
372 oconfig_item_t *child = ci->children + i;
374 if (strcasecmp ("Statement", child->key) == 0)
375 status = cdbi_config_set_string (&q->statement, child);
376 else if (strcasecmp ("Type", child->key) == 0)
377 status = cdbi_config_set_string (&q->type, child);
378 else if (strcasecmp ("InstancesFrom", child->key) == 0)
379 status = cdbi_config_add_string (&q->instances, &q->instances_num, child);
380 else if (strcasecmp ("ValuesFrom", child->key) == 0)
381 status = cdbi_config_add_string (&q->values, &q->values_num, child);
384 WARNING ("dbi plugin: Option `%s' not allowed here.", child->key);
392 /* Check that all necessary options have been given. */
395 if (q->statement == NULL)
397 WARNING ("dbi plugin: `Statement' not given for query `%s'", q->name);
402 WARNING ("dbi plugin: `Type' not given for query `%s'", q->name);
405 if (q->instances == NULL)
407 WARNING ("dbi plugin: `InstancesFrom' not given for query `%s'", q->name);
410 if (q->values == NULL)
412 WARNING ("dbi plugin: `ValuesFrom' not given for query `%s'", q->name);
417 } /* while (status == 0) */
419 /* If all went well, add this query to the list of queries within the
420 * database structure. */
425 temp = (cdbi_query_t **) realloc (queries,
426 sizeof (*queries) * (queries_num + 1));
429 ERROR ("dbi plugin: realloc failed");
435 queries[queries_num] = q;
447 } /* }}} int cdbi_config_add_query */
449 static int cdbi_config_add_database_driver_option (cdbi_database_t *db, /* {{{ */
452 cdbi_driver_option_t *option;
454 if ((ci->values_num != 2)
455 || (ci->values[0].type != OCONFIG_TYPE_STRING)
456 || (ci->values[1].type != OCONFIG_TYPE_STRING))
458 WARNING ("dbi plugin: The `DriverOption' config option "
459 "needs exactly two string arguments.");
463 option = (cdbi_driver_option_t *) realloc (db->driver_options,
464 sizeof (*option) * (db->driver_options_num + 1));
467 ERROR ("dbi plugin: realloc failed");
471 db->driver_options = option;
472 option = db->driver_options + db->driver_options_num;
474 option->key = strdup (ci->values[0].value.string);
475 if (option->key == NULL)
477 ERROR ("dbi plugin: strdup failed.");
481 option->value = strdup (ci->values[1].value.string);
482 if (option->value == NULL)
484 ERROR ("dbi plugin: strdup failed.");
489 db->driver_options_num++;
491 } /* }}} int cdbi_config_add_database_driver_option */
493 static int cdbi_config_add_database_query (cdbi_database_t *db, /* {{{ */
500 if ((ci->values_num != 1)
501 || (ci->values[0].type != OCONFIG_TYPE_STRING))
503 WARNING ("dbi plugin: The `Query' config option "
504 "needs exactly one string argument.");
509 for (i = 0; i < queries_num; i++)
511 if (strcasecmp (queries[i]->name, ci->values[0].value.string) == 0)
520 WARNING ("dbi plugin: Database `%s': Unknown query `%s'. "
521 "Please make sure that the <Query \"%s\"> block comes before "
522 "the <Database \"%s\"> block.",
523 db->name, ci->values[0].value.string,
524 ci->values[0].value.string, db->name);
528 temp = (cdbi_query_t **) realloc (db->queries,
529 sizeof (*db->queries) * (db->queries_num + 1));
532 ERROR ("dbi plugin: realloc failed");
538 db->queries[db->queries_num] = q;
543 } /* }}} int cdbi_config_add_database_query */
545 static int cdbi_config_add_database (oconfig_item_t *ci) /* {{{ */
551 if ((ci->values_num != 1)
552 || (ci->values[0].type != OCONFIG_TYPE_STRING))
554 WARNING ("dbi plugin: The `Database' block "
555 "needs exactly one string argument.");
559 db = (cdbi_database_t *) malloc (sizeof (*db));
562 ERROR ("dbi plugin: malloc failed.");
565 memset (db, 0, sizeof (*db));
567 status = cdbi_config_set_string (&db->name, ci);
574 /* Fill the `cdbi_database_t' structure.. */
575 for (i = 0; i < ci->children_num; i++)
577 oconfig_item_t *child = ci->children + i;
579 if (strcasecmp ("Driver", child->key) == 0)
580 status = cdbi_config_set_string (&db->driver, child);
581 else if (strcasecmp ("DriverOption", child->key) == 0)
582 status = cdbi_config_add_database_driver_option (db, child);
583 else if (strcasecmp ("SelectDB", child->key) == 0)
584 status = cdbi_config_set_string (&db->select_db, child);
585 else if (strcasecmp ("Query", child->key) == 0)
586 status = cdbi_config_add_database_query (db, child);
589 WARNING ("dbi plugin: Option `%s' not allowed here.", child->key);
597 /* Check that all necessary options have been given. */
600 if (db->driver == NULL)
602 WARNING ("dbi plugin: `Driver' not given for database `%s'", db->name);
605 if (db->driver_options_num == 0)
607 WARNING ("dbi plugin: No `DriverOption' given for database `%s'. "
608 "This will likely not work.", db->name);
612 } /* while (status == 0) */
614 /* If all went well, add this database to the global list of databases. */
617 cdbi_database_t **temp;
619 temp = (cdbi_database_t **) realloc (databases,
620 sizeof (*databases) * (databases_num + 1));
623 ERROR ("dbi plugin: realloc failed");
629 databases[databases_num] = db;
636 cdbi_database_free (db);
641 } /* }}} int cdbi_config_add_database */
643 static int cdbi_config (oconfig_item_t *ci) /* {{{ */
647 for (i = 0; i < ci->children_num; i++)
649 oconfig_item_t *child = ci->children + i;
650 if (strcasecmp ("Query", child->key) == 0)
651 cdbi_config_add_query (child);
652 else if (strcasecmp ("Database", child->key) == 0)
653 cdbi_config_add_database (child);
656 WARNING ("snmp plugin: Ignoring unknown config option `%s'.", child->key);
658 } /* for (ci->children) */
661 } /* }}} int cdbi_config */
663 /* }}} End of configuration handling functions */
665 static int cdbi_init (void) /* {{{ */
667 static int did_init = 0;
673 if (queries_num == 0)
675 ERROR ("dbi plugin: No <Query> blocks have been found. Without them, "
676 "this plugin can't do anything useful, so we will returns an error.");
680 if (databases_num == 0)
682 ERROR ("dbi plugin: No <Database> blocks have been found. Without them, "
683 "this plugin can't do anything useful, so we will returns an error.");
687 status = dbi_initialize (NULL);
690 ERROR ("dbi plugin: cdbi_init: dbi_initialize failed with status %i.",
694 else if (status == 0)
696 ERROR ("dbi plugin: `dbi_initialize' could not load any drivers. Please "
697 "install at least one `DBD' or check your installation.");
700 DEBUG ("dbi plugin: cdbi_init: dbi_initialize reports %i driver%s.",
701 status, (status == 1) ? "" : "s");
704 } /* }}} int cdbi_init */
706 static int cdbi_read_database_query (cdbi_database_t *db, /* {{{ */
712 const data_set_t *ds;
720 /* Macro that cleans up dynamically allocated memory and returns the
721 * specified status. */
722 #define BAIL_OUT(status) \
723 if (res != NULL) { dbi_result_free (res); res = NULL; } \
724 if (instances != NULL) { sfree (instances[0]); sfree (instances); } \
728 ds = plugin_get_ds (q->type);
731 ERROR ("dbi plugin: cdbi_read_database_query: Query `%s': Type `%s' is not "
732 "known by the daemon. See types.db(5) for details.",
737 if (((size_t) ds->ds_num) != q->values_num)
739 ERROR ("dbi plugin: cdbi_read_database_query: Query `%s': The type `%s' "
740 "requires exactly %i value%s, but the configuration specifies %zu.",
742 ds->ds_num, (ds->ds_num == 1) ? "" : "s",
747 /* Allocate `instances' and `values' {{{ */
748 instances = (char **) malloc (sizeof (*instances) * q->instances_num);
749 if (instances == NULL)
751 ERROR ("dbi plugin: malloc failed.");
755 instances[0] = (char *) malloc (q->instances_num * DATA_MAX_NAME_LEN);
756 if (instances[0] == NULL)
758 ERROR ("dbi plugin: malloc failed.");
761 for (i = 1; i < q->instances_num; i++)
762 instances[i] = instances[i - 1] + DATA_MAX_NAME_LEN;
764 values = (value_t *) malloc (sizeof (*values) * q->values_num);
767 ERROR ("dbi plugin: malloc failed.");
772 res = dbi_conn_query (db->connection, q->statement);
776 ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
777 "dbi_conn_query failed: %s",
779 cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
783 status = dbi_result_first_row (res);
787 ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
788 "dbi_result_first_row failed: %s. Maybe the statement didn't "
791 cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
797 /* Get instance names and values from the result: */
798 for (i = 0; i < q->instances_num; i++) /* {{{ */
802 inst = dbi_result_get_string (res, q->instances[i]);
803 if (dbi_conn_error (db->connection, NULL) != 0)
806 ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
807 "dbi_result_get_string (%s) failed: %s",
808 db->name, q->name, q->instances[i],
809 cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
813 sstrncpy (instances[i], (inst == NULL) ? "" : inst, DATA_MAX_NAME_LEN);
814 DEBUG ("dbi plugin: cdbi_read_database_query (%s, %s): "
815 "instances[%zu] = %s;",
816 db->name, q->name, i, instances[i]);
817 } /* }}} for (i = 0; i < q->instances_num; i++) */
819 for (i = 0; i < q->values_num; i++) /* {{{ */
821 status = cdbi_result_get_field (res, q->values[i], ds->ds[i].type,
828 if (ds->ds[i].type == DS_TYPE_COUNTER)
830 DEBUG ("dbi plugin: cdbi_read_database_query (%s, %s): values[%zu] = %llu;",
831 db->name, q->name, i, values[i].counter);
835 DEBUG ("dbi plugin: cdbi_read_database_query (%s, %s): values[%zu] = %g;",
836 db->name, q->name, i, values[i].gauge);
838 } /* }}} for (i = 0; i < q->values_num; i++) */
840 /* Dispatch this row to the daemon. */
841 cdbi_submit (db, q, instances, values);
843 /* Get the next row from the database. */
844 status = dbi_result_next_row (res);
847 if (dbi_conn_error (db->connection, NULL) != 0)
850 WARNING ("dbi plugin: cdbi_read_database_query (%s, %s): "
851 "dbi_result_next_row failed: %s.",
853 cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
861 } /* }}} int cdbi_read_database_query */
863 static int cdbi_connect_database (cdbi_database_t *db) /* {{{ */
870 if (db->connection != NULL)
872 status = dbi_conn_ping (db->connection);
873 if (status != 0) /* connection is alive */
876 dbi_conn_close (db->connection);
877 db->connection = NULL;
880 driver = dbi_driver_open (db->driver);
883 ERROR ("dbi plugin: cdbi_connect_database: dbi_driver_open (%s) failed.",
885 INFO ("dbi plugin: Maybe the driver isn't installed? "
886 "Known drivers are:");
887 for (driver = dbi_driver_list (NULL);
889 driver = dbi_driver_list (driver))
891 INFO ("dbi plugin: * %s", dbi_driver_get_name (driver));
896 connection = dbi_conn_open (driver);
897 if (connection == NULL)
899 ERROR ("dbi plugin: cdbi_connect_database: dbi_conn_open (%s) failed.",
904 /* Set all the driver options. Because this is a very very very generic
905 * interface, the error handling is kind of long. If an invalid option is
906 * encountered, it will get a list of options understood by the driver and
907 * report that as `INFO'. This way, users hopefully don't have too much
908 * trouble finding out how to configure the plugin correctly.. */
909 for (i = 0; i < db->driver_options_num; i++)
911 DEBUG ("dbi plugin: cdbi_connect_database (%s): "
912 "key = %s; value = %s;",
914 db->driver_options[i].key,
915 db->driver_options[i].value);
917 status = dbi_conn_set_option (connection,
918 db->driver_options[i].key, db->driver_options[i].value);
924 ERROR ("dbi plugin: cdbi_connect_database (%s): "
925 "dbi_conn_set_option (%s, %s) failed: %s.",
927 db->driver_options[i].key, db->driver_options[i].value,
928 cdbi_strerror (connection, errbuf, sizeof (errbuf)));
930 INFO ("dbi plugin: This is a list of all options understood "
931 "by the `%s' driver:", db->driver);
932 for (opt = dbi_conn_get_option_list (connection, NULL);
934 opt = dbi_conn_get_option_list (connection, opt))
936 INFO ("dbi plugin: * %s", opt);
939 dbi_conn_close (connection);
942 } /* for (i = 0; i < db->driver_options_num; i++) */
944 status = dbi_conn_connect (connection);
948 ERROR ("dbi plugin: cdbi_connect_database (%s): "
949 "dbi_conn_connect failed: %s",
950 db->name, cdbi_strerror (connection, errbuf, sizeof (errbuf)));
951 dbi_conn_close (connection);
955 if (db->select_db != NULL)
957 status = dbi_conn_select_db (connection, db->select_db);
961 WARNING ("dbi plugin: cdbi_connect_database (%s): "
962 "dbi_conn_select_db (%s) failed: %s. Check the `SelectDB' option.",
963 db->name, db->select_db,
964 cdbi_strerror (connection, errbuf, sizeof (errbuf)));
965 dbi_conn_close (connection);
970 db->connection = connection;
972 } /* }}} int cdbi_connect_database */
974 static int cdbi_read_database (cdbi_database_t *db) /* {{{ */
980 status = cdbi_connect_database (db);
983 assert (db->connection != NULL);
986 for (i = 0; i < db->queries_num; i++)
988 status = cdbi_read_database_query (db, db->queries[i]);
995 ERROR ("dbi plugin: All queries failed for database `%s'.", db->name);
1000 } /* }}} int cdbi_read_database */
1002 static int cdbi_read (void) /* {{{ */
1008 for (i = 0; i < databases_num; i++)
1010 status = cdbi_read_database (databases[i]);
1017 ERROR ("dbi plugin: No database could be read. Will return an error so "
1018 "the plugin will be delayed.");
1023 } /* }}} int cdbi_read */
1025 static int cdbi_shutdown (void) /* {{{ */
1029 for (i = 0; i < databases_num; i++)
1031 if (databases[i]->connection != NULL)
1033 dbi_conn_close (databases[i]->connection);
1034 databases[i]->connection = NULL;
1036 cdbi_database_free (databases[i]);
1041 for (i = 0; i < queries_num; i++)
1042 cdbi_query_free (queries[i]);
1047 } /* }}} int cdbi_shutdown */
1049 void module_register (void) /* {{{ */
1051 plugin_register_complex_config ("dbi", cdbi_config);
1052 plugin_register_init ("dbi", cdbi_init);
1053 plugin_register_read ("dbi", cdbi_read);
1054 plugin_register_shutdown ("dbi", cdbi_shutdown);
1055 } /* }}} void module_register */
1058 * vim: shiftwidth=2 softtabstop=2 et fdm=marker