From bbee4c8ac8e6cc7fb7e970f50e0bdff25ae9f39b Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Sun, 30 Aug 2009 15:23:33 +0200 Subject: [PATCH] write_http plugin: Make it possible to send values as JSON. --- src/Makefile.am | 3 +- src/collectd.conf.pod | 8 ++ src/utils_format_json.c | 268 ++++++++++++++++++++++++++++++++++++++++++++++++ src/utils_format_json.h | 36 +++++++ src/write_http.c | 150 +++++++++++++++++++++++++-- 5 files changed, 457 insertions(+), 8 deletions(-) create mode 100644 src/utils_format_json.c create mode 100644 src/utils_format_json.h diff --git a/src/Makefile.am b/src/Makefile.am index 0dd5ff31..25711d6c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1044,7 +1044,8 @@ endif if BUILD_PLUGIN_WRITE_HTTP pkglib_LTLIBRARIES += write_http.la -write_http_la_SOURCES = write_http.c +write_http_la_SOURCES = write_http.c \ + utils_format_json.c utils_format_json.h write_http_la_LDFLAGS = -module -avoid-version write_http_la_CFLAGS = $(AM_CFLAGS) write_http_la_LIBADD = diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 9419fb99..69f7308b 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -3502,6 +3502,14 @@ File that holds one or more SSL certificates. If you want to use HTTPS you will possibly need this option. What CA certificates come bundled with C and are checked by default depends on the distribution you use. +=item B B|B + +Format of the output to generate. If set to B, will create output that +is understood by the I and I plugins. When set to B, will +create output in the I (JSON). + +Defaults to B. + =back =head1 THRESHOLD CONFIGURATION diff --git a/src/utils_format_json.c b/src/utils_format_json.c new file mode 100644 index 00000000..a9193160 --- /dev/null +++ b/src/utils_format_json.c @@ -0,0 +1,268 @@ +/** + * collectd - src/utils_format_json.c + * Copyright (C) 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 + * 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: + * Florian octo Forster + **/ + +#include "collectd.h" +#include "plugin.h" +#include "common.h" + +#include "utils_format_json.h" + +static int escape_string (char *buffer, size_t buffer_size, /* {{{ */ + const char *string) +{ + size_t src_pos; + size_t dst_pos; + + if ((buffer == NULL) || (string == NULL)) + return (-EINVAL); + + if (buffer_size < 3) + return (-ENOMEM); + + dst_pos = 0; + +#define BUFFER_ADD(c) do { \ + if (dst_pos >= (buffer_size - 1)) { \ + buffer[buffer_size - 1] = 0; \ + return (-ENOMEM); \ + } \ + buffer[dst_pos] = (c); \ + dst_pos++; \ +} while (0) + + /* Escape special characters */ + BUFFER_ADD ('"'); + for (src_pos = 0; string[src_pos] != 0; src_pos++) + { + if ((string[src_pos] == '"') + || (string[src_pos] == '\\')) + { + BUFFER_ADD ('\\'); + BUFFER_ADD (string[src_pos]); + } + else if (string[src_pos] <= 0x001F) + BUFFER_ADD ('?'); + else + BUFFER_ADD (string[src_pos]); + } /* for */ + BUFFER_ADD ('"'); + buffer[dst_pos] = 0; + +#undef BUFFER_ADD + + return (0); +} /* }}} int buffer_add_string */ + +static int values_to_json (char *buffer, size_t buffer_size, /* {{{ */ + const data_set_t *ds, const value_list_t *vl) +{ + size_t offset = 0; + int i; + + memset (buffer, 0, buffer_size); + +#define BUFFER_ADD(...) do { \ + int status; \ + status = ssnprintf (buffer + offset, buffer_size - offset, \ + __VA_ARGS__); \ + if (status < 1) \ + return (-1); \ + else if (((size_t) status) >= (buffer_size - offset)) \ + return (-ENOMEM); \ + else \ + offset += ((size_t) status); \ +} while (0) + + BUFFER_ADD ("["); + for (i = 0; i < ds->ds_num; i++) + { + if (i > 0) + BUFFER_ADD (","); + + if (ds->ds[i].type == DS_TYPE_GAUGE) + BUFFER_ADD ("%g", vl->values[i].gauge); + else if (ds->ds[i].type == DS_TYPE_COUNTER) + BUFFER_ADD ("%llu", vl->values[i].counter); + else if (ds->ds[i].type == DS_TYPE_DERIVE) + BUFFER_ADD ("%"PRIi64, vl->values[i].derive); + else if (ds->ds[i].type == DS_TYPE_ABSOLUTE) + BUFFER_ADD ("%"PRIu64, vl->values[i].absolute); + else + { + ERROR ("format_json: Unknown data source type: %i", + ds->ds[i].type); + return (-1); + } + } /* for ds->ds_num */ + BUFFER_ADD ("]"); + +#undef BUFFER_ADD + + DEBUG ("format_json: values_to_json: buffer = %s;", buffer); + + return (0); +} /* }}} int values_to_json */ + +static int value_list_to_json (char *buffer, size_t buffer_size, /* {{{ */ + const data_set_t *ds, const value_list_t *vl) +{ + char temp[512]; + size_t offset = 0; + int status; + + memset (buffer, 0, buffer_size); + +#define BUFFER_ADD(...) do { \ + status = ssnprintf (buffer + offset, buffer_size - offset, \ + __VA_ARGS__); \ + if (status < 1) \ + return (-1); \ + else if (((size_t) status) >= (buffer_size - offset)) \ + return (-ENOMEM); \ + else \ + offset += ((size_t) status); \ +} while (0) + + /* All value lists have a leading comma. The first one will be replaced with + * a square bracket in `format_json_finalize'. */ + BUFFER_ADD (",{"); + + status = values_to_json (temp, sizeof (temp), ds, vl); + if (status != 0) + return (status); + BUFFER_ADD ("\"values\":%s", temp); + + BUFFER_ADD (",\"time\":%lu", (unsigned long) vl->time); + BUFFER_ADD (",\"interval\":%i", vl->interval); + +#define BUFFER_ADD_KEYVAL(key, value) do { \ + status = escape_string (temp, sizeof (temp), (value)); \ + if (status != 0) \ + return (status); \ + BUFFER_ADD (",\"%s\":%s", (key), temp); \ +} while (0) + + BUFFER_ADD_KEYVAL ("host", vl->host); + BUFFER_ADD_KEYVAL ("plugin", vl->plugin); + BUFFER_ADD_KEYVAL ("plugin_instance", vl->plugin_instance); + BUFFER_ADD_KEYVAL ("type", vl->type); + BUFFER_ADD_KEYVAL ("type_instance", vl->type_instance); + + BUFFER_ADD ("}"); + +#undef BUFFER_ADD_KEYVAL +#undef BUFFER_ADD + + DEBUG ("format_json: value_list_to_json: buffer = %s;", buffer); + + return (0); +} /* }}} int value_list_to_json */ + +static int format_json_value_list_nocheck (char *buffer, /* {{{ */ + size_t *ret_buffer_fill, size_t *ret_buffer_free, + const data_set_t *ds, const value_list_t *vl, + size_t temp_size) +{ + char temp[temp_size]; + int status; + + status = value_list_to_json (temp, sizeof (temp), ds, vl); + if (status != 0) + return (status); + temp_size = strlen (temp); + + memcpy (buffer + (*ret_buffer_fill), temp, temp_size + 1); + (*ret_buffer_fill) += temp_size; + (*ret_buffer_free) -= temp_size; + + return (0); +} /* }}} int format_json_value_list_nocheck */ + +int format_json_initialize (char *buffer, /* {{{ */ + size_t *ret_buffer_fill, size_t *ret_buffer_free) +{ + size_t buffer_fill; + size_t buffer_free; + + if ((buffer == NULL) || (ret_buffer_fill == NULL) || (ret_buffer_free == NULL)) + return (-EINVAL); + + buffer_fill = *ret_buffer_fill; + buffer_free = *ret_buffer_free; + + buffer_free = buffer_fill + buffer_free; + buffer_fill = 0; + + if (buffer_free < 3) + return (-ENOMEM); + + memset (buffer, 0, buffer_free); + *ret_buffer_fill = buffer_fill; + *ret_buffer_free = buffer_free; + + return (0); +} /* }}} int format_json_initialize */ + +int format_json_finalize (char *buffer, /* {{{ */ + size_t *ret_buffer_fill, size_t *ret_buffer_free) +{ + size_t pos; + + if ((buffer == NULL) || (ret_buffer_fill == NULL) || (ret_buffer_free == NULL)) + return (-EINVAL); + + if (*ret_buffer_free < 2) + return (-ENOMEM); + + /* Replace the leading comma added in `value_list_to_json' with a square + * bracket. */ + if (buffer[0] != ',') + return (-EINVAL); + buffer[0] = '['; + + pos = *ret_buffer_fill; + buffer[pos] = ']'; + buffer[pos+1] = 0; + + (*ret_buffer_fill)++; + (*ret_buffer_free)--; + + return (0); +} /* }}} int format_json_finalize */ + +int format_json_value_list (char *buffer, /* {{{ */ + size_t *ret_buffer_fill, size_t *ret_buffer_free, + const data_set_t *ds, const value_list_t *vl) +{ + if ((buffer == NULL) + || (ret_buffer_fill == NULL) || (ret_buffer_free == NULL) + || (ds == NULL) || (vl == NULL)) + return (-EINVAL); + + if (*ret_buffer_free < 3) + return (-ENOMEM); + + return (format_json_value_list_nocheck (buffer, + ret_buffer_fill, ret_buffer_free, ds, vl, + (*ret_buffer_free) - 2)); +} /* }}} int format_json_value_list */ + +/* vim: set sw=2 sts=2 et fdm=marker : */ diff --git a/src/utils_format_json.h b/src/utils_format_json.h new file mode 100644 index 00000000..880a79dc --- /dev/null +++ b/src/utils_format_json.h @@ -0,0 +1,36 @@ +/** + * collectd - src/utils_format_json.c + * Copyright (C) 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 + * 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: + * Florian octo Forster + **/ + +#ifndef UTILS_FORMAT_JSON_H +#define UTILS_FORMAT_JSON_H 1 + +#include "collectd.h" +#include "plugin.h" + +int format_json_initialize (char *buffer, + size_t *ret_buffer_fill, size_t *ret_buffer_free); +int format_json_value_list (char *buffer, + size_t *ret_buffer_fill, size_t *ret_buffer_free, + const data_set_t *ds, const value_list_t *vl); +int format_json_finalize (char *buffer, + size_t *ret_buffer_fill, size_t *ret_buffer_free); + +#endif /* UTILS_FORMAT_JSON_H */ diff --git a/src/write_http.c b/src/write_http.c index 30693e02..f14636bd 100644 --- a/src/write_http.c +++ b/src/write_http.c @@ -28,6 +28,7 @@ #include "common.h" #include "utils_cache.h" #include "utils_parse_option.h" +#include "utils_format_json.h" #if HAVE_PTHREAD_H # include @@ -49,6 +50,10 @@ struct wh_callback_s int verify_host; char *cacert; +#define WH_FORMAT_COMMAND 0 +#define WH_FORMAT_JSON 1 + int format; + CURL *curl; char curl_errbuf[CURL_ERROR_SIZE]; @@ -67,6 +72,13 @@ static void wh_reset_buffer (wh_callback_t *cb) /* {{{ */ cb->send_buffer_free = sizeof (cb->send_buffer); cb->send_buffer_fill = 0; cb->send_buffer_init_time = time (NULL); + + if (cb->format == WH_FORMAT_JSON) + { + format_json_initialize (cb->send_buffer, + &cb->send_buffer_fill, + &cb->send_buffer_free); + } } /* }}} wh_reset_buffer */ static int wh_send_buffer (wh_callback_t *cb) /* {{{ */ @@ -102,7 +114,10 @@ static int wh_callback_init (wh_callback_t *cb) /* {{{ */ headers = NULL; headers = curl_slist_append (headers, "Accept: */*"); - headers = curl_slist_append (headers, "Content-Type: text/plain"); + if (cb->format == WH_FORMAT_JSON) + headers = curl_slist_append (headers, "Content-Type: application/json"); + else + headers = curl_slist_append (headers, "Content-Type: text/plain"); headers = curl_slist_append (headers, "Expect:"); curl_easy_setopt (cb->curl, CURLOPT_HTTPHEADER, headers); @@ -158,14 +173,46 @@ static int wh_flush_nolock (int timeout, wh_callback_t *cb) /* {{{ */ return (0); } - if (cb->send_buffer_fill <= 0) + if (cb->format == WH_FORMAT_COMMAND) { - cb->send_buffer_init_time = time (NULL); - return (0); + if (cb->send_buffer_fill <= 0) + { + cb->send_buffer_init_time = time (NULL); + return (0); + } + + status = wh_send_buffer (cb); + wh_reset_buffer (cb); } + else if (cb->format == WH_FORMAT_JSON) + { + if (cb->send_buffer_fill <= 2) + { + cb->send_buffer_init_time = time (NULL); + return (0); + } - status = wh_send_buffer (cb); - wh_reset_buffer (cb); + status = format_json_finalize (cb->send_buffer, + &cb->send_buffer_fill, + &cb->send_buffer_free); + if (status != 0) + { + ERROR ("write_http: wh_flush_nolock: " + "format_json_finalize failed."); + wh_reset_buffer (cb); + return (status); + } + + status = wh_send_buffer (cb); + wh_reset_buffer (cb); + } + else + { + ERROR ("write_http: wh_flush_nolock: " + "Unknown format: %i", + cb->format); + return (-1); + } return (status); } /* }}} wh_flush_nolock */ @@ -355,6 +402,60 @@ static int wh_write_command (const data_set_t *ds, const value_list_t *vl, /* {{ return (0); } /* }}} int wh_write_command */ +static int wh_write_json (const data_set_t *ds, const value_list_t *vl, /* {{{ */ + wh_callback_t *cb) +{ + int status; + + pthread_mutex_lock (&cb->send_lock); + + if (cb->curl == NULL) + { + status = wh_callback_init (cb); + if (status != 0) + { + ERROR ("write_http plugin: wh_callback_init failed."); + pthread_mutex_unlock (&cb->send_lock); + return (-1); + } + } + + status = format_json_value_list (cb->send_buffer, + &cb->send_buffer_fill, + &cb->send_buffer_free, + ds, vl); + if (status == (-ENOMEM)) + { + status = wh_flush_nolock (/* timeout = */ -1, cb); + if (status != 0) + { + wh_reset_buffer (cb); + pthread_mutex_unlock (&cb->send_lock); + return (status); + } + + status = format_json_value_list (cb->send_buffer, + &cb->send_buffer_fill, + &cb->send_buffer_free, + ds, vl); + } + if (status != 0) + { + pthread_mutex_unlock (&cb->send_lock); + return (status); + } + + DEBUG ("write_http plugin: <%s> buffer %zu/%zu (%g%%)", + cb->location, + cb->send_buffer_fill, sizeof (cb->send_buffer), + 100.0 * ((double) cb->send_buffer_fill) / ((double) sizeof (cb->send_buffer))); + + /* Check if we have enough space for this command. */ + pthread_mutex_unlock (&cb->send_lock); + + return (0); +} /* }}} int wh_write_json */ + static int wh_write (const data_set_t *ds, const value_list_t *vl, /* {{{ */ user_data_t *user_data) { @@ -366,7 +467,11 @@ static int wh_write (const data_set_t *ds, const value_list_t *vl, /* {{{ */ cb = user_data->data; - status = wh_write_command (ds, vl, cb); + if (cb->format == WH_FORMAT_JSON) + status = wh_write_json (ds, vl, cb); + else + status = wh_write_command (ds, vl, cb); + return (status); } /* }}} int wh_write */ @@ -411,6 +516,34 @@ static int config_set_boolean (int *dest, oconfig_item_t *ci) /* {{{ */ return (0); } /* }}} int config_set_boolean */ +static int config_set_format (wh_callback_t *cb, /* {{{ */ + oconfig_item_t *ci) +{ + char *string; + + if ((ci->values_num != 1) + || (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + WARNING ("write_http plugin: The `%s' config option " + "needs exactly one string argument.", ci->key); + return (-1); + } + + string = ci->values[0].value.string; + if (strcasecmp ("Command", string) == 0) + cb->format = WH_FORMAT_COMMAND; + else if (strcasecmp ("JSON", string) == 0) + cb->format = WH_FORMAT_JSON; + else + { + ERROR ("write_http plugin: Invalid format string: %s", + string); + return (-1); + } + + return (0); +} /* }}} int config_set_string */ + static int wh_config_url (oconfig_item_t *ci) /* {{{ */ { wh_callback_t *cb; @@ -431,6 +564,7 @@ static int wh_config_url (oconfig_item_t *ci) /* {{{ */ cb->verify_peer = 1; cb->verify_host = 1; cb->cacert = NULL; + cb->format = WH_FORMAT_COMMAND; cb->curl = NULL; pthread_mutex_init (&cb->send_lock, /* attr = */ NULL); @@ -453,6 +587,8 @@ static int wh_config_url (oconfig_item_t *ci) /* {{{ */ config_set_boolean (&cb->verify_host, child); else if (strcasecmp ("CACert", child->key) == 0) config_set_string (&cb->cacert, child); + else if (strcasecmp ("Format", child->key) == 0) + config_set_format (cb, child); else { ERROR ("write_http plugin: Invalid configuration " -- 2.11.0