Add request specific statistics to all CURL-based plugins.
[collectd.git] / src / utils_curl_stats.c
diff --git a/src/utils_curl_stats.c b/src/utils_curl_stats.c
new file mode 100644 (file)
index 0000000..6e98bcf
--- /dev/null
@@ -0,0 +1,245 @@
+/**
+ * collectd - src/utils_curl_stats.c
+ * Copyright (C) 2015       Sebastian 'tokkee' Harl
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *   Sebastian Harl <sh@tokkee.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_curl_stats.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct curl_stats_s
+{
+       bool total_time;
+       bool namelookup_time;
+       bool connect_time;
+       bool pretransfer_time;
+       bool size_upload;
+       bool size_download;
+       bool speed_download;
+       bool speed_upload;
+       bool header_size;
+       bool request_size;
+       bool content_length_download;
+       bool content_length_upload;
+       bool starttransfer_time;
+       bool redirect_time;
+       bool redirect_count;
+       bool num_connects;
+       bool appconnect_time;
+};
+
+/*
+ * Private functions
+ */
+
+static int dispatch_gauge (CURL *curl, CURLINFO info, value_list_t *vl)
+{
+       CURLcode code;
+       value_t v;
+
+       code = curl_easy_getinfo (curl, info, &v.gauge);
+       if (code != CURLE_OK)
+               return -1;
+
+       vl->values = &v;
+       vl->values_len = 1;
+
+       return plugin_dispatch_values (vl);
+} /* dispatch_gauge */
+
+/* dispatch a speed, in bytes/second */
+static int dispatch_speed (CURL *curl, CURLINFO info, value_list_t *vl)
+{
+       CURLcode code;
+       value_t v;
+
+       code = curl_easy_getinfo (curl, info, &v.gauge);
+       if (code != CURLE_OK)
+               return -1;
+
+       v.gauge *= 8;
+
+       vl->values = &v;
+       vl->values_len = 1;
+
+       return plugin_dispatch_values (vl);
+} /* dispatch_speed */
+
+/* dispatch a size/count, reported as a long value */
+static int dispatch_size (CURL *curl, CURLINFO info, value_list_t *vl)
+{
+       CURLcode code;
+       value_t v;
+       long raw;
+
+       code = curl_easy_getinfo (curl, info, &raw);
+       if (code != CURLE_OK)
+               return -1;
+
+       v.gauge = (double)raw;
+
+       vl->values = &v;
+       vl->values_len = 1;
+
+       return plugin_dispatch_values (vl);
+} /* dispatch_size */
+
+static struct {
+       const char *name;
+       size_t offset;
+
+       int (*dispatcher)(CURL *, CURLINFO, value_list_t *);
+       const char *type;
+       CURLINFO info;
+} field_specs[] = {
+#define SPEC(name, dispatcher, type, info) \
+       { #name, offsetof (curl_stats_t, name), dispatcher, type, info }
+
+       SPEC (total_time,              dispatch_gauge, "duration", CURLINFO_TOTAL_TIME),
+       SPEC (namelookup_time,         dispatch_gauge, "duration", CURLINFO_NAMELOOKUP_TIME),
+       SPEC (connect_time,            dispatch_gauge, "duration", CURLINFO_CONNECT_TIME),
+       SPEC (pretransfer_time,        dispatch_gauge, "duration", CURLINFO_PRETRANSFER_TIME),
+       SPEC (size_upload,             dispatch_gauge, "bytes",    CURLINFO_SIZE_UPLOAD),
+       SPEC (size_download,           dispatch_gauge, "bytes",    CURLINFO_SIZE_DOWNLOAD),
+       SPEC (speed_download,          dispatch_speed, "bitrate",  CURLINFO_SPEED_DOWNLOAD),
+       SPEC (speed_upload,            dispatch_speed, "bitrate",  CURLINFO_SPEED_UPLOAD),
+       SPEC (header_size,             dispatch_size,  "bytes",    CURLINFO_HEADER_SIZE),
+       SPEC (request_size,            dispatch_size,  "bytes",    CURLINFO_REQUEST_SIZE),
+       SPEC (content_length_download, dispatch_gauge, "bytes",    CURLINFO_CONTENT_LENGTH_DOWNLOAD),
+       SPEC (content_length_upload,   dispatch_gauge, "bytes",    CURLINFO_CONTENT_LENGTH_UPLOAD),
+       SPEC (starttransfer_time,      dispatch_gauge, "duration", CURLINFO_STARTTRANSFER_TIME),
+       SPEC (redirect_time,           dispatch_gauge, "duration", CURLINFO_REDIRECT_TIME),
+       SPEC (redirect_count,          dispatch_size,  "count",    CURLINFO_REDIRECT_COUNT),
+       SPEC (num_connects,            dispatch_size,  "count",    CURLINFO_NUM_CONNECTS),
+       SPEC (appconnect_time,         dispatch_gauge, "duration", CURLINFO_APPCONNECT_TIME),
+
+#undef SPEC
+};
+
+static void enable_field (curl_stats_t *s, size_t offset)
+{
+       *(bool *)((char *)s + offset) = true;
+} /* enable_field */
+
+static bool field_enabled (curl_stats_t *s, size_t offset)
+{
+       return *(bool *)((char *)s + offset);
+} /* field_enabled */
+
+/*
+ * Public API
+ */
+curl_stats_t *curl_stats_from_config (oconfig_item_t *ci)
+{
+       curl_stats_t *s;
+       int i;
+
+       if (ci == NULL)
+               return NULL;
+
+       s = calloc (sizeof (*s), 1);
+       if (s == NULL)
+               return NULL;
+
+       for (i = 0; i < ci->children_num; ++i)
+       {
+               oconfig_item_t *c = ci->children + i;
+               size_t field;
+
+               for (field = 0; field < STATIC_ARRAY_SIZE (field_specs); ++field)
+                       if (! strcasecmp (c->key, field_specs[field].name))
+                               break;
+               if (field >= STATIC_ARRAY_SIZE (field_specs))
+               {
+                       ERROR ("curl stats: Unknown field name %s", c->key);
+                       free (s);
+                       return NULL;
+               }
+
+               if ((c->values_num != 1)
+                               || ((c->values[0].type != OCONFIG_TYPE_STRING)
+                                       && (c->values[0].type != OCONFIG_TYPE_BOOLEAN))) {
+                       ERROR ("curl stats: `%s' expects a single boolean argument", c->key);
+                       free (s);
+                       return NULL;
+               }
+
+               if (((c->values[0].type == OCONFIG_TYPE_STRING)
+                                       && IS_TRUE (c->values[0].value.string))
+                               || ((c->values[0].type == OCONFIG_TYPE_BOOLEAN)
+                                       && c->values[0].value.boolean))
+                       enable_field (s, field_specs[field].offset);
+       }
+
+       return s;
+} /* curl_stats_from_config */
+
+void curl_stats_destroy (curl_stats_t *s)
+{
+       if (s != NULL)
+               free (s);
+} /* curl_stats_destroy */
+
+int curl_stats_dispatch (curl_stats_t *s, CURL *curl,
+               const char *hostname, const char *plugin, const char *plugin_instance,
+               const char *instance_prefix)
+{
+       value_list_t vl = VALUE_LIST_INIT;
+       size_t field;
+
+       if (s == NULL)
+               return 0;
+       if (curl == NULL)
+               return -1;
+
+       if (hostname != NULL)
+               sstrncpy (vl.host, hostname, sizeof (vl.host));
+       if (plugin != NULL)
+               sstrncpy (vl.plugin, plugin, sizeof (vl.plugin));
+       if (plugin_instance != NULL)
+               sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
+
+       for (field = 0; field < STATIC_ARRAY_SIZE (field_specs); ++field)
+       {
+               int status;
+
+               if (! field_enabled (s, field_specs[field].offset))
+                       continue;
+
+               sstrncpy (vl.type, field_specs[field].type, sizeof (vl.type));
+               ssnprintf (vl.type_instance, sizeof (vl.type_instance), "%s%s",
+                               instance_prefix ? instance_prefix : "", field_specs[field].name);
+
+               vl.values = NULL;
+               vl.values_len = 0;
+               status = field_specs[field].dispatcher (curl, field_specs[field].info, &vl);
+               if (status < 0)
+                       return status;
+       }
+
+       return 0;
+} /* curl_stats_dispatch */