Code

store: Use the JSON writer when querying the in-memory store.
[sysdb.git] / src / core / store_json.c
1 /*
2  * SysDB - src/core/store_json.c
3  * Copyright (C) 2013-2015 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 /*
29  * This module implements JSON support.
30  */
32 #if HAVE_CONFIG_H
33 #       include "config.h"
34 #endif /* HAVE_CONFIG_H */
36 #include "sysdb.h"
37 #include "core/store-private.h"
38 #include "utils/error.h"
40 #include <assert.h>
42 #include <ctype.h>
43 #include <stdlib.h>
44 #include <string.h>
46 /*
47  * private data types
48  */
50 struct sdb_store_json_formatter {
51         sdb_object_t super;
53         /* The string buffer to write to */
54         sdb_strbuf_t *buf;
56         /* The context describes the state of the formatter through
57          * the path pointing to the current object */
58         int context[8];
59         size_t current;
61         int type;
62         int flags;
63 };
64 #define F(obj) ((sdb_store_json_formatter_t *)(obj))
66 static int
67 formatter_init(sdb_object_t *obj, va_list ap)
68 {
69         sdb_store_json_formatter_t *f = F(obj);
71         f->buf = va_arg(ap, sdb_strbuf_t *);
72         if (! f->buf)
73                 return -1;
75         f->type = va_arg(ap, int);
76         if ((f->type != SDB_HOST) && (f->type != SDB_SERVICE) && (f->type != SDB_METRIC))
77                 return -1;
79         f->flags = va_arg(ap, int);
81         f->context[0] = 0;
82         f->current = 0;
83         return 0;
84 } /* formatter_init */
86 static sdb_type_t formatter_type = {
87         /* size = */ sizeof(sdb_store_json_formatter_t),
88         /* init = */ formatter_init,
89         /* destroy = */ NULL,
90 };
92 /* A generic representation of a stored object. */
93 typedef struct {
94         /* identifier */
95         int type;
96         const char *name;
98         /* attribute value (optional) */
99         sdb_data_t *value;
101         /* metric's timeseries (optional)
102          * -1: unset
103          *  0: false
104          *  1: true */
105         int timeseries;
107         /* generic meta-data */
108         sdb_time_t last_update;
109         sdb_time_t interval;
110         size_t backends_num;
111         const char * const *backends;
112 } obj_t;
114 /*
115  * private helper functions
116  */
118 static void
119 escape_string(const char *src, char *dest)
121         size_t i = 1;
122         dest[0] = '"';
123         for ( ; *src; ++src) {
124                 char c = *src;
125                 if ((c == '"') || (c == '\\') || iscntrl((int)c)) {
126                         dest[i] = '\\';
127                         ++i;
128                 }
129                 switch (c) {
130                         case '\a': dest[i] = 'a'; break;
131                         case '\b': dest[i] = 'b'; break;
132                         case '\t': dest[i] = 't'; break;
133                         case '\n': dest[i] = 'n'; break;
134                         case '\v': dest[i] = 'v'; break;
135                         case '\f': dest[i] = 'f'; break;
136                         case '\r': dest[i] = 'r'; break;
137                         default: dest[i] = c; break;
138                 }
139                 ++i;
140         }
141         dest[i] = '"';
142         dest[i + 1] = '\0';
143 } /* escape_string */
145 /* handle_new_object takes care of all maintenance logic related to adding a
146  * new object. That is, it manages context information and emit the prefix and
147  * suffix of an object. */
148 static int
149 handle_new_object(sdb_store_json_formatter_t *f, int type)
151         /* first top-level object */
152         if (! f->context[0]) {
153                 if ((type != f->type) && (type != SDB_HOST)) {
154                         sdb_log(SDB_LOG_ERR, "store: Unexpected object of type %s "
155                                         "as the first element during %s JSON serialization",
156                                         SDB_STORE_TYPE_TO_NAME(type),
157                                         SDB_STORE_TYPE_TO_NAME(f->type));
158                         return -1;
159                 }
160                 if (f->flags & SDB_WANT_ARRAY)
161                         sdb_strbuf_append(f->buf, "[");
162                 assert(f->current == 0);
163                 f->context[f->current] = type;
164                 return 0;
165         }
167         if ((f->context[f->current] != SDB_HOST)
168                         && (type != SDB_ATTRIBUTE)) {
169                 /* new entry of the same type or a parent object;
170                  * rewind to the right state */
171                 while ((f->current > 0)
172                                 && (f->context[f->current] != type)) {
173                         sdb_strbuf_append(f->buf, "}]");
174                         --f->current;
175                 }
176         }
178         if (type == f->context[f->current]) {
179                 /* new entry of the same type */
180                 sdb_strbuf_append(f->buf, "},");
181         }
182         else if ((f->context[f->current] == SDB_HOST)
183                         || (type == SDB_ATTRIBUTE)) {
184                 assert(type != SDB_HOST);
185                 /* all object types may be children of a host;
186                  * attributes may be children of any type */
187                 sdb_strbuf_append(f->buf, ", \"%ss\": [",
188                                 SDB_STORE_TYPE_TO_NAME(type));
189                 ++f->current;
190         }
191         else {
192                 sdb_log(SDB_LOG_ERR, "store: Unexpected object of type %s "
193                                 "on level %zu during JSON serialization",
194                                 SDB_STORE_TYPE_TO_NAME(type), f->current);
195                 return -1;
196         }
198         assert(f->current < SDB_STATIC_ARRAY_LEN(f->context));
199         f->context[f->current] = type;
200         return 0;
201 } /* handle_new_object */
203 static int
204 json_emit(sdb_store_json_formatter_t *f, obj_t *obj)
206         char time_str[64];
207         char interval_str[64];
208         char name[2 * strlen(obj->name) + 3];
209         size_t i;
211         assert(f && obj);
213         handle_new_object(f, obj->type);
215         escape_string(obj->name, name);
216         sdb_strbuf_append(f->buf, "{\"name\": %s, ", name);
217         if ((obj->type == SDB_ATTRIBUTE) && (obj->value)) {
218                 char tmp[sdb_data_strlen(obj->value) + 1];
219                 char val[2 * sizeof(tmp) + 3];
220                 if (! sdb_data_format(obj->value, tmp, sizeof(tmp),
221                                         SDB_DOUBLE_QUOTED))
222                         snprintf(tmp, sizeof(tmp), "<error>");
224                 if (tmp[0] == '"') {
225                         /* a string; escape_string handles quoting */
226                         tmp[strlen(tmp) - 1] = '\0';
227                         escape_string(tmp + 1, val);
228                         sdb_strbuf_append(f->buf, "\"value\": %s, ", val);
229                 }
230                 else
231                         sdb_strbuf_append(f->buf, "\"value\": %s, ", tmp);
232         }
233         else if ((obj->type == SDB_METRIC) && (obj->timeseries >= 0)) {
234                 if (obj->timeseries)
235                         sdb_strbuf_append(f->buf, "\"timeseries\": true, ");
236                 else
237                         sdb_strbuf_append(f->buf, "\"timeseries\": false, ");
238         }
240         /* TODO: make time and interval formats configurable */
241         if (! sdb_strftime(time_str, sizeof(time_str), obj->last_update))
242                 snprintf(time_str, sizeof(time_str), "<error>");
243         time_str[sizeof(time_str) - 1] = '\0';
245         if (! sdb_strfinterval(interval_str, sizeof(interval_str),
246                                 obj->interval))
247                 snprintf(interval_str, sizeof(interval_str), "<error>");
248         interval_str[sizeof(interval_str) - 1] = '\0';
250         sdb_strbuf_append(f->buf, "\"last_update\": \"%s\", "
251                         "\"update_interval\": \"%s\", \"backends\": [",
252                         time_str, interval_str);
254         for (i = 0; i < obj->backends_num; ++i) {
255                 sdb_strbuf_append(f->buf, "\"%s\"", obj->backends[i]);
256                 if (i < obj->backends_num - 1)
257                         sdb_strbuf_append(f->buf, ",");
258         }
259         sdb_strbuf_append(f->buf, "]");
260         return 0;
261 } /* json_emit */
263 static int
264 emit_host(sdb_store_host_t *host, sdb_object_t *user_data)
266         sdb_store_json_formatter_t *f = F(user_data);
268         if ((! host) || (! user_data))
269                 return -1;
271         {
272                 obj_t o = {
273                         SDB_HOST,
274                         host->name,
276                         /* value */ NULL,
277                         /* timeseries */ -1,
279                         host->last_update,
280                         host->interval,
281                         host->backends_num,
282                         (const char * const *)host->backends,
283                 };
285                 return json_emit(f, &o);
286         }
287 } /* emit_host */
289 static int
290 emit_service(sdb_store_service_t *service, sdb_object_t *user_data)
292         sdb_store_json_formatter_t *f = F(user_data);
294         if ((! service) || (! user_data))
295                 return -1;
297         {
298                 obj_t o = {
299                         SDB_SERVICE,
300                         service->name,
302                         /* value */ NULL,
303                         /* timeseries */ -1,
305                         service->last_update,
306                         service->interval,
307                         service->backends_num,
308                         (const char * const *)service->backends,
309                 };
311                 return json_emit(f, &o);
312         }
313 } /* emit_service */
315 static int
316 emit_metric(sdb_store_metric_t *metric, sdb_object_t *user_data)
318         sdb_store_json_formatter_t *f = F(user_data);
320         if ((! metric) || (! user_data))
321                 return -1;
323         {
324                 obj_t o = {
325                         SDB_METRIC,
326                         metric->name,
328                         /* value */ NULL,
329                         /* timeseries */ metric->store.type != NULL,
331                         metric->last_update,
332                         metric->interval,
333                         metric->backends_num,
334                         (const char * const *)metric->backends,
335                 };
337                 return json_emit(f, &o);
338         }
339 } /* emit_metric */
341 static int
342 emit_attribute(sdb_store_attribute_t *attr, sdb_object_t *user_data)
344         sdb_store_json_formatter_t *f = F(user_data);
346         if ((! attr) || (! user_data))
347                 return -1;
349         {
350                 obj_t o = {
351                         SDB_ATTRIBUTE,
352                         attr->key,
354                         /* value */ &attr->value,
355                         /* timeseries */ -1,
357                         attr->last_update,
358                         attr->interval,
359                         attr->backends_num,
360                         (const char * const *)attr->backends,
361                 };
363                 return json_emit(f, &o);
364         }
365 } /* emit_attribute */
367 /*
368  * public API
369  */
371 sdb_store_writer_t sdb_store_json_writer = {
372         emit_host, emit_service, emit_metric, emit_attribute,
373 };
375 sdb_store_json_formatter_t *
376 sdb_store_json_formatter(sdb_strbuf_t *buf, int type, int flags)
378         return F(sdb_object_create("json-formatter", formatter_type,
379                                 buf, type, flags));
380 } /* sdb_store_json_formatter */
382 /* TODO: Move sdb_store_emit* somewhere else. */
384 int
385 sdb_store_emit(sdb_store_obj_t *obj, sdb_store_writer_t *w, sdb_object_t *wd)
387         if ((! obj) || (! w)
388                         || (! w->store_host) || (! w->store_service)
389                         || (! w->store_metric) || (! w->store_attribute))
390                 return -1;
392         switch (obj->type) {
393         case SDB_HOST:
394                 {
395                         sdb_store_host_t host = {
396                                 obj->_name,
397                                 obj->last_update,
398                                 obj->interval,
399                                 (const char * const *)obj->backends,
400                                 obj->backends_num,
401                         };
402                         return w->store_host(&host, wd);
403                 }
404         case SDB_SERVICE:
405                 {
406                         sdb_store_service_t service = {
407                                 obj->parent ? obj->parent->_name : NULL,
408                                 obj->_name,
409                                 obj->last_update,
410                                 obj->interval,
411                                 (const char * const *)obj->backends,
412                                 obj->backends_num,
413                         };
414                         return w->store_service(&service, wd);
415                 }
416         case SDB_METRIC:
417                 {
418                         sdb_store_metric_t metric = {
419                                 obj->parent ? obj->parent->_name : NULL,
420                                 obj->_name,
421                                 {
422                                         METRIC(obj)->store.type,
423                                         METRIC(obj)->store.id,
424                                 },
425                                 obj->last_update,
426                                 obj->interval,
427                                 (const char * const *)obj->backends,
428                                 obj->backends_num,
429                         };
430                         return w->store_metric(&metric, wd);
431                 }
432         case SDB_ATTRIBUTE:
433                 {
434                         sdb_store_attribute_t attr = {
435                                 NULL,
436                                 obj->parent ? obj->parent->type : 0,
437                                 obj->parent ? obj->parent->_name : NULL,
438                                 obj->_name,
439                                 ATTR(obj)->value,
440                                 obj->last_update,
441                                 obj->interval,
442                                 (const char * const *)obj->backends,
443                                 obj->backends_num,
444                         };
445                         if (obj->parent && (obj->parent->type != SDB_HOST)
446                                         && obj->parent->parent)
447                                 attr.hostname = obj->parent->parent->_name;
448                         return w->store_attribute(&attr, wd);
449                 }
450         }
452         return -1;
453 } /* sdb_store_emit */
455 int
456 sdb_store_emit_full(sdb_store_obj_t *obj, sdb_store_matcher_t *filter,
457                 sdb_store_writer_t *w, sdb_object_t *wd)
459         sdb_avltree_t *trees[] = { NULL, NULL, NULL };
460         size_t i;
462         if (sdb_store_emit(obj, w, wd))
463                 return -1;
465         if (obj->type == SDB_HOST) {
466                 trees[0] = HOST(obj)->attributes;
467                 trees[1] = HOST(obj)->metrics;
468                 trees[2] = HOST(obj)->services;
469         }
470         else if (obj->type == SDB_SERVICE)
471                 trees[0] = SVC(obj)->attributes;
472         else if (obj->type == SDB_METRIC)
473                 trees[0] = METRIC(obj)->attributes;
474         else if (obj->type == SDB_ATTRIBUTE)
475                 return 0;
476         else
477                 return -1;
479         for (i = 0; i < SDB_STATIC_ARRAY_LEN(trees); ++i) {
480                 sdb_avltree_iter_t *iter;
482                 if (! trees[i])
483                         continue;
485                 iter = sdb_avltree_get_iter(trees[i]);
486                 while (sdb_avltree_iter_has_next(iter)) {
487                         sdb_store_obj_t *child;
488                         child = STORE_OBJ(sdb_avltree_iter_get_next(iter));
490                         if (filter && (! sdb_store_matcher_matches(filter, child, NULL)))
491                                 continue;
493                         if (sdb_store_emit_full(child, filter, w, wd)) {
494                                 sdb_avltree_iter_destroy(iter);
495                                 return -1;
496                         }
497                 }
498                 sdb_avltree_iter_destroy(iter);
499         }
500         return 0;
501 } /* sdb_store_emit_full */
503 int
504 sdb_store_json_finish(sdb_store_json_formatter_t *f)
506         if (! f)
507                 return -1;
509         if (! f->context[0]) {
510                 /* no content */
511                 if (f->flags & SDB_WANT_ARRAY)
512                         sdb_strbuf_append(f->buf, "[]");
513                 return 0;
514         }
516         while (f->current > 0) {
517                 sdb_strbuf_append(f->buf, "}]");
518                 --f->current;
519         }
520         sdb_strbuf_append(f->buf, "}");
522         if (f->flags & SDB_WANT_ARRAY)
523                 sdb_strbuf_append(f->buf, "]");
524         return 0;
525 } /* sdb_store_json_finish */
527 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */