1 /*
2 * SysDB - src/core/store.c
3 * Copyright (C) 2012-2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
28 #if HAVE_CONFIG_H
29 # include "config.h"
30 #endif /* HAVE_CONFIG_H */
32 #include "sysdb.h"
33 #include "core/store-private.h"
34 #include "core/plugin.h"
35 #include "utils/avltree.h"
36 #include "utils/error.h"
38 #include <assert.h>
40 #include <errno.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
46 #include <math.h>
47 #include <pthread.h>
49 /*
50 * private variables
51 */
53 typedef struct {
54 sdb_object_t super;
56 /* hosts are the top-level entries and
57 * reference everything else */
58 sdb_avltree_t *hosts;
59 pthread_rwlock_t host_lock;
60 } sdb_store_t;
61 #define ST(obj) ((sdb_store_t *)(obj))
63 sdb_store_t *global_store = NULL;
65 /*
66 * private types
67 */
69 static sdb_type_t host_type;
70 static sdb_type_t service_type;
71 static sdb_type_t metric_type;
72 static sdb_type_t attribute_type;
74 static int
75 store_init(sdb_object_t *obj, va_list __attribute__((unused)) ap)
76 {
77 int err;
78 if (! (ST(obj)->hosts = sdb_avltree_create()))
79 return -1;
80 if ((err = pthread_rwlock_init(&ST(obj)->host_lock, /* attr = */ NULL))) {
81 char errbuf[128];
82 sdb_log(SDB_LOG_ERR, "store: Failed to initialize lock: %s",
83 sdb_strerror(err, errbuf, sizeof(errbuf)));
84 return -1;
85 }
86 return 0;
87 } /* store_init */
89 static void
90 store_destroy(sdb_object_t *obj)
91 {
92 int err;
93 if ((err = pthread_rwlock_destroy(&ST(obj)->host_lock))) {
94 char errbuf[128];
95 sdb_log(SDB_LOG_ERR, "store: Failed to destroy lock: %s",
96 sdb_strerror(err, errbuf, sizeof(errbuf)));
97 return;
98 }
99 sdb_avltree_destroy(ST(obj)->hosts);
100 ST(obj)->hosts = NULL;
101 } /* store_destroy */
103 static int
104 store_obj_init(sdb_object_t *obj, va_list ap)
105 {
106 sdb_store_obj_t *sobj = STORE_OBJ(obj);
108 sobj->type = va_arg(ap, int);
110 sobj->last_update = va_arg(ap, sdb_time_t);
111 sobj->interval = 0;
112 sobj->backends = NULL;
113 sobj->backends_num = 0;
114 sobj->parent = NULL;
115 return 0;
116 } /* store_obj_init */
118 static void
119 store_obj_destroy(sdb_object_t *obj)
120 {
121 sdb_store_obj_t *sobj = STORE_OBJ(obj);
122 size_t i;
124 for (i = 0; i < sobj->backends_num; ++i)
125 free(sobj->backends[i]);
126 free(sobj->backends);
127 sobj->backends = NULL;
128 sobj->backends_num = 0;
130 // We don't currently keep an extra reference for parent objects to
131 // avoid circular self-references which are not handled correctly by
132 // the ref-count base management layer.
133 //sdb_object_deref(SDB_OBJ(sobj->parent));
134 } /* store_obj_destroy */
136 static int
137 host_init(sdb_object_t *obj, va_list ap)
138 {
139 sdb_host_t *sobj = HOST(obj);
140 int ret;
142 /* this will consume the first argument (type) of ap */
143 ret = store_obj_init(obj, ap);
144 if (ret)
145 return ret;
147 sobj->services = sdb_avltree_create();
148 if (! sobj->services)
149 return -1;
150 sobj->metrics = sdb_avltree_create();
151 if (! sobj->metrics)
152 return -1;
153 sobj->attributes = sdb_avltree_create();
154 if (! sobj->attributes)
155 return -1;
156 return 0;
157 } /* host_init */
159 static void
160 host_destroy(sdb_object_t *obj)
161 {
162 sdb_host_t *sobj = HOST(obj);
163 assert(obj);
165 store_obj_destroy(obj);
167 if (sobj->services)
168 sdb_avltree_destroy(sobj->services);
169 if (sobj->metrics)
170 sdb_avltree_destroy(sobj->metrics);
171 if (sobj->attributes)
172 sdb_avltree_destroy(sobj->attributes);
173 } /* host_destroy */
175 static int
176 service_init(sdb_object_t *obj, va_list ap)
177 {
178 sdb_service_t *sobj = SVC(obj);
179 int ret;
181 /* this will consume the first argument (type) of ap */
182 ret = store_obj_init(obj, ap);
183 if (ret)
184 return ret;
186 sobj->attributes = sdb_avltree_create();
187 if (! sobj->attributes)
188 return -1;
189 return 0;
190 } /* service_init */
192 static void
193 service_destroy(sdb_object_t *obj)
194 {
195 sdb_service_t *sobj = SVC(obj);
196 assert(obj);
198 store_obj_destroy(obj);
200 if (sobj->attributes)
201 sdb_avltree_destroy(sobj->attributes);
202 } /* service_destroy */
204 static int
205 metric_init(sdb_object_t *obj, va_list ap)
206 {
207 sdb_metric_t *sobj = METRIC(obj);
208 int ret;
210 /* this will consume the first argument (type) of ap */
211 ret = store_obj_init(obj, ap);
212 if (ret)
213 return ret;
215 sobj->attributes = sdb_avltree_create();
216 if (! sobj->attributes)
217 return -1;
219 sobj->store.type = sobj->store.id = NULL;
220 return 0;
221 } /* metric_init */
223 static void
224 metric_destroy(sdb_object_t *obj)
225 {
226 sdb_metric_t *sobj = METRIC(obj);
227 assert(obj);
229 store_obj_destroy(obj);
231 if (sobj->attributes)
232 sdb_avltree_destroy(sobj->attributes);
234 if (sobj->store.type)
235 free(sobj->store.type);
236 if (sobj->store.id)
237 free(sobj->store.id);
238 } /* metric_destroy */
240 static int
241 attr_init(sdb_object_t *obj, va_list ap)
242 {
243 const sdb_data_t *value;
244 int ret;
246 /* this will consume the first two arguments
247 * (type and last_update) of ap */
248 ret = store_obj_init(obj, ap);
249 if (ret)
250 return ret;
251 value = va_arg(ap, const sdb_data_t *);
253 if (value)
254 if (sdb_data_copy(&ATTR(obj)->value, value))
255 return -1;
256 return 0;
257 } /* attr_init */
259 static void
260 attr_destroy(sdb_object_t *obj)
261 {
262 assert(obj);
264 store_obj_destroy(obj);
265 sdb_data_free_datum(&ATTR(obj)->value);
266 } /* attr_destroy */
268 static sdb_type_t store_type = {
269 /* size = */ sizeof(sdb_store_t),
270 /* init = */ store_init,
271 /* destroy = */ store_destroy,
272 };
274 static sdb_type_t host_type = {
275 /* size = */ sizeof(sdb_host_t),
276 /* init = */ host_init,
277 /* destroy = */ host_destroy
278 };
280 static sdb_type_t service_type = {
281 /* size = */ sizeof(sdb_service_t),
282 /* init = */ service_init,
283 /* destroy = */ service_destroy
284 };
286 static sdb_type_t metric_type = {
287 /* size = */ sizeof(sdb_metric_t),
288 /* init = */ metric_init,
289 /* destroy = */ metric_destroy
290 };
292 static sdb_type_t attribute_type = {
293 /* size = */ sizeof(sdb_attribute_t),
294 /* init = */ attr_init,
295 /* destroy = */ attr_destroy
296 };
298 /*
299 * private helper functions
300 */
302 static sdb_host_t *
303 lookup_host(sdb_store_t *st, const char *name, bool canonicalize)
304 {
305 sdb_host_t *host;
306 char *cname;
308 assert(name);
309 if (! canonicalize)
310 return HOST(sdb_avltree_lookup(st->hosts, name));
312 cname = strdup(name);
313 cname = sdb_plugin_cname(cname);
314 if (! cname) {
315 sdb_log(SDB_LOG_ERR, "store: strdup failed");
316 return NULL;
317 }
319 host = HOST(sdb_avltree_lookup(st->hosts, cname));
320 free(cname);
321 return host;
322 } /* lookup_host */
324 static int
325 record_backend(sdb_store_obj_t *obj)
326 {
327 const sdb_plugin_info_t *info;
328 char **tmp;
329 size_t i;
331 info = sdb_plugin_current();
332 if (! info)
333 return 0;
335 for (i = 0; i < obj->backends_num; ++i)
336 if (!strcasecmp(obj->backends[i], info->plugin_name))
337 return 0;
339 tmp = realloc(obj->backends,
340 (obj->backends_num + 1) * sizeof(*obj->backends));
341 if (! tmp)
342 return -1;
344 obj->backends = tmp;
345 obj->backends[obj->backends_num] = strdup(info->plugin_name);
346 if (! obj->backends[obj->backends_num])
347 return -1;
349 ++obj->backends_num;
350 return 0;
351 } /* record_backend */
353 static int
354 store_obj(sdb_store_obj_t *parent, sdb_avltree_t *parent_tree,
355 int type, const char *name, sdb_time_t last_update,
356 sdb_store_obj_t **updated_obj)
357 {
358 sdb_store_obj_t *old, *new;
359 int status = 0;
361 assert(parent_tree);
363 if (last_update <= 0)
364 last_update = sdb_gettime();
366 old = STORE_OBJ(sdb_avltree_lookup(parent_tree, name));
367 if (old) {
368 if (old->last_update > last_update) {
369 sdb_log(SDB_LOG_DEBUG, "store: Cannot update %s '%s' - "
370 "value too old (%"PRIsdbTIME" < %"PRIsdbTIME")",
371 SDB_STORE_TYPE_TO_NAME(type), name,
372 last_update, old->last_update);
373 /* don't report an error; the object may be updated by multiple
374 * backends */
375 status = 1;
376 }
377 else if (old->last_update == last_update) {
378 /* don't report an error and also don't even log this to avoid
379 * excessive noise on high sampling frequencies */
380 status = 1;
381 }
382 else {
383 sdb_time_t interval = last_update - old->last_update;
384 old->last_update = last_update;
385 if (interval) {
386 if (old->interval)
387 old->interval = (sdb_time_t)((0.9 * (double)old->interval)
388 + (0.1 * (double)interval));
389 else
390 old->interval = interval;
391 }
392 }
394 new = old;
395 sdb_object_deref(SDB_OBJ(old));
396 }
397 else {
398 if (type == SDB_ATTRIBUTE) {
399 /* the value will be updated by the caller */
400 new = STORE_OBJ(sdb_object_create(name, attribute_type,
401 type, last_update, NULL));
402 }
403 else {
404 sdb_type_t t;
405 t = type == SDB_HOST
406 ? host_type
407 : type == SDB_SERVICE
408 ? service_type
409 : metric_type;
410 new = STORE_OBJ(sdb_object_create(name, t, type, last_update));
411 }
413 if (new) {
414 status = sdb_avltree_insert(parent_tree, SDB_OBJ(new));
416 /* pass control to the tree or destroy in case of an error */
417 sdb_object_deref(SDB_OBJ(new));
418 }
419 else {
420 char errbuf[1024];
421 sdb_log(SDB_LOG_ERR, "store: Failed to create %s '%s': %s",
422 SDB_STORE_TYPE_TO_NAME(type), name,
423 sdb_strerror(errno, errbuf, sizeof(errbuf)));
424 status = -1;
425 }
426 }
428 if (status < 0)
429 return status;
430 assert(new);
432 if (new->parent != parent) {
433 // Avoid circular self-references which are not handled
434 // correctly by the ref-count based management layer.
435 //sdb_object_deref(SDB_OBJ(new->parent));
436 //sdb_object_ref(SDB_OBJ(parent));
437 new->parent = parent;
438 }
440 if (updated_obj)
441 *updated_obj = new;
443 if (record_backend(new))
444 return -1;
445 return status;
446 } /* store_obj */
448 static int
449 store_attr(sdb_store_obj_t *parent, sdb_avltree_t *attributes,
450 const char *key, const sdb_data_t *value, sdb_time_t last_update)
451 {
452 sdb_store_obj_t *attr = NULL;
453 int status;
455 status = store_obj(parent, attributes, SDB_ATTRIBUTE,
456 key, last_update, &attr);
457 if (status)
458 return status;
460 /* don't update unchanged values */
461 if (! sdb_data_cmp(&ATTR(attr)->value, value))
462 return status;
464 assert(attr);
465 if (sdb_data_copy(&ATTR(attr)->value, value))
466 return -1;
467 return status;
468 } /* store_attr */
470 static int
471 store_metric_store(sdb_metric_t *metric, sdb_metric_store_t *store)
472 {
473 char *type = metric->store.type;
474 char *id = metric->store.id;
476 if ((! metric->store.type) || strcasecmp(metric->store.type, store->type)) {
477 if (! (type = strdup(store->type)))
478 return -1;
479 }
480 if ((! metric->store.id) || strcasecmp(metric->store.id, store->id)) {
481 if (! (id = strdup(store->id))) {
482 if (type != metric->store.type)
483 free(type);
484 return -1;
485 }
486 }
488 if (type != metric->store.type) {
489 if (metric->store.type)
490 free(metric->store.type);
491 metric->store.type = type;
492 }
493 if (id != metric->store.id) {
494 if (metric->store.id)
495 free(metric->store.id);
496 metric->store.id = id;
497 }
498 return 0;
499 } /* store_metric_store */
501 /* The store's host_lock has to be acquired before calling this function. */
502 static sdb_avltree_t *
503 get_host_children(sdb_host_t *host, int type)
504 {
505 if ((type != SDB_SERVICE) && (type != SDB_METRIC)
506 && (type != SDB_ATTRIBUTE))
507 return NULL;
509 if (! host)
510 return NULL;
512 if (type == SDB_ATTRIBUTE)
513 return host->attributes;
514 else if (type == SDB_METRIC)
515 return host->metrics;
516 else
517 return host->services;
518 } /* get_host_children */
520 /*
521 * ts_tojson serializes a time-series to JSON.
522 *
523 * The function never returns an error. Rather, an error message will be part
524 * of the serialized data.
525 */
526 static void
527 ts_tojson(sdb_timeseries_t *ts, sdb_strbuf_t *buf)
528 {
529 char start_str[64];
530 char end_str[64];
532 size_t i;
534 /* TODO: make time format configurable */
535 if (! sdb_strftime(start_str, sizeof(start_str), ts->start))
536 snprintf(start_str, sizeof(start_str), "<error>");
537 start_str[sizeof(start_str) - 1] = '\0';
538 if (! sdb_strftime(end_str, sizeof(end_str), ts->end))
539 snprintf(end_str, sizeof(end_str), "<error>");
540 end_str[sizeof(end_str) - 1] = '\0';
542 sdb_strbuf_append(buf, "{\"start\": \"%s\", \"end\": \"%s\", \"data\": {",
543 start_str, end_str);
545 for (i = 0; i < ts->data_names_len; ++i) {
546 size_t j;
547 sdb_strbuf_append(buf, "\"%s\": [", ts->data_names[i]);
549 for (j = 0; j < ts->data_len; ++j) {
550 char time_str[64];
552 if (! sdb_strftime(time_str, sizeof(time_str), ts->data[i][j].timestamp))
553 snprintf(time_str, sizeof(time_str), "<error>");
554 time_str[sizeof(time_str) - 1] = '\0';
556 /* Some GNU libc versions may print '-nan' which we dont' want */
557 if (isnan(ts->data[i][j].value))
558 sdb_strbuf_append(buf, "{\"timestamp\": \"%s\", "
559 "\"value\": \"nan\"}", time_str);
560 else
561 sdb_strbuf_append(buf, "{\"timestamp\": \"%s\", "
562 "\"value\": \"%f\"}", time_str, ts->data[i][j].value);
564 if (j < ts->data_len - 1)
565 sdb_strbuf_append(buf, ",");
566 }
568 if (i < ts->data_names_len - 1)
569 sdb_strbuf_append(buf, "],");
570 else
571 sdb_strbuf_append(buf, "]");
572 }
573 sdb_strbuf_append(buf, "}}");
574 } /* ts_tojson */
576 /*
577 * store writer API
578 */
580 static int
581 store_attribute(const char *hostname,
582 const char *key, const sdb_data_t *value,
583 sdb_time_t last_update, sdb_object_t *user_data)
584 {
585 sdb_store_t *st = ST(user_data);
587 sdb_host_t *host;
588 sdb_avltree_t *attrs;
589 int status = 0;
591 if ((! hostname) || (! key))
592 return -1;
594 pthread_rwlock_wrlock(&st->host_lock);
595 host = lookup_host(st, hostname, /* canonicalize = */ 1);
596 attrs = get_host_children(host, SDB_ATTRIBUTE);
597 if (! attrs) {
598 sdb_log(SDB_LOG_ERR, "store: Failed to store attribute '%s' - "
599 "host '%s' not found", key, hostname);
600 status = -1;
601 }
603 if (! status)
604 status = store_attr(STORE_OBJ(host), attrs, key, value, last_update);
606 sdb_object_deref(SDB_OBJ(host));
607 pthread_rwlock_unlock(&st->host_lock);
609 return status;
610 } /* store_attribute */
612 static int
613 store_host(const char *name, sdb_time_t last_update, sdb_object_t *user_data)
614 {
615 sdb_store_t *st = ST(user_data);
617 char *cname = NULL;
618 int status = 0;
620 if (! name)
621 return -1;
623 cname = sdb_plugin_cname(strdup(name));
624 if (! cname) {
625 sdb_log(SDB_LOG_ERR, "store: strdup failed");
626 return -1;
627 }
629 pthread_rwlock_wrlock(&st->host_lock);
630 status = store_obj(NULL, st->hosts,
631 SDB_HOST, cname, last_update, NULL);
632 pthread_rwlock_unlock(&st->host_lock);
634 free(cname);
635 return status;
636 } /* store_host */
638 static int
639 store_service_attr(const char *hostname, const char *service,
640 const char *key, const sdb_data_t *value, sdb_time_t last_update,
641 sdb_object_t *user_data)
642 {
643 sdb_store_t *st = ST(user_data);
645 sdb_host_t *host;
646 sdb_service_t *svc;
647 sdb_avltree_t *services;
648 int status = 0;
650 if ((! hostname) || (! service) || (! key))
651 return -1;
653 pthread_rwlock_wrlock(&st->host_lock);
654 host = lookup_host(st, hostname, /* canonicalize = */ 1);
655 services = get_host_children(host, SDB_SERVICE);
656 sdb_object_deref(SDB_OBJ(host));
657 if (! services) {
658 sdb_log(SDB_LOG_ERR, "store: Failed to store attribute '%s' "
659 "for service '%s' - host '%ss' not found",
660 key, service, hostname);
661 pthread_rwlock_unlock(&st->host_lock);
662 return -1;
663 }
665 svc = SVC(sdb_avltree_lookup(services, service));
666 if (! svc) {
667 sdb_log(SDB_LOG_ERR, "store: Failed to store attribute '%s' - "
668 "service '%s/%s' not found", key, hostname, service);
669 status = -1;
670 }
672 if (! status)
673 status = store_attr(STORE_OBJ(svc), svc->attributes,
674 key, value, last_update);
676 sdb_object_deref(SDB_OBJ(svc));
677 pthread_rwlock_unlock(&st->host_lock);
679 return status;
680 } /* store_service_attr */
682 static int
683 store_service(const char *hostname, const char *name,
684 sdb_time_t last_update, sdb_object_t *user_data)
685 {
686 sdb_store_t *st = ST(user_data);
688 sdb_host_t *host;
689 sdb_avltree_t *services;
690 sdb_data_t d;
692 int status = 0;
694 if ((! hostname) || (! name))
695 return -1;
697 pthread_rwlock_wrlock(&st->host_lock);
698 host = lookup_host(st, hostname, /* canonicalize = */ 1);
699 services = get_host_children(host, SDB_SERVICE);
700 if (! services) {
701 sdb_log(SDB_LOG_ERR, "store: Failed to store service '%s' - "
702 "host '%s' not found", name, hostname);
703 status = -1;
704 }
706 if (! status)
707 status = store_obj(STORE_OBJ(host), services, SDB_SERVICE,
708 name, last_update, NULL);
710 sdb_object_deref(SDB_OBJ(host));
711 pthread_rwlock_unlock(&st->host_lock);
713 if (status)
714 return status;
716 /* record the hostname as an attribute */
717 d.type = SDB_TYPE_STRING;
718 d.data.string = SDB_OBJ(host)->name;
719 if (store_service_attr(hostname, name, "hostname", &d, last_update, user_data))
720 status = -1;
721 return status;
722 } /* store_service */
724 static int
725 store_metric_attr(const char *hostname, const char *metric,
726 const char *key, const sdb_data_t *value, sdb_time_t last_update,
727 sdb_object_t *user_data)
728 {
729 sdb_store_t *st = ST(user_data);
731 sdb_avltree_t *metrics;
732 sdb_host_t *host;
733 sdb_metric_t *m;
734 int status = 0;
736 if ((! hostname) || (! metric) || (! key))
737 return -1;
739 pthread_rwlock_wrlock(&st->host_lock);
740 host = lookup_host(st, hostname, /* canonicalize = */ 1);
741 metrics = get_host_children(host, SDB_METRIC);
742 sdb_object_deref(SDB_OBJ(host));
743 if (! metrics) {
744 sdb_log(SDB_LOG_ERR, "store: Failed to store attribute '%s' "
745 "for metric '%s' - host '%s' not found",
746 key, metric, hostname);
747 pthread_rwlock_unlock(&st->host_lock);
748 return -1;
749 }
751 m = METRIC(sdb_avltree_lookup(metrics, metric));
752 if (! m) {
753 sdb_log(SDB_LOG_ERR, "store: Failed to store attribute '%s' - "
754 "metric '%s/%s' not found", key, hostname, metric);
755 status = -1;
756 }
758 if (! status)
759 status = store_attr(STORE_OBJ(m), m->attributes,
760 key, value, last_update);
762 sdb_object_deref(SDB_OBJ(m));
763 pthread_rwlock_unlock(&st->host_lock);
765 return status;
766 } /* store_metric_attr */
768 static int
769 store_metric(const char *hostname, const char *name,
770 sdb_metric_store_t *store, sdb_time_t last_update,
771 sdb_object_t *user_data)
772 {
773 sdb_store_t *st = ST(user_data);
775 sdb_store_obj_t *obj = NULL;
776 sdb_host_t *host;
777 sdb_metric_t *metric;
778 sdb_data_t d;
780 sdb_avltree_t *metrics;
782 int status = 0;
784 if ((! hostname) || (! name))
785 return -1;
787 if (store) {
788 if ((store->type != NULL) != (store->id != NULL))
789 return -1;
790 else if (! store->type)
791 store = NULL;
792 }
794 pthread_rwlock_wrlock(&st->host_lock);
795 host = lookup_host(st, hostname, /* canonicalize = */ 1);
796 metrics = get_host_children(host, SDB_METRIC);
797 if (! metrics) {
798 sdb_log(SDB_LOG_ERR, "store: Failed to store metric '%s' - "
799 "host '%s' not found", name, hostname);
800 status = -1;
801 }
803 if (! status)
804 status = store_obj(STORE_OBJ(host), metrics, SDB_METRIC,
805 name, last_update, &obj);
806 sdb_object_deref(SDB_OBJ(host));
808 if (status) {
809 pthread_rwlock_unlock(&st->host_lock);
810 return status;
811 }
813 assert(obj);
814 metric = METRIC(obj);
816 if (store)
817 if (store_metric_store(metric, store))
818 status = -1;
819 pthread_rwlock_unlock(&st->host_lock);
821 /* record the hostname as an attribute */
822 d.type = SDB_TYPE_STRING;
823 d.data.string = SDB_OBJ(host)->name;
824 if (store_metric_attr(hostname, name, "hostname", &d, last_update, user_data))
825 status = -1;
826 return status;
827 } /* store_metric */
829 static sdb_store_writer_t store_writer = {
830 store_host, store_service, store_metric,
831 store_attribute, store_service_attr, store_metric_attr,
832 };
834 /*
835 * public API
836 */
838 int
839 sdb_store_init(void)
840 {
841 if (global_store)
842 return 0;
844 global_store = ST(sdb_object_create("store", store_type));
845 if (! global_store) {
846 sdb_log(SDB_LOG_ERR, "store: Failed to allocate store");
847 return -1;
848 }
849 return sdb_plugin_register_writer("memstore",
850 &store_writer, SDB_OBJ(global_store));
851 } /* sdb_store_init */
853 void
854 sdb_store_clear(void)
855 {
856 if (! global_store)
857 return;
858 sdb_avltree_clear(global_store->hosts);
859 } /* sdb_store_clear */
861 sdb_store_obj_t *
862 sdb_store_get_host(const char *name)
863 {
864 sdb_host_t *host;
866 if ((! global_store) || (! name))
867 return NULL;
869 host = lookup_host(global_store, name, /* canonicalize = */ 0);
870 if (! host)
871 return NULL;
873 return STORE_OBJ(host);
874 } /* sdb_store_get_host */
876 sdb_store_obj_t *
877 sdb_store_get_child(sdb_store_obj_t *host, int type, const char *name)
878 {
879 sdb_avltree_t *children;
881 if ((! host) || (host->type != SDB_HOST) || (! name))
882 return NULL;
884 children = get_host_children(HOST(host), type);
885 if (! children)
886 return NULL;
887 return STORE_OBJ(sdb_avltree_lookup(children, name));
888 } /* sdb_store_get_child */
890 int
891 sdb_store_get_field(sdb_store_obj_t *obj, int field, sdb_data_t *res)
892 {
893 sdb_data_t tmp;
895 if (! obj)
896 return -1;
898 switch (field) {
899 case SDB_FIELD_NAME:
900 tmp.type = SDB_TYPE_STRING;
901 tmp.data.string = strdup(SDB_OBJ(obj)->name);
902 if (! tmp.data.string)
903 return -1;
904 break;
905 case SDB_FIELD_LAST_UPDATE:
906 tmp.type = SDB_TYPE_DATETIME;
907 tmp.data.datetime = obj->last_update;
908 break;
909 case SDB_FIELD_AGE:
910 tmp.type = SDB_TYPE_DATETIME;
911 tmp.data.datetime = sdb_gettime() - obj->last_update;
912 break;
913 case SDB_FIELD_INTERVAL:
914 tmp.type = SDB_TYPE_DATETIME;
915 tmp.data.datetime = obj->interval;
916 break;
917 case SDB_FIELD_BACKEND:
918 if (! res)
919 return 0;
920 tmp.type = SDB_TYPE_ARRAY | SDB_TYPE_STRING;
921 tmp.data.array.length = obj->backends_num;
922 tmp.data.array.values = obj->backends;
923 return sdb_data_copy(res, &tmp);
924 case SDB_FIELD_VALUE:
925 if (obj->type != SDB_ATTRIBUTE)
926 return -1;
927 if (! res)
928 return 0;
929 return sdb_data_copy(res, &ATTR(obj)->value);
930 case SDB_FIELD_TIMESERIES:
931 if (obj->type != SDB_METRIC)
932 return -1;
933 tmp.type = SDB_TYPE_BOOLEAN;
934 tmp.data.boolean = METRIC(obj)->store.type != NULL;
935 default:
936 return -1;
937 }
938 if (res)
939 *res = tmp;
940 else
941 sdb_data_free_datum(&tmp);
942 return 0;
943 } /* sdb_store_get_field */
945 int
946 sdb_store_get_attr(sdb_store_obj_t *obj, const char *name, sdb_data_t *res,
947 sdb_store_matcher_t *filter)
948 {
949 sdb_avltree_t *tree = NULL;
950 sdb_store_obj_t *attr;
952 if ((! obj) || (! name))
953 return -1;
955 if (obj->type == SDB_HOST)
956 tree = HOST(obj)->attributes;
957 else if (obj->type == SDB_SERVICE)
958 tree = SVC(obj)->attributes;
959 else if (obj->type == SDB_METRIC)
960 tree = METRIC(obj)->attributes;
962 if (! tree)
963 return -1;
965 attr = STORE_OBJ(sdb_avltree_lookup(tree, name));
966 if (! attr)
967 return -1;
968 if (filter && (! sdb_store_matcher_matches(filter, attr, NULL))) {
969 sdb_object_deref(SDB_OBJ(attr));
970 return -1;
971 }
973 assert(STORE_OBJ(attr)->type == SDB_ATTRIBUTE);
974 if (res)
975 sdb_data_copy(res, &ATTR(attr)->value);
976 sdb_object_deref(SDB_OBJ(attr));
977 return 0;
978 } /* sdb_store_get_attr */
980 /* TODO: sdb_store_fetch_timeseries should move into the plugin module */
982 int
983 sdb_store_fetch_timeseries(const char *hostname, const char *metric,
984 sdb_timeseries_opts_t *opts, sdb_strbuf_t *buf)
985 {
986 sdb_avltree_t *metrics;
987 sdb_host_t *host;
988 sdb_metric_t *m;
990 sdb_timeseries_t *ts;
992 int status = 0;
994 if ((! global_store) || (! hostname) || (! metric) || (! opts) || (! buf))
995 return -1;
997 pthread_rwlock_rdlock(&global_store->host_lock);
998 host = lookup_host(global_store, hostname, /* canonicalize = */ 1);
999 metrics = get_host_children(host, SDB_METRIC);
1000 sdb_object_deref(SDB_OBJ(host));
1001 if (! metrics) {
1002 sdb_log(SDB_LOG_ERR, "store: Failed to fetch time-series '%s/%s' "
1003 "- host '%s' not found", hostname, metric, hostname);
1004 pthread_rwlock_unlock(&global_store->host_lock);
1005 return -1;
1006 }
1008 m = METRIC(sdb_avltree_lookup(metrics, metric));
1009 if (! m) {
1010 sdb_log(SDB_LOG_ERR, "store: Failed to fetch time-series '%s/%s' "
1011 "- metric '%s' not found", hostname, metric, metric);
1012 pthread_rwlock_unlock(&global_store->host_lock);
1013 return -1;
1014 }
1016 if ((! m->store.type) || (! m->store.id)) {
1017 sdb_log(SDB_LOG_ERR, "store: Failed to fetch time-series '%s/%s' "
1018 "- no data-store configured for the stored metric",
1019 hostname, metric);
1020 sdb_object_deref(SDB_OBJ(m));
1021 pthread_rwlock_unlock(&global_store->host_lock);
1022 return -1;
1023 }
1025 {
1026 char type[strlen(m->store.type) + 1];
1027 char id[strlen(m->store.id) + 1];
1029 strncpy(type, m->store.type, sizeof(type));
1030 strncpy(id, m->store.id, sizeof(id));
1031 pthread_rwlock_unlock(&global_store->host_lock);
1033 ts = sdb_plugin_fetch_timeseries(type, id, opts);
1034 if (! ts) {
1035 sdb_log(SDB_LOG_ERR, "store: Failed to fetch time-series '%s/%s' "
1036 "- %s fetcher callback returned no data for '%s'",
1037 hostname, metric, type, id);
1038 status = -1;
1039 }
1040 }
1042 ts_tojson(ts, buf);
1043 sdb_object_deref(SDB_OBJ(m));
1044 sdb_timeseries_destroy(ts);
1045 return status;
1046 } /* sdb_store_fetch_timeseries */
1048 int
1049 sdb_store_scan(int type, sdb_store_matcher_t *m, sdb_store_matcher_t *filter,
1050 sdb_store_lookup_cb cb, void *user_data)
1051 {
1052 sdb_avltree_iter_t *host_iter = NULL;
1053 int status = 0;
1055 if ((! global_store) || (! cb))
1056 return -1;
1058 if ((type != SDB_HOST) && (type != SDB_SERVICE) && (type != SDB_METRIC)) {
1059 sdb_log(SDB_LOG_ERR, "store: Cannot scan objects of type %d", type);
1060 return -1;
1061 }
1063 pthread_rwlock_rdlock(&global_store->host_lock);
1064 host_iter = sdb_avltree_get_iter(global_store->hosts);
1065 if (! host_iter)
1066 status = -1;
1068 /* has_next returns false if the iterator is NULL */
1069 while (sdb_avltree_iter_has_next(host_iter)) {
1070 sdb_store_obj_t *host;
1071 sdb_avltree_iter_t *iter = NULL;
1073 host = STORE_OBJ(sdb_avltree_iter_get_next(host_iter));
1074 assert(host);
1076 if (! sdb_store_matcher_matches(filter, host, NULL))
1077 continue;
1079 if (type == SDB_SERVICE)
1080 iter = sdb_avltree_get_iter(HOST(host)->services);
1081 else if (type == SDB_METRIC)
1082 iter = sdb_avltree_get_iter(HOST(host)->metrics);
1084 if (iter) {
1085 while (sdb_avltree_iter_has_next(iter)) {
1086 sdb_store_obj_t *obj;
1087 obj = STORE_OBJ(sdb_avltree_iter_get_next(iter));
1088 assert(obj);
1090 if (sdb_store_matcher_matches(m, obj, filter)) {
1091 if (cb(obj, filter, user_data)) {
1092 sdb_log(SDB_LOG_ERR, "store: Callback returned "
1093 "an error while scanning");
1094 status = -1;
1095 break;
1096 }
1097 }
1098 }
1099 }
1100 else if (sdb_store_matcher_matches(m, host, filter)) {
1101 if (cb(host, filter, user_data)) {
1102 sdb_log(SDB_LOG_ERR, "store: Callback returned "
1103 "an error while scanning");
1104 status = -1;
1105 }
1106 }
1108 sdb_avltree_iter_destroy(iter);
1109 if (status)
1110 break;
1111 }
1113 sdb_avltree_iter_destroy(host_iter);
1114 pthread_rwlock_unlock(&global_store->host_lock);
1115 return status;
1116 } /* sdb_store_scan */
1118 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */