From d73d3542ba0eb8fc1ba30b08f112c8a228a3ba42 Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Fri, 12 Jun 2015 19:40:16 +0100 Subject: [PATCH] src/utils_parse_json.c: Implement parse_json(). This allows us to parse JSON encoded value lists, as emitted src/utils_format_json.c. --- Makefile.am | 19 +++ src/utils_parse_json.c | 285 ++++++++++++++++++++++++++++++++++++++++++++ src/utils_parse_json.h | 38 ++++++ src/utils_parse_json_test.c | 190 +++++++++++++++++++++++++++++ 4 files changed, 532 insertions(+) create mode 100644 src/utils_parse_json.c create mode 100644 src/utils_parse_json.h create mode 100644 src/utils_parse_json_test.c diff --git a/Makefile.am b/Makefile.am index aed8eef1..39f1d3ac 100644 --- a/Makefile.am +++ b/Makefile.am @@ -445,6 +445,25 @@ test_format_json_LDADD = \ -lm endif +if BUILD_WITH_LIBYAJL2 +EXTRA_LTLIBRARIES += libparse_json.la +libparse_json_la_SOURCES = \ + src/utils_parse_json.c \ + src/utils_parse_json.h +libparse_json_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBYAJL_CPPFLAGS) +libparse_json_la_LDFLAGS = $(AM_LDFLAGS) $(BUILD_WITH_LIBYAJL_LDFLAGS) +libparse_json_la_LIBADD = $(BUILD_WITH_LIBYAJL_LIBS) + +check_PROGRAMS += test_parse_json + +test_parse_json_SOURCES = \ + src/utils_parse_json_test.c \ + src/testing.h +test_parse_json_LDADD = \ + libparse_json.la \ + libplugin_mock.la +endif + libstrbuf_la_SOURCES = \ src/utils_strbuf.c \ src/utils_strbuf.h diff --git a/src/utils_parse_json.c b/src/utils_parse_json.c new file mode 100644 index 00000000..db2cee4d --- /dev/null +++ b/src/utils_parse_json.c @@ -0,0 +1,285 @@ +/** + * collectd - src/utils_parse_json.c + * Copyright (C) 2018 Florian Forster + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Florian Forster + **/ + +#include "collectd.h" + +#include "common.h" +#include "plugin.h" +#include "utils_parse_json.h" + +#include + +static int parse_json_gauge(yajl_val value_val, value_t *v) /* {{{ */ +{ + if (YAJL_IS_DOUBLE(value_val)) + v->gauge = (gauge_t)YAJL_GET_DOUBLE(value_val); + else if (YAJL_IS_NULL(value_val)) + v->gauge = NAN; + else + return EINVAL; + + return 0; +} /* }}} int parse_json_gauge */ + +static int parse_json_derive(yajl_val value_val, value_t *v) /* {{{ */ +{ + if (!YAJL_IS_INTEGER(value_val)) { + if (YAJL_IS_NUMBER(value_val)) { + WARNING("parse_json_derive: %s is not an integer. The most common cause " + "is that the publisher has the StoreRates option enabled.", + YAJL_GET_NUMBER(value_val)); + } + return EINVAL; + } + + v->derive = (derive_t)YAJL_GET_INTEGER(value_val); + return 0; +} /* }}} int parse_json_derive */ + +static int parse_json_counter(yajl_val value_val, value_t *v) /* {{{ */ +{ + if (!YAJL_IS_INTEGER(value_val)) { + if (YAJL_IS_NUMBER(value_val)) { + WARNING("parse_json_counter: %s is not an integer. The most common cause " + "is that the publisher has the StoreRates option enabled.", + YAJL_GET_NUMBER(value_val)); + } + return EINVAL; + } + + v->counter = (counter_t)YAJL_GET_INTEGER(value_val); + return 0; +} /* }}} int parse_json_counter */ + +static int parse_json_absolute(yajl_val value_val, value_t *v) /* {{{ */ +{ + if (!YAJL_IS_INTEGER(value_val)) { + if (YAJL_IS_NUMBER(value_val)) { + WARNING("parse_json_absolute: %s is not an integer. The most common " + "cause is that the publisher has the StoreRates option enabled.", + YAJL_GET_NUMBER(value_val)); + } + return EINVAL; + } + + v->absolute = (absolute_t)YAJL_GET_INTEGER(value_val); + return 0; +} /* }}} int parse_json_absolute */ + +static int parse_json_value(yajl_val value_val, yajl_val dstype_val, + value_t *v) /* {{{ */ +{ + char const *dstype = YAJL_GET_STRING(dstype_val); + + if (dstype == NULL) { + NOTICE("parse_json_value: unable to get data source type"); + return EINVAL; + } else if (strcasecmp("gauge", dstype) == 0) + return parse_json_gauge(value_val, v); + else if (strcasecmp("derive", dstype) == 0) + return parse_json_derive(value_val, v); + else if (strcasecmp("counter", dstype) == 0) + return parse_json_counter(value_val, v); + else if (strcasecmp("absolute", dstype) == 0) + return parse_json_absolute(value_val, v); + else { + NOTICE("parse_json_value: unexpected data source type \"%s\"", dstype); + return EINVAL; + } +} /* }}} int parse_json_value */ + +static int parse_json_values(yajl_val root, value_list_t *vl) /* {{{ */ +{ + char const *values_path[] = {"values", NULL}; + char const *dstypes_path[] = {"dstypes", NULL}; + + yajl_val values_val = yajl_tree_get(root, values_path, yajl_t_array); + yajl_val dstypes_val = yajl_tree_get(root, dstypes_path, yajl_t_array); + + size_t i; + + if (values_val == NULL) { + NOTICE("parse_json_values: key \"values\" not found (or not an array)"); + return EINVAL; + } + if (dstypes_val == NULL) { + NOTICE("parse_json_values: key \"dstypes\" not found (or not an array)"); + return EINVAL; + } + if (YAJL_GET_ARRAY(values_val)->len != YAJL_GET_ARRAY(dstypes_val)->len) { + NOTICE("parse_json_values: lengths of \"values\" (%zu) and " + "\"dstypes\" (%zu) differ", + YAJL_GET_ARRAY(values_val)->len, YAJL_GET_ARRAY(dstypes_val)->len); + return EINVAL; + } + + vl->values = calloc(YAJL_GET_ARRAY(values_val)->len, sizeof(*vl->values)); + if (vl->values == NULL) + return ENOMEM; + vl->values_len = (int)YAJL_GET_ARRAY(values_val)->len; + + for (i = 0; i < YAJL_GET_ARRAY(values_val)->len; i++) { + int status = parse_json_value(YAJL_GET_ARRAY(values_val)->values[i], + YAJL_GET_ARRAY(dstypes_val)->values[i], + &vl->values[i]); + if (status != 0) { + sfree(vl->values); + vl->values = NULL; + vl->values_len = 0; + return status; + } + } + + return 0; +} /* }}} int parse_json_values */ + +static int parse_json_string(yajl_val root, char *key, char *buffer, + size_t buffer_size) /* {{{ */ +{ + char const *path[] = {key, NULL}; + yajl_val string_val = yajl_tree_get(root, path, yajl_t_string); + + if (!YAJL_IS_STRING(string_val)) + return EINVAL; + + sstrncpy(buffer, YAJL_GET_STRING(string_val), buffer_size); + return 0; +} /* }}} int parse_json_string */ + +static int parse_json_time(yajl_val root, char *key, cdtime_t *t) /* {{{ */ +{ + char const *path[] = {key, NULL}; + yajl_val double_val = yajl_tree_get(root, path, yajl_t_number); + + if (!YAJL_IS_DOUBLE(double_val)) + return EINVAL; + + *t = DOUBLE_TO_CDTIME_T(YAJL_GET_DOUBLE(double_val)); + return 0; +} /* }}} int parse_json_time */ + +static int parse_json_vl(yajl_val root, value_list_t *vl) /* {{{ */ +{ + int status; + + memset(vl, 0, sizeof(*vl)); + + status = parse_json_string(root, "host", vl->host, sizeof(vl->host)); + if (status != 0) { + NOTICE("parse_json: parsing key \"host\" failed"); + return status; + } + + status = parse_json_string(root, "plugin", vl->plugin, sizeof(vl->plugin)); + if (status != 0) { + NOTICE("parse_json: parsing key \"plugin\" failed"); + return status; + } + parse_json_string(root, "plugin_instance", vl->plugin_instance, + sizeof(vl->plugin_instance)); + + status = parse_json_string(root, "type", vl->type, sizeof(vl->type)); + if (status != 0) { + NOTICE("parse_json: parsing key \"type\" failed"); + return status; + } + parse_json_string(root, "type_instance", vl->type_instance, + sizeof(vl->type_instance)); + + status = parse_json_time(root, "time", &vl->time); + if (status != 0) { + NOTICE("parse_json: parsing key \"time\" failed"); + return status; + } + + status = parse_json_time(root, "interval", &vl->interval); + if (status != 0) { + NOTICE("parse_json: parsing key \"interval\" failed"); + return status; + } + + status = parse_json_values(root, vl); + if (status != 0) { + return status; + } + + return 0; +} /* }}} int parse_json_vl */ + +int parse_json(char const *json, value_list_t ***ret_vls, + size_t *ret_vls_num) /* {{{ */ +{ + yajl_val root; + char errbuf[1024]; + size_t i; + + value_list_t **vls = NULL; + size_t vls_num = 0; + + root = yajl_tree_parse(json, errbuf, sizeof(errbuf)); + if (root == NULL) { + ERROR("parse_json: yajl_tree_parse failed: %s", errbuf); + return -1; + } + + if (!YAJL_IS_ARRAY(root)) { + NOTICE("parse_json: root is not an array"); + return -1; + } + + for (i = 0; i < YAJL_GET_ARRAY(root)->len; i++) { + yajl_val vl_json = YAJL_GET_ARRAY(root)->values[i]; + value_list_t *vl; + + vl = malloc(sizeof(*vl)); + if (vl == NULL) + continue; + + int status = parse_json_vl(vl_json, vl); + if (status != 0) { + sfree(vl); + continue; + } + + value_list_t **tmp = realloc(vls, sizeof(*vls) * (vls_num + 1)); + if (tmp == NULL) { + ERROR("parse_json: realloc failed"); + sfree(vl->values); + sfree(vl); + continue; + } + vls = tmp; + + vls[vls_num] = vl; + vls_num++; + } + + *ret_vls = vls; + *ret_vls_num = vls_num; + return 0; +} /* }}} int parse_json */ diff --git a/src/utils_parse_json.h b/src/utils_parse_json.h new file mode 100644 index 00000000..cb56d648 --- /dev/null +++ b/src/utils_parse_json.h @@ -0,0 +1,38 @@ +/** + * collectd - src/utils_parse_json.h + * Copyright (C) 2018 Florian Forster + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Florian Forster + **/ + +#ifndef UTILS_PARSE_JSON_H +#define UTILS_PARSE_JSON_H 1 + +#include "collectd.h" + +#include "plugin.h" + +int parse_json(char const *json, value_list_t ***vls, size_t *vls_num); + +#endif diff --git a/src/utils_parse_json_test.c b/src/utils_parse_json_test.c new file mode 100644 index 00000000..761e0c05 --- /dev/null +++ b/src/utils_parse_json_test.c @@ -0,0 +1,190 @@ +/** + * collectd - src/tests/utils_parse_json_test.c + * Copyright (C) 2018 Florian Forster + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Florian Forster + **/ + +#include "utils_parse_json.h" +#include "testing.h" + +DEF_TEST(single_value) /* {{{ */ +{ + char const *json = + "[" + /* gauge */ + "{\"host\":\"example.com\",\"plugin\":\"test\",\"type\":\"answer\"," + "\"time\":1434357493.398,\"interval\":10.000," + "\"dstypes\":[\"gauge\"],\"values\":[42.0]}," + /* gauge (NaN) */ + "{\"host\":\"example.com\",\"plugin\":\"test\",\"type\":\"gauge\"," + "\"time\":1434357493.398,\"interval\":10.000," + "\"dstypes\":[\"gauge\"],\"values\":[null]}," + /* derive */ + "{\"host\":\"example.com\",\"plugin\":\"test\",\"type\":\"total_things\"," + "\"plugin_instance\":\"foo\",\"type_instance\":\"bar\"," + "\"time\":1434357493.398,\"interval\":10.000," + "\"dstypes\":[\"derive\"],\"values\":[31337]}" + "]"; + + value_list_t **vls = NULL; + size_t vls_num = 0; + + CHECK_ZERO(parse_json(json, &vls, &vls_num)); + OK(vls_num == 3); + if (vls_num != 3) + return (-1); + + value_list_t *vl = vls[0]; + EXPECT_EQ_STR("example.com", vl->host); + EXPECT_EQ_STR("test", vl->plugin); + EXPECT_EQ_STR("", vl->plugin_instance); + EXPECT_EQ_STR("answer", vl->type); + EXPECT_EQ_STR("", vl->type_instance); + OK(vl->time == 1540129631229236480); + OK(vl->interval == 10737418240); + OK(vl->values_len == 1); + OK(vl->values[0].gauge == 42.0); + + vl = vls[1]; + OK(vl->values_len == 1); + OK(isnan(vl->values[0].gauge)); + + vl = vls[2]; + EXPECT_EQ_STR("example.com", vl->host); + EXPECT_EQ_STR("test", vl->plugin); + EXPECT_EQ_STR("foo", vl->plugin_instance); + EXPECT_EQ_STR("total_things", vl->type); + EXPECT_EQ_STR("bar", vl->type_instance); + OK(vl->time == 1540129631229236480); + OK(vl->interval == 10737418240); + OK(vl->values_len == 1); + OK(vl->values[0].derive == 31337); + + free(vls[0]->values); + free(vls[0]); + free(vls[1]->values); + free(vls[1]); + free(vls); + return (0); +} /* }}} single_value */ + +DEF_TEST(multi_value) /* {{{ */ +{ + char const *json = + "[" + "{\"host\":\"example.com\",\"plugin\":\"load\",\"type\":\"load\"," + "\"time\":1434357493,\"interval\":10," + "\"dstypes\":[\"gauge\",\"gauge\",\"gauge\"],\"values\":[1.12,0.56,0.64]}" + "]"; + + value_list_t **vls = NULL; + size_t vls_num = 0; + + CHECK_ZERO(parse_json(json, &vls, &vls_num)); + OK(vls_num == 1); + + value_list_t *vl = vls[0]; + EXPECT_EQ_STR("example.com", vl->host); + EXPECT_EQ_STR("load", vl->plugin); + EXPECT_EQ_STR("load", vl->type); + OK(vl->time == 1540129630801887232); + OK(vl->interval == 10737418240); + + OK(vl->values_len == 3); + OK(vl->values[0].gauge == 1.12); + OK(vl->values[1].gauge == 0.56); + OK(vl->values[2].gauge == 0.64); + + free(vl->values); + free(vl); + free(vls); + return (0); +} /* }}} multi_value */ + +DEF_TEST(failures) /* {{{ */ +{ + char const *json = + "[" + /* host missing */ + "{\"plugin\":\"test\",\"type\":\"answer\"," + "\"time\":1434357493.398,\"interval\":10.000," + "\"dstypes\":[\"gauge\"],\"values\":[42.0]}," + /* plugin missing */ + "{\"host\":\"example.com\",\"type\":\"answer\"," + "\"time\":1434357493.398,\"interval\":10.000," + "\"dstypes\":[\"gauge\"],\"values\":[42.0]}," + /* type missing */ + "{\"host\":\"example.com\",\"plugin\":\"test\"," + "\"time\":1434357493.398,\"interval\":10.000," + "\"dstypes\":[\"gauge\"],\"values\":[42.0]}," + /* time missing */ + "{\"host\":\"example.com\",\"plugin\":\"test\",\"type\":\"answer\"," + "\"interval\":10.000," + "\"dstypes\":[\"gauge\"],\"values\":[42.0]}," + /* interval missing */ + "{\"host\":\"example.com\",\"plugin\":\"test\",\"type\":\"answer\"," + "\"time\":1434357493.398," + "\"dstypes\":[\"gauge\"],\"values\":[42.0]}," + /* derive -> floating point mismatch */ + "{\"host\":\"example.com\",\"plugin\":\"test\",\"type\":\"answer\"," + "\"time\":1434357493.398,\"interval\":10.000," + "\"dstypes\":[\"derive\"],\"values\":[42.0]}," + /* len(dstypes) != len(values) */ + "{\"host\":\"example.com\",\"plugin\":\"test\",\"type\":\"answer\"," + "\"time\":1434357493.398,\"interval\":10.000," + "\"dstypes\":[\"gauge\"],\"values\":[42.0, 23.0]}," + /* type mismatch: got boolean, want string */ + "{\"host\":true,\"plugin\":\"test\",\"type\":\"answer\"," + "\"time\":1434357493.398,\"interval\":10.000," + "\"dstypes\":[\"gauge\"],\"values\":[42.0]}," + /* type mismatch: got boolean, want number */ + "{\"host\":\"example.com\",\"plugin\":\"test\",\"type\":\"answer\"," + "\"time\":true,\"interval\":10.000," + "\"dstypes\":[\"gauge\"],\"values\":[42.0]}," + /* type mismatch: got string/number, want array/array */ + "{\"host\":\"example.com\",\"plugin\":\"test\",\"type\":\"answer\"," + "\"time\":1434357493.398,\"interval\":10.000," + "\"dstypes\":\"gauge\",\"values\":42.0}" + "]"; + + value_list_t **vls = NULL; + size_t vls_num = 0; + + CHECK_ZERO(parse_json(json, &vls, &vls_num)); + CHECK_ZERO(vls_num); + + return (0); +} /* }}} failures */ + +int main(int argc, char **argv) /* {{{ */ +{ + RUN_TEST(single_value); + RUN_TEST(multi_value); + RUN_TEST(failures); + + END_TEST; +} /* }}} int main */ + +/* vim: set sw=2 sts=2 et fdm=marker : */ -- 2.11.0