mirror of
https://github.com/netdata/netdata.git
synced 2025-04-12 08:48:12 +00:00

This PR was created to fix #3414, here I am completing the job initiated by Christopher, among the newest features that we are bring we have JSON inside the core - We are bringing to the core the capacity to work with JSON files, this is available either using the JSON-C library case it is present in the system or using JSMN library that was incorporated to our core. The preference is to have JSON-C, because it is a more complete library, but case the user does not have the library installed we are keeping the JSMN for we do not lose the feature. Health LIST - We are bringing more one command to the Health API, now with the LIST it is possible to get in JSON format the alarms active with Netdata. Health reorganized - Previously we had duplicated code in different files, this PR is fixing this (Thanks @cakrit !), the Health is now better organized. Removing memory leak - The first implementation of the json.c was creating SILENCERS without to link it in anywhere. Now it has been linked properly. Script updated - We are bringing some changes to the script that tests the Health. This PR also fixes the race condition created by the previous new position of the SILENCERS creation, I had to move it to daemon/main.c, because after various tests, it was confirmed that the error could happen in different parts of the code, case it was not initialized before the threads starts. Component Name health directory health-cmd Additional Information Fixes #6356 and #3414
546 lines
No EOL
14 KiB
C
546 lines
No EOL
14 KiB
C
#include "jsmn.h"
|
|
#include "../libnetdata.h"
|
|
#include "json.h"
|
|
#include "libnetdata/libnetdata.h"
|
|
#include "../../health/health.h"
|
|
|
|
#define JSON_TOKENS 1024
|
|
|
|
int json_tokens = JSON_TOKENS;
|
|
|
|
/**
|
|
* Json Tokenise
|
|
*
|
|
* Map the string given inside tokens.
|
|
*
|
|
* @param js is the string used to create the tokens
|
|
* @param len is the string length
|
|
* @param count the number of tokens present in the string
|
|
*
|
|
* @return it returns the json parsed in tokens
|
|
*/
|
|
#ifdef ENABLE_JSONC
|
|
json_object *json_tokenise(char *js) {
|
|
if(!js) {
|
|
error("JSON: json string is empty.");
|
|
return NULL;
|
|
}
|
|
|
|
json_object *token = json_tokener_parse(js);
|
|
if(!token) {
|
|
error("JSON: Invalid json string.");
|
|
return NULL;
|
|
}
|
|
|
|
return token;
|
|
}
|
|
#else
|
|
jsmntok_t *json_tokenise(char *js, size_t len, size_t *count)
|
|
{
|
|
int n = json_tokens;
|
|
if(!js || !len) {
|
|
error("JSON: json string is empty.");
|
|
return NULL;
|
|
}
|
|
|
|
jsmn_parser parser;
|
|
jsmn_init(&parser);
|
|
|
|
jsmntok_t *tokens = mallocz(sizeof(jsmntok_t) * n);
|
|
if(!tokens) return NULL;
|
|
|
|
int ret = jsmn_parse(&parser, js, len, tokens, n);
|
|
while (ret == JSMN_ERROR_NOMEM) {
|
|
n *= 2;
|
|
jsmntok_t *new = reallocz(tokens, sizeof(jsmntok_t) * n);
|
|
if(!new) {
|
|
freez(tokens);
|
|
return NULL;
|
|
}
|
|
tokens = new;
|
|
ret = jsmn_parse(&parser, js, len, tokens, n);
|
|
}
|
|
|
|
if (ret == JSMN_ERROR_INVAL) {
|
|
error("JSON: Invalid json string.");
|
|
freez(tokens);
|
|
return NULL;
|
|
}
|
|
else if (ret == JSMN_ERROR_PART) {
|
|
error("JSON: Truncated JSON string.");
|
|
freez(tokens);
|
|
return NULL;
|
|
}
|
|
|
|
if(count) *count = (size_t)ret;
|
|
|
|
if(json_tokens < n) json_tokens = n;
|
|
return tokens;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Callback Print
|
|
*
|
|
* Set callback print case necesary and wrinte an information inside a buffer to write in the log.
|
|
*
|
|
* @param e a pointer for a structure that has the complete information about json structure.
|
|
*
|
|
* @return It always return 0
|
|
*/
|
|
int json_callback_print(JSON_ENTRY *e)
|
|
{
|
|
BUFFER *wb=buffer_create(300);
|
|
|
|
buffer_sprintf(wb,"%s = ", e->name);
|
|
char txt[50];
|
|
switch(e->type) {
|
|
case JSON_OBJECT:
|
|
e->callback_function = json_callback_print;
|
|
buffer_strcat(wb,"OBJECT");
|
|
break;
|
|
|
|
case JSON_ARRAY:
|
|
e->callback_function = json_callback_print;
|
|
sprintf(txt,"ARRAY[%lu]", e->data.items);
|
|
buffer_strcat(wb, txt);
|
|
break;
|
|
|
|
case JSON_STRING:
|
|
buffer_strcat(wb, e->data.string);
|
|
break;
|
|
|
|
case JSON_NUMBER:
|
|
sprintf(txt,"%Lf", e->data.number);
|
|
buffer_strcat(wb,txt);
|
|
|
|
break;
|
|
|
|
case JSON_BOOLEAN:
|
|
buffer_strcat(wb, e->data.boolean?"TRUE":"FALSE");
|
|
break;
|
|
|
|
case JSON_NULL:
|
|
buffer_strcat(wb,"NULL");
|
|
break;
|
|
}
|
|
info("JSON: %s", buffer_tostring(wb));
|
|
buffer_free(wb);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* JSONC Set String
|
|
*
|
|
* Set the string value of the structure JSON_ENTRY.
|
|
*
|
|
* @param e the output structure
|
|
*/
|
|
static inline void json_jsonc_set_string(JSON_ENTRY *e,char *key,const char *value) {
|
|
size_t length = strlen(key);
|
|
e->type = JSON_STRING;
|
|
memcpy(e->name,key,length);
|
|
e->name[length] = 0x00;
|
|
e->data.string = (char *) value;
|
|
}
|
|
|
|
|
|
#ifdef ENABLE_JSONC
|
|
/**
|
|
* JSONC set Boolean
|
|
*
|
|
* Set the boolean value of the structure JSON_ENTRY
|
|
*
|
|
* @param e the output structure
|
|
* @param value the input value
|
|
*/
|
|
static inline void json_jsonc_set_boolean(JSON_ENTRY *e,int value) {
|
|
e->type = JSON_BOOLEAN;
|
|
e->data.boolean = value;
|
|
}
|
|
|
|
/**
|
|
* Parse Array
|
|
*
|
|
* Parse the array object.
|
|
*
|
|
* @param ptr the pointer for the object that we will parse.
|
|
* @param callback_data additional data to be used together the callback function
|
|
* @param callback_function function used to create a silencer.
|
|
*/
|
|
static inline void json_jsonc_parse_array(json_object *ptr, void *callback_data,int (*callback_function)(struct json_entry *)) {
|
|
int end = json_object_array_length(ptr);
|
|
JSON_ENTRY e;
|
|
|
|
if(end) {
|
|
int i;
|
|
i = 0;
|
|
|
|
enum json_type type;
|
|
do {
|
|
json_object *jvalue = json_object_array_get_idx(ptr, i);
|
|
if(jvalue) {
|
|
e.callback_data = callback_data;
|
|
e.type = JSON_OBJECT;
|
|
callback_function(&e);
|
|
json_object_object_foreach(jvalue, key, val) {
|
|
type = json_object_get_type(val);
|
|
if (type == json_type_array) {
|
|
e.type = JSON_ARRAY;
|
|
json_jsonc_parse_array(val, callback_data, callback_function);
|
|
} else if (type == json_type_object) {
|
|
json_walk(val,callback_data,callback_function);
|
|
} else if (type == json_type_string) {
|
|
json_jsonc_set_string(&e,key,json_object_get_string(val));
|
|
callback_function(&e);
|
|
} else if (type == json_type_boolean) {
|
|
json_jsonc_set_boolean(&e,json_object_get_boolean(val));
|
|
callback_function(&e);
|
|
}
|
|
}
|
|
}
|
|
|
|
} while (++i < end);
|
|
}
|
|
}
|
|
#else
|
|
|
|
/**
|
|
* Walk string
|
|
*
|
|
* Set JSON_ENTRY to string and map the values from jsmntok_t.
|
|
*
|
|
* @param js the original string
|
|
* @param t the tokens
|
|
* @param start the first position
|
|
* @param e the output structure.
|
|
*
|
|
* @return It always return 1
|
|
*/
|
|
size_t json_walk_string(char *js, jsmntok_t *t, size_t start, JSON_ENTRY *e)
|
|
{
|
|
char old = js[t[start].end];
|
|
js[t[start].end] = '\0';
|
|
e->original_string = &js[t[start].start];
|
|
|
|
e->type = JSON_STRING;
|
|
e->data.string = e->original_string;
|
|
if(e->callback_function) e->callback_function(e);
|
|
js[t[start].end] = old;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Walk Primitive
|
|
*
|
|
* Define the data type of the string
|
|
*
|
|
* @param js the original string
|
|
* @param t the tokens
|
|
* @param start the first position
|
|
* @param e the output structure.
|
|
*
|
|
* @return It always return 1
|
|
*/
|
|
size_t json_walk_primitive(char *js, jsmntok_t *t, size_t start, JSON_ENTRY *e)
|
|
{
|
|
char old = js[t[start].end];
|
|
js[t[start].end] = '\0';
|
|
e->original_string = &js[t[start].start];
|
|
|
|
switch(e->original_string[0]) {
|
|
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
|
|
case '8': case '9': case '-': case '.':
|
|
e->type = JSON_NUMBER;
|
|
e->data.number = strtold(e->original_string, NULL);
|
|
break;
|
|
|
|
case 't': case 'T':
|
|
e->type = JSON_BOOLEAN;
|
|
e->data.boolean = 1;
|
|
break;
|
|
|
|
case 'f': case 'F':
|
|
e->type = JSON_BOOLEAN;
|
|
e->data.boolean = 0;
|
|
break;
|
|
|
|
case 'n': case 'N':
|
|
default:
|
|
e->type = JSON_NULL;
|
|
break;
|
|
}
|
|
if(e->callback_function) e->callback_function(e);
|
|
js[t[start].end] = old;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Array
|
|
*
|
|
* Measure the array length
|
|
*
|
|
* @param js the original string
|
|
* @param t the tokens
|
|
* @param nest the length of structure t
|
|
* @param start the first position
|
|
* @param e the output structure.
|
|
*
|
|
* @return It returns the array length
|
|
*/
|
|
size_t json_walk_array(char *js, jsmntok_t *t, size_t nest, size_t start, JSON_ENTRY *e)
|
|
{
|
|
JSON_ENTRY ne = {
|
|
.name = "",
|
|
.fullname = "",
|
|
.callback_data = NULL,
|
|
.callback_function = NULL
|
|
};
|
|
|
|
char old = js[t[start].end];
|
|
js[t[start].end] = '\0';
|
|
ne.original_string = &js[t[start].start];
|
|
|
|
memcpy(&ne, e, sizeof(JSON_ENTRY));
|
|
ne.type = JSON_ARRAY;
|
|
ne.data.items = t[start].size;
|
|
ne.callback_function = NULL;
|
|
ne.name[0]='\0';
|
|
ne.fullname[0]='\0';
|
|
if(e->callback_function) e->callback_function(&ne);
|
|
js[t[start].end] = old;
|
|
|
|
size_t i, init = start, size = t[start].size;
|
|
|
|
start++;
|
|
for(i = 0; i < size ; i++) {
|
|
ne.pos = i;
|
|
if (!e->name || !e->fullname || strlen(e->name) > JSON_NAME_LEN - 24 || strlen(e->fullname) > JSON_FULLNAME_LEN -24) {
|
|
info("JSON: JSON walk_array ignoring element with name:%s fullname:%s",e->name, e->fullname);
|
|
continue;
|
|
}
|
|
sprintf(ne.name, "%s[%lu]", e->name, i);
|
|
sprintf(ne.fullname, "%s[%lu]", e->fullname, i);
|
|
|
|
switch(t[start].type) {
|
|
case JSMN_PRIMITIVE:
|
|
start += json_walk_primitive(js, t, start, &ne);
|
|
break;
|
|
|
|
case JSMN_OBJECT:
|
|
start += json_walk_object(js, t, nest + 1, start, &ne);
|
|
break;
|
|
|
|
case JSMN_ARRAY:
|
|
start += json_walk_array(js, t, nest + 1, start, &ne);
|
|
break;
|
|
|
|
case JSMN_STRING:
|
|
start += json_walk_string(js, t, start, &ne);
|
|
break;
|
|
}
|
|
}
|
|
return start - init;
|
|
}
|
|
|
|
/**
|
|
* Object
|
|
*
|
|
* Measure the Object length
|
|
*
|
|
* @param js the original string
|
|
* @param t the tokens
|
|
* @param nest the length of structure t
|
|
* @param start the first position
|
|
* @param e the output structure.
|
|
*
|
|
* @return It returns the Object length
|
|
*/
|
|
size_t json_walk_object(char *js, jsmntok_t *t, size_t nest, size_t start, JSON_ENTRY *e)
|
|
{
|
|
JSON_ENTRY ne = {
|
|
.name = "",
|
|
.fullname = "",
|
|
.callback_data = NULL,
|
|
.callback_function = NULL
|
|
};
|
|
|
|
char old = js[t[start].end];
|
|
js[t[start].end] = '\0';
|
|
ne.original_string = &js[t[start].start];
|
|
memcpy(&ne, e, sizeof(JSON_ENTRY));
|
|
ne.type = JSON_OBJECT;
|
|
ne.callback_function = NULL;
|
|
if(e->callback_function) e->callback_function(&ne);
|
|
js[t[start].end] = old;
|
|
|
|
int key = 1;
|
|
size_t i, init = start, size = t[start].size;
|
|
|
|
start++;
|
|
for(i = 0; i < size ; i++) {
|
|
switch(t[start].type) {
|
|
case JSMN_PRIMITIVE:
|
|
start += json_walk_primitive(js, t, start, &ne);
|
|
key = 1;
|
|
break;
|
|
|
|
case JSMN_OBJECT:
|
|
start += json_walk_object(js, t, nest + 1, start, &ne);
|
|
key = 1;
|
|
break;
|
|
|
|
case JSMN_ARRAY:
|
|
start += json_walk_array(js, t, nest + 1, start, &ne);
|
|
key = 1;
|
|
break;
|
|
|
|
case JSMN_STRING:
|
|
default:
|
|
if(key) {
|
|
int len = t[start].end - t[start].start;
|
|
if (unlikely(len>JSON_NAME_LEN)) len=JSON_NAME_LEN;
|
|
strncpy(ne.name, &js[t[start].start], len);
|
|
ne.name[len] = '\0';
|
|
len=strlen(e->fullname) + strlen(e->fullname[0]?".":"") + strlen(ne.name);
|
|
char *c = mallocz((len+1)*sizeof(char));
|
|
sprintf(c,"%s%s%s", e->fullname, e->fullname[0]?".":"", ne.name);
|
|
if (unlikely(len>JSON_FULLNAME_LEN)) len=JSON_FULLNAME_LEN;
|
|
strncpy(ne.fullname, c, len);
|
|
freez(c);
|
|
start++;
|
|
key = 0;
|
|
}
|
|
else {
|
|
start += json_walk_string(js, t, start, &ne);
|
|
key = 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return start - init;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Tree
|
|
*
|
|
* Call the correct walk function according its type.
|
|
*
|
|
* @param t the json object to work
|
|
* @param callback_data additional data to be used together the callback function
|
|
* @param callback_function function used to create a silencer.
|
|
*
|
|
* @return It always return 1
|
|
*/
|
|
#ifdef ENABLE_JSONC
|
|
size_t json_walk(json_object *t, void *callback_data, int (*callback_function)(struct json_entry *)) {
|
|
JSON_ENTRY e;
|
|
|
|
e.callback_data = callback_data;
|
|
enum json_type type;
|
|
json_object_object_foreach(t, key, val) {
|
|
type = json_object_get_type(val);
|
|
if (type == json_type_array) {
|
|
e.type = JSON_ARRAY;
|
|
json_jsonc_parse_array(val,NULL,health_silencers_json_read_callback);
|
|
} else if (type == json_type_object) {
|
|
e.type = JSON_OBJECT;
|
|
} else if (type == json_type_string) {
|
|
json_jsonc_set_string(&e,key,json_object_get_string(val));
|
|
callback_function(&e);
|
|
} else if (type == json_type_boolean) {
|
|
json_jsonc_set_boolean(&e,json_object_get_boolean(val));
|
|
callback_function(&e);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
#else
|
|
/**
|
|
* Tree
|
|
*
|
|
* Call the correct walk function according its type.
|
|
*
|
|
* @param js the original string
|
|
* @param t the tokens
|
|
* @param callback_data additional data to be used together the callback function
|
|
* @param callback_function function used to create a silencer.
|
|
*
|
|
* @return It always return 1
|
|
*/
|
|
size_t json_walk_tree(char *js, jsmntok_t *t, void *callback_data, int (*callback_function)(struct json_entry *))
|
|
{
|
|
JSON_ENTRY e = {
|
|
.name = "",
|
|
.fullname = "",
|
|
.callback_data = callback_data,
|
|
.callback_function = callback_function
|
|
};
|
|
|
|
switch (t[0].type) {
|
|
case JSMN_OBJECT:
|
|
e.type = JSON_OBJECT;
|
|
json_walk_object(js, t, 0, 0, &e);
|
|
break;
|
|
|
|
case JSMN_ARRAY:
|
|
e.type = JSON_ARRAY;
|
|
json_walk_array(js, t, 0, 0, &e);
|
|
break;
|
|
|
|
case JSMN_PRIMITIVE:
|
|
case JSMN_STRING:
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* JSON Parse
|
|
*
|
|
* Parse the json message with the callback function
|
|
*
|
|
* @param js the string that the callback function will parse
|
|
* @param callback_data additional data to be used together the callback function
|
|
* @param callback_function function used to create a silencer.
|
|
*
|
|
* @return JSON_OK case everything happend as expected, JSON_CANNOT_PARSE case there were errors in the
|
|
* parsing procces and JSON_CANNOT_DOWNLOAD case the string given(js) is NULL.
|
|
*/
|
|
int json_parse(char *js, void *callback_data, int (*callback_function)(JSON_ENTRY *))
|
|
{
|
|
if(js) {
|
|
#ifdef ENABLE_JSONC
|
|
json_object *tokens = json_tokenise(js);
|
|
#else
|
|
size_t count;
|
|
jsmntok_t *tokens = json_tokenise(js, strlen(js), &count);
|
|
#endif
|
|
|
|
if(tokens) {
|
|
#ifdef ENABLE_JSONC
|
|
json_walk(tokens, callback_data, callback_function);
|
|
json_object_put(tokens);
|
|
#else
|
|
json_walk_tree(js, tokens, callback_data, callback_function);
|
|
freez(tokens);
|
|
#endif
|
|
return JSON_OK;
|
|
}
|
|
|
|
return JSON_CANNOT_PARSE;
|
|
}
|
|
|
|
return JSON_CANNOT_DOWNLOAD;
|
|
}
|
|
|
|
/*
|
|
int json_test(char *str)
|
|
{
|
|
return json_parse(str, NULL, json_callback_print);
|
|
}
|
|
*/ |