26a0854eeb4ef09957d7cd400bc5e699496cd7bc
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 /*
264 * public API
265 */
267 sdb_store_json_formatter_t *
268 sdb_store_json_formatter(sdb_strbuf_t *buf, int type, int flags)
269 {
270 return F(sdb_object_create("json-formatter", formatter_type,
271 buf, type, flags));
272 } /* sdb_store_json_formatter */
274 int
275 sdb_store_json_emit(sdb_store_json_formatter_t *f, sdb_store_obj_t *obj)
276 {
277 if ((! f) || (! obj))
278 return -1;
280 {
281 obj_t o = {
282 obj->type,
283 obj->_name,
285 /* value */ NULL,
286 /* timeseries */ -1,
288 obj->last_update,
289 obj->interval,
290 obj->backends_num,
291 (const char * const *)obj->backends,
292 };
294 if (obj->type == SDB_ATTRIBUTE)
295 o.value = &ATTR(obj)->value;
296 if (obj->type == SDB_METRIC)
297 o.timeseries = METRIC(obj)->store.type != NULL;
299 return json_emit(f, &o);
300 }
301 } /* sdb_store_json_emit */
303 int
304 sdb_store_json_emit_full(sdb_store_json_formatter_t *f, sdb_store_obj_t *obj,
305 sdb_store_matcher_t *filter)
306 {
307 sdb_avltree_t *trees[] = { NULL, NULL, NULL };
308 size_t i;
310 if (sdb_store_json_emit(f, obj))
311 return -1;
313 if (obj->type == SDB_HOST) {
314 trees[0] = HOST(obj)->attributes;
315 trees[1] = HOST(obj)->metrics;
316 trees[2] = HOST(obj)->services;
317 }
318 else if (obj->type == SDB_SERVICE)
319 trees[0] = SVC(obj)->attributes;
320 else if (obj->type == SDB_METRIC)
321 trees[0] = METRIC(obj)->attributes;
322 else if (obj->type == SDB_ATTRIBUTE)
323 return 0;
324 else
325 return -1;
327 for (i = 0; i < SDB_STATIC_ARRAY_LEN(trees); ++i) {
328 sdb_avltree_iter_t *iter;
330 if (! trees[i])
331 continue;
333 iter = sdb_avltree_get_iter(trees[i]);
334 while (sdb_avltree_iter_has_next(iter)) {
335 sdb_store_obj_t *child;
336 child = STORE_OBJ(sdb_avltree_iter_get_next(iter));
338 if (filter && (! sdb_store_matcher_matches(filter, child, NULL)))
339 continue;
341 if (sdb_store_json_emit_full(f, child, filter)) {
342 sdb_avltree_iter_destroy(iter);
343 return -1;
344 }
345 }
346 sdb_avltree_iter_destroy(iter);
347 }
348 return 0;
349 } /* sdb_store_json_emit_full */
351 int
352 sdb_store_json_finish(sdb_store_json_formatter_t *f)
353 {
354 if (! f)
355 return -1;
357 if (! f->context[0]) {
358 /* no content */
359 if (f->flags & SDB_WANT_ARRAY)
360 sdb_strbuf_append(f->buf, "[]");
361 return 0;
362 }
364 while (f->current > 0) {
365 sdb_strbuf_append(f->buf, "}]");
366 --f->current;
367 }
368 sdb_strbuf_append(f->buf, "}");
370 if (f->flags & SDB_WANT_ARRAY)
371 sdb_strbuf_append(f->buf, "]");
372 return 0;
373 } /* sdb_store_json_finish */
375 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */