#endif
#define CJ_DEFAULT_HOST "localhost"
-#define CJ_KEY_MAGIC 0x43484b59UL /* CHKY */
-#define CJ_IS_KEY(key) ((key)->magic == CJ_KEY_MAGIC)
#define CJ_ANY "*"
#define COUCH_MIN(x, y) ((x) < (y) ? (x) : (y))
typedef struct cj_key_s cj_key_t;
struct cj_key_s /* {{{ */
{
- unsigned long magic;
char *path;
char *type;
char *instance;
};
/* }}} */
+/* cj_tree_entry_t is a union of either a metric configuration ("key") or a tree
+ * mapping array indexes / map keys to a descendant cj_tree_entry_t*. */
+typedef struct {
+ enum { KEY, TREE } type;
+ union {
+ c_avl_tree_t *tree;
+ cj_key_t *key;
+ };
+} cj_tree_entry_t;
+
+/* cj_state_t is a stack providing the configuration relevant for the context
+ * that is currently being parsed. If entry->type == KEY, the parser should
+ * expect a metric (a numeric value). If entry->type == TREE, the parser should
+ * expect an array of map to descent into. If entry == NULL, no configuration
+ * exists for this part of the JSON structure. */
+typedef struct {
+ cj_tree_entry_t *entry;
+ _Bool in_array;
+ int index;
+ char name[DATA_MAX_NAME_LEN];
+} cj_state_t;
+
struct cj_s /* {{{ */
{
char *instance;
yajl_handle yajl;
c_avl_tree_t *tree;
- cj_key_t *key;
int depth;
- struct {
- union {
- c_avl_tree_t *tree;
- cj_key_t *key;
- };
- _Bool in_array;
- int index;
- char name[DATA_MAX_NAME_LEN];
- } state[YAJL_MAX_DEPTH];
+ cj_state_t state[YAJL_MAX_DEPTH];
};
typedef struct cj_s cj_t; /* }}} */
#endif
static int cj_read(user_data_t *ud);
-static void cj_submit(cj_t *db, cj_key_t *key, value_t *value);
+static void cj_submit_impl(cj_t *db, cj_key_t *key, value_t *value);
+
+/* cj_submit is a function pointer to cj_submit_impl, allowing the unit-test to
+ * overwrite which function is called. */
+static void (*cj_submit)(cj_t *, cj_key_t *, value_t *) = cj_submit_impl;
static size_t cj_curl_callback(void *buf, /* {{{ */
size_t size, size_t nmemb, void *user_data) {
len = size * nmemb;
if (len == 0)
- return (len);
+ return len;
db = user_data;
if (db == NULL)
- return (0);
+ return 0;
status = yajl_parse(db->yajl, (unsigned char *)buf, len);
if (status == yajl_status_ok)
- return (len);
+ return len;
#if !HAVE_YAJL_V2
else if (status == yajl_status_insufficient_data)
- return (len);
+ return len;
#endif
unsigned char *msg =
/* jsonText = */ (unsigned char *)buf, (unsigned int)len);
ERROR("curl_json plugin: yajl_parse failed: %s", msg);
yajl_free_error(db->yajl, msg);
- return (0); /* abort write callback */
+ return 0; /* abort write callback */
} /* }}} size_t cj_curl_callback */
static int cj_get_type(cj_key_t *key) {
- const data_set_t *ds;
-
- if ((key == NULL) || !CJ_IS_KEY(key))
+ if (key == NULL)
return -EINVAL;
- ds = plugin_get_ds(key->type);
+ const data_set_t *ds = plugin_get_ds(key->type);
if (ds == NULL) {
static char type[DATA_MAX_NAME_LEN] = "!!!invalid!!!";
sstrncpy(db->state[db->depth].name, key, sizeof(db->state[db->depth].name));
- c_avl_tree_t *tree = db->state[db->depth - 1].tree;
- if (tree == NULL) {
+ if (db->state[db->depth - 1].entry == NULL ||
+ db->state[db->depth - 1].entry->type != TREE) {
return 0;
}
- /* the parent has a key, so the tree pointer is invalid. */
- if (CJ_IS_KEY(db->state[db->depth - 1].key)) {
- return 0;
- }
+ c_avl_tree_t *tree = db->state[db->depth - 1].entry->tree;
+ cj_tree_entry_t *e = NULL;
- void *value = NULL;
- if (c_avl_get(tree, key, (void *)&value) == 0) {
- if (CJ_IS_KEY((cj_key_t *)value)) {
- db->state[db->depth].key = value;
- } else {
- db->state[db->depth].tree = value;
- }
- } else if (c_avl_get(tree, CJ_ANY, (void *)&value) == 0) {
- if (CJ_IS_KEY((cj_key_t *)value)) {
- db->state[db->depth].key = value;
- } else {
- db->state[db->depth].tree = value;
- }
+ if (c_avl_get(tree, key, (void *)&e) == 0) {
+ db->state[db->depth].entry = e;
+ } else if (c_avl_get(tree, CJ_ANY, (void *)&e) == 0) {
+ db->state[db->depth].entry = e;
} else {
- db->state[db->depth].key = NULL;
+ db->state[db->depth].entry = NULL;
}
return 0;
db->state[db->depth].index++;
char name[DATA_MAX_NAME_LEN];
- ssnprintf(name, sizeof(name), "%d", db->state[db->depth].index);
+ snprintf(name, sizeof(name), "%d", db->state[db->depth].index);
cj_load_key(db, name);
}
static int cj_cb_boolean(void *ctx, int boolVal) {
cj_advance_array(ctx);
- return (CJ_CB_CONTINUE);
+ return CJ_CB_CONTINUE;
}
static int cj_cb_null(void *ctx) {
cj_advance_array(ctx);
- return (CJ_CB_CONTINUE);
+ return CJ_CB_CONTINUE;
}
static int cj_cb_number(void *ctx, const char *number, yajl_len_t number_len) {
- char buffer[number_len + 1];
-
cj_t *db = (cj_t *)ctx;
- cj_key_t *key = db->state[db->depth].key;
/* Create a null-terminated version of the string. */
+ char buffer[number_len + 1];
memcpy(buffer, number, number_len);
buffer[sizeof(buffer) - 1] = 0;
-
-
- if (key == NULL) {
- /* no config for this element. */
- cj_advance_array(ctx);
- return CJ_CB_CONTINUE;
- } else if (!CJ_IS_KEY(key)) {
- /* the config expects a map or an array. */
- NOTICE(
- "curl_json plugin: Found \"%s\", but the configuration expects a map.",
- buffer);
+ if (db->state[db->depth].entry == NULL ||
+ db->state[db->depth].entry->type != KEY) {
+ if (db->state[db->depth].entry != NULL) {
+ NOTICE("curl_json plugin: Found \"%s\", but the configuration expects a "
+ "map.",
+ buffer);
+ }
cj_advance_array(ctx);
return CJ_CB_CONTINUE;
}
+ cj_key_t *key = db->state[db->depth].entry->key;
+
int type = cj_get_type(key);
value_t vt;
int status = parse_value(buffer, &vt, type);
if (status != 0) {
NOTICE("curl_json plugin: Unable to parse number: \"%s\"", buffer);
cj_advance_array(ctx);
- return (CJ_CB_CONTINUE);
+ return CJ_CB_CONTINUE;
}
cj_submit(db, key, &vt);
cj_advance_array(ctx);
- return (CJ_CB_CONTINUE);
+ return CJ_CB_CONTINUE;
} /* int cj_cb_number */
/* Queries the key-tree of the parent context for "in_name" and, if found,
static int cj_cb_string(void *ctx, const unsigned char *val, yajl_len_t len) {
/* Handle the string as if it was a number. */
- return (cj_cb_number(ctx, (const char *)val, len));
+ return cj_cb_number(ctx, (const char *)val, len);
} /* int cj_cb_string */
static int cj_cb_end(void *ctx) {
cj_t *db = (cj_t *)ctx;
- db->state[db->depth].tree = NULL;
+ memset(&db->state[db->depth], 0, sizeof(db->state[db->depth]));
db->depth--;
cj_advance_array(ctx);
- return (CJ_CB_CONTINUE);
+ return CJ_CB_CONTINUE;
}
static int cj_cb_start_map(void *ctx) {
if ((db->depth + 1) >= YAJL_MAX_DEPTH) {
ERROR("curl_json plugin: %s depth exceeds max, aborting.",
db->url ? db->url : db->sock);
- return (CJ_CB_ABORT);
+ return CJ_CB_ABORT;
}
db->depth++;
- return (CJ_CB_CONTINUE);
+ return CJ_CB_CONTINUE;
}
static int cj_cb_end_map(void *ctx) { return cj_cb_end(ctx); }
static void cj_tree_free(c_avl_tree_t *tree) /* {{{ */
{
char *name;
- void *value;
+ cj_tree_entry_t *e;
- while (c_avl_pick(tree, (void *)&name, (void *)&value) == 0) {
- cj_key_t *key = (cj_key_t *)value;
+ while (c_avl_pick(tree, (void *)&name, (void *)&e) == 0) {
+ sfree(name);
- if (CJ_IS_KEY(key))
- cj_key_free(key);
+ if (e->type == KEY)
+ cj_key_free(e->key);
else
- cj_tree_free((c_avl_tree_t *)value);
-
- sfree(name);
+ cj_tree_free(e->tree);
+ sfree(e);
}
c_avl_destroy(tree);
struct curl_slist *temp = NULL;
if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
WARNING("curl_json plugin: `%s' needs exactly one string argument.", name);
- return (-1);
+ return -1;
}
temp = curl_slist_append(*dest, ci->values[0].value.string);
if (temp == NULL)
- return (-1);
+ return -1;
*dest = temp;
- return (0);
+ return 0;
} /* }}} int cj_config_append_string */
+/* cj_append_key adds key to the configuration stored in db.
+ *
+ * For example:
+ * "httpd/requests/count",
+ * "httpd/requests/current" ->
+ * { "httpd": { "requests": { "count": $key, "current": $key } } }
+ */
+static int cj_append_key(cj_t *db, cj_key_t *key) { /* {{{ */
+ if (db->tree == NULL)
+ db->tree = cj_avl_create();
+
+ c_avl_tree_t *tree = db->tree;
+
+ char const *start = key->path;
+ if (*start == '/')
+ ++start;
+
+ char const *end;
+ while ((end = strchr(start, '/')) != NULL) {
+ char name[PATH_MAX];
+
+ size_t len = end - start;
+ if (len == 0)
+ break;
+
+ len = COUCH_MIN(len, sizeof(name) - 1);
+ sstrncpy(name, start, len + 1);
+
+ cj_tree_entry_t *e;
+ if (c_avl_get(tree, name, (void *)&e) != 0) {
+ e = calloc(1, sizeof(*e));
+ if (e == NULL)
+ return ENOMEM;
+ e->type = TREE;
+ e->tree = cj_avl_create();
+
+ c_avl_insert(tree, strdup(name), e);
+ }
+
+ if (e->type != TREE)
+ return EINVAL;
+
+ tree = e->tree;
+ start = end + 1;
+ }
+
+ if (strlen(start) == 0) {
+ ERROR("curl_json plugin: invalid key: %s", key->path);
+ return -1;
+ }
+
+ cj_tree_entry_t *e = calloc(1, sizeof(*e));
+ if (e == NULL)
+ return ENOMEM;
+ e->type = KEY;
+ e->key = key;
+
+ c_avl_insert(tree, strdup(start), e);
+ return 0;
+} /* }}} int cj_append_key */
+
static int cj_config_add_key(cj_t *db, /* {{{ */
oconfig_item_t *ci) {
cj_key_t *key;
if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
WARNING("curl_json plugin: The `Key' block "
"needs exactly one string argument.");
- return (-1);
+ return -1;
}
key = calloc(1, sizeof(*key));
if (key == NULL) {
ERROR("curl_json plugin: calloc failed.");
- return (-1);
+ return -1;
}
- key->magic = CJ_KEY_MAGIC;
if (strcasecmp("Key", ci->key) == 0) {
status = cf_util_get_string(ci, &key->path);
if (status != 0) {
sfree(key);
- return (status);
+ return status;
}
} else {
ERROR("curl_json plugin: cj_config: "
"Invalid key: %s",
ci->key);
cj_key_free(key);
- return (-1);
+ return -1;
}
status = 0;
if (status != 0) {
cj_key_free(key);
- return (-1);
+ return -1;
}
if (key->type == NULL) {
WARNING("curl_json plugin: `Type' missing in `Key' block.");
cj_key_free(key);
- return (-1);
- }
-
- /* store path in a tree that will match the json map structure, example:
- * "httpd/requests/count",
- * "httpd/requests/current" ->
- * { "httpd": { "requests": { "count": $key, "current": $key } } }
- */
- char *ptr;
- char *name;
- c_avl_tree_t *tree;
-
- if (db->tree == NULL)
- db->tree = cj_avl_create();
-
- tree = db->tree;
- ptr = key->path;
- if (*ptr == '/')
- ++ptr;
-
- name = ptr;
- while ((ptr = strchr(name, '/')) != NULL) {
- char ent[PATH_MAX];
- c_avl_tree_t *value;
- size_t len;
-
- len = ptr - name;
- if (len == 0)
- break;
-
- len = COUCH_MIN(len, sizeof(ent) - 1);
- sstrncpy(ent, name, len + 1);
-
- if (c_avl_get(tree, ent, (void *)&value) != 0) {
- value = cj_avl_create();
- c_avl_insert(tree, strdup(ent), value);
- }
-
- tree = value;
- name = ptr + 1;
+ return -1;
}
- if (strlen(name) == 0) {
- ERROR("curl_json plugin: invalid key: %s", key->path);
+ status = cj_append_key(db, key);
+ if (status != 0) {
cj_key_free(key);
- return (-1);
+ return -1;
}
- c_avl_insert(tree, strdup(name), key);
- return (status);
+ return 0;
} /* }}} int cj_config_add_key */
static int cj_init_curl(cj_t *db) /* {{{ */
db->curl = curl_easy_init();
if (db->curl == NULL) {
ERROR("curl_json plugin: curl_easy_init failed.");
- return (-1);
+ return -1;
}
curl_easy_setopt(db->curl, CURLOPT_NOSIGNAL, 1L);
db->credentials = malloc(credentials_size);
if (db->credentials == NULL) {
ERROR("curl_json plugin: malloc failed.");
- return (-1);
+ return -1;
}
- ssnprintf(db->credentials, credentials_size, "%s:%s", db->user,
- (db->pass == NULL) ? "" : db->pass);
+ snprintf(db->credentials, credentials_size, "%s:%s", db->user,
+ (db->pass == NULL) ? "" : db->pass);
curl_easy_setopt(db->curl, CURLOPT_USERPWD, db->credentials);
#endif
(long)CDTIME_T_TO_MS(plugin_get_interval()));
#endif
- return (0);
+ return 0;
} /* }}} int cj_init_curl */
static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
WARNING("curl_json plugin: The `URL' block "
"needs exactly one string argument.");
- return (-1);
+ return -1;
}
db = calloc(1, sizeof(*db));
if (db == NULL) {
ERROR("curl_json plugin: calloc failed.");
- return (-1);
+ return -1;
}
db->timeout = -1;
"Invalid key: %s",
ci->key);
cj_free(db);
- return (-1);
+ return -1;
}
if (status != 0) {
sfree(db);
- return (status);
+ return status;
}
/* Fill the `cj_t' structure.. */
cb_name = ssnprintf_alloc("curl_json-%s-%s", db->instance,
db->url ? db->url : db->sock);
- user_data_t ud = {.data = db, .free_func = cj_free};
-
plugin_register_complex_read(/* group = */ NULL, cb_name, cj_read,
- /* interval = */ db->interval, &ud);
+ /* interval = */ db->interval,
+ &(user_data_t){
+ .data = db, .free_func = cj_free,
+ });
sfree(cb_name);
} else {
cj_free(db);
- return (-1);
+ return -1;
}
- return (0);
+ return 0;
}
/* }}} int cj_config_add_database */
if ((success == 0) && (errors > 0)) {
ERROR("curl_json plugin: All statements failed.");
- return (-1);
+ return -1;
}
- return (0);
+ return 0;
} /* }}} int cj_config */
/* }}} End of configuration handling functions */
return db->host;
} /* }}} cj_host */
-static void cj_submit(cj_t *db, cj_key_t *key, value_t *value) /* {{{ */
+static void cj_submit_impl(cj_t *db, cj_key_t *key, value_t *value) /* {{{ */
{
value_list_t vl = VALUE_LIST_INIT;
if (key->instance == NULL) {
int len = 0;
for (int i = 0; i < db->depth; i++)
- len += ssnprintf(vl.type_instance + len, sizeof(vl.type_instance) - len,
- i ? "-%s" : "%s", db->state[i + 1].name);
+ len += snprintf(vl.type_instance + len, sizeof(vl.type_instance) - len,
+ i ? "-%s" : "%s", db->state[i + 1].name);
} else
sstrncpy(vl.type_instance, key->instance, sizeof(vl.type_instance));
vl.interval = db->interval;
plugin_dispatch_values(&vl);
-} /* }}} int cj_submit */
+} /* }}} int cj_submit_impl */
static int cj_sock_perform(cj_t *db) /* {{{ */
{
char errbuf[1024];
- struct sockaddr_un sa_unix = {0};
- sa_unix.sun_family = AF_UNIX;
+ struct sockaddr_un sa_unix = {
+ .sun_family = AF_UNIX,
+ };
sstrncpy(sa_unix.sun_path, db->sock, sizeof(sa_unix.sun_path));
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
- return (-1);
+ return -1;
if (connect(fd, (struct sockaddr *)&sa_unix, sizeof(sa_unix)) < 0) {
ERROR("curl_json plugin: connect(%s) failed: %s",
(db->sock != NULL) ? db->sock : "<null>",
sstrerror(errno, errbuf, sizeof(errbuf)));
close(fd);
- return (-1);
+ return -1;
}
ssize_t red;
(db->sock != NULL) ? db->sock : "<null>",
sstrerror(errno, errbuf, sizeof(errbuf)));
close(fd);
- return (-1);
+ return -1;
}
if (!cj_curl_callback(buffer, red, 1, db))
break;
} while (red > 0);
close(fd);
- return (0);
+ return 0;
} /* }}} int cj_sock_perform */
static int cj_curl_perform(cj_t *db) /* {{{ */
if (status != CURLE_OK) {
ERROR("curl_json plugin: curl_easy_perform failed with status %i: %s (%s)",
status, db->curl_errbuf, db->url);
- return (-1);
+ return -1;
}
if (db->stats != NULL)
curl_stats_dispatch(db->stats, db->curl, cj_host(db), "curl_json",
ERROR("curl_json plugin: curl_easy_perform failed with "
"response code %ld (%s)",
rc, url);
- return (-1);
+ return -1;
}
- return (0);
+ return 0;
} /* }}} int cj_curl_perform */
static int cj_perform(cj_t *db) /* {{{ */
if (db->yajl == NULL) {
ERROR("curl_json plugin: yajl_alloc failed.");
db->yajl = yprev;
- return (-1);
+ return -1;
}
if (db->url)
if (status < 0) {
yajl_free(db->yajl);
db->yajl = yprev;
- return (-1);
+ return -1;
}
#if HAVE_YAJL_V2
yajl_free_error(db->yajl, errmsg);
yajl_free(db->yajl);
db->yajl = yprev;
- return (-1);
+ return -1;
}
yajl_free(db->yajl);
db->yajl = yprev;
- return (0);
+ return 0;
} /* }}} int cj_perform */
static int cj_read(user_data_t *ud) /* {{{ */
if ((ud == NULL) || (ud->data == NULL)) {
ERROR("curl_json plugin: cj_read: Invalid user data.");
- return (-1);
+ return -1;
}
db = (cj_t *)ud->data;
db->depth = 0;
memset(&db->state, 0, sizeof(db->state));
- db->state[db->depth].tree = db->tree;
- db->key = NULL;
- return cj_perform(db);
+ /* This is not a compound literal because EPEL6's GCC is not cool enough to
+ * handle anonymous unions within compound literals. */
+ cj_tree_entry_t root = {0};
+ root.type = TREE;
+ root.tree = db->tree;
+ db->state[0].entry = &root;
+
+ int status = cj_perform(db);
+
+ db->state[0].entry = NULL;
+
+ return status;
} /* }}} int cj_read */
static int cj_init(void) /* {{{ */
/* Call this while collectd is still single-threaded to avoid
* initialization issues in libgcrypt. */
curl_global_init(CURL_GLOBAL_SSL);
- return (0);
+ return 0;
} /* }}} int cj_init */
void module_register(void) {
plugin_register_complex_config("curl_json", cj_config);
plugin_register_init("curl_json", cj_init);
} /* void module_register */
-
-/* vim: set sw=2 sts=2 et fdm=marker : */