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.h"
38 #include "utils/error.h"
39 #include "utils/strings.h"
41 #include <assert.h>
43 #include <ctype.h>
44 #include <stdlib.h>
45 #include <string.h>
47 /*
48 * private data types
49 */
51 struct sdb_store_json_formatter {
52 sdb_object_t super;
54 /* The string buffer to write to */
55 sdb_strbuf_t *buf;
57 /* The context describes the state of the formatter through
58 * the path pointing to the current object */
59 int context[8];
60 size_t current;
62 int type;
63 int flags;
64 };
65 #define F(obj) ((sdb_store_json_formatter_t *)(obj))
67 static int
68 formatter_init(sdb_object_t *obj, va_list ap)
69 {
70 sdb_store_json_formatter_t *f = F(obj);
72 f->buf = va_arg(ap, sdb_strbuf_t *);
73 if (! f->buf)
74 return -1;
76 f->type = va_arg(ap, int);
77 if ((f->type != SDB_HOST) && (f->type != SDB_SERVICE) && (f->type != SDB_METRIC))
78 return -1;
80 f->flags = va_arg(ap, int);
82 f->context[0] = 0;
83 f->current = 0;
84 return 0;
85 } /* formatter_init */
87 static sdb_type_t formatter_type = {
88 /* size = */ sizeof(sdb_store_json_formatter_t),
89 /* init = */ formatter_init,
90 /* destroy = */ NULL,
91 };
93 /* A generic representation of a stored object. */
94 typedef struct {
95 /* identifier */
96 int type;
97 const char *name;
99 /* attribute value (optional) */
100 sdb_data_t *value;
102 /* metric's timeseries (optional)
103 * -1: unset
104 * 0: false
105 * 1: true */
106 int timeseries;
107 char **data_names;
108 size_t data_names_len;
110 /* generic meta-data */
111 sdb_time_t last_update;
112 sdb_time_t interval;
113 size_t backends_num;
114 const char * const *backends;
115 } obj_t;
117 /*
118 * private helper functions
119 */
121 static void
122 escape_string(const char *src, char *dest)
123 {
124 size_t i = 1;
125 dest[0] = '"';
126 for ( ; *src; ++src) {
127 char c = *src;
128 if ((c == '"') || (c == '\\') || iscntrl((int)c)) {
129 dest[i] = '\\';
130 ++i;
131 }
132 switch (c) {
133 case '\a': dest[i] = 'a'; break;
134 case '\b': dest[i] = 'b'; break;
135 case '\t': dest[i] = 't'; break;
136 case '\n': dest[i] = 'n'; break;
137 case '\v': dest[i] = 'v'; break;
138 case '\f': dest[i] = 'f'; break;
139 case '\r': dest[i] = 'r'; break;
140 default: dest[i] = c; break;
141 }
142 ++i;
143 }
144 dest[i] = '"';
145 dest[i + 1] = '\0';
146 } /* escape_string */
148 /* handle_new_object takes care of all maintenance logic related to adding a
149 * new object. That is, it manages context information and emit the prefix and
150 * suffix of an object. */
151 static int
152 handle_new_object(sdb_store_json_formatter_t *f, int type)
153 {
154 /* first top-level object */
155 if (! f->context[0]) {
156 if ((type != f->type) && (type != SDB_HOST)) {
157 sdb_log(SDB_LOG_ERR, "store: Unexpected object of type %s "
158 "as the first element during %s JSON serialization",
159 SDB_STORE_TYPE_TO_NAME(type),
160 SDB_STORE_TYPE_TO_NAME(f->type));
161 return -1;
162 }
163 if (f->flags & SDB_WANT_ARRAY)
164 sdb_strbuf_append(f->buf, "[");
165 assert(f->current == 0);
166 f->context[f->current] = type;
167 return 0;
168 }
170 if ((f->context[f->current] != SDB_HOST)
171 && (type != SDB_ATTRIBUTE)) {
172 /* new entry of the same type or a parent object;
173 * rewind to the right state */
174 while ((f->current > 0)
175 && (f->context[f->current] != type)) {
176 sdb_strbuf_append(f->buf, "}]");
177 --f->current;
178 }
179 }
181 if (type == f->context[f->current]) {
182 /* new entry of the same type */
183 sdb_strbuf_append(f->buf, "},");
184 }
185 else if ((f->context[f->current] == SDB_HOST)
186 || (type == SDB_ATTRIBUTE)) {
187 assert(type != SDB_HOST);
188 /* all object types may be children of a host;
189 * attributes may be children of any type */
190 sdb_strbuf_append(f->buf, ", \"%ss\": [",
191 SDB_STORE_TYPE_TO_NAME(type));
192 ++f->current;
193 }
194 else {
195 sdb_log(SDB_LOG_ERR, "store: Unexpected object of type %s "
196 "on level %zu during JSON serialization",
197 SDB_STORE_TYPE_TO_NAME(type), f->current);
198 return -1;
199 }
201 assert(f->current < SDB_STATIC_ARRAY_LEN(f->context));
202 f->context[f->current] = type;
203 return 0;
204 } /* handle_new_object */
206 static int
207 json_emit(sdb_store_json_formatter_t *f, obj_t *obj)
208 {
209 char time_str[64];
210 char interval_str[64];
211 char name[2 * strlen(obj->name) + 3];
212 size_t i;
214 assert(f && obj);
216 handle_new_object(f, obj->type);
218 escape_string(obj->name, name);
219 sdb_strbuf_append(f->buf, "{\"name\": %s, ", name);
220 if ((obj->type == SDB_ATTRIBUTE) && (obj->value)) {
221 char tmp[sdb_data_strlen(obj->value) + 1];
222 char val[2 * sizeof(tmp) + 3];
223 if (! sdb_data_format(obj->value, tmp, sizeof(tmp),
224 SDB_DOUBLE_QUOTED))
225 snprintf(tmp, sizeof(tmp), "<error>");
227 if (tmp[0] == '"') {
228 /* a string; escape_string handles quoting */
229 tmp[strlen(tmp) - 1] = '\0';
230 escape_string(tmp + 1, val);
231 sdb_strbuf_append(f->buf, "\"value\": %s, ", val);
232 }
233 else
234 sdb_strbuf_append(f->buf, "\"value\": %s, ", tmp);
235 }
236 else if ((obj->type == SDB_METRIC) && (obj->timeseries >= 0)) {
237 if (obj->timeseries)
238 sdb_strbuf_append(f->buf, "\"timeseries\": true, ");
239 else
240 sdb_strbuf_append(f->buf, "\"timeseries\": false, ");
242 if (obj->data_names_len > 0) {
243 sdb_strbuf_append(f->buf, "\"data_names\": [");
244 for (i = 0; i < obj->data_names_len; i++) {
245 char dn[2 * strlen(obj->data_names[i]) + 3];
246 escape_string(obj->data_names[i], dn);
247 sdb_strbuf_append(f->buf, "%s", dn);
248 if (i < obj->data_names_len - 1)
249 sdb_strbuf_append(f->buf, ", ");
250 }
251 sdb_strbuf_append(f->buf, "], ");
252 }
253 }
255 /* TODO: make time and interval formats configurable */
256 if (! sdb_strftime(time_str, sizeof(time_str), obj->last_update))
257 snprintf(time_str, sizeof(time_str), "<error>");
258 time_str[sizeof(time_str) - 1] = '\0';
260 if (! sdb_strfinterval(interval_str, sizeof(interval_str),
261 obj->interval))
262 snprintf(interval_str, sizeof(interval_str), "<error>");
263 interval_str[sizeof(interval_str) - 1] = '\0';
265 sdb_strbuf_append(f->buf, "\"last_update\": \"%s\", "
266 "\"update_interval\": \"%s\", \"backends\": [",
267 time_str, interval_str);
269 for (i = 0; i < obj->backends_num; ++i) {
270 sdb_strbuf_append(f->buf, "\"%s\"", obj->backends[i]);
271 if (i < obj->backends_num - 1)
272 sdb_strbuf_append(f->buf, ",");
273 }
274 sdb_strbuf_append(f->buf, "]");
275 return 0;
276 } /* json_emit */
278 static int
279 emit_host(sdb_store_host_t *host, sdb_object_t *user_data)
280 {
281 sdb_store_json_formatter_t *f = F(user_data);
283 if ((! host) || (! user_data))
284 return -1;
286 {
287 obj_t o = {
288 SDB_HOST,
289 host->name,
291 /* value */ NULL,
292 /* timeseries */ -1, NULL, 0,
294 host->last_update,
295 host->interval,
296 host->backends_num,
297 (const char * const *)host->backends,
298 };
300 return json_emit(f, &o);
301 }
302 } /* emit_host */
304 static int
305 emit_service(sdb_store_service_t *service, sdb_object_t *user_data)
306 {
307 sdb_store_json_formatter_t *f = F(user_data);
309 if ((! service) || (! user_data))
310 return -1;
312 {
313 obj_t o = {
314 SDB_SERVICE,
315 service->name,
317 /* value */ NULL,
318 /* timeseries */ -1, NULL, 0,
320 service->last_update,
321 service->interval,
322 service->backends_num,
323 (const char * const *)service->backends,
324 };
326 return json_emit(f, &o);
327 }
328 } /* emit_service */
330 static int
331 emit_metric(sdb_store_metric_t *metric, sdb_object_t *user_data)
332 {
333 sdb_store_json_formatter_t *f = F(user_data);
334 int status;
335 size_t i;
337 if ((! metric) || (! user_data))
338 return -1;
340 {
341 obj_t o = {
342 SDB_METRIC,
343 metric->name,
345 /* value */ NULL,
346 /* timeseries */ metric->stores_num > 0, NULL, 0,
348 metric->last_update,
349 metric->interval,
350 metric->backends_num,
351 (const char * const *)metric->backends,
352 };
354 for (i = 0; i < metric->stores_num; i++) {
355 const sdb_metric_store_t *s = metric->stores + i;
356 size_t j;
358 if (! s->info)
359 continue;
361 if (! o.data_names) {
362 stringv_copy(&o.data_names, &o.data_names_len,
363 (const char * const *)s->info->data_names,
364 s->info->data_names_len);
365 continue;
366 }
368 for (j = 0; j < s->info->data_names_len; j++)
369 stringv_append_if_missing(&o.data_names, &o.data_names_len,
370 s->info->data_names[j]);
371 }
373 status = json_emit(f, &o);
374 stringv_free(&o.data_names, &o.data_names_len);
375 return status;
376 }
377 } /* emit_metric */
379 static int
380 emit_attribute(sdb_store_attribute_t *attr, sdb_object_t *user_data)
381 {
382 sdb_store_json_formatter_t *f = F(user_data);
384 if ((! attr) || (! user_data))
385 return -1;
387 {
388 obj_t o = {
389 SDB_ATTRIBUTE,
390 attr->key,
392 /* value */ &attr->value,
393 /* timeseries */ -1, NULL, 0,
395 attr->last_update,
396 attr->interval,
397 attr->backends_num,
398 (const char * const *)attr->backends,
399 };
401 return json_emit(f, &o);
402 }
403 } /* emit_attribute */
405 /*
406 * public API
407 */
409 sdb_store_writer_t sdb_store_json_writer = {
410 emit_host, emit_service, emit_metric, emit_attribute,
411 };
413 sdb_store_json_formatter_t *
414 sdb_store_json_formatter(sdb_strbuf_t *buf, int type, int flags)
415 {
416 return F(sdb_object_create("json-formatter", formatter_type,
417 buf, type, flags));
418 } /* sdb_store_json_formatter */
420 int
421 sdb_store_json_finish(sdb_store_json_formatter_t *f)
422 {
423 if (! f)
424 return -1;
426 if (! f->context[0]) {
427 /* no content */
428 if (f->flags & SDB_WANT_ARRAY)
429 sdb_strbuf_append(f->buf, "[]");
430 return 0;
431 }
433 while (f->current > 0) {
434 sdb_strbuf_append(f->buf, "}]");
435 --f->current;
436 }
437 sdb_strbuf_append(f->buf, "}");
439 if (f->flags & SDB_WANT_ARRAY)
440 sdb_strbuf_append(f->buf, "]");
441 return 0;
442 } /* sdb_store_json_finish */
444 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */