Coverage Report

Created: 2026-06-20 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fluent-bit/lib/cmetrics/src/cmt_decode_prometheus.c
Line
Count
Source
1
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*  CMetrics
4
 *  ========
5
 *  Copyright 2021-2022 The CMetrics Authors
6
 *
7
 *  Licensed under the Apache License, Version 2.0 (the "License");
8
 *  you may not use this file except in compliance with the License.
9
 *  You may obtain a copy of the License at
10
 *
11
 *      http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 *  Unless required by applicable law or agreed to in writing, software
14
 *  distributed under the License is distributed on an "AS IS" BASIS,
15
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 *  See the License for the specific language governing permissions and
17
 *  limitations under the License.
18
 */
19
20
#include <ctype.h>
21
#include <errno.h>
22
#include <math.h>
23
#include <stdarg.h>
24
#include <stdint.h>
25
26
#include <cmetrics/cmetrics.h>
27
#include <cmetrics/cmt_gauge.h>
28
#include <cmetrics/cmt_untyped.h>
29
#include <cmetrics/cmt_histogram.h>
30
#include <cmetrics/cmt_summary.h>
31
#include <cmetrics/cmt_counter.h>
32
#include <cmetrics/cmt_decode_prometheus.h>
33
34
#include <cmt_decode_prometheus_parser.h>
35
#include <stdio.h>
36
#include <string.h>
37
#include <cmetrics/cmt_map.h>
38
39
static void reset_context(struct cmt_decode_prometheus_context *context,
40
                          bool reset_summary)
41
1.64k
{
42
1.64k
    int i;
43
1.64k
    struct cmt_decode_prometheus_context_sample *sample;
44
45
1.64k
    while (!cfl_list_is_empty(&context->metric.samples)) {
46
0
        sample = cfl_list_entry_first(&context->metric.samples,
47
0
                                     struct cmt_decode_prometheus_context_sample, _head);
48
0
        for (i = 0; i < context->metric.label_count; i++) {
49
0
            cfl_sds_destroy(sample->label_values[i]);
50
0
        }
51
0
        cfl_list_del(&sample->_head);
52
0
        free(sample);
53
0
    }
54
55
1.64k
    for (i = 0; i < context->metric.label_count; i++) {
56
0
        cfl_sds_destroy(context->metric.labels[i]);
57
0
    }
58
59
1.64k
    if (context->metric.ns) {
60
0
        if ((void *) context->metric.ns != (void *) "") {
61
            /* when namespace is empty, "name" contains a pointer to the
62
             * allocated string.
63
             *
64
             * Note : When the metric name doesn't include the namespace
65
             * ns is set to a constant empty string and we need to
66
             * differentiate that case from the case where an empty
67
             * namespace is provided.
68
             */
69
70
0
            free(context->metric.ns);
71
0
        }
72
0
        else {
73
0
            free(context->metric.name);
74
0
        }
75
0
    }
76
77
1.64k
    cfl_sds_destroy(context->strbuf);
78
1.64k
    context->strbuf = NULL;
79
1.64k
    if (reset_summary) {
80
1.64k
        context->current.summary = NULL;
81
1.64k
    }
82
1.64k
    cfl_sds_destroy(context->metric.name_orig);
83
1.64k
    cfl_sds_destroy(context->metric.docstring);
84
1.64k
    memset(&context->metric,
85
1.64k
            0,
86
1.64k
            sizeof(struct cmt_decode_prometheus_context_metric));
87
1.64k
    cfl_list_init(&context->metric.samples);
88
1.64k
}
89
90
91
int cmt_decode_prometheus_create(
92
        struct cmt **out_cmt,
93
        const char *in_buf,
94
        size_t in_size,
95
        struct cmt_decode_prometheus_parse_opts *opts)
96
1.64k
{
97
1.64k
    yyscan_t scanner;
98
1.64k
    YY_BUFFER_STATE buf;
99
1.64k
    struct cmt *cmt;
100
1.64k
    struct cmt_decode_prometheus_context context;
101
1.64k
    int result;
102
103
1.64k
    cmt = cmt_create();
104
105
1.64k
    if (cmt == NULL) {
106
0
        return CMT_DECODE_PROMETHEUS_ALLOCATION_ERROR;
107
0
    }
108
109
1.64k
    memset(&context, 0, sizeof(context));
110
1.64k
    context.cmt = cmt;
111
1.64k
    if (opts) {
112
1.64k
        context.opts = *opts;
113
1.64k
    }
114
1.64k
    cfl_list_init(&(context.metric.samples));
115
1.64k
    cmt_decode_prometheus_lex_init(&scanner);
116
1.64k
    if (!in_size) {
117
0
        in_size = strlen(in_buf);
118
0
    }
119
1.64k
    buf = cmt_decode_prometheus__scan_bytes((char *)in_buf, in_size, scanner);
120
1.64k
    if (!buf) {
121
0
        cmt_destroy(cmt);
122
0
        return CMT_DECODE_PROMETHEUS_ALLOCATION_ERROR;
123
0
    }
124
125
1.64k
    result = cmt_decode_prometheus_parse(scanner, &context);
126
127
1.64k
    if (result == 0) {
128
0
        *out_cmt = cmt;
129
0
    }
130
1.64k
    else {
131
1.64k
        cmt_destroy(cmt);
132
1.64k
        if (context.errcode) {
133
1.64k
            result = context.errcode;
134
1.64k
        }
135
1.64k
        reset_context(&context, true);
136
1.64k
    }
137
138
1.64k
    cmt_decode_prometheus__delete_buffer(buf, scanner);
139
1.64k
    cmt_decode_prometheus_lex_destroy(scanner);
140
141
1.64k
    return result;
142
1.64k
}
143
144
void cmt_decode_prometheus_destroy(struct cmt *cmt)
145
0
{
146
0
    cmt_destroy(cmt);
147
0
}
148
149
static int report_error(struct cmt_decode_prometheus_context *context,
150
                         int errcode,
151
                         const char *format, ...)
152
1.64k
{
153
1.64k
    va_list args;
154
1.64k
    va_start(args, format);
155
1.64k
    context->errcode = errcode;
156
1.64k
    if (context->opts.errbuf && context->opts.errbuf_size) {
157
1.64k
        vsnprintf(context->opts.errbuf, context->opts.errbuf_size - 1, format, args);
158
1.64k
    }
159
1.64k
    va_end(args);
160
1.64k
    return errcode;
161
1.64k
}
162
163
static int split_metric_name(struct cmt_decode_prometheus_context *context,
164
        cfl_sds_t metric_name, char **ns,
165
        char **subsystem, char **name)
166
0
{
167
    /* split the name */
168
0
    *ns = strdup(metric_name);
169
0
    if (!*ns) {
170
0
        return report_error(context,
171
0
                CMT_DECODE_PROMETHEUS_ALLOCATION_ERROR,
172
0
                "memory allocation failed");
173
0
    }
174
0
    *subsystem = strchr(*ns, '_');
175
0
    if (!(*subsystem)) {
176
0
        *name = *ns;
177
0
        *subsystem = "";
178
0
        *ns = "";
179
0
    }
180
0
    else {
181
0
        **subsystem = 0;  /* split */
182
0
        (*subsystem)++;
183
0
        *name = strchr(*subsystem, '_');
184
0
        if (!(*name)) {
185
0
            *name = *subsystem;
186
0
            *subsystem = "";
187
0
        }
188
0
        else {
189
0
            **name = 0;
190
0
            (*name)++;
191
0
        }
192
0
    }
193
0
    return 0;
194
0
}
195
196
/* Use this helper function to return a stub value for docstring when it is not
197
 * available. This is necessary for now because the metric constructors require
198
 * a docstring, even though it is not required by prometheus spec. */
199
static char *get_docstring(struct cmt_decode_prometheus_context *context)
200
0
{
201
0
    return context->metric.docstring && strlen(context->metric.docstring) ?
202
0
        context->metric.docstring : " ";
203
0
}
204
205
static int parse_uint64(const char *in, uint64_t *out)
206
0
{
207
0
    char *end;
208
0
    int64_t val;
209
210
0
    errno = 0;
211
0
    val = strtoll(in, &end, 10);
212
0
    if (end == in || *end != 0 || errno)  {
213
0
        return -1;
214
0
    }
215
216
    /* Even though prometheus text format supports negative numbers, cmetrics
217
     * doesn't, so we truncate to 0 */
218
0
    if (val < 0) {
219
0
        val = 0;
220
0
    }
221
0
    *out = val;
222
0
    return 0;
223
0
}
224
225
static int parse_double(const char *in, double *out)
226
0
{
227
0
    char *end;
228
0
    double val;
229
0
    errno = 0;
230
0
    val = strtod(in, &end);
231
0
    if (end == in || *end != 0 || errno) {
232
0
        return -1;
233
0
    }
234
0
    *out = val;
235
0
    return 0;
236
0
}
237
238
static int parse_timestamp(struct cmt_decode_prometheus_context *context,
239
                           char *data_source, uint64_t *timestamp)
240
0
{
241
0
    int result;
242
243
0
    result = CMT_DECODE_PROMETHEUS_SUCCESS;
244
245
0
    if (data_source != NULL && strlen(data_source) > 0) {
246
0
        result = parse_uint64(data_source, timestamp);
247
248
0
        if (result) {
249
0
            result = report_error(context,
250
0
                                  CMT_DECODE_PROMETHEUS_PARSE_TIMESTAMP_FAILED,
251
0
                                  "failed to parse sample: \"%s\" is not a valid "
252
0
                                  "timestamp", data_source);
253
0
        }
254
0
        else {
255
            /* prometheus text format timestamps are expressed in milliseconds,
256
             * while cmetrics expresses them in nanoseconds, so multiply by 10e5
257
             */
258
259
0
            *timestamp *= 10e5;
260
0
        }
261
0
    }
262
263
0
    return result;
264
0
}
265
266
static int parse_value_timestamp(
267
        struct cmt_decode_prometheus_context *context,
268
        struct cmt_decode_prometheus_context_sample *sample,
269
        double *value,
270
        uint64_t *timestamp)
