Coverage Report

Created: 2026-03-21 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/src/stats-json.c
Line
Count
Source
1
#include <haproxy/stats-json.h>
2
3
#include <stdio.h>
4
5
#include <haproxy/applet.h>
6
#include <haproxy/buf.h>
7
#include <haproxy/chunk.h>
8
#include <haproxy/stats.h>
9
10
/* Emits an encoding of the field type as JSON.
11
  * Returns non-zero on success, 0 if the buffer is full.
12
  */
13
static int stats_emit_json_field_tags(struct buffer *out, const struct field *f)
14
0
{
15
0
  const char *origin, *nature, *scope;
16
0
  int old_len;
17
18
0
  switch (field_origin(f, 0)) {
19
0
  case FO_METRIC:  origin = "Metric";  break;
20
0
  case FO_STATUS:  origin = "Status";  break;
21
0
  case FO_KEY:     origin = "Key";     break;
22
0
  case FO_CONFIG:  origin = "Config";  break;
23
0
  case FO_PRODUCT: origin = "Product"; break;
24
0
  default:         origin = "Unknown"; break;
25
0
  }
26
27
0
  switch (field_nature(f, 0)) {
28
0
  case FN_GAUGE:    nature = "Gauge";    break;
29
0
  case FN_LIMIT:    nature = "Limit";    break;
30
0
  case FN_MIN:      nature = "Min";      break;
31
0
  case FN_MAX:      nature = "Max";      break;
32
0
  case FN_RATE:     nature = "Rate";     break;
33
0
  case FN_COUNTER:  nature = "Counter";  break;
34
0
  case FN_DURATION: nature = "Duration"; break;
35
0
  case FN_AGE:      nature = "Age";      break;
36
0
  case FN_TIME:     nature = "Time";     break;
37
0
  case FN_NAME:     nature = "Name";     break;
38
0
  case FN_OUTPUT:   nature = "Output";   break;
39
0
  case FN_AVG:      nature = "Avg";      break;
40
0
  default:          nature = "Unknown";  break;
41
0
  }
42
43
0
  switch (field_scope(f, 0)) {
44
0
  case FS_PROCESS: scope = "Process"; break;
45
0
  case FS_SERVICE: scope = "Service"; break;
46
0
  case FS_SYSTEM:  scope = "System";  break;
47
0
  case FS_CLUSTER: scope = "Cluster"; break;
48
0
  default:         scope = "Unknown"; break;
49
0
  }
50
51
0
  old_len = out->data;
52
0
  chunk_appendf(out, "\"tags\":{"
53
0
          "\"origin\":\"%s\","
54
0
          "\"nature\":\"%s\","
55
0
          "\"scope\":\"%s\""
56
0
         "}", origin, nature, scope);
57
0
  return !(old_len == out->data);
58
0
}
59
60
/* Limit JSON integer values to the range [-(2**53)+1, (2**53)-1] as per
61
 * the recommendation for interoperable integers in section 6 of RFC 7159.
62
 */
63
0
#define JSON_INT_MAX ((1LL << 53) - 1)
64
0
#define JSON_INT_MIN (0 - JSON_INT_MAX)
65
66
/* Emits a stats field value and its type in JSON.
67
 * Returns non-zero on success, 0 on error.
68
 */
69
static int stats_emit_json_data_field(struct buffer *out, const struct field *f)
70
0
{
71
0
  int old_len;
72
0
  char buf[20];
73
0
  const char *type, *value = buf, *quote = "";
74
75
0
  switch (field_format(f, 0)) {
76
0
  case FF_EMPTY: return 1;
77
0
  case FF_S32:   type = "\"s32\"";
78
0
           snprintf(buf, sizeof(buf), "%d", f->u.s32);
79
0
           break;
80
0
  case FF_U32:   type = "\"u32\"";
81
0
           snprintf(buf, sizeof(buf), "%u", f->u.u32);
82
0
           break;
83
0
  case FF_S64:   type = "\"s64\"";
84
0
           if (f->u.s64 < JSON_INT_MIN || f->u.s64 > JSON_INT_MAX)
85
0
             return 0;
86
0
           type = "\"s64\"";
87
0
           snprintf(buf, sizeof(buf), "%lld", (long long)f->u.s64);
88
0
           break;
89
0
  case FF_U64:   if (f->u.u64 > JSON_INT_MAX)
90
0
             return 0;
91
0
           type = "\"u64\"";
92
0
           snprintf(buf, sizeof(buf), "%llu",
93
0
        (unsigned long long) f->u.u64);
94
0
           break;
95
0
  case FF_FLT:   type = "\"flt\"";
96
0
           flt_trim(buf, 0, snprintf(buf, sizeof(buf), "%f", f->u.flt));
97
0
           break;
98
0
  case FF_STR:   type = "\"str\"";
99
0
           value = field_str(f, 0);
100
0
           quote = "\"";
101
0
           break;
102
0
  default:       snprintf(buf, sizeof(buf), "%u", f->type);
103
0
           type = buf;
104
0
           value = "unknown";
105
0
           quote = "\"";
106
0
           break;
107
0
  }
108
109
0
  old_len = out->data;
110
0
  chunk_appendf(out, ",\"value\":{\"type\":%s,\"value\":%s%s%s}",
111
0
          type, quote, value, quote);
112
0
  return !(old_len == out->data);
113
0
}
114
115
static void stats_print_proxy_field_json(struct buffer *out,
116
                                         const struct field *stat,
117
                                         const char *name,
118
                                         int pos,
119
                                         uint32_t field_type,
120
                                         uint32_t iid,
121
                                         uint32_t sid,
122
                                         uint32_t pid)
