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:
parent
b8c1fdf24e
commit
0109c0658d
2
Makefile
2
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
|
||||
|
24
include/haproxy/stats-json.h
Normal file
24
include/haproxy/stats-json.h
Normal 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 */
|
@ -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
533
src/stats-json.c
Normal 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;
|
||||
}
|
530
src/stats.c
530
src/stats.c
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user