271
0
{
272
273
0
    if (parse_double(sample->value1, value)) {
274
0
        return report_error(context,
275
0
                CMT_DECODE_PROMETHEUS_PARSE_VALUE_FAILED,
276
0
                "failed to parse sample: \"%s\" is not a valid "
277
0
                "value", sample->value1);
278
0
    }
279
280
0
    if (context->opts.override_timestamp) {
281
0
        *timestamp = context->opts.override_timestamp;
282
0
    }
283
0
    else if (!strlen(sample->value2)) {
284
        /* No timestamp was specified, use default value */
285
0
        *timestamp = context->opts.default_timestamp;
286
0
        return 0;
287
0
    }
288
0
    else if (parse_uint64(sample->value2, timestamp)) {
289
0
        return report_error(context,
290
0
                CMT_DECODE_PROMETHEUS_PARSE_TIMESTAMP_FAILED,
291
0
                "failed to parse sample: \"%s\" is not a valid "
292
0
                "timestamp", sample->value2);
293
0
    }
294
295
    /* prometheus text format timestamps are in milliseconds, while cmetrics is in
296
     * nanoseconds, so multiply by 10e5 */
297
0
    *timestamp = *timestamp * 10e5;
298
299
0
    return 0;
300
0
}
301
302
static int add_metric_counter(struct cmt_decode_prometheus_context *context)
303
0
{
304
0
    int ret;
305
0
    size_t label_count;
306
0
    struct cmt_counter *c;
307
0
    struct cfl_list *head;
308
0
    struct cfl_list *tmp;
309
0
    struct cmt_decode_prometheus_context_sample *sample;
310
0
    double value;
311
0
    uint64_t timestamp;
312
313
0
    c = cmt_counter_create(context->cmt,
314
0
            context->metric.ns,
315
0
            context->metric.subsystem,
316
0
            context->metric.name,
317
0
            get_docstring(context),
318
0
            context->metric.label_count,
319
0
            context->metric.labels);
320
321
0
    if (!c) {
322
0
        return report_error(context,
323
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
324
0
                "cmt_counter_create failed");
325
0
    }
326
327
0
    cfl_list_foreach_safe(head, tmp, &context->metric.samples) {
328
0
        sample = cfl_list_entry(head, struct cmt_decode_prometheus_context_sample, _head);
329
0
        label_count = context->metric.label_count;
330
0
        ret = parse_value_timestamp(context, sample, &value, &timestamp);
331
0
        if (ret) {
332
0
            return ret;
333
0
        }
334
0
        if (cmt_counter_set(c,
335
0
                    timestamp,
336
0
                    value,
337
0
                    label_count,
338
0
                    label_count ? sample->label_values : NULL)) {
339
0
            return report_error(context,
340
0
                    CMT_DECODE_PROMETHEUS_CMT_SET_ERROR,
341
0
                    "cmt_counter_set failed");
342
0
        }
343
0
    }
344
345
0
    return 0;
346
0
}
347
348
static int add_metric_gauge(struct cmt_decode_prometheus_context *context)
349
0
{
350
0
    int ret;
351
0
    size_t label_count;
352
0
    struct cmt_gauge *c;
353
0
    struct cfl_list *head;
354
0
    struct cfl_list *tmp;
355
0
    struct cmt_decode_prometheus_context_sample *sample;
356
0
    double value;
357
0
    uint64_t timestamp;
358
359
0
    c = cmt_gauge_create(context->cmt,
360
0
            context->metric.ns,
361
0
            context->metric.subsystem,
362
0
            context->metric.name,
363
0
            get_docstring(context),
364
0
            context->metric.label_count,
365
0
            context->metric.labels);
366
367
0
    if (!c) {
368
0
        return report_error(context,
369
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
370
0
                "cmt_gauge_create failed");
371
0
    }
372
373
0
    cfl_list_foreach_safe(head, tmp, &context->metric.samples) {
374
0
        sample = cfl_list_entry(head, struct cmt_decode_prometheus_context_sample, _head);
375
0
        label_count = context->metric.label_count;
376
0
        ret = parse_value_timestamp(context, sample, &value, &timestamp);
377
0
        if (ret) {
378
0
            return ret;
379
0
        }
380
0
        if (cmt_gauge_set(c,
381
0
                    timestamp,
382
0
                    value,
383
0
                    label_count,
384
0
                    label_count ? sample->label_values : NULL)) {
385
0
            return report_error(context,
386
0
                    CMT_DECODE_PROMETHEUS_CMT_SET_ERROR,
387
0
                    "cmt_gauge_set failed");
388
0
        }
389
0
    }
390
391
0
    return 0;
392
0
}
393
394
static int add_metric_untyped(struct cmt_decode_prometheus_context *context)
395
0
{
396
0
    int ret;
397
0
    size_t label_count;
398
0
    struct cmt_untyped *c;
399
0
    struct cfl_list *head;
400
0
    struct cfl_list *tmp;
401
0
    struct cmt_decode_prometheus_context_sample *sample;
402
0
    double value;
403
0
    uint64_t timestamp;
404
405
0
    c = cmt_untyped_create(context->cmt,
406
0
            context->metric.ns,
407
0
            context->metric.subsystem,
408
0
            context->metric.name,
409
0
            get_docstring(context),
410
0
            context->metric.label_count,
411
0
            context->metric.labels);
412
413
0
    if (!c) {
414
0
        return report_error(context,
415
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
416
0
                "cmt_untyped_create failed");
417
0
    }
418
419
0
    cfl_list_foreach_safe(head, tmp, &context->metric.samples) {
420
0
        sample = cfl_list_entry(head, struct cmt_decode_prometheus_context_sample, _head);
421
0
        label_count = context->metric.label_count;
422
0
        ret = parse_value_timestamp(context, sample, &value, &timestamp);
423
0
        if (ret) {
424
0
            return ret;
425
0
        }
426
0
        if (cmt_untyped_set(c,
427
0
                    timestamp,
428
0
                    value,
429
0
                    label_count,
430
0
                    label_count ? sample->label_values : NULL)) {
431
0
            return report_error(context,
432
0
                    CMT_DECODE_PROMETHEUS_CMT_SET_ERROR,
433
0
                    "cmt_untyped_set failed");
434
0
        }
435
0
    }
436
437
0
    return 0;
438
0
}
439
440
static int add_metric_histogram(struct cmt_decode_prometheus_context *context)
441
0
{
442
0
    int ret = 0;
443
0
    int i;
444
0
    int has_le = CMT_FALSE;
445
0
    size_t bucket_count;
446
0
    size_t bucket_index;
447
0
    size_t label_count = 0;
448
0
    double *buckets = NULL;
449
0
    uint64_t *bucket_defaults = NULL;
450
0
    double sum = 0;
451
0
    uint64_t count = 0;
452
0
    double count_dbl;
453
0
    struct cfl_list *head;
454
0
    struct cfl_list *tmp;
455
0
    struct cmt_decode_prometheus_context_sample *sample;
456
0
    size_t le_label_index = 0;
457
0
    struct cmt_histogram *h;
458
0
    struct cmt_histogram_buckets *cmt_buckets;
459
0
    cfl_sds_t *labels_without_le = NULL;
460
0
    cfl_sds_t *values_without_le = NULL;
461
0
    int label_i;
462
0
    uint64_t timestamp = 0;
463
464
0
    if (cfl_list_size(&context->metric.samples) < 3) {
465
0
        return report_error(context,
466
0
                CMT_DECODE_PROMETHEUS_SYNTAX_ERROR,
467
0
                "not enough samples for histogram");
468
0
    }
469
470
    /* bucket_count = sample count - 3:
471
     * - "Inf" bucket
472
     * - sum
473
     * - count */
474
0
    bucket_count = cfl_list_size(&context->metric.samples) - 3;
475
0
    if (context->opts.override_timestamp) {
476
0
        timestamp = context->opts.override_timestamp;
477
0
    }
478
479
0
    bucket_defaults = calloc(bucket_count + 1, sizeof(*bucket_defaults));
480
0
    if (!bucket_defaults) {
481
0
        ret = report_error(context,
482
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
483
0
                "failed to allocate bucket defaults");
484
0
        goto end;
485
0
    }
486
0
    buckets = calloc(bucket_count, sizeof(*buckets));
487
0
    if (!buckets) {
488
0
        ret = report_error(context,
489
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
490
0
                "failed to allocate buckets");
491
0
        goto end;
492
0
    }
493
0
    for (i = 0; i < context->metric.label_count; i++) {
494
0
        if (!strcmp(context->metric.labels[i], "le")) {
495
0
            has_le = CMT_TRUE;
496
0
        }
497
0
        else {
498
0
            label_count++;
499
0
        }
500
0
    }
501
502
0
    if (!has_le) {
503
0
        ret = report_error(context,
504
0
                CMT_DECODE_PROMETHEUS_SYNTAX_ERROR,
505
0
                "missing histogram bucket \"le\" label");
506
0
        goto end;
507
0
    }
508
509
0
    labels_without_le = calloc(label_count, sizeof(*labels_without_le));
510
0
    if (!labels_without_le) {
511
0
        ret = report_error(context,
512
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
513
0
                "failed to allocate labels_without_le");
514
0
        goto end;
515
0
    }
516
0
    values_without_le = calloc(label_count, sizeof(*values_without_le));
517
0
    if (!values_without_le) {
518
0
        ret = report_error(context,
519
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
520
0
                "failed to allocate values_without_le");
521
0
        goto end;
522
0
    }
523
524
525
0
    label_i = 0;
526
0
    sample = cfl_list_entry_first(&context->metric.samples, struct cmt_decode_prometheus_context_sample, _head);
527
0
    for (i = 0; i < context->metric.label_count; i++) {
528
0
        if (!strcmp(context->metric.labels[i], "le")) {
529
0
            le_label_index = i;
530
0
        } else {
531
0
            labels_without_le[label_i] = context->metric.labels[i];
532
0
            values_without_le[label_i] = sample->label_values[i];
533
0
            label_i++;
534
0
        }
535
0
    }
536
537
0
    bucket_index = 0;
538
0
    cfl_list_foreach_safe(head, tmp, &context->metric.samples) {
539
0
        sample = cfl_list_entry(head, struct cmt_decode_prometheus_context_sample, _head);
540
0
        switch (sample->type) {
541
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_BUCKET:
542
0
                if (bucket_index == bucket_count) {
543
                    /* probably last bucket, which has "Inf" */
544
0
                    break;
545
0
                }
546
0
                if (!sample->label_values[le_label_index] ||
547
0
                    sample->label_values[le_label_index][0] == '\0') {
548
0
                    ret = report_error(context,
549
0
                            CMT_DECODE_PROMETHEUS_SYNTAX_ERROR,
550
0
                            "missing histogram bucket \"le\" value");
551
0
                    goto end;
552
0
                }
553
0
                if (parse_double(sample->label_values[le_label_index],
554
0
                            buckets + bucket_index)) {
555
0
                    ret = report_error(context,
556
0
                            CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
557
0
                            "failed to parse bucket");
558
0
                    goto end;
559
0
                }
560
0
                if (parse_uint64(sample->value1,
561
0
                            bucket_defaults + bucket_index)) {
562
                    /* Count is supposed to be integer, but apparently
563
                     * some tools can generate count in a floating format.
564
                     * Try to parse as a double and then cast to uint64_t */
565
0
                    if (parse_double(sample->value1, &count_dbl) || count_dbl < 0) {
566
0
                        ret = report_error(context,
567
0
                                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
568
0
                                "failed to parse count");
569
0
                        goto end;
570
0
                    } else {
571
0
                        *(bucket_defaults + bucket_index) = (uint64_t)count_dbl;
572
0
                    }
573
0
                }
574
0
                bucket_index++;
575
576
0
                if (!timestamp) {
577
0
                    ret = parse_timestamp(context, sample->value2, &timestamp);
578
579
0
                    if (ret) {
580
0
                        goto end;
581
0
                    }
582
0
                }
583
584
0
                break;
585
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM:
586
0
                if (parse_double(sample->value1, &sum)) {
587
0
                    ret = report_error(context,
588
0
                            CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
589
0
                            "failed to parse sum");
590
0
                    goto end;
591
0
                }
592
593
0
                if (!timestamp) {
594
0
                    ret = parse_timestamp(context, sample->value2, &timestamp);
595
596
0
                    if (ret) {
597
0
                        goto end;
598
0
                    }
599
0
                }
600
601
0
                break;
602
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT:
603
0
                if (parse_uint64(sample->value1, &count)) {
604
                    /* Count is supposed to be integer, but apparently
605
                     * some tools can generate count in a floating format.
606
                     * Try to parse as a double and then cast to uint64_t */
607
0
                    if (parse_double(sample->value1, &count_dbl) || count_dbl < 0) {
608
0
                        ret = report_error(context,
609
0
                                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
610
0
                                "failed to parse count");
611
0
                        goto end;
612
0
                    } else {
613
0
                        count = (uint64_t)count_dbl;
614
0
                    }
615
0
                }
616
0
                bucket_defaults[bucket_index] = count;
617
618
0
                if (!timestamp) {
619
0
                    ret = parse_timestamp(context, sample->value2, &timestamp);
620
621
0
                    if (ret) {
622
0
                        goto end;
623
0
                    }
624
0
                }
625
626
0
                break;
627
0
        }
628
0
    }
629
630
0
    if (!timestamp) {
631
        /* No timestamp was specified, use default value */
632
0
        timestamp = context->opts.default_timestamp;
633
0
    }
634
635
0
    h = context->current.histogram;
636
0
    if (!h || label_i != h->map->label_count) {
637
0
        cmt_buckets = cmt_histogram_buckets_create_size(buckets, bucket_count);
638
0
        if (!cmt_buckets) {
639
0
            ret = report_error(context,
640
0
                               CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
641
0
                               "cmt_histogram_buckets_create_size failed");
642
0
            goto end;
643
0
        }
644
645
0
        h = cmt_histogram_create(context->cmt,
646
0
                                 context->metric.ns,
647
0
                                 context->metric.subsystem,
648
0
                                 context->metric.name,
649
0
                                 get_docstring(context),
650
0
                                 cmt_buckets,
651
0
                                 label_i,
652
0
                                 label_i ? labels_without_le : NULL);
653
654
0
        if (!h) {
655
0
            ret = report_error(context,
656
0
                    CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
657
0
                    "cmt_histogram_create failed");
658
0
            goto end;
659
0
        }
660
661
0
        context->current.histogram = h;
662
0
    }
663
664
665
0
    if (cmt_histogram_set_default(h, timestamp, bucket_defaults, sum, count,
666
0
                label_i,
667
0
                label_i ? values_without_le : NULL)) {
668
0
        ret = report_error(context,
669
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
670
0
                "cmt_histogram_set_default failed");
671
0
    }
672
673
674
0
end:
675
0
    if (buckets) {
676
0
        free(buckets);
677
0
    }
678
0
    if (bucket_defaults) {
679
0
        free(bucket_defaults);
680
0
    }
681
0
    if (labels_without_le) {
682
0
        free(labels_without_le);
683
0
    }
684
0
    if (values_without_le) {
685
0
        free(values_without_le);
686
0
    }
687
688
0
    return ret;
689
0
}
690
691
static int add_metric_summary(struct cmt_decode_prometheus_context *context)
692
0
{
693
0
    int ret = 0;
694
0
    int i;
695
0
    size_t quantile_count;
696
0
    size_t quantile_index;
697
0
    double *quantiles = NULL;
698
0
    double *quantile_defaults = NULL;
699
0
    double sum = 0.0;
700
0
    double count_dbl;
701
0
    size_t label_count;
702
0
    uint64_t count = 0;
703
0
    struct cfl_list *head;
704
0
    struct cfl_list *tmp;
705
0
    struct cmt_decode_prometheus_context_sample *sample;
706
0
    size_t quantile_label_index = 0;
707
0
    struct cmt_summary *s;
708
0
    cfl_sds_t *labels_without_quantile = NULL;
709
0
    cfl_sds_t *values_without_quantile = NULL;
710
0
    int label_i;
711
0
    uint64_t timestamp = 0;
712
713
0
    if (cfl_list_size(&context->metric.samples) < 2) {
714
0
        return report_error(context,
715
0
                CMT_DECODE_PROMETHEUS_SYNTAX_ERROR,
716
0
                "not enough samples for summary");
717
0
    }
718
719
    /* quantile_count = sample count - 2:
720
     * - sum
721
     * - count */
722
0
    quantile_count = cfl_list_size(&context->metric.samples) - 2;
723
0
    if (context->opts.override_timestamp) {
724
0
        timestamp = context->opts.override_timestamp;
725
0
    }
726
727
0
    quantile_defaults = calloc(quantile_count, sizeof(*quantile_defaults));
728
0
    if (!quantile_defaults) {
729
0
        ret = report_error(context,
730
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
731
0
                "failed to allocate quantile defaults");
732
0
        goto end;
733
0
    }
734
0
    quantiles = calloc(quantile_count, sizeof(*quantiles));
735
0
    if (!quantiles) {
736
0
        ret = report_error(context,
737
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
738
0
                "failed to allocate quantiles");
739
0
        goto end;
740
0
    }
741
742
0
    label_count = 0;
743
0
    for (i = 0; i < context->metric.label_count; i++) {
744
0
        if (strcmp(context->metric.labels[i], "quantile")) {
745
            /* quantile is not a label */
746
0
            label_count++;
747
0
        }
748
0
    }
749
750
0
    labels_without_quantile = calloc(label_count, sizeof(*labels_without_quantile));
751
0
    if (!labels_without_quantile) {
752
0
        ret = report_error(context,
753
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
754
0
                "failed to allocate labels_without_quantile");
755
0
        goto end;
756
0
    }
757
0
    values_without_quantile = calloc(label_count, sizeof(*labels_without_quantile));
758
0
    if (!values_without_quantile) {
759
0
        ret = report_error(context,
760
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
761
0
                "failed to allocate values_without_quantile");
762
0
        goto end;
763
0
    }
764
765
0
    label_i = 0;
766
0
    sample = cfl_list_entry_first(&context->metric.samples,
767
0
            struct cmt_decode_prometheus_context_sample, _head);
768
0
    for (i = 0; i < context->metric.label_count; i++) {
769
0
        if (!strcmp(context->metric.labels[i], "quantile")) {
770
0
            quantile_label_index = i;
771
0
            break;
772
0
        } else {
773
0
            labels_without_quantile[label_i] = context->metric.labels[i];
774
0
            values_without_quantile[label_i] = sample->label_values[i];
775
0
            label_i++;
776
0
        }
777
0
    }
778
779
0
    quantile_index = 0;
780
0
    cfl_list_foreach_safe(head, tmp, &context->metric.samples) {
781
0
        sample = cfl_list_entry(head, struct cmt_decode_prometheus_context_sample, _head);
782
0
        switch (sample->type) {
783
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_NORMAL:
784
0
                if (parse_double(sample->label_values[quantile_label_index],
785
0
                            quantiles + quantile_index)) {
786
0
                    ret = report_error(context,
787
0
                            CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
788
0
                            "failed to parse bucket");
789
0
                    goto end;
790
0
                }
791
0
                if (parse_double(sample->value1,
792
0
                            quantile_defaults + quantile_index)) {
793
0
                    ret = report_error(context,
794
0
                            CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
795
0
                            "failed to parse quantile value");
796
0
                    goto end;
797
0
                }
798
0
                quantile_index++;
799
800
0
                if (!timestamp) {
801
0
                    ret = parse_timestamp(context, sample->value2, &timestamp);
802
803
0
                    if (ret) {
804
0
                        goto end;
805
0
                    }
806
0
                }
807
808
0
                break;
809
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM:
810
0
                if (parse_double(sample->value1, &sum)) {
811
0
                    ret = report_error(context,
812
0
                            CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
813
0
                            "failed to parse summary sum");
814
0
                    goto end;
815
0
                }
816
817
0
                if (!timestamp) {
818
0
                    ret = parse_timestamp(context, sample->value2, &timestamp);
819
820
0
                    if (ret) {
821
0
                        goto end;
822
0
                    }
823
0
                }
824
825
0
                break;
826
827
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT:
828
0
                if (parse_uint64(sample->value1, &count)) {
829
                    /* Count is supposed to be integer, but apparently
830
                     * some tools can generate count in a floating format.
831
                     * Try to parse as a double and then cast to uint64_t */
832
0
                    if (parse_double(sample->value1, &count_dbl) || count_dbl < 0) {
833
0
                        ret = report_error(context,
834
0
                                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
835
0
                                "failed to parse count");
836
0
                        goto end;
837
0
                    } else {
838
0
                        count = (uint64_t)count_dbl;
839
0
                    }
840
0
                }
841
842
0
                if (!timestamp) {
843
0
                    ret = parse_timestamp(context, sample->value2, &timestamp);
844
845
0
                    if (ret) {
846
0
                        goto end;
847
0
                    }
848
0
                }
849
850
0
                break;
851
0
        }
852
0
    }
853
854
0
    if (!timestamp) {
855
        /* No timestamp was specified, use default value */
856
0
        timestamp = context->opts.default_timestamp;
857
0
    }
858
859
0
    s = context->current.summary;
860
0
    if (!s || label_i != s->map->label_count) {
861
0
        s = cmt_summary_create(context->cmt,
862
0
                               context->metric.ns,
863
0
                               context->metric.subsystem,
864
0
                               context->metric.name,
865
0
                               get_docstring(context),
866
0
                               quantile_count,
867
0
                               quantiles,
868
0
                               label_i,
869
0
                               label_i ? labels_without_quantile : NULL);
870
871
0
        if (!s) {
872
0
            ret = report_error(context,
873
0
                    CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
874
0
                    "cmt_summary_create failed");
875
0
            goto end;
876
0
        }
877
878
0
        context->current.summary = s;
879
0
    }
880
881
0
    if (cmt_summary_set_default(s, timestamp, quantile_defaults, sum, count,
882
0
                label_i,
883
0
                label_i ? values_without_quantile : NULL)) {
884
0
        ret = report_error(context,
885
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
886
0
                "cmt_summary_set_default failed");
887
0
    }
888
889
890
0
end:
891
0
    if (quantile_defaults) {
892
0
        free(quantile_defaults);
893
0
    }
894
0
    if (quantiles) {
895
0
        free(quantiles);
896
0
    }
897
0
    if (labels_without_quantile) {
898
0
        free(labels_without_quantile);
899
0
    }
900
0
    if (values_without_quantile) {
901
0
        free(values_without_quantile);
902
0
    }
903
904
0
    return ret;
905
0
}
906
907
static int finish_metric(struct cmt_decode_prometheus_context *context,
908
                         bool reset_summary,
909
                         cfl_sds_t current_metric_name)