123
0
{
124
0
  const char *obj_type;
125
0
  switch (field_type) {
126
0
    case STATS_TYPE_FE: obj_type = "Frontend"; break;
127
0
    case STATS_TYPE_BE: obj_type = "Backend";  break;
128
0
    case STATS_TYPE_SO: obj_type = "Listener"; break;
129
0
    case STATS_TYPE_SV: obj_type = "Server";   break;
130
0
    default:            obj_type = "Unknown";  break;
131
0
  }
132
133
0
  chunk_appendf(out,
134
0
                "{"
135
0
                "\"objType\":\"%s\","
136
0
                "\"proxyId\":%u,"
137
0
                "\"id\":%u,"
138
0
                "\"field\":{\"pos\":%d,\"name\":\"%s\"},"
139
0
                "\"processNum\":%u,",
140
0
                obj_type, iid, sid, pos, name, pid);
141
0
}
142
143
static void stats_print_rslv_field_json(struct buffer *out,
144
                                        const struct field *stat,
145
                                        const char *name,
146
                                        int pos)
147
0
{
148
0
  chunk_appendf(out,
149
0
                "{"
150
0
                "\"field\":{\"pos\":%d,\"name\":\"%s\"},",
151
0
                pos, name);
152
0
}
153
154
155
/* Dumps the stats JSON header to <out> buffer. The caller is responsible for
156
 * clearing it if needed.
157
 */
158
void stats_dump_json_header(struct buffer *out)
159
0
{
160
0
  chunk_strcat(out, "[");
161
0
}
162
163
/* Dump all fields from <line> into <out> using a typed "field:desc:type:value" format */
164
int stats_dump_fields_json(struct buffer *out,
165
                           const struct field *line, size_t stats_count,
166
                           struct show_stat_ctx *ctx)
167
0
{
168
0
  int flags = ctx->flags;
169
0
  int domain = ctx->domain;
170
0
  int started = (ctx->field) ? 1 : 0;
171
0
  int ready_data = 0;
172
173
0
  if (!started && (flags & STAT_F_STARTED) && !chunk_strcat(out, ","))
174
0
    return 0;
175
0
  if (!started && !chunk_strcat(out, "["))
176
0
    return 0;
177
178
0
  for (; ctx->field < stats_count; ctx->field++) {
179
0
    int old_len;
180
0
    int i = ctx->field;
181
182
0
    if (!line[i].type)
183
0
      continue;
184
185
0
    if (started && !chunk_strcat(out, ","))
186
0
      goto err;
187
0
    started = 1;
188
189
0
    old_len = out->data;
190
0
    if (domain == STATS_DOMAIN_PROXY) {
191
0
      stats_print_proxy_field_json(out, &line[i],
192
0
                                   stat_cols[domain][i].name,
193
0
                                   i,
194
0
                                   line[ST_I_PX_TYPE].u.u32,
195
0
                                   line[ST_I_PX_IID].u.u32,
196
0
                                   line[ST_I_PX_SID].u.u32,
197
0
                                   line[ST_I_PX_PID].u.u32);
198
0
    } else if (domain == STATS_DOMAIN_RESOLVERS) {
199
0
      stats_print_rslv_field_json(out, &line[i],
200
0
                                  stat_cols[domain][i].name,
201
0
                                  i);
202
0
    }
203
204
0
    if (old_len == out->data)
205
0
      goto err;
206
207
0
    if (!stats_emit_json_field_tags(out, &line[i]))
208
0
      goto err;
209
210
0
    if (!stats_emit_json_data_field(out, &line[i]))
211
0
      goto err;
212
213
0
    if (!chunk_strcat(out, "}"))
214
0
      goto err;
215
0
    ready_data = out->data;
216
0
  }
217
218
0
  if (!chunk_strcat(out, "]"))
219
0
    goto err;
220
221
0
  ctx->field = 0; /* we're done */
222
0
  return 1;
223
224
0
err:
225
0
  if (!ready_data) {
226
    /* not enough buffer space for a single entry.. */
227
0
    chunk_reset(out);
228
0
    if (ctx->flags & STAT_F_STARTED)
229
0
      chunk_strcat(out, ",");
230
0
    chunk_appendf(out, "{\"errorStr\":\"output buffer too short\"}");
231
0
    return 0; /* hard error */
232
0
  }
233
  /* push ready data and wait for a new buffer to complete the dump */
234
0
  out->data = ready_data;
235
0
  return 1;
236
0
}
237
238
/* Dumps the JSON stats trailer block to <out> buffer. The caller is
239
 * responsible for clearing it if needed.
240
 */
