File Coverage

blib/lib/Prometheus/Tiny.pm
Criterion Covered Total %
statement 96 96 100.0
branch 38 44 86.3
condition 43 51 84.3
subroutine 17 17 100.0
pod 11 11 100.0
total 205 219 93.6


line stmt bran cond sub pod time code
1             package Prometheus::Tiny;
2             $Prometheus::Tiny::VERSION = '0.010';
3             # ABSTRACT: A tiny Prometheus client
4              
5 14     14   839064 use warnings;
  14         145  
  14         463  
6 14     14   70 use strict;
  14         20  
  14         340  
7              
8 14     14   66 use Carp qw(croak carp);
  14         27  
  14         773  
9 14     14   89 use Scalar::Util qw(looks_like_number);
  14         19  
  14         20195  
10              
11             my $DEFAULT_BUCKETS = [
12             0.005,
13             0.01, 0.025, 0.05, 0.075,
14             0.1, 0.25, 0.5, 0.75,
15             1.0, 2.5, 5.0, 7.5,
16             10
17             ];
18              
19             sub new {
20 49     49 1 19691 my ($class, %arg) = @_;
21 49 100       149 my %defaults = $arg{default_labels} ? %{$arg{default_labels}} : ();
  3         11  
22 49         232 return bless {
23             metrics => {},
24             meta => {},
25             default_labels => \%defaults,
26             }, $class;
27             }
28              
29             sub _format_labels {
30 195     195   270 my ($self, $labels) = @_;
31              
32             # Avoid copying the labels hash unless we need to add defaults.
33             my $to_format = $self->{default_labels}
34 195 100       341 ? { %{$self->{default_labels}}, %{$labels || {}} }
  195 50       295  
  195         544  
35             : $labels;
36              
37             join ',', map {
38 195         523 my $lv = $to_format->{$_};
  142         180  
39 142         309 $lv =~ s/(["\\])/\\$1/sg;
40 142         225 $lv =~ s/\n/\\n/sg;
41 142         537 qq{$_="$lv"}
42             } sort keys %$to_format;
43             }
44              
45             sub set {
46 60     60 1 368 my ($self, $name, $value, $labels, $timestamp) = @_;
47 60 100       175 unless (looks_like_number $value) {
48 1         20 carp "setting '$name' to non-numeric value, using 0 instead";
49 1         564 $value = 0;
50             }
51 60         99 my $f_label = $self->_format_labels($labels);
52 60         171 $self->{metrics}{$name}{$f_label} = [ $value, $timestamp ];
53 60         123 return;
54             }
55              
56             sub add {
57 135     135 1 286 my ($self, $name, $value, $labels) = @_;
58 135 100       264 unless (looks_like_number $value) {
59 1         10 carp "adjusting '$name' by non-numeric value, adding 0 instead";
60 1         361 $value = 0;
61             }
62 135         233 $self->{metrics}{$name}{$self->_format_labels($labels)}->[0] += $value;
63 135         251 return;
64             }
65              
66             sub inc {
67 24     24 1 57 my ($self, $name, $labels) = @_;
68 24         43 return $self->add($name, 1, $labels);
69             }
70              
71             sub dec {
72 5     5 1 20 my ($self, $name, $labels) = @_;
73 5         11 return $self->add($name, -1, $labels);
74             }
75              
76             sub clear {
77 1     1 1 4 my ($self, $name) = @_;
78 1         5 $self->{metrics} = {};
79 1         2 return;
80             }
81              
82             sub histogram_observe {
83 9     9 1 33 my ($self, $name, $value, $labels) = @_;
84              
85 9         22 $self->inc($name.'_count', $labels);
86 9         23 $self->add($name.'_sum', $value, $labels);
87              
88 9 100       10 my @buckets = @{$self->{meta}{$name}{buckets} || $DEFAULT_BUCKETS};
  9         28  
89              
90 9         18 my $bucket_metric = $name.'_bucket';
91 9         11 for my $bucket (@buckets) {
92 90 100       158 $self->add($bucket_metric, $value <= $bucket ? 1 : 0, { %{$labels || {}} , le => $bucket });
  90 50       295  
93             }
94 9 50       15 $self->inc($bucket_metric, { %{$labels || {}}, le => '+Inf' });
  9         45  
95              
96 9         20 return;
97             }
98              
99             sub enum_set {
100 2     2 1 9 my ($self, $name, $value, $labels, $timestamp) = @_;
101              
102             my $enum_label = $self->{meta}{$name}{enum} ||
103 2   33     6 croak "enum not declared for '$name'";
104              
105 2 50       2 for my $ev (@{$self->{meta}{$name}{enum_values} || []}) {
  2         6  
106 6 100       13 $self->set($name, $value eq $ev ? 1 : 0, { %{$labels || {}}, $enum_label => $ev }, $timestamp);
  6 100       25  
107             }
108             }
109              
110             sub declare {
111 29     29 1 5921 my ($self, $name, %meta) = @_;
112              
113 29 100       101 if (my $old = $self->{meta}{$name}) {
114 13 100 100     137 if (
      100        
      66        
      33        
      66        
      100        
      100        
      66        
      100        
      100        
      100        
      100        
      100        
      100        
      66        
      100        
115             ((exists $old->{type} ^ exists $meta{type}) ||
116             (exists $old->{type} && $old->{type} ne $meta{type})) ||
117             ((exists $old->{help} ^ exists $meta{help}) ||
118             (exists $old->{help} && $old->{help} ne $meta{help})) ||
119             ((exists $old->{enum} ^ exists $meta{enum}) ||
120             (exists $old->{enum} && $old->{enum} ne $meta{enum})) ||
121             ((exists $old->{buckets} ^ exists $meta{buckets}) ||
122             (exists $old->{buckets} && (
123             @{$old->{buckets}} ne @{$meta{buckets}} ||
124             grep { $old->{buckets}[$_] != $meta{buckets}[$_] } (0 .. $#{$meta{buckets}})
125             ))
126             ) ||
127             ((exists $old->{enum_values} ^ exists $meta{enum_values}) ||
128             (exists $old->{enum_values} && (
129             @{$old->{enum_values}} ne @{$meta{enum_values}} ||
130             grep { $old->{enum_values}[$_] ne $meta{enum_values}[$_] } (0 .. $#{$meta{enum_values}})
131             ))
132             )
133             ) {
134 10         81 croak "redeclaration of '$name' with mismatched meta";
135             }
136             }
137              
138 19         92 $self->{meta}{$name} = { %meta };
139 19         58 return;
140             }
141              
142             sub format {
143 51     51 1 1018 my ($self) = @_;
144 51         64 my %names = map { $_ => 1 } (keys %{$self->{metrics}}, keys %{$self->{meta}});
  82         182  
  51         131  
  51         117  
145             return join '', map {
146 51         212 my $name = $_;
  70         105  
147             (
148             (defined $self->{meta}{$name}{help} ?
149             ("# HELP $name $self->{meta}{$name}{help}\n") : ()),
150             (defined $self->{meta}{$name}{type} ?
151             ("# TYPE $name $self->{meta}{$name}{type}\n") : ()),
152             (map {
153 105         147 my $v = join ' ', grep { defined $_ } @{$self->{metrics}{$name}{$_}};
  161         321  
  105         176  
154 105 100       520 $_ ?
155             join '', $name, '{', $_, '} ', $v, "\n" :
156             join '', $name, ' ', $v, "\n"
157             } sort {
158             $name =~ m/_bucket$/ ?
159 106 100       203 do {
160 93         116 my $t_a = $a; $t_a =~ s/le="([^"]+)"//; my $le_a = $1;
  93         215  
  93         144  
161 93         113 my $t_b = $b; $t_b =~ s/le="([^"]+)"//; my $le_b = $1;
  93         193  
  93         135  
162             $t_a eq $t_b ?
163 93 50       125 do {
164 93 100       173 $le_a eq '+Inf' ? 1 :
    100          
165             $le_b eq '+Inf' ? -1 :
166             ($a cmp $b)
167             } :
168             ($a cmp $b)
169             } :
170             ($a cmp $b)
171 70 100       243 } keys %{$self->{metrics}{$name}}),
  70 100       203  
172             )
173             } sort keys %names;
174             }
175              
176             sub psgi {
177 1     1 1 6 my ($self) = @_;
178             return sub {
179 1     1   39501 my ($env) = @_;
180 1 50       7 return [ 405, [], [] ] unless $env->{REQUEST_METHOD} eq 'GET';
181 1         26 return [ 200, [ 'Content-Type' => 'text/plain' ], [ $self->format ] ];
182 1         17 };
183             }
184              
185             1;
186              
187             __END__