Coverage Report

Created: 2026-03-22 07:09

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.62k
{
42
1.62k
    int i;
43
1.62k
    struct cmt_decode_prometheus_context_sample *sample;
44
45
1.62k
    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.62k
    for (i = 0; i < context->metric.label_count; i++) {
56
0
        cfl_sds_destroy(context->metric.labels[i]);
57
0
    }
58
59
1.62k
    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.62k
    cfl_sds_destroy(context->strbuf);
78
1.62k
    context->strbuf = NULL;
79
1.62k
    if (reset_summary) {
80
1.62k
        context->current.summary = NULL;
81
1.62k
    }
82
1.62k
    cfl_sds_destroy(context->metric.name_orig);
83
1.62k
    cfl_sds_destroy(context->metric.docstring);
84
1.62k
    memset(&context->metric,
85
1.62k
            0,
86
1.62k
            sizeof(struct cmt_decode_prometheus_context_metric));
87
1.62k
    cfl_list_init(&context->metric.samples);
88
1.62k
}
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.62k
{
97
1.62k
    yyscan_t scanner;
98
1.62k
    YY_BUFFER_STATE buf;
99
1.62k
    struct cmt *cmt;
100
1.62k
    struct cmt_decode_prometheus_context context;
101
1.62k
    int result;
102
103
1.62k
    cmt = cmt_create();
104
105
1.62k
    if (cmt == NULL) {
106
0
        return CMT_DECODE_PROMETHEUS_ALLOCATION_ERROR;
107
0
    }
108
109
1.62k
    memset(&context, 0, sizeof(context));
110
1.62k
    context.cmt = cmt;
111
1.62k
    if (opts) {
112
1.62k
        context.opts = *opts;
113
1.62k
    }
114
1.62k
    cfl_list_init(&(context.metric.samples));
115
1.62k
    cmt_decode_prometheus_lex_init(&scanner);
116
1.62k
    if (!in_size) {
117
0
        in_size = strlen(in_buf);
118
0
    }
119
1.62k
    buf = cmt_decode_prometheus__scan_bytes((char *)in_buf, in_size, scanner);
120
1.62k
    if (!buf) {
121
0
        cmt_destroy(cmt);
122
0
        return CMT_DECODE_PROMETHEUS_ALLOCATION_ERROR;
123
0
    }
124
125
1.62k
    result = cmt_decode_prometheus_parse(scanner, &context);
126
127
1.62k
    if (result == 0) {
128
0
        *out_cmt = cmt;
129
0
    }
130
1.62k
    else {
131
1.62k
        cmt_destroy(cmt);
132
1.62k
        if (context.errcode) {
133
1.62k
            result = context.errcode;
134
1.62k
        }
135
1.62k
        reset_context(&context, true);
136
1.62k
    }
137
138
1.62k
    cmt_decode_prometheus__delete_buffer(buf, scanner);
139
1.62k
    cmt_decode_prometheus_lex_destroy(scanner);
140
141
1.62k
    return result;
142
1.62k
}
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.62k
{
153
1.62k
    va_list args;
154
1.62k
    va_start(args, format);
155
1.62k
    context->errcode = errcode;
156
1.62k
    if (context->opts.errbuf && context->opts.errbuf_size) {
157
1.62k
        vsnprintf(context->opts.errbuf, context->opts.errbuf_size - 1, format, args);
158
1.62k
    }
159
1.62k
    va_end(args);
160
1.62k
    return errcode;
161
1.62k
}
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
    size_t bucket_count;
445
0
    size_t bucket_index;
446
0
    double *buckets = NULL;
447
0
    uint64_t *bucket_defaults = NULL;
448
0
    double sum = 0;
449
0
    uint64_t count = 0;
450
0
    double count_dbl;
451
0
    struct cfl_list *head;
452
0
    struct cfl_list *tmp;
453
0
    struct cmt_decode_prometheus_context_sample *sample;
454
0
    size_t le_label_index = 0;
455
0
    struct cmt_histogram *h;
