4fd309b3ec4ac6fa81f982fd9e71b2d3226d0af7
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)
120 {
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)
150 {
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)
205 {
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)
265 {
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)
291 {
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)
317 {
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)
343 {
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)
377 {
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)
386 {
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)
458 {
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)
505 {
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 : */