910
0
{
911
0
    int rv = 0;
912
913
0
    if (cfl_list_is_empty(&context->metric.samples)) {
914
0
        goto end;
915
0
    }
916
917
0
    switch (context->metric.type) {
918
0
        case COUNTER:
919
0
            rv = add_metric_counter(context);
920
0
            break;
921
0
        case GAUGE:
922
0
            rv = add_metric_gauge(context);
923
0
            break;
924
0
        case HISTOGRAM:
925
0
            rv = add_metric_histogram(context);
926
0
            break;
927
0
        case SUMMARY:
928
0
            rv = add_metric_summary(context);
929
0
            break;
930
0
        default:
931
0
            rv = add_metric_untyped(context);
932
0
            break;
933
0
    }
934
935
0
end:
936
0
    reset_context(context, reset_summary);
937
938
0
    if (current_metric_name) {
939
0
        context->metric.name_orig = current_metric_name;
940
0
        rv = split_metric_name(context,
941
0
                               current_metric_name,
942
0
                               &(context->metric.ns),
943
0
                               &(context->metric.subsystem),
944
0
                               &(context->metric.name));
945
0
    }
946
0
    return rv;
947
0
}
948
949
/* special case for summary */
950
static int finish_duplicate_histogram_summary_sum_count(
951
        struct cmt_decode_prometheus_context *context,
952
        cfl_sds_t metric_name,
953
        int type)