456
0
    struct cmt_histogram_buckets *cmt_buckets;
457
0
    cfl_sds_t *labels_without_le = NULL;
458
0
    cfl_sds_t *values_without_le = NULL;
459
0
    int label_i;
460
0
    uint64_t timestamp = 0;
461
462
0
    if (cfl_list_size(&context->metric.samples) < 3) {
463
0
        return report_error(context,
464
0
                CMT_DECODE_PROMETHEUS_SYNTAX_ERROR,
465
0
                "not enough samples for histogram");
466
0
    }
467
468
    /* bucket_count = sample count - 3:
469
     * - "Inf" bucket
470
     * - sum
471
     * - count */
472
0
    bucket_count = cfl_list_size(&context->metric.samples) - 3;
473
0
    if (context->opts.override_timestamp) {
474
0
        timestamp = context->opts.override_timestamp;
475
0
    }
476
477
0
    bucket_defaults = calloc(bucket_count + 1, sizeof(*bucket_defaults));
478
0
    if (!bucket_defaults) {
479
0
        ret = report_error(context,
480
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
481
0
                "failed to allocate bucket defaults");
482
0
        goto end;
483
0
    }
484
0
    buckets = calloc(bucket_count, sizeof(*buckets));
485
0
    if (!buckets) {
486
0
        ret = report_error(context,
487
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
488
0
                "failed to allocate buckets");
489
0
        goto end;
490
0
    }
491
0
    labels_without_le = calloc(context->metric.label_count - 1, sizeof(*labels_without_le));
492
0
    if (!labels_without_le) {
493
0
        ret = report_error(context,
494
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
495
0
                "failed to allocate labels_without_le");
496
0
        goto end;
497
0
    }
498
0
    values_without_le = calloc(context->metric.label_count - 1, sizeof(*labels_without_le));
499
0
    if (!values_without_le) {
500
0
        ret = report_error(context,
501
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
502
0
                "failed to allocate values_without_le");
503
0
        goto end;
504
0
    }
505
506
507
0
    label_i = 0;
508
0
    sample = cfl_list_entry_first(&context->metric.samples, struct cmt_decode_prometheus_context_sample, _head);
509
0
    for (i = 0; i < context->metric.label_count; i++) {
510
0
        if (!strcmp(context->metric.labels[i], "le")) {
511
0
            le_label_index = i;
512
0
        } else {
513
0
            labels_without_le[label_i] = context->metric.labels[i];
514
0
            values_without_le[label_i] = sample->label_values[i];
515
0
            label_i++;
516
0
        }
517
0
    }
518
519
0
    bucket_index = 0;
520
0
    cfl_list_foreach_safe(head, tmp, &context->metric.samples) {
521
0
        sample = cfl_list_entry(head, struct cmt_decode_prometheus_context_sample, _head);
522
0
        switch (sample->type) {
523
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_BUCKET:
524
0
                if (bucket_index == bucket_count) {
525
                    /* probably last bucket, which has "Inf" */
526
0
                    break;
527
0
                }
528
0
                if (parse_double(sample->label_values[le_label_index],
529
0
                            buckets + bucket_index)) {
530
0
                    ret = report_error(context,
531
0
                            CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
532
0
                            "failed to parse bucket");
533
0
                    goto end;
534
0
                }
535
0
                if (parse_uint64(sample->value1,
536
0
                            bucket_defaults + bucket_index)) {
537
                    /* Count is supposed to be integer, but apparently
538
                     * some tools can generate count in a floating format.
539
                     * Try to parse as a double and then cast to uint64_t */
540
0
                    if (parse_double(sample->value1, &count_dbl) || count_dbl < 0) {
541
0
                        ret = report_error(context,
542
0
                                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
543
0
                                "failed to parse count");
544
0
                        goto end;
545
0
                    } else {
546
0
                        *(bucket_defaults + bucket_index) = (uint64_t)count_dbl;
547
0
                    }
548
0
                }
549
0
                bucket_index++;
550
551
0
                if (!timestamp) {
552
0
                    ret = parse_timestamp(context, sample->value2, &timestamp);
553
554
0
                    if (ret) {
555
0
                        goto end;
556
0
                    }
557
0
                }
558
559
0
                break;
560
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM:
561
0
                if (parse_double(sample->value1, &sum)) {
562
0
                    ret = report_error(context,
563
0
                            CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
564
0
                            "failed to parse sum");
565
0
                    goto end;
566
0
                }
567
568
0
                if (!timestamp) {
569
0
                    ret = parse_timestamp(context, sample->value2, &timestamp);
570
571
0
                    if (ret) {
572
0
                        goto end;
573
0
                    }
574
0
                }
575
576
0
                break;
577
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT:
578
0
                if (parse_uint64(sample->value1, &count)) {
579
                    /* Count is supposed to be integer, but apparently
580
                     * some tools can generate count in a floating format.
581
                     * Try to parse as a double and then cast to uint64_t */
582
0
                    if (parse_double(sample->value1, &count_dbl) || count_dbl < 0) {
583
0
                        ret = report_error(context,
584
0
                                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
585
0
                                "failed to parse count");
586
0
                        goto end;
587
0
                    } else {
588
0
                        count = (uint64_t)count_dbl;
589
0
                    }
590
0
                }
591
0
                bucket_defaults[bucket_index] = count;
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
        }
603
0
    }
