mirror of
https://github.com/netdata/netdata.git
synced 2025-04-05 22:15:32 +00:00

* rrdset - in progress * rrdset optimal constructor; rrdset conflict * rrdset final touches * re-organization of rrdset object members * prevent use-after-free * dictionary dfe supports also counting of iterations * rrddim managed by dictionary * rrd.h cleanup * DICTIONARY_ITEM now is referencing actual dictionary items in the code * removed rrdset linked list * Revert "removed rrdset linked list" This reverts commit 690d6a588b4b99619c2c5e10f84e8f868ae6def5. * removed rrdset linked list * added comments * Switch chart uuid to static allocation in rrdset Remove unused functions * rrdset_archive() and friends... * always create rrdfamily * enable ml_free_dimension * rrddim_foreach done with dfe * most custom rrddim loops replaced with rrddim_foreach * removed accesses to rrddim->dimensions * removed locks that are no longer needed * rrdsetvar is now managed by the dictionary * set rrdset is rrdsetvar, fixes https://github.com/netdata/netdata/pull/13646#issuecomment-1242574853 * conflict callback of rrdsetvar now properly checks if it has to reset the variable * dictionary registered callbacks accept as first parameter the DICTIONARY_ITEM * dictionary dfe now uses internal counter to report; avoided excess variables defined with dfe * dictionary walkthrough callbacks get dictionary acquired items * dictionary reference counters that can be dupped from zero * added advanced functions for get and del * rrdvar managed by dictionaries * thread safety for rrdsetvar * faster rrdvar initialization * rrdvar string lengths should match in all add, del, get functions * rrdvar internals hidden from the rest of the world * rrdvar is now acquired throughout netdata * hide the internal structures of rrdsetvar * rrdsetvar is now acquired through out netdata * rrddimvar managed by dictionary; rrddimvar linked list removed; rrddimvar structures hidden from the rest of netdata * better error handling * dont create variables if not initialized for health * dont create variables if not initialized for health again * rrdfamily is now managed by dictionaries; references of it are acquired dictionary items * type checking on acquired objects * rrdcalc renaming of functions * type checking for rrdfamily_acquired * rrdcalc managed by dictionaries * rrdcalc double free fix * host rrdvars is always needed * attempt to fix deadlock 1 * attempt to fix deadlock 2 * Remove unused variable * attempt to fix deadlock 3 * snprintfz * rrdcalc index in rrdset fix * Stop storing active charts and computing chart hashes * Remove store active chart function * Remove compute chart hash function * Remove sql_store_chart_hash function * Remove store_active_dimension function * dictionary delayed destruction * formatting and cleanup * zero dictionary base on rrdsetvar * added internal error to log delayed destructions of dictionaries * typo in rrddimvar * added debugging info to dictionary * debug info * fix for rrdcalc keys being empty * remove forgotten unlock * remove deadlock * Switch to metadata version 5 and drop chart_hash chart_hash_map chart_active dimension_active v_chart_hash * SQL cosmetic changes * do not busy wait while destroying a referenced dictionary * remove deadlock * code cleanup; re-organization; * fast cleanup and flushing of dictionaries * number formatting fixes * do not delete configured alerts when archiving a chart * rrddim obsolete linked list management outside dictionaries * removed duplicate contexts call * fix crash when rrdfamily is not initialized * dont keep rrddimvar referenced * properly cleanup rrdvar * removed some locks * Do not attempt to cleanup chart_hash / chart_hash_map * rrdcalctemplate managed by dictionary * register callbacks on the right dictionary * removed some more locks * rrdcalc secondary index replaced with linked-list; rrdcalc labels updates are now executed by health thread * when looking up for an alarm look using both chart id and chart name * host initialization a bit more modular * init rrdlabels on host update * preparation for dictionary views * improved comment * unused variables without internal checks * service threads isolation and worker info * more worker info in service thread * thread cancelability debugging with internal checks * strings data races addressed; fixes https://github.com/netdata/netdata/issues/13647 * dictionary modularization * Remove unused SQL statement definition * unit-tested thread safety of dictionaries; removed data race conditions on dictionaries and strings; dictionaries now can detect if the caller is holds a write lock and automatically all the calls become their unsafe versions; all direct calls to unsafe version is eliminated * remove worker_is_idle() from the exit of service functions, because we lose the lock time between loops * rewritten dictionary to have 2 separate locks, one for indexing and another for traversal * Update collectors/cgroups.plugin/sys_fs_cgroup.c Co-authored-by: Vladimir Kobal <vlad@prokk.net> * Update collectors/cgroups.plugin/sys_fs_cgroup.c Co-authored-by: Vladimir Kobal <vlad@prokk.net> * Update collectors/proc.plugin/proc_net_dev.c Co-authored-by: Vladimir Kobal <vlad@prokk.net> * fix memory leak in rrdset cache_dir * minor dictionary changes * dont use index locks in single threaded * obsolete dict option * rrddim options and flags separation; rrdset_done() optimization to keep array of reference pointers to rrddim; * fix jump on uninitialized value in dictionary; remove double free of cache_dir * addressed codacy findings * removed debugging code * use the private refcount on dictionaries * make dictionary item desctructors work on dictionary destruction; strictier control on dictionary API; proper cleanup sequence on rrddim; * more dictionary statistics * global statistics about dictionary operations, memory, items, callbacks * dictionary support for views - missing the public API * removed warning about unused parameter * chart and context name for cloud * chart and context name for cloud, again * dictionary statistics fixed; first implementation of dictionary views - not currently used * only the master can globally delete an item * context needs netdata prefix * fix context and chart it of spins * fix for host variables when health is not enabled * run garbage collector on item insert too * Fix info message; remove extra "using" * update dict unittest for new placement of garbage collector * we need RRDHOST->rrdvars for maintaining custom host variables * Health initialization needs the host->host_uuid * split STRING to its own files; no code changes other than that * initialize health unconditionally * unit tests do not pollute the global scope with their variables * Skip initialization when creating archived hosts on startup. When a child connects it will initialize properly Co-authored-by: Stelios Fragkakis <52996999+stelfrag@users.noreply.github.com> Co-authored-by: Vladimir Kobal <vlad@prokk.net>
404 lines
14 KiB
C
404 lines
14 KiB
C
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include "json_wrapper.h"
|
|
|
|
struct value_output {
|
|
int c;
|
|
BUFFER *wb;
|
|
};
|
|
|
|
static int value_list_output_callback(const DICTIONARY_ITEM *item __maybe_unused, void *entry, void *data) {
|
|
struct value_output *ap = (struct value_output *)data;
|
|
BUFFER *wb = ap->wb;
|
|
char *output = (char *) entry;
|
|
if(ap->c) buffer_strcat(wb, ",");
|
|
buffer_strcat(wb, output);
|
|
(ap->c)++;
|
|
return 0;
|
|
}
|
|
|
|
static int fill_formatted_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) {
|
|
(void)ls;
|
|
DICTIONARY *dict = (DICTIONARY *)data;
|
|
char n[RRD_ID_LENGTH_MAX * 2 + 2];
|
|
char output[RRD_ID_LENGTH_MAX * 2 + 8];
|
|
char v[RRD_ID_LENGTH_MAX * 2 + 1];
|
|
|
|
sanitize_json_string(v, (char *)value, RRD_ID_LENGTH_MAX * 2);
|
|
int len = snprintfz(output, RRD_ID_LENGTH_MAX * 2 + 7, "[\"%s\", \"%s\"]", name, v);
|
|
snprintfz(n, RRD_ID_LENGTH_MAX * 2, "%s:%s", name, v);
|
|
dictionary_set(dict, n, output, len + 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, int string_value,
|
|
RRDR_GROUPING group_method, QUERY_PARAMS *rrdset_query_data)
|
|
{
|
|
struct context_param *context_param_list = rrdset_query_data->context_param_list;
|
|
char *chart_label_key = rrdset_query_data->chart_label_key;
|
|
|
|
RRDDIM *temp_rd = context_param_list ? context_param_list->rd : NULL;
|
|
int should_lock = (!context_param_list || !(context_param_list->flags & CONTEXT_FLAGS_ARCHIVE));
|
|
uint8_t context_mode = (!context_param_list || (context_param_list->flags & CONTEXT_FLAGS_CONTEXT));
|
|
|
|
if (should_lock)
|
|
rrdset_check_rdlock(r->st);
|
|
|
|
long rows = rrdr_rows(r);
|
|
long c, i;
|
|
RRDDIM *rd;
|
|
|
|
//info("JSONWRAPPER(): %s: BEGIN", r->st->id);
|
|
char kq[2] = "", // key quote
|
|
sq[2] = ""; // string quote
|
|
|
|
if( options & RRDR_OPTION_GOOGLE_JSON ) {
|
|
kq[0] = '\0';
|
|
sq[0] = '\'';
|
|
}
|
|
else {
|
|
kq[0] = '"';
|
|
sq[0] = '"';
|
|
}
|
|
|
|
buffer_sprintf(wb, "{\n"
|
|
" %sapi%s: 1,\n"
|
|
" %sid%s: %s%s%s,\n"
|
|
" %sname%s: %s%s%s,\n"
|
|
" %sview_update_every%s: %d,\n"
|
|
" %supdate_every%s: %d,\n"
|
|
" %sfirst_entry%s: %u,\n"
|
|
" %slast_entry%s: %u,\n"
|
|
" %sbefore%s: %u,\n"
|
|
" %safter%s: %u,\n"
|
|
" %sgroup%s: %s%s%s,\n"
|
|
" %soptions%s: %s"
|
|
, kq, kq
|
|
, kq, kq, sq, context_mode && temp_rd?rrdset_context(r->st):rrdset_id(r->st), sq
|
|
, kq, kq, sq, context_mode && temp_rd?rrdset_context(r->st):rrdset_name(r->st), sq
|
|
, kq, kq, r->update_every
|
|
, kq, kq, r->st->update_every
|
|
, kq, kq, (uint32_t) (context_param_list ? context_param_list->first_entry_t : rrdset_first_entry_t(r->st))
|
|
, kq, kq, (uint32_t) (context_param_list ? context_param_list->last_entry_t : rrdset_last_entry_t(r->st))
|
|
, kq, kq, (uint32_t)r->before
|
|
, kq, kq, (uint32_t)r->after
|
|
, kq, kq, sq, web_client_api_request_v1_data_group_to_string(group_method), sq
|
|
, kq, kq, sq);
|
|
|
|
web_client_api_request_v1_data_options_to_string(wb, r->internal.query_options);
|
|
|
|
buffer_sprintf(wb, "%s,\n %sdimension_names%s: [", sq, kq, kq);
|
|
|
|
for(c = 0, i = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) {
|
|
if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue;
|
|
if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue;
|
|
|
|
if(i) buffer_strcat(wb, ", ");
|
|
buffer_strcat(wb, sq);
|
|
buffer_strcat(wb, rrddim_name(rd));
|
|
buffer_strcat(wb, sq);
|
|
i++;
|
|
}
|
|
if(!i) {
|
|
#ifdef NETDATA_INTERNAL_CHECKS
|
|
error("RRDR is empty for %s (RRDR has %d dimensions, options is 0x%08x)", rrdset_id(r->st), r->d, options);
|
|
#endif
|
|
rows = 0;
|
|
buffer_strcat(wb, sq);
|
|
buffer_strcat(wb, "no data");
|
|
buffer_strcat(wb, sq);
|
|
}
|
|
|
|
buffer_sprintf(wb, "],\n"
|
|
" %sdimension_ids%s: ["
|
|
, kq, kq);
|
|
|
|
for(c = 0, i = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) {
|
|
if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue;
|
|
if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue;
|
|
|
|
if(i) buffer_strcat(wb, ", ");
|
|
buffer_strcat(wb, sq);
|
|
buffer_strcat(wb, rrddim_id(rd));
|
|
buffer_strcat(wb, sq);
|
|
i++;
|
|
}
|
|
if(!i) {
|
|
rows = 0;
|
|
buffer_strcat(wb, sq);
|
|
buffer_strcat(wb, "no data");
|
|
buffer_strcat(wb, sq);
|
|
}
|
|
buffer_strcat(wb, "],\n");
|
|
|
|
if (rrdset_query_data->show_dimensions) {
|
|
buffer_sprintf(wb, " %sfull_dimension_list%s: [", kq, kq);
|
|
|
|
char name[RRD_ID_LENGTH_MAX * 2 + 2];
|
|
char output[RRD_ID_LENGTH_MAX * 2 + 8];
|
|
|
|
struct value_output co = {.c = 0, .wb = wb};
|
|
|
|
DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
|
|
for (i = 0, rd = temp_rd ? temp_rd : r->st->dimensions; rd; rd = rd->next) {
|
|
snprintfz(name, RRD_ID_LENGTH_MAX * 2, "%s:%s", rrddim_id(rd), rrddim_name(rd));
|
|
int len = snprintfz(output, RRD_ID_LENGTH_MAX * 2 + 7, "[\"%s\",\"%s\"]", rrddim_id(rd), rrddim_name(rd));
|
|
dictionary_set(dict, name, output, len+1);
|
|
}
|
|
dictionary_walkthrough_read(dict, value_list_output_callback, &co);
|
|
dictionary_destroy(dict);
|
|
|
|
co.c = 0;
|
|
buffer_sprintf(wb, "],\n %sfull_chart_list%s: [", kq, kq);
|
|
dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
|
|
for (i = 0, rd = temp_rd ? temp_rd : r->st->dimensions; rd; rd = rd->next) {
|
|
int len = snprintfz(output, RRD_ID_LENGTH_MAX * 2 + 7, "[\"%s\",\"%s\"]", rrdset_id(rd->rrdset), rrdset_name(rd->rrdset));
|
|
snprintfz(name, RRD_ID_LENGTH_MAX * 2, "%s:%s", rrdset_id(rd->rrdset), rrdset_name(rd->rrdset));
|
|
dictionary_set(dict, name, output, len + 1);
|
|
}
|
|
|
|
dictionary_walkthrough_read(dict, value_list_output_callback, &co);
|
|
dictionary_destroy(dict);
|
|
|
|
RRDSET *st;
|
|
co.c = 0;
|
|
buffer_sprintf(wb, "],\n %sfull_chart_labels%s: [", kq, kq);
|
|
dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
|
|
for (i = 0, rd = temp_rd ? temp_rd : r->st->dimensions; rd; rd = rd->next) {
|
|
st = rd->rrdset;
|
|
if (st->rrdlabels)
|
|
rrdlabels_walkthrough_read(st->rrdlabels, fill_formatted_callback, dict);
|
|
}
|
|
dictionary_walkthrough_read(dict, value_list_output_callback, &co);
|
|
dictionary_destroy(dict);
|
|
buffer_strcat(wb, "],\n");
|
|
}
|
|
|
|
// Composite charts
|
|
if (context_mode && temp_rd) {
|
|
buffer_sprintf(
|
|
wb,
|
|
" %schart_ids%s: [",
|
|
kq, kq);
|
|
|
|
for (c = 0, i = 0, rd = temp_rd ; rd && c < r->d; c++, rd = rd->next) {
|
|
if (unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN))
|
|
continue;
|
|
if (unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO)))
|
|
continue;
|
|
|
|
if (i)
|
|
buffer_strcat(wb, ", ");
|
|
buffer_strcat(wb, sq);
|
|
buffer_strcat(wb, rrdset_id(rd->rrdset));
|
|
buffer_strcat(wb, sq);
|
|
i++;
|
|
}
|
|
if (!i) {
|
|
rows = 0;
|
|
buffer_strcat(wb, sq);
|
|
buffer_strcat(wb, "no data");
|
|
buffer_strcat(wb, sq);
|
|
}
|
|
buffer_strcat(wb, "],\n");
|
|
if (chart_label_key) {
|
|
buffer_sprintf(wb, " %schart_labels%s: { ", kq, kq);
|
|
|
|
SIMPLE_PATTERN *pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT);
|
|
SIMPLE_PATTERN *original_pattern = pattern;
|
|
char *label_key = NULL;
|
|
int keys = 0;
|
|
while (pattern && (label_key = simple_pattern_iterate(&pattern))) {
|
|
|
|
if (keys)
|
|
buffer_strcat(wb, ", ");
|
|
buffer_sprintf(wb, "%s%s%s : [", kq, label_key, kq);
|
|
keys++;
|
|
|
|
for (c = 0, i = 0, rd = temp_rd; rd && c < r->d; c++, rd = rd->next) {
|
|
if (unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN))
|
|
continue;
|
|
if (unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO)))
|
|
continue;
|
|
if (i)
|
|
buffer_strcat(wb, ", ");
|
|
|
|
rrdlabels_get_value_to_buffer_or_null(rd->rrdset->rrdlabels, wb, label_key, sq, "null");
|
|
i++;
|
|
}
|
|
if (!i) {
|
|
rows = 0;
|
|
buffer_strcat(wb, sq);
|
|
buffer_strcat(wb, "no data");
|
|
buffer_strcat(wb, sq);
|
|
}
|
|
buffer_strcat(wb, "]");
|
|
}
|
|
buffer_strcat(wb, "},\n");
|
|
simple_pattern_free(original_pattern);
|
|
}
|
|
}
|
|
|
|
buffer_sprintf(wb, " %slatest_values%s: ["
|
|
, kq, kq);
|
|
|
|
for(c = 0, i = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) {
|
|
if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue;
|
|
if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue;
|
|
|
|
if(i) buffer_strcat(wb, ", ");
|
|
i++;
|
|
|
|
NETDATA_DOUBLE value = rd->last_stored_value;
|
|
if (NAN == value)
|
|
buffer_strcat(wb, "null");
|
|
else
|
|
buffer_rrd_value(wb, value);
|
|
/*
|
|
storage_number n = rd->values[rrdset_last_slot(r->st)];
|
|
|
|
if(!does_storage_number_exist(n))
|
|
buffer_strcat(wb, "null");
|
|
else
|
|
buffer_rrd_value(wb, unpack_storage_number(n));
|
|
*/
|
|
}
|
|
if(!i) {
|
|
rows = 0;
|
|
buffer_strcat(wb, "null");
|
|
}
|
|
|
|
buffer_sprintf(wb, "],\n"
|
|
" %sview_latest_values%s: ["
|
|
, kq, kq);
|
|
|
|
i = 0;
|
|
if(rows) {
|
|
NETDATA_DOUBLE total = 1;
|
|
|
|
if(unlikely(options & RRDR_OPTION_PERCENTAGE)) {
|
|
total = 0;
|
|
for(c = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) {
|
|
NETDATA_DOUBLE *cn = &r->v[ (rrdr_rows(r) - 1) * r->d ];
|
|
NETDATA_DOUBLE n = cn[c];
|
|
|
|
if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0))
|
|
n = -n;
|
|
|
|
total += n;
|
|
}
|
|
// prevent a division by zero
|
|
if(total == 0) total = 1;
|
|
}
|
|
|
|
for(c = 0, i = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) {
|
|
if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue;
|
|
if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue;
|
|
|
|
if(i) buffer_strcat(wb, ", ");
|
|
i++;
|
|
|
|
NETDATA_DOUBLE *cn = &r->v[ (rrdr_rows(r) - 1) * r->d ];
|
|
RRDR_VALUE_FLAGS *co = &r->o[ (rrdr_rows(r) - 1) * r->d ];
|
|
NETDATA_DOUBLE n = cn[c];
|
|
|
|
if(co[c] & RRDR_VALUE_EMPTY) {
|
|
if(options & RRDR_OPTION_NULL2ZERO)
|
|
buffer_strcat(wb, "0");
|
|
else
|
|
buffer_strcat(wb, "null");
|
|
}
|
|
else {
|
|
if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0))
|
|
n = -n;
|
|
|
|
if(unlikely(options & RRDR_OPTION_PERCENTAGE))
|
|
n = n * 100 / total;
|
|
|
|
buffer_rrd_value(wb, n);
|
|
}
|
|
}
|
|
}
|
|
if(!i) {
|
|
rows = 0;
|
|
buffer_strcat(wb, "null");
|
|
}
|
|
|
|
buffer_sprintf(wb, "],\n"
|
|
" %sdimensions%s: %ld,\n"
|
|
" %spoints%s: %ld,\n"
|
|
" %sformat%s: %s"
|
|
, kq, kq, i
|
|
, kq, kq, rows
|
|
, kq, kq, sq
|
|
);
|
|
|
|
rrdr_buffer_print_format(wb, format);
|
|
|
|
buffer_sprintf(wb, "%s,\n"
|
|
" %sdb_points_per_tier%s: [ "
|
|
, sq
|
|
, kq, kq
|
|
);
|
|
|
|
for(int tier = 0; tier < storage_tiers ; tier++)
|
|
buffer_sprintf(wb, "%s%zu", tier>0?", ":"", r->internal.tier_points_read[tier]);
|
|
|
|
buffer_strcat(wb, " ]");
|
|
|
|
if((options & RRDR_OPTION_CUSTOM_VARS) && (options & RRDR_OPTION_JSON_WRAP)) {
|
|
buffer_sprintf(wb, ",\n %schart_variables%s: ", kq, kq);
|
|
health_api_v1_chart_custom_variables2json(r->st, wb);
|
|
}
|
|
|
|
buffer_sprintf(wb, ",\n %sresult%s: ", kq, kq);
|
|
|
|
if(string_value) buffer_strcat(wb, sq);
|
|
//info("JSONWRAPPER(): %s: END", r->st->id);
|
|
}
|
|
|
|
void rrdr_json_wrapper_anomaly_rates(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value) {
|
|
(void)r;
|
|
(void)format;
|
|
|
|
char kq[2] = "", // key quote
|
|
sq[2] = ""; // string quote
|
|
|
|
if( options & RRDR_OPTION_GOOGLE_JSON ) {
|
|
kq[0] = '\0';
|
|
sq[0] = '\'';
|
|
}
|
|
else {
|
|
kq[0] = '"';
|
|
sq[0] = '"';
|
|
}
|
|
|
|
if(string_value) buffer_strcat(wb, sq);
|
|
|
|
buffer_sprintf(wb, ",\n %sanomaly_rates%s: ", kq, kq);
|
|
}
|
|
|
|
void rrdr_json_wrapper_end(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value) {
|
|
(void)format;
|
|
|
|
char kq[2] = "", // key quote
|
|
sq[2] = ""; // string quote
|
|
|
|
if( options & RRDR_OPTION_GOOGLE_JSON ) {
|
|
kq[0] = '\0';
|
|
sq[0] = '\'';
|
|
}
|
|
else {
|
|
kq[0] = '"';
|
|
sq[0] = '"';
|
|
}
|
|
|
|
if(string_value) buffer_strcat(wb, sq);
|
|
|
|
buffer_sprintf(wb, ",\n %smin%s: ", kq, kq);
|
|
buffer_rrd_value(wb, r->min);
|
|
buffer_sprintf(wb, ",\n %smax%s: ", kq, kq);
|
|
buffer_rrd_value(wb, r->max);
|
|
buffer_strcat(wb, "\n}\n");
|
|
}
|