954
0
{
955
0
    int rv;
956
0
    int current_metric_type;
957
0
    cfl_sds_t current_metric_docstring;
958
959
0
    if (type == CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT) {
960
0
        cfl_sds_set_len(metric_name, cfl_sds_len(metric_name) - 6);
961
0
    } else if (type == CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM) {
962
0
        cfl_sds_set_len(metric_name, cfl_sds_len(metric_name) - 4);
963
0
    } else if (type == CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_BUCKET) {
964
0
        cfl_sds_set_len(metric_name, cfl_sds_len(metric_name) - 7);
965
0
    }
966
0
    metric_name[cfl_sds_len(metric_name)] = 0;
967
968
0
    current_metric_type = context->metric.type;
969
0
    current_metric_docstring = cfl_sds_create(context->metric.docstring);
970
971
0
    rv = finish_metric(context, false, metric_name);
972
0
    if (rv) {
973
0
        cfl_sds_destroy(current_metric_docstring);
974
0
        return rv;
975
0
    }
976
977
0
    context->metric.type = current_metric_type;
978
0
    context->metric.docstring = current_metric_docstring;
979
0
    context->metric.current_sample_type = type;
980
981
0
    return 0;
982
0
}
983
984
static int parse_histogram_summary_name(
985
        struct cmt_decode_prometheus_context *context,
986
        cfl_sds_t metric_name)