241
void stats_dump_json_end(struct buffer *out)
242
0
{
243
0
  chunk_strcat(out, "]\n");
244
0
}
245
246
/* Dump all fields from <stats> into <out> using the "show info json" format */
247
int stats_dump_json_info_fields(struct buffer *out,
248
                                const struct field *info,
249
                                struct show_stat_ctx *ctx)
250
0
{
251
0
  int started = (ctx->field) ? 1 : 0;
252
0
  int ready_data = 0;
253
254
0
  if (!started && !chunk_strcat(out, "["))
255
0
    return 0;
256
257
0
  for (; ctx->field < ST_I_INF_MAX; ctx->field++) {
258
0
    int old_len;
259
0
    int i = ctx->field;
260
261
0
    if (!field_format(info, i))
262
0
      continue;
263
264
0
    if (started && !chunk_strcat(out, ","))
265
0
      goto err;
266
0
    started = 1;
267
268
0
    old_len = out->data;
269
0
    chunk_appendf(out,
270
0
            "{\"field\":{\"pos\":%d,\"name\":\"%s\"},"
271
0
            "\"processNum\":%u,",
272
0
            i, stat_cols_info[i].name,
273
0
            info[ST_I_INF_PROCESS_NUM].u.u32);
274
0
    if (old_len == out->data)
275
0
      goto err;
276
277
0
    if (!stats_emit_json_field_tags(out, &info[i]))
278
0
      goto err;
279
280
0
    if (!stats_emit_json_data_field(out, &info[i]))
281
0
      goto err;
282
283
0
    if (!chunk_strcat(out, "}"))
284
0
      goto err;
285
0
    ready_data = out->data;
286
0
  }
287
288
0
  if (!chunk_strcat(out, "]\n"))
289
0
    goto err;
290
0
  ctx->field = 0; /* we're done */
291
0
  return 1;
292
293
0
err:
294
0
  if (!ready_data) {
295
    /* not enough buffer space for a single entry.. */
296
0
    chunk_reset(out);
297
0
    chunk_appendf(out, "{\"errorStr\":\"output buffer too short\"}\n");
298
0
    return 0; /* hard error */
299
0
  }
300
  /* push ready data and wait for a new buffer to complete the dump */
301
0
  out->data = ready_data;
302
0
  return 1;
303
0
}
304
305
/* This function dumps the schema onto the stream connector's read buffer.
306
 * It returns 0 as long as it does not complete, non-zero upon completion.
307
 * No state is used.
308
 *
309
 * Integer values bounded to the range [-(2**53)+1, (2**53)-1] as
310
 * per the recommendation for interoperable integers in section 6 of RFC 7159.
311
 */
