X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fcore%2Fstore.c;h=8cb30efaea88a250284cb569db2ae07b05d7fa12;hb=ef3e099a424694e6247252a40c7b3be343f3cebd;hp=7611ec4329e0ff4a03b55795610581edca06db58;hpb=dfa8850d084337223b41c03385317bf7359c8643;p=sysdb.git diff --git a/src/core/store.c b/src/core/store.c index 7611ec4..8cb30ef 100644 --- a/src/core/store.c +++ b/src/core/store.c @@ -174,6 +174,8 @@ sdb_metric_init(sdb_object_t *obj, va_list ap) sobj->attributes = sdb_avltree_create(); if (! sobj->attributes) return -1; + + sobj->store.type = sobj->store.id = NULL; return 0; } /* sdb_metric_init */ @@ -187,6 +189,11 @@ sdb_metric_destroy(sdb_object_t *obj) if (sobj->attributes) sdb_avltree_destroy(sobj->attributes); + + if (sobj->store.type) + free(sobj->store.type); + if (sobj->store.id) + free(sobj->store.id); } /* sdb_metric_destroy */ static int @@ -395,7 +402,8 @@ get_host_children(const char *hostname, int type) sdb_host_t *host; assert(hostname); - assert((type == SDB_SERVICE) || (type == SDB_ATTRIBUTE)); + assert((type == SDB_SERVICE) || (type == SDB_METRIC) + || (type == SDB_ATTRIBUTE)); if (! hosts) return NULL; @@ -433,6 +441,7 @@ store_common_tojson(sdb_store_obj_t *obj, sdb_strbuf_t *buf) char interval_str[64]; size_t i; + /* TODO: make time and interval formats configurable */ if (! sdb_strftime(time_str, sizeof(time_str), "%F %T %z", obj->last_update)) snprintf(time_str, sizeof(time_str), ""); @@ -456,7 +465,7 @@ store_common_tojson(sdb_store_obj_t *obj, sdb_strbuf_t *buf) } /* store_common_tojson */ /* - * store_obj_tojson serializes attribute / service objects to JSON. + * store_obj_tojson serializes attribute / metric / service objects to JSON. * * The function never returns an error. Rather, an error message will be part * of the serialized data. @@ -522,6 +531,60 @@ store_obj_tojson(sdb_avltree_t *tree, int type, sdb_strbuf_t *buf, sdb_strbuf_append(buf, "]"); } /* store_obj_tojson */ +/* + * ts_tojson serializes a time-series to JSON. + * + * The function never returns an error. Rather, an error message will be part + * of the serialized data. + */ +static void +ts_tojson(sdb_timeseries_t *ts, sdb_strbuf_t *buf) +{ + char start_str[64]; + char end_str[64]; + + size_t i; + + /* TODO: make time format configurable */ + if (! sdb_strftime(start_str, sizeof(start_str), + "%F %T %z", ts->start)) + snprintf(start_str, sizeof(start_str), ""); + start_str[sizeof(start_str) - 1] = '\0'; + if (! sdb_strftime(end_str, sizeof(end_str), + "%F %T %z", ts->end)) + snprintf(end_str, sizeof(end_str), ""); + end_str[sizeof(end_str) - 1] = '\0'; + + sdb_strbuf_append(buf, "{\"start\": \"%s\", \"end\": \"%s\", \"data\": {", + start_str, end_str); + + for (i = 0; i < ts->data_names_len; ++i) { + size_t j; + sdb_strbuf_append(buf, "\"%s\": [", ts->data_names[i]); + + for (j = 0; j < ts->data_len; ++j) { + char time_str[64]; + + if (! sdb_strftime(time_str, sizeof(time_str), + "%F %T %z", ts->data[i][j].timestamp)) + snprintf(time_str, sizeof(time_str), ""); + time_str[sizeof(time_str) - 1] = '\0'; + + sdb_strbuf_append(buf, "{\"timestamp\": \"%s\", " + "\"value\": \"%f\"}", time_str, ts->data[i][j].value); + + if (j < ts->data_len - 1) + sdb_strbuf_append(buf, ","); + } + + if (i < ts->data_names_len - 1) + sdb_strbuf_append(buf, "],"); + else + sdb_strbuf_append(buf, "]"); + } + sdb_strbuf_append(buf, "}}"); +} /* ts_tojson */ + /* * public API */ @@ -675,6 +738,159 @@ sdb_store_service_attr(const char *hostname, const char *service, return status; } /* sdb_store_service_attr */ +int +sdb_store_metric(const char *hostname, const char *name, + sdb_metric_store_t *store, sdb_time_t last_update) +{ + sdb_store_obj_t *obj = NULL; + sdb_metric_t *metric; + + sdb_avltree_t *metrics; + + int status = 0; + + if ((! hostname) || (! name)) + return -1; + if (store && ((! store->type) || (! store->id))) + return -1; + + pthread_rwlock_wrlock(&host_lock); + metrics = get_host_children(hostname, SDB_METRIC); + if (! metrics) { + sdb_log(SDB_LOG_ERR, "store: Failed to store metric '%s' - " + "host '%s' not found", name, hostname); + status = -1; + } + + if (! status) + status = store_obj(metrics, SDB_METRIC, name, last_update, &obj); + + if (status || (! store)) { + pthread_rwlock_unlock(&host_lock); + return status; + } + + assert(obj); + metric = METRIC(obj); + + if ((! metric->store.type) || strcasecmp(metric->store.type, store->type)) { + if (metric->store.type) + free(metric->store.type); + metric->store.type = strdup(store->type); + } + if ((! metric->store.id) || strcasecmp(metric->store.id, store->id)) { + if (metric->store.id) + free(metric->store.id); + metric->store.id = strdup(store->id); + } + + if ((! metric->store.type) || (! metric->store.id)) { + if (metric->store.type) + free(metric->store.type); + if (metric->store.id) + free(metric->store.id); + metric->store.type = metric->store.id = NULL; + status = -1; + } + pthread_rwlock_unlock(&host_lock); + return status; +} /* sdb_store_metric */ + +int +sdb_store_metric_attr(const char *hostname, const char *metric, + const char *key, const sdb_data_t *value, sdb_time_t last_update) +{ + sdb_avltree_t *metrics; + sdb_metric_t *m; + int status = 0; + + if ((! hostname) || (! metric) || (! key)) + return -1; + + pthread_rwlock_wrlock(&host_lock); + metrics = get_host_children(hostname, SDB_METRIC); + if (! metrics) { + sdb_log(SDB_LOG_ERR, "store: Failed to store attribute '%s' " + "for metric '%s' - host '%s' not found", + key, metric, hostname); + pthread_rwlock_unlock(&host_lock); + return -1; + } + + m = METRIC(sdb_avltree_lookup(metrics, metric)); + if (! m) { + sdb_log(SDB_LOG_ERR, "store: Failed to store attribute '%s' - " + "metric '%s/%s' not found", key, hostname, metric); + status = -1; + } + + if (! status) + status = store_attr(m->attributes, key, value, last_update); + + sdb_object_deref(SDB_OBJ(m)); + pthread_rwlock_unlock(&host_lock); + return status; +} /* sdb_store_metric_attr */ + +int +sdb_store_fetch_timeseries(const char *hostname, const char *metric, + sdb_timeseries_opts_t *opts, sdb_strbuf_t *buf) +{ + sdb_avltree_t *metrics; + sdb_metric_t *m; + + sdb_timeseries_t *ts; + + if ((! hostname) || (! metric) || (! opts) || (! buf)) + return -1; + + pthread_rwlock_rdlock(&host_lock); + metrics = get_host_children(hostname, SDB_METRIC); + if (! metrics) { + sdb_log(SDB_LOG_ERR, "store: Failed to fetch time-series '%s/%s' " + "- host '%s' not found", hostname, metric, hostname); + pthread_rwlock_unlock(&host_lock); + return -1; + } + + m = METRIC(sdb_avltree_lookup(metrics, metric)); + if (! m) { + sdb_log(SDB_LOG_ERR, "store: Failed to fetch time-series '%s/%s' " + "- metric '%s' not found", hostname, metric, metric); + pthread_rwlock_unlock(&host_lock); + return -1; + } + + if ((! m->store.type) || (! m->store.id)) { + sdb_log(SDB_LOG_ERR, "store: Failed to fetch time-series '%s/%s' " + "- no data-store configured for the stored metric", + hostname, metric); + pthread_rwlock_unlock(&host_lock); + return -1; + } + + { + char type[strlen(m->store.type) + 1]; + char id[strlen(m->store.id) + 1]; + + strncpy(type, m->store.type, sizeof(type)); + strncpy(id, m->store.id, sizeof(id)); + pthread_rwlock_unlock(&host_lock); + + ts = sdb_plugin_fetch_timeseries(type, id, opts); + if (! ts) { + sdb_log(SDB_LOG_ERR, "store: Failed to fetch time-series '%s/%s' " + "- %s fetcher callback returned no data for '%s'", + hostname, metric, type, id); + return -1; + } + } + + ts_tojson(ts, buf); + sdb_timeseries_destroy(ts); + return 0; +} /* sdb_store_fetch_timeseries */ + int sdb_store_get_field(sdb_store_obj_t *obj, int field, sdb_data_t *res) { @@ -712,6 +928,10 @@ sdb_store_host_tojson(sdb_store_obj_t *h, sdb_strbuf_t *buf, if ((! h) || (h->type != SDB_HOST) || (! buf)) return -1; + /* This function ignores SKIP_EMPTY flags given that the current + * implementation sucks and it's nut currently used when calling this + * function directly. */ + sdb_strbuf_append(buf, "{\"name\": \"%s\", ", SDB_OBJ(host)->name); store_common_tojson(h, buf); @@ -734,6 +954,26 @@ sdb_store_host_tojson(sdb_store_obj_t *h, sdb_strbuf_t *buf, return 0; } /* sdb_store_host_tojson */ +static _Bool +has_children(sdb_avltree_t *tree, sdb_store_matcher_t *filter) +{ + sdb_avltree_iter_t *iter; + + if (! filter) + return sdb_avltree_size(tree) > 0; + + iter = sdb_avltree_get_iter(tree); + while (sdb_avltree_iter_has_next(iter)) { + sdb_store_obj_t *sobj = STORE_OBJ(sdb_avltree_iter_get_next(iter)); + if (sdb_store_matcher_matches(filter, sobj, NULL)) { + sdb_avltree_iter_destroy(iter); + return 1; + } + } + sdb_avltree_iter_destroy(iter); + return 0; +} /* has_children */ + int sdb_store_tojson(sdb_strbuf_t *buf, sdb_store_matcher_t *filter, int flags) { @@ -751,7 +991,7 @@ sdb_store_tojson(sdb_strbuf_t *buf, sdb_store_matcher_t *filter, int flags) return -1; } - sdb_strbuf_append(buf, "{\"hosts\":["); + sdb_strbuf_append(buf, "["); len = sdb_strbuf_len(buf); while (sdb_avltree_iter_has_next(host_iter)) { @@ -763,6 +1003,19 @@ sdb_store_tojson(sdb_strbuf_t *buf, sdb_store_matcher_t *filter, int flags) if (filter && (! sdb_store_matcher_matches(filter, host, NULL))) continue; + /* + * XXX: This approach sucks but it's the best we can do at the moment. + * In the future, all store lookups should be split into multiple + * steps instead: first, retrieve all relevant objects and apply all + * pre-processing operations and then format it for the wire. + */ + if ((flags & SDB_SKIP_EMPTY_SERVICES) + && (! has_children(HOST(host)->services, filter))) + continue; + if ((flags & SDB_SKIP_EMPTY_METRICS) + && (! has_children(HOST(host)->metrics, filter))) + continue; + if (sdb_strbuf_len(buf) > len) sdb_strbuf_append(buf, ","); len = sdb_strbuf_len(buf); @@ -771,7 +1024,7 @@ sdb_store_tojson(sdb_strbuf_t *buf, sdb_store_matcher_t *filter, int flags) return -1; } - sdb_strbuf_append(buf, "]}"); + sdb_strbuf_append(buf, "]"); sdb_avltree_iter_destroy(host_iter); pthread_rwlock_unlock(&host_lock);