REORG: stats: extract JSON related functions

This commit is similar to the previous one. This time it deals with
functions related to stats JSON output.
This commit is contained in:
Amaury Denoyelle 2024-04-16 14:57:54 +02:00
parent b8c1fdf24e
commit 0109c0658d
5 changed files with 561 additions and 529 deletions

View File

@ -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

View File

@ -0,0 +1,24 @@
#ifndef _HAPROXY_STATS_JSON_H
#define _HAPROXY_STATS_JSON_H
#include <haproxy/applet-t.h>
#include <haproxy/buf-t.h>
#include <haproxy/stats-t.h>
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 */

View File

@ -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);

533
src/stats-json.c Normal file
View File

@ -0,0 +1,533 @@
#include <haproxy/stats-json.h>
#include <stdio.h>
#include <haproxy/applet.h>
#include <haproxy/buf.h>
#include <haproxy/chunk.h>
#include <haproxy/stats.h>
/* 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 <out> 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 <stats> into <out> 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 <out> 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 <stats> into <out> 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;
}

View File

@ -58,6 +58,7 @@
#include <haproxy/session.h>
#include <haproxy/stats.h>
#include <haproxy/stats-html.h>
#include <haproxy/stats-json.h>
#include <haproxy/stconn.h>
#include <haproxy/stream.h>
#include <haproxy/task.h>
@ -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 <stats> into <out> 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 <stats> into <out> 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 <stats> into <out> 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 @@ more:
return 0;
}
/* Dumps the stats JSON header to <out> 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 <out> buffer. The caller is
* responsible for clearing it if needed.
*/
static void stats_dump_json_end(struct buffer *out)
{
chunk_strcat(out, "]\n");
}
/* Uses <appctx.ctx.stats.obj1> as a pointer to the current proxy and <obj2> as
* a pointer to the current server/listener.
*/
@ -2793,236 +2497,6 @@ more:
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;