604
605
0
    if (!timestamp) {
606
        /* No timestamp was specified, use default value */
607
0
        timestamp = context->opts.default_timestamp;
608
0
    }
609
610
0
    h = context->current.histogram;
611
0
    if (!h || label_i != h->map->label_count) {
612
0
        cmt_buckets = cmt_histogram_buckets_create_size(buckets, bucket_count);
613
0
        if (!cmt_buckets) {
614
0
            ret = report_error(context,
615
0
                               CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
616
0
                               "cmt_histogram_buckets_create_size failed");
617
0
            goto end;
618
0
        }
619
620
0
        h = cmt_histogram_create(context->cmt,
621
0
                                 context->metric.ns,
622
0
                                 context->metric.subsystem,
623
0
                                 context->metric.name,
624
0
                                 get_docstring(context),
625
0
                                 cmt_buckets,
626
0
                                 label_i,
627
0
                                 label_i ? labels_without_le : NULL);
628
629
0
        if (!h) {
630
0
            ret = report_error(context,
631
0
                    CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
632
0
                    "cmt_histogram_create failed");
633
0
            goto end;
634
0
        }
635
636
0
        context->current.histogram = h;
637
0
    }
638
639
640
0
    if (cmt_histogram_set_default(h, timestamp, bucket_defaults, sum, count,
641
0
                label_i,
642
0
                label_i ? values_without_le : NULL)) {
643
0
        ret = report_error(context,
644
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
645
0
                "cmt_histogram_set_default failed");
646
0
    }
647
648
649
0
end:
650
0
    if (buckets) {
651
0
        free(buckets);
652
0
    }
653
0
    if (bucket_defaults) {
654
0
        free(bucket_defaults);
655
0
    }
656
0
    if (labels_without_le) {
657
0
        free(labels_without_le);
658
0
    }
659
0
    if (values_without_le) {
660
0
        free(values_without_le);
661
0
    }
662
663
0
    return ret;