987
0
{
988
0
    bool sum_found;
989
0
    bool count_found;
990
0
    bool has_buckets;
991
0
    bool is_previous_sum_or_count;
992
0
    bool name_matched = false;
993
0
    struct cfl_list *head;
994
0
    struct cfl_list *tmp;
995
0
    size_t current_name_len;
996
0
    size_t parsed_name_len;
997
0
    struct cmt_decode_prometheus_context_sample *sample;
998
999
0
    current_name_len = strlen(metric_name);
1000
0
    parsed_name_len = strlen(context->metric.name_orig);
1001
0
    if (current_name_len < parsed_name_len) {
1002
        /* current name length cannot be less than the length already parsed. That means
1003
         * another metric has started */
1004
0
        return finish_metric(context, true, metric_name);
1005
0
    }
1006
1007
0
    if (strncmp(context->metric.name_orig, metric_name, parsed_name_len)) {
1008
        /* the name prefix must be the same or we are starting a new metric */
1009
0
        return finish_metric(context, true, metric_name);
1010
0
    }
1011
0
    else if (parsed_name_len == current_name_len) {
1012
0
        name_matched = true;
1013
0
    }
1014
1015
0
    sum_found = false;
1016
0
    count_found = false;
1017
0
    has_buckets = false;
1018
1019
0
    cfl_list_foreach_safe(head, tmp, &context->metric.samples) {
1020
0
        sample = cfl_list_entry(head, struct cmt_decode_prometheus_context_sample, _head);
1021
1022
0
        switch (sample->type) {
1023
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM:
1024
0
                sum_found = true;
1025
0
                break;
1026
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT:
1027
0
                count_found = true;
1028
0
                break;
1029
0
            default:
1030
0
                has_buckets = true;
1031
0
                break;
1032
0
        }
1033
0
    }
1034
1035
0
    sample = cfl_list_entry_last(&context->metric.samples,
1036
0
            struct cmt_decode_prometheus_context_sample, _head);
1037
0
    is_previous_sum_or_count = sample->type == CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM ||
1038
0
        sample->type == CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT;
1039
1040
0
    if (name_matched) {
1041
0
        if (sum_found && count_found) {
1042
            /* finish instance of the summary/histogram */
1043
0
            return finish_duplicate_histogram_summary_sum_count(context, metric_name, -1);
1044
0
        }
1045
0
        else {
1046
            /* parsing HELP after TYPE */
1047
0
            cfl_sds_destroy(metric_name);
1048
0
            return 0;
1049
0
        }
1050
0
    }
1051
1052
    /* invalid histogram/summary suffix, treat it as a different metric */
1053
0
    if (!strcmp(metric_name + parsed_name_len, "_bucket")) {
1054
0
        if (sum_found && count_found && has_buckets && is_previous_sum_or_count) {
1055
            /* already found both sum and count, so this is a new metric */
1056
0
            return finish_duplicate_histogram_summary_sum_count(
1057
0
                    context,
1058
0
                    metric_name,
1059
0
                    CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_BUCKET);
1060
0
        }
1061
0
        context->metric.current_sample_type = CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_BUCKET;
1062
0
    }
1063
0
    else if (!strcmp(metric_name + parsed_name_len, "_sum")) {
1064
0
        if (sum_found) {
1065
            /* already found a `_sum` for this metric, so this must necessarily be a new
1066
             * one */
1067
0
            return finish_duplicate_histogram_summary_sum_count(
1068
0
                    context,
1069
0
                    metric_name,
1070
0
                    CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM);
1071
0
        }
1072
0
        context->metric.current_sample_type = CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM;
1073
0
        sum_found = true;
1074
0
    }
1075
0
    else if (!strcmp(metric_name + parsed_name_len, "_count")) {
1076
0
        if (count_found) {
1077
            /* already found a `_count` for this metric, so this must necessarily be a new
1078
             * one */
1079
0
            return finish_duplicate_histogram_summary_sum_count(
1080
0
                    context,
1081
0
                    metric_name,
1082
0
                    CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT);
1083
0
        }
1084
0
        context->metric.current_sample_type = CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT;
1085
0
        count_found = true;
1086
0
    }
1087
0
    else {
1088
        /* invalid histogram/summary suffix, treat it as a different metric */
1089
0
        return finish_metric(context, true, metric_name);
1090
0
    }
1091
1092
    /* still in the same metric */
1093
0
    cfl_sds_destroy(metric_name);
1094
0
    return 0;
1095
0
}
1096
1097
static int parse_metric_name(
1098
        struct cmt_decode_prometheus_context *context,
1099
        cfl_sds_t metric_name)
