diff --git a/Makefile b/Makefile index b97d69b7b..57be1b145 100644 --- a/Makefile +++ b/Makefile @@ -975,7 +975,7 @@ OBJS += src/mux_h2.o src/mux_fcgi.o src/mux_h1.o src/tcpcheck.o \ src/dynbuf.o src/wdt.o src/pipe.o src/init.o src/http_acl.o \ src/hpack-huff.o src/hpack-enc.o src/dict.o src/freq_ctr.o \ src/ebtree.o src/hash.o src/dgram.o src/version.o src/proto_rhttp.o \ - src/guid.o src/stats-html.o + src/guid.o src/stats-html.o src/stats-json.o ifneq ($(TRACE),) OBJS += src/calltrace.o diff --git a/include/haproxy/stats-json.h b/include/haproxy/stats-json.h new file mode 100644 index 000000000..d6c438261 --- /dev/null +++ b/include/haproxy/stats-json.h @@ -0,0 +1,24 @@ +#ifndef _HAPROXY_STATS_JSON_H +#define _HAPROXY_STATS_JSON_H + +#include +#include +#include + +void stats_dump_json_header(struct buffer *out); + +int stats_dump_fields_json(struct buffer *out, + const struct field *stats, size_t stats_count, + struct show_stat_ctx *ctx); + +void stats_dump_json_end(struct buffer *out); + +int stats_dump_json_info_fields(struct buffer *out, + const struct field *info, + struct show_stat_ctx *ctx); + +void stats_dump_json_schema(struct buffer *out); + +int stats_dump_json_schema_to_buffer(struct appctx *appctx); + +#endif /* _HAPROXY_STATS_JSON_H */ diff --git a/include/haproxy/stats.h b/include/haproxy/stats.h index 6affd5e95..e86c1299d 100644 --- a/include/haproxy/stats.h +++ b/include/haproxy/stats.h @@ -45,6 +45,7 @@ extern struct applet http_stats_applet; extern struct list stats_module_list[]; extern THREAD_LOCAL struct field info[]; extern THREAD_LOCAL struct field *stat_l[]; +extern struct name_desc *stat_f[STATS_DOMAIN_COUNT]; struct htx; int stats_putchk(struct appctx *appctx, struct buffer *buf, struct htx *htx); diff --git a/src/stats-json.c b/src/stats-json.c new file mode 100644 index 000000000..454e01871 --- /dev/null +++ b/src/stats-json.c @@ -0,0 +1,533 @@ +#include + +#include + +#include +#include +#include +#include + +/* Emits an encoding of the field type as JSON. + * Returns non-zero on success, 0 if the buffer is full. + */ +static int stats_emit_json_field_tags(struct buffer *out, const struct field *f) +{ + const char *origin, *nature, *scope; + int old_len; + + switch (field_origin(f, 0)) { + case FO_METRIC: origin = "Metric"; break; + case FO_STATUS: origin = "Status"; break; + case FO_KEY: origin = "Key"; break; + case FO_CONFIG: origin = "Config"; break; + case FO_PRODUCT: origin = "Product"; break; + default: origin = "Unknown"; break; + } + + switch (field_nature(f, 0)) { + case FN_GAUGE: nature = "Gauge"; break; + case FN_LIMIT: nature = "Limit"; break; + case FN_MIN: nature = "Min"; break; + case FN_MAX: nature = "Max"; break; + case FN_RATE: nature = "Rate"; break; + case FN_COUNTER: nature = "Counter"; break; + case FN_DURATION: nature = "Duration"; break; + case FN_AGE: nature = "Age"; break; + case FN_TIME: nature = "Time"; break; + case FN_NAME: nature = "Name"; break; + case FN_OUTPUT: nature = "Output"; break; + case FN_AVG: nature = "Avg"; break; + default: nature = "Unknown"; break; + } + + switch (field_scope(f, 0)) { + case FS_PROCESS: scope = "Process"; break; + case FS_SERVICE: scope = "Service"; break; + case FS_SYSTEM: scope = "System"; break; + case FS_CLUSTER: scope = "Cluster"; break; + default: scope = "Unknown"; break; + } + + old_len = out->data; + chunk_appendf(out, "\"tags\":{" + "\"origin\":\"%s\"," + "\"nature\":\"%s\"," + "\"scope\":\"%s\"" + "}", origin, nature, scope); + return !(old_len == out->data); +} + +/* Limit JSON integer values to the range [-(2**53)+1, (2**53)-1] as per + * the recommendation for interoperable integers in section 6 of RFC 7159. + */ +#define JSON_INT_MAX ((1ULL << 53) - 1) +#define JSON_INT_MIN (0 - JSON_INT_MAX) + +/* Emits a stats field value and its type in JSON. + * Returns non-zero on success, 0 on error. + */ +static int stats_emit_json_data_field(struct buffer *out, const struct field *f) +{ + int old_len; + char buf[20]; + const char *type, *value = buf, *quote = ""; + + switch (field_format(f, 0)) { + case FF_EMPTY: return 1; + case FF_S32: type = "\"s32\""; + snprintf(buf, sizeof(buf), "%d", f->u.s32); + break; + case FF_U32: type = "\"u32\""; + snprintf(buf, sizeof(buf), "%u", f->u.u32); + break; + case FF_S64: type = "\"s64\""; + if (f->u.s64 < JSON_INT_MIN || f->u.s64 > JSON_INT_MAX) + return 0; + type = "\"s64\""; + snprintf(buf, sizeof(buf), "%lld", (long long)f->u.s64); + break; + case FF_U64: if (f->u.u64 > JSON_INT_MAX) + return 0; + type = "\"u64\""; + snprintf(buf, sizeof(buf), "%llu", + (unsigned long long) f->u.u64); + break; + case FF_FLT: type = "\"flt\""; + flt_trim(buf, 0, snprintf(buf, sizeof(buf), "%f", f->u.flt)); + break; + case FF_STR: type = "\"str\""; + value = field_str(f, 0); + quote = "\""; + break; + default: snprintf(buf, sizeof(buf), "%u", f->type); + type = buf; + value = "unknown"; + quote = "\""; + break; + } + + old_len = out->data; + chunk_appendf(out, ",\"value\":{\"type\":%s,\"value\":%s%s%s}", + type, quote, value, quote); + return !(old_len == out->data); +} + +static void stats_print_proxy_field_json(struct buffer *out, + const struct field *stat, + const char *name, + int pos, + uint32_t field_type, + uint32_t iid, + uint32_t sid, + uint32_t pid) +{ + const char *obj_type; + switch (field_type) { + case STATS_TYPE_FE: obj_type = "Frontend"; break; + case STATS_TYPE_BE: obj_type = "Backend"; break; + case STATS_TYPE_SO: obj_type = "Listener"; break; + case STATS_TYPE_SV: obj_type = "Server"; break; + default: obj_type = "Unknown"; break; + } + + chunk_appendf(out, + "{" + "\"objType\":\"%s\"," + "\"proxyId\":%u," + "\"id\":%u," + "\"field\":{\"pos\":%d,\"name\":\"%s\"}," + "\"processNum\":%u,", + obj_type, iid, sid, pos, name, pid); +} + +static void stats_print_rslv_field_json(struct buffer *out, + const struct field *stat, + const char *name, + int pos) +{ + chunk_appendf(out, + "{" + "\"field\":{\"pos\":%d,\"name\":\"%s\"},", + pos, name); +} + + +/* Dumps the stats JSON header to buffer. The caller is responsible for + * clearing it if needed. + */ +void stats_dump_json_header(struct buffer *out) +{ + chunk_strcat(out, "["); +} + +/* Dump all fields from into using a typed "field:desc:type:value" format */ +int stats_dump_fields_json(struct buffer *out, + const struct field *stats, size_t stats_count, + struct show_stat_ctx *ctx) +{ + int flags = ctx->flags; + int domain = ctx->domain; + int started = (ctx->field) ? 1 : 0; + int ready_data = 0; + + if (!started && (flags & STAT_STARTED) && !chunk_strcat(out, ",")) + return 0; + if (!started && !chunk_strcat(out, "[")) + return 0; + + for (; ctx->field < stats_count; ctx->field++) { + int old_len; + int field = ctx->field; + + if (!stats[field].type) + continue; + + if (started && !chunk_strcat(out, ",")) + goto err; + started = 1; + + old_len = out->data; + if (domain == STATS_DOMAIN_PROXY) { + stats_print_proxy_field_json(out, &stats[field], + stat_f[domain][field].name, + field, + stats[ST_F_TYPE].u.u32, + stats[ST_F_IID].u.u32, + stats[ST_F_SID].u.u32, + stats[ST_F_PID].u.u32); + } else if (domain == STATS_DOMAIN_RESOLVERS) { + stats_print_rslv_field_json(out, &stats[field], + stat_f[domain][field].name, + field); + } + + if (old_len == out->data) + goto err; + + if (!stats_emit_json_field_tags(out, &stats[field])) + goto err; + + if (!stats_emit_json_data_field(out, &stats[field])) + goto err; + + if (!chunk_strcat(out, "}")) + goto err; + ready_data = out->data; + } + + if (!chunk_strcat(out, "]")) + goto err; + + ctx->field = 0; /* we're done */ + return 1; + +err: + if (!ready_data) { + /* not enough buffer space for a single entry.. */ + chunk_reset(out); + if (ctx->flags & STAT_STARTED) + chunk_strcat(out, ","); + chunk_appendf(out, "{\"errorStr\":\"output buffer too short\"}"); + return 0; /* hard error */ + } + /* push ready data and wait for a new buffer to complete the dump */ + out->data = ready_data; + return 1; +} + +/* Dumps the JSON stats trailer block to buffer. The caller is + * responsible for clearing it if needed. + */ +void stats_dump_json_end(struct buffer *out) +{ + chunk_strcat(out, "]\n"); +} + +/* Dump all fields from into using the "show info json" format */ +int stats_dump_json_info_fields(struct buffer *out, + const struct field *info, + struct show_stat_ctx *ctx) +{ + int started = (ctx->field) ? 1 : 0; + int ready_data = 0; + + if (!started && !chunk_strcat(out, "[")) + return 0; + + for (; ctx->field < INF_TOTAL_FIELDS; ctx->field++) { + int old_len; + int field = ctx->field; + + if (!field_format(info, field)) + continue; + + if (started && !chunk_strcat(out, ",")) + goto err; + started = 1; + + old_len = out->data; + chunk_appendf(out, + "{\"field\":{\"pos\":%d,\"name\":\"%s\"}," + "\"processNum\":%u,", + field, info_fields[field].name, + info[INF_PROCESS_NUM].u.u32); + if (old_len == out->data) + goto err; + + if (!stats_emit_json_field_tags(out, &info[field])) + goto err; + + if (!stats_emit_json_data_field(out, &info[field])) + goto err; + + if (!chunk_strcat(out, "}")) + goto err; + ready_data = out->data; + } + + if (!chunk_strcat(out, "]\n")) + goto err; + ctx->field = 0; /* we're done */ + return 1; + +err: + if (!ready_data) { + /* not enough buffer space for a single entry.. */ + chunk_reset(out); + chunk_appendf(out, "{\"errorStr\":\"output buffer too short\"}\n"); + return 0; /* hard error */ + } + /* push ready data and wait for a new buffer to complete the dump */ + out->data = ready_data; + return 1; +} + +/* This function dumps the schema onto the stream connector's read buffer. + * It returns 0 as long as it does not complete, non-zero upon completion. + * No state is used. + * + * Integer values bounded to the range [-(2**53)+1, (2**53)-1] as + * per the recommendation for interoperable integers in section 6 of RFC 7159. + */ +void stats_dump_json_schema(struct buffer *out) +{ + + int old_len = out->data; + + chunk_strcat(out, + "{" + "\"$schema\":\"http://json-schema.org/draft-04/schema#\"," + "\"oneOf\":[" + "{" + "\"title\":\"Info\"," + "\"type\":\"array\"," + "\"items\":{" + "\"title\":\"InfoItem\"," + "\"type\":\"object\"," + "\"properties\":{" + "\"field\":{\"$ref\":\"#/definitions/field\"}," + "\"processNum\":{\"$ref\":\"#/definitions/processNum\"}," + "\"tags\":{\"$ref\":\"#/definitions/tags\"}," + "\"value\":{\"$ref\":\"#/definitions/typedValue\"}" + "}," + "\"required\":[\"field\",\"processNum\",\"tags\"," + "\"value\"]" + "}" + "}," + "{" + "\"title\":\"Stat\"," + "\"type\":\"array\"," + "\"items\":{" + "\"title\":\"InfoItem\"," + "\"type\":\"object\"," + "\"properties\":{" + "\"objType\":{" + "\"enum\":[\"Frontend\",\"Backend\",\"Listener\"," + "\"Server\",\"Unknown\"]" + "}," + "\"proxyId\":{" + "\"type\":\"integer\"," + "\"minimum\":0" + "}," + "\"id\":{" + "\"type\":\"integer\"," + "\"minimum\":0" + "}," + "\"field\":{\"$ref\":\"#/definitions/field\"}," + "\"processNum\":{\"$ref\":\"#/definitions/processNum\"}," + "\"tags\":{\"$ref\":\"#/definitions/tags\"}," + "\"typedValue\":{\"$ref\":\"#/definitions/typedValue\"}" + "}," + "\"required\":[\"objType\",\"proxyId\",\"id\"," + "\"field\",\"processNum\",\"tags\"," + "\"value\"]" + "}" + "}," + "{" + "\"title\":\"Error\"," + "\"type\":\"object\"," + "\"properties\":{" + "\"errorStr\":{" + "\"type\":\"string\"" + "}" + "}," + "\"required\":[\"errorStr\"]" + "}" + "]," + "\"definitions\":{" + "\"field\":{" + "\"type\":\"object\"," + "\"pos\":{" + "\"type\":\"integer\"," + "\"minimum\":0" + "}," + "\"name\":{" + "\"type\":\"string\"" + "}," + "\"required\":[\"pos\",\"name\"]" + "}," + "\"processNum\":{" + "\"type\":\"integer\"," + "\"minimum\":1" + "}," + "\"tags\":{" + "\"type\":\"object\"," + "\"origin\":{" + "\"type\":\"string\"," + "\"enum\":[\"Metric\",\"Status\",\"Key\"," + "\"Config\",\"Product\",\"Unknown\"]" + "}," + "\"nature\":{" + "\"type\":\"string\"," + "\"enum\":[\"Gauge\",\"Limit\",\"Min\",\"Max\"," + "\"Rate\",\"Counter\",\"Duration\"," + "\"Age\",\"Time\",\"Name\",\"Output\"," + "\"Avg\", \"Unknown\"]" + "}," + "\"scope\":{" + "\"type\":\"string\"," + "\"enum\":[\"Cluster\",\"Process\",\"Service\"," + "\"System\",\"Unknown\"]" + "}," + "\"required\":[\"origin\",\"nature\",\"scope\"]" + "}," + "\"typedValue\":{" + "\"type\":\"object\"," + "\"oneOf\":[" + "{\"$ref\":\"#/definitions/typedValue/definitions/s32Value\"}," + "{\"$ref\":\"#/definitions/typedValue/definitions/s64Value\"}," + "{\"$ref\":\"#/definitions/typedValue/definitions/u32Value\"}," + "{\"$ref\":\"#/definitions/typedValue/definitions/u64Value\"}," + "{\"$ref\":\"#/definitions/typedValue/definitions/strValue\"}" + "]," + "\"definitions\":{" + "\"s32Value\":{" + "\"properties\":{" + "\"type\":{" + "\"type\":\"string\"," + "\"enum\":[\"s32\"]" + "}," + "\"value\":{" + "\"type\":\"integer\"," + "\"minimum\":-2147483648," + "\"maximum\":2147483647" + "}" + "}," + "\"required\":[\"type\",\"value\"]" + "}," + "\"s64Value\":{" + "\"properties\":{" + "\"type\":{" + "\"type\":\"string\"," + "\"enum\":[\"s64\"]" + "}," + "\"value\":{" + "\"type\":\"integer\"," + "\"minimum\":-9007199254740991," + "\"maximum\":9007199254740991" + "}" + "}," + "\"required\":[\"type\",\"value\"]" + "}," + "\"u32Value\":{" + "\"properties\":{" + "\"type\":{" + "\"type\":\"string\"," + "\"enum\":[\"u32\"]" + "}," + "\"value\":{" + "\"type\":\"integer\"," + "\"minimum\":0," + "\"maximum\":4294967295" + "}" + "}," + "\"required\":[\"type\",\"value\"]" + "}," + "\"u64Value\":{" + "\"properties\":{" + "\"type\":{" + "\"type\":\"string\"," + "\"enum\":[\"u64\"]" + "}," + "\"value\":{" + "\"type\":\"integer\"," + "\"minimum\":0," + "\"maximum\":9007199254740991" + "}" + "}," + "\"required\":[\"type\",\"value\"]" + "}," + "\"strValue\":{" + "\"properties\":{" + "\"type\":{" + "\"type\":\"string\"," + "\"enum\":[\"str\"]" + "}," + "\"value\":{\"type\":\"string\"}" + "}," + "\"required\":[\"type\",\"value\"]" + "}," + "\"unknownValue\":{" + "\"properties\":{" + "\"type\":{" + "\"type\":\"integer\"," + "\"minimum\":0" + "}," + "\"value\":{" + "\"type\":\"string\"," + "\"enum\":[\"unknown\"]" + "}" + "}," + "\"required\":[\"type\",\"value\"]" + "}" + "}" + "}" + "}" + "}"); + + if (old_len == out->data) { + chunk_reset(out); + chunk_appendf(out, + "{\"errorStr\":\"output buffer too short\"}"); + } + chunk_appendf(out, "\n"); +} + +/* This function dumps the schema onto the stream connector's read buffer. + * It returns 0 as long as it does not complete, non-zero upon completion. + * No state is used. + */ +int stats_dump_json_schema_to_buffer(struct appctx *appctx) +{ + struct show_stat_ctx *ctx = appctx->svcctx; + struct buffer *chk = &ctx->chunk; + + chunk_reset(chk); + + stats_dump_json_schema(chk); + + if (applet_putchk(appctx, chk) == -1) + return 0; + + return 1; +} diff --git a/src/stats.c b/src/stats.c index fa035f016..df87c45b1 100644 --- a/src/stats.c +++ b/src/stats.c @@ -58,6 +58,7 @@ #include #include #include +#include #include #include #include @@ -283,7 +284,7 @@ const struct name_desc stat_fields[ST_F_TOTAL_FIELDS] = { THREAD_LOCAL struct field info[INF_TOTAL_FIELDS]; /* description of statistics (static and dynamic) */ -static struct name_desc *stat_f[STATS_DOMAIN_COUNT]; +struct name_desc *stat_f[STATS_DOMAIN_COUNT]; static size_t stat_count[STATS_DOMAIN_COUNT]; /* one line for stats */ @@ -298,8 +299,6 @@ struct list stats_module_list[STATS_DOMAIN_COUNT] = { THREAD_LOCAL void *trash_counters; -static void stats_dump_json_schema(struct buffer *out); - int stats_putchk(struct appctx *appctx, struct buffer *buf, struct htx *htx) { struct show_stat_ctx *ctx = appctx->svcctx; @@ -458,61 +457,6 @@ int stats_emit_typed_data_field(struct buffer *out, const struct field *f) } } -/* Limit JSON integer values to the range [-(2**53)+1, (2**53)-1] as per - * the recommendation for interoperable integers in section 6 of RFC 7159. - */ -#define JSON_INT_MAX ((1ULL << 53) - 1) -#define JSON_INT_MIN (0 - JSON_INT_MAX) - -/* Emits a stats field value and its type in JSON. - * Returns non-zero on success, 0 on error. - */ -int stats_emit_json_data_field(struct buffer *out, const struct field *f) -{ - int old_len; - char buf[20]; - const char *type, *value = buf, *quote = ""; - - switch (field_format(f, 0)) { - case FF_EMPTY: return 1; - case FF_S32: type = "\"s32\""; - snprintf(buf, sizeof(buf), "%d", f->u.s32); - break; - case FF_U32: type = "\"u32\""; - snprintf(buf, sizeof(buf), "%u", f->u.u32); - break; - case FF_S64: type = "\"s64\""; - if (f->u.s64 < JSON_INT_MIN || f->u.s64 > JSON_INT_MAX) - return 0; - type = "\"s64\""; - snprintf(buf, sizeof(buf), "%lld", (long long)f->u.s64); - break; - case FF_U64: if (f->u.u64 > JSON_INT_MAX) - return 0; - type = "\"u64\""; - snprintf(buf, sizeof(buf), "%llu", - (unsigned long long) f->u.u64); - break; - case FF_FLT: type = "\"flt\""; - flt_trim(buf, 0, snprintf(buf, sizeof(buf), "%f", f->u.flt)); - break; - case FF_STR: type = "\"str\""; - value = field_str(f, 0); - quote = "\""; - break; - default: snprintf(buf, sizeof(buf), "%u", f->type); - type = buf; - value = "unknown"; - quote = "\""; - break; - } - - old_len = out->data; - chunk_appendf(out, ",\"value\":{\"type\":%s,\"value\":%s%s%s}", - type, quote, value, quote); - return !(old_len == out->data); -} - /* Emits an encoding of the field type on 3 characters followed by a delimiter. * Returns non-zero on success, 0 if the buffer is full. */ @@ -557,56 +501,6 @@ int stats_emit_field_tags(struct buffer *out, const struct field *f, return chunk_appendf(out, "%c%c%c%c", origin, nature, scope, delim); } -/* Emits an encoding of the field type as JSON. - * Returns non-zero on success, 0 if the buffer is full. - */ -int stats_emit_json_field_tags(struct buffer *out, const struct field *f) -{ - const char *origin, *nature, *scope; - int old_len; - - switch (field_origin(f, 0)) { - case FO_METRIC: origin = "Metric"; break; - case FO_STATUS: origin = "Status"; break; - case FO_KEY: origin = "Key"; break; - case FO_CONFIG: origin = "Config"; break; - case FO_PRODUCT: origin = "Product"; break; - default: origin = "Unknown"; break; - } - - switch (field_nature(f, 0)) { - case FN_GAUGE: nature = "Gauge"; break; - case FN_LIMIT: nature = "Limit"; break; - case FN_MIN: nature = "Min"; break; - case FN_MAX: nature = "Max"; break; - case FN_RATE: nature = "Rate"; break; - case FN_COUNTER: nature = "Counter"; break; - case FN_DURATION: nature = "Duration"; break; - case FN_AGE: nature = "Age"; break; - case FN_TIME: nature = "Time"; break; - case FN_NAME: nature = "Name"; break; - case FN_OUTPUT: nature = "Output"; break; - case FN_AVG: nature = "Avg"; break; - default: nature = "Unknown"; break; - } - - switch (field_scope(f, 0)) { - case FS_PROCESS: scope = "Process"; break; - case FS_SERVICE: scope = "Service"; break; - case FS_SYSTEM: scope = "System"; break; - case FS_CLUSTER: scope = "Cluster"; break; - default: scope = "Unknown"; break; - } - - old_len = out->data; - chunk_appendf(out, "\"tags\":{" - "\"origin\":\"%s\"," - "\"nature\":\"%s\"," - "\"scope\":\"%s\"" - "}", origin, nature, scope); - return !(old_len == out->data); -} - /* Dump all fields from into using CSV format */ static int stats_dump_fields_csv(struct buffer *out, const struct field *stats, size_t stats_count, @@ -686,179 +580,6 @@ static int stats_dump_fields_typed(struct buffer *out, return 1; } -/* Dump all fields from into using the "show info json" format */ -static int stats_dump_json_info_fields(struct buffer *out, - const struct field *info, - struct show_stat_ctx *ctx) -{ - int started = (ctx->field) ? 1 : 0; - int ready_data = 0; - - if (!started && !chunk_strcat(out, "[")) - return 0; - - for (; ctx->field < INF_TOTAL_FIELDS; ctx->field++) { - int old_len; - int field = ctx->field; - - if (!field_format(info, field)) - continue; - - if (started && !chunk_strcat(out, ",")) - goto err; - started = 1; - - old_len = out->data; - chunk_appendf(out, - "{\"field\":{\"pos\":%d,\"name\":\"%s\"}," - "\"processNum\":%u,", - field, info_fields[field].name, - info[INF_PROCESS_NUM].u.u32); - if (old_len == out->data) - goto err; - - if (!stats_emit_json_field_tags(out, &info[field])) - goto err; - - if (!stats_emit_json_data_field(out, &info[field])) - goto err; - - if (!chunk_strcat(out, "}")) - goto err; - ready_data = out->data; - } - - if (!chunk_strcat(out, "]\n")) - goto err; - ctx->field = 0; /* we're done */ - return 1; - -err: - if (!ready_data) { - /* not enough buffer space for a single entry.. */ - chunk_reset(out); - chunk_appendf(out, "{\"errorStr\":\"output buffer too short\"}\n"); - return 0; /* hard error */ - } - /* push ready data and wait for a new buffer to complete the dump */ - out->data = ready_data; - return 1; -} - -static void stats_print_proxy_field_json(struct buffer *out, - const struct field *stat, - const char *name, - int pos, - uint32_t field_type, - uint32_t iid, - uint32_t sid, - uint32_t pid) -{ - const char *obj_type; - switch (field_type) { - case STATS_TYPE_FE: obj_type = "Frontend"; break; - case STATS_TYPE_BE: obj_type = "Backend"; break; - case STATS_TYPE_SO: obj_type = "Listener"; break; - case STATS_TYPE_SV: obj_type = "Server"; break; - default: obj_type = "Unknown"; break; - } - - chunk_appendf(out, - "{" - "\"objType\":\"%s\"," - "\"proxyId\":%u," - "\"id\":%u," - "\"field\":{\"pos\":%d,\"name\":\"%s\"}," - "\"processNum\":%u,", - obj_type, iid, sid, pos, name, pid); -} - -static void stats_print_rslv_field_json(struct buffer *out, - const struct field *stat, - const char *name, - int pos) -{ - chunk_appendf(out, - "{" - "\"field\":{\"pos\":%d,\"name\":\"%s\"},", - pos, name); -} - - -/* Dump all fields from into using a typed "field:desc:type:value" format */ -static int stats_dump_fields_json(struct buffer *out, - const struct field *stats, size_t stats_count, - struct show_stat_ctx *ctx) -{ - int flags = ctx->flags; - int domain = ctx->domain; - int started = (ctx->field) ? 1 : 0; - int ready_data = 0; - - if (!started && (flags & STAT_STARTED) && !chunk_strcat(out, ",")) - return 0; - if (!started && !chunk_strcat(out, "[")) - return 0; - - for (; ctx->field < stats_count; ctx->field++) { - int old_len; - int field = ctx->field; - - if (!stats[field].type) - continue; - - if (started && !chunk_strcat(out, ",")) - goto err; - started = 1; - - old_len = out->data; - if (domain == STATS_DOMAIN_PROXY) { - stats_print_proxy_field_json(out, &stats[field], - stat_f[domain][field].name, - field, - stats[ST_F_TYPE].u.u32, - stats[ST_F_IID].u.u32, - stats[ST_F_SID].u.u32, - stats[ST_F_PID].u.u32); - } else if (domain == STATS_DOMAIN_RESOLVERS) { - stats_print_rslv_field_json(out, &stats[field], - stat_f[domain][field].name, - field); - } - - if (old_len == out->data) - goto err; - - if (!stats_emit_json_field_tags(out, &stats[field])) - goto err; - - if (!stats_emit_json_data_field(out, &stats[field])) - goto err; - - if (!chunk_strcat(out, "}")) - goto err; - ready_data = out->data; - } - - if (!chunk_strcat(out, "]")) - goto err; - - ctx->field = 0; /* we're done */ - return 1; - -err: - if (!ready_data) { - /* not enough buffer space for a single entry.. */ - chunk_reset(out); - if (ctx->flags & STAT_STARTED) - chunk_strcat(out, ","); - chunk_appendf(out, "{\"errorStr\":\"output buffer too short\"}"); - return 0; /* hard error */ - } - /* push ready data and wait for a new buffer to complete the dump */ - out->data = ready_data; - return 1; -} int stats_dump_one_line(const struct field *stats, size_t stats_count, struct appctx *appctx) @@ -2408,23 +2129,6 @@ int stats_dump_proxy_to_buffer(struct stconn *sc, struct buffer *buf, struct htx return 0; } -/* Dumps the stats JSON header to buffer. The caller is responsible for - * clearing it if needed. - */ -static void stats_dump_json_header(struct buffer *out) -{ - chunk_strcat(out, "["); -} - - -/* Dumps the JSON stats trailer block to buffer. The caller is - * responsible for clearing it if needed. - */ -static void stats_dump_json_end(struct buffer *out) -{ - chunk_strcat(out, "]\n"); -} - /* Uses as a pointer to the current proxy and as * a pointer to the current server/listener. */ @@ -2793,236 +2497,6 @@ static int stats_dump_info_to_buffer(struct stconn *sc) return 1; } -/* This function dumps the schema onto the stream connector's read buffer. - * It returns 0 as long as it does not complete, non-zero upon completion. - * No state is used. - * - * Integer values bounded to the range [-(2**53)+1, (2**53)-1] as - * per the recommendation for interoperable integers in section 6 of RFC 7159. - */ -static void stats_dump_json_schema(struct buffer *out) -{ - - int old_len = out->data; - - chunk_strcat(out, - "{" - "\"$schema\":\"http://json-schema.org/draft-04/schema#\"," - "\"oneOf\":[" - "{" - "\"title\":\"Info\"," - "\"type\":\"array\"," - "\"items\":{" - "\"title\":\"InfoItem\"," - "\"type\":\"object\"," - "\"properties\":{" - "\"field\":{\"$ref\":\"#/definitions/field\"}," - "\"processNum\":{\"$ref\":\"#/definitions/processNum\"}," - "\"tags\":{\"$ref\":\"#/definitions/tags\"}," - "\"value\":{\"$ref\":\"#/definitions/typedValue\"}" - "}," - "\"required\":[\"field\",\"processNum\",\"tags\"," - "\"value\"]" - "}" - "}," - "{" - "\"title\":\"Stat\"," - "\"type\":\"array\"," - "\"items\":{" - "\"title\":\"InfoItem\"," - "\"type\":\"object\"," - "\"properties\":{" - "\"objType\":{" - "\"enum\":[\"Frontend\",\"Backend\",\"Listener\"," - "\"Server\",\"Unknown\"]" - "}," - "\"proxyId\":{" - "\"type\":\"integer\"," - "\"minimum\":0" - "}," - "\"id\":{" - "\"type\":\"integer\"," - "\"minimum\":0" - "}," - "\"field\":{\"$ref\":\"#/definitions/field\"}," - "\"processNum\":{\"$ref\":\"#/definitions/processNum\"}," - "\"tags\":{\"$ref\":\"#/definitions/tags\"}," - "\"typedValue\":{\"$ref\":\"#/definitions/typedValue\"}" - "}," - "\"required\":[\"objType\",\"proxyId\",\"id\"," - "\"field\",\"processNum\",\"tags\"," - "\"value\"]" - "}" - "}," - "{" - "\"title\":\"Error\"," - "\"type\":\"object\"," - "\"properties\":{" - "\"errorStr\":{" - "\"type\":\"string\"" - "}" - "}," - "\"required\":[\"errorStr\"]" - "}" - "]," - "\"definitions\":{" - "\"field\":{" - "\"type\":\"object\"," - "\"pos\":{" - "\"type\":\"integer\"," - "\"minimum\":0" - "}," - "\"name\":{" - "\"type\":\"string\"" - "}," - "\"required\":[\"pos\",\"name\"]" - "}," - "\"processNum\":{" - "\"type\":\"integer\"," - "\"minimum\":1" - "}," - "\"tags\":{" - "\"type\":\"object\"," - "\"origin\":{" - "\"type\":\"string\"," - "\"enum\":[\"Metric\",\"Status\",\"Key\"," - "\"Config\",\"Product\",\"Unknown\"]" - "}," - "\"nature\":{" - "\"type\":\"string\"," - "\"enum\":[\"Gauge\",\"Limit\",\"Min\",\"Max\"," - "\"Rate\",\"Counter\",\"Duration\"," - "\"Age\",\"Time\",\"Name\",\"Output\"," - "\"Avg\", \"Unknown\"]" - "}," - "\"scope\":{" - "\"type\":\"string\"," - "\"enum\":[\"Cluster\",\"Process\",\"Service\"," - "\"System\",\"Unknown\"]" - "}," - "\"required\":[\"origin\",\"nature\",\"scope\"]" - "}," - "\"typedValue\":{" - "\"type\":\"object\"," - "\"oneOf\":[" - "{\"$ref\":\"#/definitions/typedValue/definitions/s32Value\"}," - "{\"$ref\":\"#/definitions/typedValue/definitions/s64Value\"}," - "{\"$ref\":\"#/definitions/typedValue/definitions/u32Value\"}," - "{\"$ref\":\"#/definitions/typedValue/definitions/u64Value\"}," - "{\"$ref\":\"#/definitions/typedValue/definitions/strValue\"}" - "]," - "\"definitions\":{" - "\"s32Value\":{" - "\"properties\":{" - "\"type\":{" - "\"type\":\"string\"," - "\"enum\":[\"s32\"]" - "}," - "\"value\":{" - "\"type\":\"integer\"," - "\"minimum\":-2147483648," - "\"maximum\":2147483647" - "}" - "}," - "\"required\":[\"type\",\"value\"]" - "}," - "\"s64Value\":{" - "\"properties\":{" - "\"type\":{" - "\"type\":\"string\"," - "\"enum\":[\"s64\"]" - "}," - "\"value\":{" - "\"type\":\"integer\"," - "\"minimum\":-9007199254740991," - "\"maximum\":9007199254740991" - "}" - "}," - "\"required\":[\"type\",\"value\"]" - "}," - "\"u32Value\":{" - "\"properties\":{" - "\"type\":{" - "\"type\":\"string\"," - "\"enum\":[\"u32\"]" - "}," - "\"value\":{" - "\"type\":\"integer\"," - "\"minimum\":0," - "\"maximum\":4294967295" - "}" - "}," - "\"required\":[\"type\",\"value\"]" - "}," - "\"u64Value\":{" - "\"properties\":{" - "\"type\":{" - "\"type\":\"string\"," - "\"enum\":[\"u64\"]" - "}," - "\"value\":{" - "\"type\":\"integer\"," - "\"minimum\":0," - "\"maximum\":9007199254740991" - "}" - "}," - "\"required\":[\"type\",\"value\"]" - "}," - "\"strValue\":{" - "\"properties\":{" - "\"type\":{" - "\"type\":\"string\"," - "\"enum\":[\"str\"]" - "}," - "\"value\":{\"type\":\"string\"}" - "}," - "\"required\":[\"type\",\"value\"]" - "}," - "\"unknownValue\":{" - "\"properties\":{" - "\"type\":{" - "\"type\":\"integer\"," - "\"minimum\":0" - "}," - "\"value\":{" - "\"type\":\"string\"," - "\"enum\":[\"unknown\"]" - "}" - "}," - "\"required\":[\"type\",\"value\"]" - "}" - "}" - "}" - "}" - "}"); - - if (old_len == out->data) { - chunk_reset(out); - chunk_appendf(out, - "{\"errorStr\":\"output buffer too short\"}"); - } - chunk_appendf(out, "\n"); -} - -/* This function dumps the schema onto the stream connector's read buffer. - * It returns 0 as long as it does not complete, non-zero upon completion. - * No state is used. - */ -static int stats_dump_json_schema_to_buffer(struct appctx *appctx) -{ - struct show_stat_ctx *ctx = appctx->svcctx; - struct buffer *chk = &ctx->chunk; - - chunk_reset(chk); - - stats_dump_json_schema(chk); - - if (applet_putchk(appctx, chk) == -1) - return 0; - - return 1; -} - static int cli_parse_clear_counters(char **args, char *payload, struct appctx *appctx, void *private) { struct proxy *px;