664
0
}
665
666
static int add_metric_summary(struct cmt_decode_prometheus_context *context)
667
0
{
668
0
    int ret = 0;
669
0
    int i;
670
0
    size_t quantile_count;
671
0
    size_t quantile_index;
672
0
    double *quantiles = NULL;
673
0
    double *quantile_defaults = NULL;
674
0
    double sum = 0.0;
675
0
    double count_dbl;
676
0
    size_t label_count;
677
0
    uint64_t count = 0;
678
0
    struct cfl_list *head;
679
0
    struct cfl_list *tmp;
680
0
    struct cmt_decode_prometheus_context_sample *sample;
681
0
    size_t quantile_label_index = 0;
682
0
    struct cmt_summary *s;
683
0
    cfl_sds_t *labels_without_quantile = NULL;
684
0
    cfl_sds_t *values_without_quantile = NULL;
685
0
    int label_i;
686
0
    uint64_t timestamp = 0;
687
688
0
    if (cfl_list_size(&context->metric.samples) < 2) {
689
0
        return report_error(context,
690
0
                CMT_DECODE_PROMETHEUS_SYNTAX_ERROR,
691
0
                "not enough samples for summary");
692
0
    }
693
694
    /* quantile_count = sample count - 2:
695
     * - sum
696
     * - count */
697
0
    quantile_count = cfl_list_size(&context->metric.samples) - 2;
698
0
    if (context->opts.override_timestamp) {
699
0
        timestamp = context->opts.override_timestamp;
700
0
    }
701
702
0
    quantile_defaults = calloc(quantile_count, sizeof(*quantile_defaults));
703
0
    if (!quantile_defaults) {
704
0
        ret = report_error(context,
705
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
706
0
                "failed to allocate quantile defaults");
707
0
        goto end;
708
0
    }
709
0
    quantiles = calloc(quantile_count, sizeof(*quantiles));
710
0
    if (!quantiles) {
711
0
        ret = report_error(context,
712
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
713
0
                "failed to allocate quantiles");
714
0
        goto end;
715
0
    }
716
717
0
    label_count = 0;
718
0
    for (i = 0; i < context->metric.label_count; i++) {
719
0
        if (strcmp(context->metric.labels[i], "quantile")) {
720
            /* quantile is not a label */
721
0
            label_count++;
722
0
        }
723
0
    }
724
725
0
    labels_without_quantile = calloc(label_count, sizeof(*labels_without_quantile));
726
0
    if (!labels_without_quantile) {
727
0
        ret = report_error(context,
728
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
729
0
                "failed to allocate labels_without_quantile");
730
0
        goto end;
731
0
    }
732
0
    values_without_quantile = calloc(label_count, sizeof(*labels_without_quantile));
733
0
    if (!values_without_quantile) {
734
0
        ret = report_error(context,
735
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
736
0
                "failed to allocate values_without_quantile");
737
0
        goto end;
738
0
    }
739
740
0
    label_i = 0;
741
0
    sample = cfl_list_entry_first(&context->metric.samples,
742
0
            struct cmt_decode_prometheus_context_sample, _head);
743
0
    for (i = 0; i < context->metric.label_count; i++) {
744
0
        if (!strcmp(context->metric.labels[i], "quantile")) {
745
0
            quantile_label_index = i;
746
0
            break;
747
0
        } else {
748
0
            labels_without_quantile[label_i] = context->metric.labels[i];
749
0
            values_without_quantile[label_i] = sample->label_values[i];
750
0
            label_i++;
751
0
        }
752
0
    }
753
754
0
    quantile_index = 0;
755
0
    cfl_list_foreach_safe(head, tmp, &context->metric.samples) {
756
0
        sample = cfl_list_entry(head, struct cmt_decode_prometheus_context_sample, _head);
757
0
        switch (sample->type) {
758
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_NORMAL:
759
0
                if (parse_double(sample->label_values[quantile_label_index],
760
0
                            quantiles + quantile_index)) {
761
0
                    ret = report_error(context,
762
0
                            CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
763
0
                            "failed to parse bucket");
764
0
                    goto end;
765
0
                }
766
0
                if (parse_double(sample->value1,
767
0
                            quantile_defaults + quantile_index)) {
768
0
                    ret = report_error(context,
769
0
                            CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
770
0
                            "failed to parse quantile value");
771
0
                    goto end;
772
0
                }
773
0
                quantile_index++;
774
775
0
                if (!timestamp) {
776
0
                    ret = parse_timestamp(context, sample->value2, &timestamp);
777
778
0
                    if (ret) {
779
0
                        goto end;
780
0
                    }
781
0
                }
782
783
0
                break;
784
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM:
785
0
                if (parse_double(sample->value1, &sum)) {
786
0
                    ret = report_error(context,
787
0
                            CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
788
0
                            "failed to parse summary sum");
789
0
                    goto end;
790
0
                }
791
792
0
                if (!timestamp) {
793
0
                    ret = parse_timestamp(context, sample->value2, &timestamp);
794
795
0
                    if (ret) {
796
0
                        goto end;
797
0
                    }
798
0
                }
799
800
0
                break;
801
802
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT:
803
0
                if (parse_uint64(sample->value1, &count)) {
804
                    /* Count is supposed to be integer, but apparently
805
                     * some tools can generate count in a floating format.
806
                     * Try to parse as a double and then cast to uint64_t */
807
0
                    if (parse_double(sample->value1, &count_dbl) || count_dbl < 0) {
808
0
                        ret = report_error(context,
809
0
                                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
810
0
                                "failed to parse count");
811
0
                        goto end;
812
0
                    } else {
813
0
                        count = (uint64_t)count_dbl;
814
0
                    }
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
0
        }
827
0
    }
828
829
0
    if (!timestamp) {
830
        /* No timestamp was specified, use default value */
831
0
        timestamp = context->opts.default_timestamp;
832
0
    }
833
834
0
    s = context->current.summary;
835
0
    if (!s || label_i != s->map->label_count) {
836
0
        s = cmt_summary_create(context->cmt,
837
0
                               context->metric.ns,
838
0
                               context->metric.subsystem,
839
0
                               context->metric.name,
840
0
                               get_docstring(context),
841
0
                               quantile_count,
842
0
                               quantiles,
843
0
                               label_i,
844
0
                               label_i ? labels_without_quantile : NULL);
845
846
0
        if (!s) {
847
0
            ret = report_error(context,
848
0
                    CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
849
0
                    "cmt_summary_create failed");
850
0
            goto end;
851
0
        }
852
853
0
        context->current.summary = s;
854
0
    }
855
856
0
    if (cmt_summary_set_default(s, timestamp, quantile_defaults, sum, count,
857
0
                label_i,
858
0
                label_i ? values_without_quantile : NULL)) {
859
0
        ret = report_error(context,
860
0
                CMT_DECODE_PROMETHEUS_CMT_CREATE_ERROR,
861
0
                "cmt_summary_set_default failed");
862
0
    }
863
864
865
0
end:
866
0
    if (quantile_defaults) {
867
0
        free(quantile_defaults);
868
0
    }
869
0
    if (quantiles) {
870
0
        free(quantiles);
871
0
    }
872
0
    if (labels_without_quantile) {
873
0
        free(labels_without_quantile);
874
0
    }
875
0
    if (values_without_quantile) {
876
0
        free(values_without_quantile);
877
0
    }
878
879
0
    return ret;
880
0
}
881
882
static int finish_metric(struct cmt_decode_prometheus_context *context,
883
                         bool reset_summary,
884
                         cfl_sds_t current_metric_name)
885
0
{
886
0
    int rv = 0;
887
888
0
    if (cfl_list_is_empty(&context->metric.samples)) {
889
0
        goto end;
890
0
    }
891
892
0
    switch (context->metric.type) {
893
0
        case COUNTER:
894
0
            rv = add_metric_counter(context);
895
0
            break;
896
0
        case GAUGE:
897
0
            rv = add_metric_gauge(context);
898
0
            break;
899
0
        case HISTOGRAM:
900
0
            rv = add_metric_histogram(context);
901
0
            break;
902
0
        case SUMMARY:
903
0
            rv = add_metric_summary(context);
904
0
            break;
905
0
        default:
906
0
            rv = add_metric_untyped(context);
907
0
            break;
908
0
    }
909
910
0
end:
911
0
    reset_context(context, reset_summary);
912
913
0
    if (current_metric_name) {
914
0
        context->metric.name_orig = current_metric_name;
915
0
        rv = split_metric_name(context,
916
0
                               current_metric_name,
917
0
                               &(context->metric.ns),
918
0
                               &(context->metric.subsystem),
919
0
                               &(context->metric.name));
920
0
    }
921
0
    return rv;
922
0
}
923
924
/* special case for summary */
925
static int finish_duplicate_histogram_summary_sum_count(
926
        struct cmt_decode_prometheus_context *context,
927
        cfl_sds_t metric_name,
928
        int type)
929
0
{
930
0
    int rv;
931
0
    int current_metric_type;
932
0
    cfl_sds_t current_metric_docstring;
933
934
0
    if (type == CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT) {
935
0
        cfl_sds_set_len(metric_name, cfl_sds_len(metric_name) - 6);
936
0
    } else if (type == CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM) {
937
0
        cfl_sds_set_len(metric_name, cfl_sds_len(metric_name) - 4);
938
0
    } else if (type == CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_BUCKET) {
939
0
        cfl_sds_set_len(metric_name, cfl_sds_len(metric_name) - 7);
940
0
    }
941
0
    metric_name[cfl_sds_len(metric_name)] = 0;
942
943
0
    current_metric_type = context->metric.type;
944
0
    current_metric_docstring = cfl_sds_create(context->metric.docstring);
945
946
0
    rv = finish_metric(context, false, metric_name);
947
0
    if (rv) {
948
0
        cfl_sds_destroy(current_metric_docstring);
949
0
        return rv;
950
0
    }
951
952
0
    context->metric.type = current_metric_type;
953
0
    context->metric.docstring = current_metric_docstring;
954
0
    context->metric.current_sample_type = type;
955
956
0
    return 0;
957
0
}
958
959
static int parse_histogram_summary_name(
960
        struct cmt_decode_prometheus_context *context,
961
        cfl_sds_t metric_name)
962
0
{
963
0
    bool sum_found;
964
0
    bool count_found;
965
0
    bool has_buckets;
966
0
    bool is_previous_sum_or_count;
967
0
    bool name_matched = false;
968
0
    struct cfl_list *head;
969
0
    struct cfl_list *tmp;
970
0
    size_t current_name_len;
971
0
    size_t parsed_name_len;
972
0
    struct cmt_decode_prometheus_context_sample *sample;
973
974
0
    current_name_len = strlen(metric_name);
975
0
    parsed_name_len = strlen(context->metric.name_orig);
976
0
    if (current_name_len < parsed_name_len) {
977
        /* current name length cannot be less than the length already parsed. That means
978
         * another metric has started */
979
0
        return finish_metric(context, true, metric_name);
980
0
    }
981
982
0
    if (strncmp(context->metric.name_orig, metric_name, parsed_name_len)) {
983
        /* the name prefix must be the same or we are starting a new metric */
984
0
        return finish_metric(context, true, metric_name);
985
0
    }
986
0
    else if (parsed_name_len == current_name_len) {
987
0
        name_matched = true;
988
0
    }
989
990
0
    sum_found = false;
991
0
    count_found = false;
992
0
    has_buckets = false;
993
994
0
    cfl_list_foreach_safe(head, tmp, &context->metric.samples) {
995
0
        sample = cfl_list_entry(head, struct cmt_decode_prometheus_context_sample, _head);
996
997
0
        switch (sample->type) {
998
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM:
999
0
                sum_found = true;
1000
0
                break;
1001
0
            case CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT:
1002
0
                count_found = true;
1003
0
                break;
1004
0
            default:
1005
0
                has_buckets = true;
1006
0
                break;
1007
0
        }
1008
0
    }
1009
1010
0
    sample = cfl_list_entry_last(&context->metric.samples,
1011
0
            struct cmt_decode_prometheus_context_sample, _head);
1012
0
    is_previous_sum_or_count = sample->type == CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM ||
1013
0
        sample->type == CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT;
1014
1015
0
    if (name_matched) {
1016
0
        if (sum_found && count_found) {
1017
            /* finish instance of the summary/histogram */
1018
0
            return finish_duplicate_histogram_summary_sum_count(context, metric_name, -1);
1019
0
        }
1020
0
        else {
1021
            /* parsing HELP after TYPE */
1022
0
            cfl_sds_destroy(metric_name);
1023
0
            return 0;
1024
0
        }
1025
0
    }
1026
1027
    /* invalid histogram/summary suffix, treat it as a different metric */
1028
0
    if (!strcmp(metric_name + parsed_name_len, "_bucket")) {
1029
0
        if (sum_found && count_found && has_buckets && is_previous_sum_or_count) {
1030
            /* already found both sum and count, so this is a new metric */
1031
0
            return finish_duplicate_histogram_summary_sum_count(
1032
0
                    context,
1033
0
                    metric_name,
1034
0
                    CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_BUCKET);
1035
0
        }
1036
0
        context->metric.current_sample_type = CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_BUCKET;
1037
0
    }
1038
0
    else if (!strcmp(metric_name + parsed_name_len, "_sum")) {
1039
0
        if (sum_found) {
1040
            /* already found a `_sum` for this metric, so this must necessarily be a new
1041
             * one */
1042
0
            return finish_duplicate_histogram_summary_sum_count(
1043
0
                    context,
1044
0
                    metric_name,
1045
0
                    CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM);
1046
0
        }
1047
0
        context->metric.current_sample_type = CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_SUM;
1048
0
        sum_found = true;
1049
0
    }
1050
0
    else if (!strcmp(metric_name + parsed_name_len, "_count")) {
1051
0
        if (count_found) {
1052
            /* already found a `_count` for this metric, so this must necessarily be a new
1053
             * one */
1054
0
            return finish_duplicate_histogram_summary_sum_count(
1055
0
                    context,
1056
0
                    metric_name,
1057
0
                    CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT);
1058
0
        }
1059
0
        context->metric.current_sample_type = CMT_DECODE_PROMETHEUS_CONTEXT_SAMPLE_TYPE_COUNT;
1060
0
        count_found = true;
1061
0
    }
1062
0
    else {
1063
        /* invalid histogram/summary suffix, treat it as a different metric */
1064
0
        return finish_metric(context, true, metric_name);
1065
0
    }
1066
1067
    /* still in the same metric */
1068
0
    cfl_sds_destroy(metric_name);
1069
0
    return 0;
1070
0
}
1071
1072
static int parse_metric_name(
1073
        struct cmt_decode_prometheus_context *context,
1074
        cfl_sds_t metric_name)
1075
0
{
1076
0
    int ret = 0;
1077
1078
0
    if (context->metric.name_orig) {
1079
0
        if (context->metric.type == HISTOGRAM || context->metric.type == SUMMARY) {
1080
0
            ret = parse_histogram_summary_name(context, metric_name);
1081
0
            if (!ret) {
1082
                /* bucket/sum/count parsed */
1083
0
                return ret;
1084
0
            }
1085
0
        }
1086
0
        else if (strcmp(context->metric.name_orig, metric_name)) {
1087
            /* new metric name means the current metric is finished */
1088
0
            return finish_metric(context, true, metric_name);
1089
0
        }
1090
0
        else {
1091
            /* same metric with name already allocated, destroy and return */
1092
0
            cfl_sds_destroy(metric_name);
1093
0
            return ret;
1094
0
        }
1095
0
    }
1096
1097
0
    if (!ret) {
1098
0
        context->metric.name_orig = metric_name;
1099
0
        ret = split_metric_name(context, metric_name,
1100
0
                &(context->metric.ns),
1101
0
                &(context->metric.subsystem),
1102
0
                &(context->metric.name));
1103
0
    }
1104
0
    else {
1105
0
        cfl_sds_destroy(metric_name);
1106
0
    }
1107
1108
0
    return ret;
1109
0
}
1110
1111
static int parse_label(
1112
        struct cmt_decode_prometheus_context *context,
1113
        cfl_sds_t name, cfl_sds_t value)
1114
0
{
1115
0
    int i;
1116
0
    struct cmt_decode_prometheus_context_sample *sample;
1117
1118
0
    if (context->metric.label_count >= CMT_DECODE_PROMETHEUS_MAX_LABEL_COUNT) {
1119
0
        cfl_sds_destroy(name);
1120
0
        cfl_sds_destroy(value);
1121
0
        return report_error(context,
1122
0
                CMT_DECODE_PROMETHEUS_MAX_LABEL_COUNT_EXCEEDED,
1123
0
                "maximum number of labels exceeded");
1124
0
    }
1125
1126
    /* check if the label is already registered */
1127
0
    for (i = 0; i < context->metric.label_count; i++) {
1128
0
        if (!strcmp(name, context->metric.labels[i])) {
1129
            /* found, free the name memory and use the existing one */
1130
0
            cfl_sds_destroy(name);
1131
0
            name = context->metric.labels[i];
1132
0
            break;
1133
0
        }
1134
0
    }
1135
0
    if (i == context->metric.label_count) {
1136
        /* didn't found the label, add it now */
1137
0
        context->metric.labels[i] = name;
1138
0
        context->metric.label_count++;
1139
0
    }
1140
1141
0
    sample = cfl_list_entry_last(&context->metric.samples,
1142
0
            struct cmt_decode_prometheus_context_sample, _head);
1143
0
    sample->label_values[i] = value;
1144
0
    return 0;
1145
0
}
1146
1147
static int sample_start(struct cmt_decode_prometheus_context *context)
1148
0
{
1149
0
    struct cmt_decode_prometheus_context_sample *sample;
1150
1151
0
    sample = malloc(sizeof(*sample));
1152
0
    if (!sample) {
1153
0
        return report_error(context,
1154
0
                CMT_DECODE_PROMETHEUS_ALLOCATION_ERROR,
1155
0
                "memory allocation failed");
1156
0
    }
1157
1158
0
    memset(sample, 0, sizeof(*sample));
1159
0
    sample->type = context->metric.current_sample_type;
1160
0
    cfl_list_add(&sample->_head, &context->metric.samples);
1161
0
    return 0;
1162
0
}
1163
1164
static int parse_sample(struct cmt_decode_prometheus_context *context,
1165
                        const char *value1,
1166
                        const char *value2)
1167
0
{
1168
0
    int len;
1169
0
    struct cmt_decode_prometheus_context_sample *sample;
1170
0
    sample = cfl_list_entry_last(&context->metric.samples,
1171
0
            struct cmt_decode_prometheus_context_sample, _head);
1172
1173
    /* value1 */
1174
0
    len = strlen(value1);
1175
0
    if (len >= sizeof(sample->value1) - 1) {
1176
0
        return report_error(context,
1177
0
                CMT_DECODE_PROMETHEUS_SAMPLE_VALUE_TOO_LONG,
1178
0
                "sample value is too long (max %zu characters)", sizeof(sample->value1) - 1);
1179
0
    }
1180
1181
0
    memcpy(sample->value1, value1, len);
1182
0
    sample->value1[len] = 0;
1183
1184
    /* value2 */
1185
0
    len = strlen(value2);
1186
0
    if (len >= sizeof(sample->value2) - 1) {
1187
0
        return report_error(context,
1188
0
                CMT_DECODE_PROMETHEUS_SAMPLE_VALUE_TOO_LONG,
1189
0
                "sample value is too long (max %zu characters)", sizeof(sample->value2) - 1);
1190
0
    }
1191
0
    memcpy(sample->value2, value2, len);
1192
0
    sample->value2[len] = 0;
1193
1194
0
    return 0;
1195
0
}
1196
1197
/* called automatically by the generated parser code on error */
1198
static int cmt_decode_prometheus_error(void *yyscanner,
1199
                                       struct cmt_decode_prometheus_context *context,
1200
                                       const char *msg)
1201
1.62k
{
1202
1.62k
    report_error(context, CMT_DECODE_PROMETHEUS_SYNTAX_ERROR, msg);
1203
1.62k
    return 0;
1204
1.62k
}