1100
0
{
1101
0
    int ret = 0;
1102
1103
0
    if (context->metric.name_orig) {
1104
0
        if (context->metric.type == HISTOGRAM || context->metric.type == SUMMARY) {
1105
0
            ret = parse_histogram_summary_name(context, metric_name);
1106
0
            if (!ret) {
1107
                /* bucket/sum/count parsed */
1108
0
                return ret;
1109
0
            }
1110
0
        }
1111
0
        else if (strcmp(context->metric.name_orig, metric_name)) {
1112
            /* new metric name means the current metric is finished */
1113
0
            return finish_metric(context, true, metric_name);
1114
0
        }
1115
0
        else {
1116
            /* same metric with name already allocated, destroy and return */
1117
0
            cfl_sds_destroy(metric_name);
1118
0
            return ret;
1119
0
        }
1120
0
    }
1121
1122
0
    if (!ret) {
1123
0
        context->metric.name_orig = metric_name;
1124
0
        ret = split_metric_name(context, metric_name,
1125
0
                &(context->metric.ns),
1126
0
                &(context->metric.subsystem),
1127
0
                &(context->metric.name));
1128
0
    }
1129
0
    else {
1130
0
        cfl_sds_destroy(metric_name);
1131
0
    }
1132
1133
0
    return ret;
1134
0
}
1135
1136
static int parse_label(
1137
        struct cmt_decode_prometheus_context *context,
1138
        cfl_sds_t name, cfl_sds_t value)
