1#!/usr/local/bin/perl
2# promdatagen - generate promdata.[ch] files
3
4# Copyright (c) 1994-2017 Carnegie Mellon University.  All rights reserved.
5
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12
13# 2. Redistributions in binary form must reproduce the above copyright
14#    notice, this list of conditions and the following disclaimer in
15#    the documentation and/or other materials provided with the
16#    distribution.
17
18# 3. The name "Carnegie Mellon University" must not be used to
19#    endorse or promote products derived from this software without
20#    prior written permission. For permission or any legal
21#    details, please contact
22#      Carnegie Mellon University
23#      Center for Technology Transfer and Enterprise Creation
24#      4615 Forbes Avenue
25#      Suite 302
26#      Pittsburgh, PA  15213
27#      (412) 268-7393, fax: (412) 268-7395
28#      innovation@andrew.cmu.edu
29
30# 4. Redistributions of any form whatsoever must retain the following
31#    acknowledgment:
32#    "This product includes software developed by Computing Services
33#     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
34
35# CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
36# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
37# AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
38# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
39# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
40# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
41# OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
42
43use warnings;
44use strict;
45
46use Data::Dumper;
47use Getopt::Std;
48
49my %types = ( counter => 'PROM_METRIC_COUNTER', gauge => 'PROM_METRIC_GAUGE' );
50
51my %options;
52my @metrics;
53my @labels;
54
55sub output_header;
56sub output_source;
57
58die "usage\n" if not getopts("h:c:v", \%options);
59
60my $lineno = 0;
61while (my $line = <>) {
62    $lineno ++;
63
64    chomp $line;
65    $line =~ s{#.*$}{};             # eat comments
66    $line =~ s{\s+$}{};             # eat trailing whitespace
67    next if $line =~ m{^\s*$};      # eat empty lines
68
69    if ($line =~ m{^\s*metric\s}) {
70        # parse a metric:
71        # metric counter imap_connections_total  The total number of IMAP connections
72        $line =~ s{^\s*metric\s+}{};
73        my ($type, $name, $help) = split /\s+/, $line, 3;
74
75        if (not exists $types{$type}) {
76            die "\"$type\" is not a valid type at line $lineno\n";
77        }
78
79        if ($name !~ m{^[a-z][a-z0-9_]*$}) {
80            die "\"$name\" is not a valid metric name at line $lineno\n";
81        }
82
83        push @metrics, { type => $type, name => $name, help => $help };
84
85    }
86    elsif ($line =~ m{^\s*label\s}) {
87        # parse a label:
88        # label imap_authenticate_count result yes no
89        $line =~ s{^\s*label\s+}{};
90        my ($name, $label, @values) = split /\s+/, $line;
91
92        if (not scalar grep { $_->{name} eq $name } @metrics) {
93            die "cannot define label \"$label\" for unknown metric \"$name\" at line $lineno\n";
94        }
95
96        if ($label !~ m{^[a-z][a-z0-9_]*$}) {
97            die "\"$label\" is not a valid label at line $lineno\n";
98        }
99
100        foreach my $v (@values) {
101            if ($v !~ m{^[a-z][a-z0-9_]*$}) {
102                die "\"$v\" is not a valid value at line $lineno\n";
103            }
104        }
105
106        foreach my $metric (@metrics) {
107            if ($metric->{name} eq $name) {
108                if (exists $metric->{label} ) {
109                    die "cannot define more than one label for metric \"$name\" at line $lineno\n";
110                }
111                $metric->{label} = { name => $name, label => $label, values => [ @values ] };
112                push @labels, $metric->{label};
113                last;
114            }
115        }
116    }
117    else {
118        warn "skipping unparseable line at line $lineno: $line\n";
119        next;
120    }
121}
122
123output_header($options{h}, \@metrics, \@labels) if $options{h};
124output_source($options{c}, \@metrics, \@labels) if $options{c};
125
126exit 0;
127
128sub output_header
129{
130    my ($fname, $metrics, $labels) = @_;
131
132    open my $header, '>', $fname or die "$fname: $!\n";
133    print $header "#ifndef INCLUDE_PROMDATA_H\n#define INCLUDE_PROMDATA_H\n";
134    print $header "/* generated from $ARGV */\n";
135
136    print $header <<OKAY;
137
138#include <sys/types.h>
139
140#include <stdint.h>
141
142enum prom_metric_type {
143    PROM_METRIC_COUNTER   = 0,
144    PROM_METRIC_GAUGE     = 1,
145    PROM_METRIC_HISTOGRAM = 2, /* unused */
146    PROM_METRIC_SUMMARY   = 3, /* unused */
147    PROM_METRIC_CONTINUED = 4, /* internal use only */
148};
149extern const char *prom_metric_type_names[];
150
151OKAY
152
153    print $header "enum prom_metric_id {\n";
154    my $first = 1;
155    foreach my $metric (@{$metrics}) {
156        if (exists $metric->{label}) {
157            foreach my $v (@{$metric->{label}->{values}}) {
158                print $header "    \U$metric->{name}_$metric->{label}->{label}_$v\E";
159                print $header q{ = 0} if $first;
160                $first = 0;
161                print $header qq{,\n};
162            }
163        }
164        else {
165            print $header q{    }, uc($metric->{name});
166            print $header q{ = 0} if $first;
167            $first = 0;
168            print $header qq{,\n};
169        }
170    }
171    print $header "\n    PROM_NUM_METRICS /* n.b. leave last! */\n";
172    print $header "};\n\n";
173
174    print $header "enum prom_labelled_metric {\n";
175    $first = 1;
176    foreach my $label (@{$labels}) {
177        print $header "    \U$label->{name}\E";
178        print $header q{ = 0} if $first;
179        $first = 0;
180        print $header qq{,\n};
181    }
182    print $header "\n    PROM_NUM_LABELLED_METRICS /* n.b. leave last! */\n";
183    print $header "};\n";
184
185print $header <<OKAY;
186
187struct prom_label_lookup_value {
188    const char *value;
189    enum prom_metric_id id;
190};
191
192extern const struct prom_label_lookup_value *prom_label_lookup_table[];
193
194OKAY
195
196    print $header <<OKAY;
197
198struct prom_metric_desc {
199    const char *name;
200    enum prom_metric_type type;
201    const char *help;
202    const char *label;
203};
204extern const struct prom_metric_desc prom_metric_descs[];
205
206struct prom_metric {
207    double value;
208    int64_t last_updated;
209};
210
211struct prom_stats {
212    char  ident[512];   /* XXX places upper limit on service names */
213    struct prom_metric metrics[PROM_NUM_METRICS];
214};
215#define PROM_STATS_INITIALIZER { {0}, {{0, 0}} }
216
217OKAY
218
219    print $header "#endif\n";
220    close $header;
221}
222
223sub output_source
224{
225    my ($fname, $metrics, $labels) = @_;
226
227    open my $source, '>', $fname or die "$fname: $!\n";
228
229    print $source <<OKAY;
230/* generated from $ARGV */
231
232#include <config.h>
233
234#include "imap/promdata.h" /* XXX */
235
236EXPORTED const char *prom_metric_type_names[] = {
237    "counter",
238    "gauge",
239    "histogram",
240    "summary",
241    NULL,
242};
243
244OKAY
245
246    print $source "EXPORTED const struct prom_metric_desc prom_metric_descs[] = {\n";
247    foreach my $metric (@{$metrics}) {
248        if (exists $metric->{label}) {
249            my $first = 1;
250            foreach my $v (@{$metric->{label}->{values}}) {
251                printf $source '    { "%s", %s, ',
252                            $metric->{name},
253                            ($first ? $types{$metric->{type}} : "PROM_METRIC_CONTINUED");
254                if ($first && defined $metric->{help}) {
255                    printf $source '"%s", ', $metric->{help};
256                }
257                else {
258                    print $source "NULL, ";
259                }
260                printf $source '"%s=\\"%s\\""', $metric->{label}->{label}, $v;
261                print $source " },\n";
262                $first = 0;
263            }
264        }
265        else {
266            printf $source '    { "%s", %s, ',
267                        $metric->{name},
268                        $types{$metric->{type}};
269            if (defined $metric->{help}) {
270                printf $source '"%s",', $metric->{help};
271            }
272            else {
273                print $source "NULL,";
274            }
275            print $source " NULL },\n";
276        }
277    }
278    print $source "    { NULL, 0, NULL, NULL },\n";
279    print $source "};\n\n";
280
281    foreach my $label(@{$labels}) {
282        print $source "static const struct prom_label_lookup_value ";
283        print $source "\U$label->{name}_$label->{label}\E_values[] = {\n";
284        foreach my $value(sort @{$label->{values}}) {
285            print $source "    { \"$value\", \U$label->{name}_$label->{label}_$value\E },\n";
286        }
287        print $source "    { NULL, 0 },\n";
288        print $source "};\n\n";
289    }
290
291    print $source "EXPORTED const struct prom_label_lookup_value *prom_label_lookup_table[] = {\n";
292    foreach my $label (@{$labels}) {
293        print $source "    \U$label->{name}_$label->{label}\E_values,\n";
294    }
295    print $source "};\n";
296
297    close $source;
298}
299