312
void stats_dump_json_schema(struct buffer *out)
313
0
{
314
315
0
  int old_len = out->data;
316
317
0
  chunk_strcat(out,
318
0
         "{"
319
0
          "\"$schema\":\"http://json-schema.org/draft-04/schema#\","
320
0
          "\"oneOf\":["
321
0
           "{"
322
0
      "\"title\":\"Info\","
323
0
      "\"type\":\"array\","
324
0
      "\"items\":{"
325
0
       "\"title\":\"InfoItem\","
326
0
       "\"type\":\"object\","
327
0
       "\"properties\":{"
328
0
        "\"field\":{\"$ref\":\"#/definitions/field\"},"
329
0
        "\"processNum\":{\"$ref\":\"#/definitions/processNum\"},"
330
0
        "\"tags\":{\"$ref\":\"#/definitions/tags\"},"
331
0
        "\"value\":{\"$ref\":\"#/definitions/typedValue\"}"
332
0
       "},"
333
0
       "\"required\":[\"field\",\"processNum\",\"tags\","
334
0
               "\"value\"]"
335
0
      "}"
336
0
           "},"
337
0
           "{"
338
0
      "\"title\":\"Stat\","
339
0
      "\"type\":\"array\","
340
0
      "\"items\":{"
341
0
       "\"title\":\"InfoItem\","
342
0
       "\"type\":\"object\","
343
0
       "\"properties\":{"
344
0
        "\"objType\":{"
345
0
         "\"enum\":[\"Frontend\",\"Backend\",\"Listener\","
346
0
             "\"Server\",\"Unknown\"]"
347
0
        "},"
348
0
        "\"proxyId\":{"
349
0
         "\"type\":\"integer\","
350
0
         "\"minimum\":0"
351
0
        "},"
352
0
        "\"id\":{"
353
0
         "\"type\":\"integer\","
354
0
         "\"minimum\":0"
355
0
        "},"
356
0
        "\"field\":{\"$ref\":\"#/definitions/field\"},"
357
0
        "\"processNum\":{\"$ref\":\"#/definitions/processNum\"},"
358
0
        "\"tags\":{\"$ref\":\"#/definitions/tags\"},"
359
0
        "\"typedValue\":{\"$ref\":\"#/definitions/typedValue\"}"
360
0
       "},"
361
0
       "\"required\":[\"objType\",\"proxyId\",\"id\","
362
0
               "\"field\",\"processNum\",\"tags\","
363
0
               "\"value\"]"
364
0
      "}"
365
0
           "},"
366
0
           "{"
367
0
      "\"title\":\"Error\","
368
0
      "\"type\":\"object\","
369
0
      "\"properties\":{"
370
0
       "\"errorStr\":{"
371
0
        "\"type\":\"string\""
372
0
       "}"
373
0
      "},"
374
0
      "\"required\":[\"errorStr\"]"
375
0
           "}"
376
0
          "],"
377
0
          "\"definitions\":{"
378
0
           "\"field\":{"
379
0
      "\"type\":\"object\","
380
0
      "\"pos\":{"
381
0
       "\"type\":\"integer\","
382
0
       "\"minimum\":0"
383
0
      "},"
384
0
      "\"name\":{"
385
0
       "\"type\":\"string\""
386
0
      "},"
387
0
      "\"required\":[\"pos\",\"name\"]"
388
0
           "},"
389
0
           "\"processNum\":{"
390
0
      "\"type\":\"integer\","
391
0
      "\"minimum\":1"
392
0
           "},"
393
0
           "\"tags\":{"
394
0
      "\"type\":\"object\","
395
0
      "\"origin\":{"
396
0
       "\"type\":\"string\","
397
0
       "\"enum\":[\"Metric\",\"Status\",\"Key\","
398
0
           "\"Config\",\"Product\",\"Unknown\"]"
399
0
      "},"
400
0
      "\"nature\":{"
401
0
       "\"type\":\"string\","
402
0
       "\"enum\":[\"Gauge\",\"Limit\",\"Min\",\"Max\","
403
0
           "\"Rate\",\"Counter\",\"Duration\","
404
0
           "\"Age\",\"Time\",\"Name\",\"Output\","
405
0
           "\"Avg\", \"Unknown\"]"
406
0
      "},"
407
0
      "\"scope\":{"
408
0
       "\"type\":\"string\","
409
0
       "\"enum\":[\"Cluster\",\"Process\",\"Service\","
410
0
           "\"System\",\"Unknown\"]"
411
0
      "},"
412
0
      "\"required\":[\"origin\",\"nature\",\"scope\"]"
413
0
           "},"
414
0
           "\"typedValue\":{"
415
0
      "\"type\":\"object\","
416
0
      "\"oneOf\":["
417
0
       "{\"$ref\":\"#/definitions/typedValue/definitions/s32Value\"},"
418
0
       "{\"$ref\":\"#/definitions/typedValue/definitions/s64Value\"},"
419
0
       "{\"$ref\":\"#/definitions/typedValue/definitions/u32Value\"},"
420
0
       "{\"$ref\":\"#/definitions/typedValue/definitions/u64Value\"},"
421
0
       "{\"$ref\":\"#/definitions/typedValue/definitions/strValue\"}"
422
0
      "],"
423
0
      "\"definitions\":{"
424
0
       "\"s32Value\":{"
425
0
        "\"properties\":{"
426
0
         "\"type\":{"
427
0
          "\"type\":\"string\","
428
0
          "\"enum\":[\"s32\"]"
429
0
         "},"
430
0
         "\"value\":{"
431
0
          "\"type\":\"integer\","
432
0
          "\"minimum\":-2147483648,"
433
0
          "\"maximum\":2147483647"
434
0
         "}"
435
0
        "},"
436
0
        "\"required\":[\"type\",\"value\"]"
437
0
       "},"
438
0
       "\"s64Value\":{"
439
0
        "\"properties\":{"
440
0
         "\"type\":{"
441
0
          "\"type\":\"string\","
442
0
          "\"enum\":[\"s64\"]"
443
0
         "},"
444
0
         "\"value\":{"
445
0
          "\"type\":\"integer\","
446
0
          "\"minimum\":-9007199254740991,"
447
0
          "\"maximum\":9007199254740991"
448
0
         "}"
449
0
        "},"
450
0
        "\"required\":[\"type\",\"value\"]"
451
0
       "},"
452
0
       "\"u32Value\":{"
453
0
        "\"properties\":{"
454
0
         "\"type\":{"
455
0
          "\"type\":\"string\","
456
0
          "\"enum\":[\"u32\"]"
457
0
         "},"
458
0
         "\"value\":{"
459
0
          "\"type\":\"integer\","
460
0
          "\"minimum\":0,"
461
0
          "\"maximum\":4294967295"
462
0
         "}"
463
0
        "},"
464
0
        "\"required\":[\"type\",\"value\"]"
465
0
       "},"
466
0
       "\"u64Value\":{"
467
0
        "\"properties\":{"
468
0
         "\"type\":{"
469
0
          "\"type\":\"string\","
470
0
          "\"enum\":[\"u64\"]"
471
0
         "},"
472
0
         "\"value\":{"
473
0
          "\"type\":\"integer\","
474
0
          "\"minimum\":0,"
475
0
          "\"maximum\":9007199254740991"
476
0
         "}"
477
0
        "},"
478
0
        "\"required\":[\"type\",\"value\"]"
479
0
       "},"
480
0
       "\"strValue\":{"
481
0
        "\"properties\":{"
482
0
         "\"type\":{"
483
0
          "\"type\":\"string\","
484
0
          "\"enum\":[\"str\"]"
485
0
         "},"
486
0
         "\"value\":{\"type\":\"string\"}"
487
0
        "},"
488
0
        "\"required\":[\"type\",\"value\"]"
489
0
       "},"
490
0
       "\"unknownValue\":{"
491
0
        "\"properties\":{"
492
0
         "\"type\":{"
493
0
          "\"type\":\"integer\","
494
0
          "\"minimum\":0"
495
0
         "},"
496
0
         "\"value\":{"
497
0
          "\"type\":\"string\","
498
0
          "\"enum\":[\"unknown\"]"
499
0
         "}"
500
0
        "},"
501
0
        "\"required\":[\"type\",\"value\"]"
502
0
       "}"
503
0
      "}"
504
0
           "}"
505
0
          "}"
506
0
         "}");
507
508
0
  if (old_len == out->data) {
509
0
    chunk_reset(out);
510
0
    chunk_appendf(out,
511
0
            "{\"errorStr\":\"output buffer too short\"}");
512
0
  }
513
0
  chunk_appendf(out, "\n");
514
0
}
515
516
/* This function dumps the schema onto the stream connector's read buffer.
517
 * It returns 0 as long as it does not complete, non-zero upon completion.
518
 * No state is used.
519
 */
520
int stats_dump_json_schema_to_buffer(struct appctx *appctx)
521
0
{
522
0
  struct show_stat_ctx *ctx = appctx->svcctx;
523
0
  struct buffer *chk = &ctx->chunk;
524
525
0
  chunk_reset(chk);
526
527
0
  stats_dump_json_schema(chk);
528
529
0
  if (applet_putchk(appctx, chk) == -1)
530
0
    return 0;
531
532
0
  return 1;
533
0
}