1139
0
{
1140
0
    int i;
1141
0
    struct cmt_decode_prometheus_context_sample *sample;
1142
1143
0
    if (context->metric.label_count >= CMT_DECODE_PROMETHEUS_MAX_LABEL_COUNT) {
1144
0
        cfl_sds_destroy(name);
1145
0
        cfl_sds_destroy(value);
1146
0
        return report_error(context,
1147
0
                CMT_DECODE_PROMETHEUS_MAX_LABEL_COUNT_EXCEEDED,
1148
0
                "maximum number of labels exceeded");
1149
0
    }
1150
1151
    /* check if the label is already registered */
1152
0
    for (i = 0; i < context->metric.label_count; i++) {
1153
0
        if (!strcmp(name, context->metric.labels[i])) {
1154
            /* found, free the name memory and use the existing one */
1155
0
            cfl_sds_destroy(name);
1156
0
            name = context->metric.labels[i];
1157
0
            break;
1158
0
        }
1159
0
    }
1160
0
    if (i == context->metric.label_count) {
1161
        /* didn't found the label, add it now */
1162
0
        context->metric.labels[i] = name;
1163
0
        context->metric.label_count++;
1164
0
    }
1165
1166
0
    sample = cfl_list_entry_last(&context->metric.samples,
1167
0
            struct cmt_decode_prometheus_context_sample, _head);
1168
0
    sample->label_values[i] = value;
1169
0
    return 0;
1170
0
}
1171
1172
static int sample_start(struct cmt_decode_prometheus_context *context)
1173
0
{
1174
0
    struct cmt_decode_prometheus_context_sample *sample;
1175
1176
0
    sample = malloc(sizeof(*sample));
1177
0
    if (!sample) {
1178
0
        return report_error(context,
1179
0
                CMT_DECODE_PROMETHEUS_ALLOCATION_ERROR,
1180
0
                "memory allocation failed");
1181
0
    }
1182
1183
0
    memset(sample, 0, sizeof(*sample));
1184
0
    sample->type = context->metric.current_sample_type;
1185
0
    cfl_list_add(&sample->_head, &context->metric.samples);
1186
0
    return 0;
1187
0
}
1188
1189
static int parse_sample(struct cmt_decode_prometheus_context *context,
1190
                        const char *value1,
1191
                        const char *value2)
1192
0
{
1193
0
    int len;
1194
0
    struct cmt_decode_prometheus_context_sample *sample;
1195
0
    sample = cfl_list_entry_last(&context->metric.samples,
1196
0
            struct cmt_decode_prometheus_context_sample, _head);
1197
1198
    /* value1 */
1199
0
    len = strlen(value1);
1200
0
    if (len >= sizeof(sample->value1) - 1) {
1201
0
        return report_error(context,
1202
0
                CMT_DECODE_PROMETHEUS_SAMPLE_VALUE_TOO_LONG,
1203
0
                "sample value is too long (max %zu characters)", sizeof(sample->value1) - 1);
1204
0
    }
1205
1206
0
    memcpy(sample->value1, value1, len);
1207
0
    sample->value1[len] = 0;
1208
1209
    /* value2 */
1210
0
    len = strlen(value2);
1211
0
    if (len >= sizeof(sample->value2) - 1) {
1212
0
        return report_error(context,
1213
0
                CMT_DECODE_PROMETHEUS_SAMPLE_VALUE_TOO_LONG,
1214
0
                "sample value is too long (max %zu characters)", sizeof(sample->value2) - 1);
1215
0
    }
1216
0
    memcpy(sample->value2, value2, len);
1217
0
    sample->value2[len] = 0;
1218
1219
0
    return 0;
1220
0
}
1221
1222
/* called automatically by the generated parser code on error */
1223
static int cmt_decode_prometheus_error(void *yyscanner,
1224
                                       struct cmt_decode_prometheus_context *context,
1225
                                       const char *msg)
1226
1.64k
{
1227
1.64k
    report_error(context, CMT_DECODE_PROMETHEUS_SYNTAX_ERROR, msg);
1228
1.64k
    return 0;
1229
1.64k
}