File Coverage

blib/lib/Prometheus/Tiny.pm
Criterion Covered Total %
statement 99 99 100.0
branch 40 46 86.9
condition 43 51 84.3
subroutine 17 17 100.0
pod 11 11 100.0
total 210 224 93.7


line stmt bran cond sub pod time code
1             package Prometheus::Tiny;
2             $Prometheus::Tiny::VERSION = '0.011';
3             # ABSTRACT: A tiny Prometheus client
4              
5 14     14   950800 use warnings;
  14         146  
  14         480  
6 14     14   79 use strict;
  14         31  
  14         337  
7              
8 14     14   68 use Carp qw(croak carp);
  14         25  
  14         785  
9 14     14   98 use Scalar::Util qw(looks_like_number);
  14         26  
  14         24605  
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 50     50 1 23048 my ($class, %arg) = @_;
21 50 100       185 my %defaults = $arg{default_labels} ? %{$arg{default_labels}} : ();
  3         13  
22 50         330 return bless {
23             metrics => {},
24             meta => {},
25             default_labels => \%defaults,
26             }, $class;
27             }
28              
29             sub _format_labels {
30 197     197   312 my ($self, $labels) = @_;
31              
32             # Avoid copying the labels hash unless we need to add defaults.
33             my $to_format = $self->{default_labels}
34 197 100       357 ? { %{$self->{default_labels}}, %{$labels || {}} }
  197 50       345  
  197         645  
35             : $labels;
36              
37             join ',', map {
38 197         610 my $lv = $to_format->{$_};
  143         259  
39 143 100       244 if (defined $lv) {
40 142         350 $lv =~ s/(["\\])/\\$1/sg;
41 142         268 $lv =~ s/\n/\\n/sg;
42 142         675 qq{$_="$lv"}
43             } else {
44 1         10 carp "label '$_' has an undefined value, dropping it";
45             ()
46 1         544 }
47             } sort keys %$to_format;
48             }
49              
50             sub set {
51 61     61 1 362 my ($self, $name, $value, $labels, $timestamp) = @_;
52 61 100       196 unless (looks_like_number $value) {
53 1         16 carp "setting '$name' to non-numeric value, using 0 instead";
54 1         611 $value = 0;
55             }
56 61         127 my $f_label = $self->_format_labels($labels);
57 61         190 $self->{metrics}{$name}{$f_label} = [ $value, $timestamp ];
58 61         144 return;
59             }
60              
61             sub add {
62 136     136 1 364 my ($self, $name, $value, $labels) = @_;
63 136 100       320 unless (looks_like_number $value) {
64 1         11 carp "adjusting '$name' by non-numeric value, adding 0 instead";
65 1         429 $value = 0;
66             }
67 136         280 $self->{metrics}{$name}{$self->_format_labels($labels)}->[0] += $value;
68 136         304 return;
69             }
70              
71             sub inc {
72 24     24 1 55 my ($self, $name, $labels) = @_;
73 24         50 return $self->add($name, 1, $labels);
74             }
75              
76             sub dec {
77 5     5 1 20 my ($self, $name, $labels) = @_;
78 5         13 return $self->add($name, -1, $labels);
79             }
80              
81             sub clear {
82 1     1 1 4 my ($self, $name) = @_;
83 1         5 $self->{metrics} = {};
84 1         2 return;
85             }
86              
87             sub histogram_observe {
88 9     9 1 40 my ($self, $name, $value, $labels) = @_;
89              
90 9         27 $self->inc($name.'_count', $labels);
91 9         31 $self->add($name.'_sum', $value, $labels);
92              
93 9 100       16 my @buckets = @{$self->{meta}{$name}{buckets} || $DEFAULT_BUCKETS};
  9         33  
94              
95 9         19 my $bucket_metric = $name.'_bucket';
96 9         16 for my $bucket (@buckets) {
97 90 100       180 $self->add($bucket_metric, $value <= $bucket ? 1 : 0, { %{$labels || {}} , le => $bucket });
  90 50       429  
98             }
99 9 50       13 $self->inc($bucket_metric, { %{$labels || {}}, le => '+Inf' });
  9         45  
100              
101 9         24 return;
102             }
103              
104             sub enum_set {
105 2     2 1 13 my ($self, $name, $value, $labels, $timestamp) = @_;
106              
107             my $enum_label = $self->{meta}{$name}{enum} ||
108 2   33     7 croak "enum not declared for '$name'";
109              
110 2 50       4 for my $ev (@{$self->{meta}{$name}{enum_values} || []}) {
  2         15  
111 6 100       14 $self->set($name, $value eq $ev ? 1 : 0, { %{$labels || {}}, $enum_label => $ev }, $timestamp);
  6 100       28  
112             }
113             }
114              
115             sub declare {
116 29     29 1 7559 my ($self, $name, %meta) = @_;
117              
118 29 100       111 if (my $old = $self->{meta}{$name}) {
119 13 100 100     172 if (
      100        
      66        
      33        
      66        
      100        
      100        
      66        
      100        
      100        
      100        
      100        
      100        
      100        
      66        
      100        
120             ((exists $old->{type} ^ exists $meta{type}) ||
121             (exists $old->{type} && $old->{type} ne $meta{type})) ||
122             ((exists $old->{help} ^ exists $meta{help}) ||
123             (exists $old->{help} && $old->{help} ne $meta{help})) ||
124             ((exists $old->{enum} ^ exists $meta{enum}) ||
125             (exists $old->{enum} && $old->{enum} ne $meta{enum})) ||
126             ((exists $old->{buckets} ^ exists $meta{buckets}) ||
127             (exists $old->{buckets} && (
128             @{$old->{buckets}} ne @{$meta{buckets}} ||
129             grep { $old->{buckets}[$_] != $meta{buckets}[$_] } (0 .. $#{$meta{buckets}})
130             ))
131             ) ||
132             ((exists $old->{enum_values} ^ exists $meta{enum_values}) ||
133             (exists $old->{enum_values} && (
134             @{$old->{enum_values}} ne @{$meta{enum_values}} ||
135             grep { $old->{enum_values}[$_] ne $meta{enum_values}[$_] } (0 .. $#{$meta{enum_values}})
136             ))
137             )
138             ) {
139 10         103 croak "redeclaration of '$name' with mismatched meta";
140             }
141             }
142              
143 19         84 $self->{meta}{$name} = { %meta };
144 19         65 return;
145             }
146              
147             sub format {
148 52     52 1 1447 my ($self) = @_;
149 52         82 my %names = map { $_ => 1 } (keys %{$self->{metrics}}, keys %{$self->{meta}});
  83         227  
  52         157  
  52         120  
150             return join '', map {
151 52         234 my $name = $_;
  71         116  
152             (
153             (defined $self->{meta}{$name}{help} ?
154             ("# HELP $name $self->{meta}{$name}{help}\n") : ()),
155             (defined $self->{meta}{$name}{type} ?
156             ("# TYPE $name $self->{meta}{$name}{type}\n") : ()),
157             (map {
158 106         164 my $v = join ' ', grep { defined $_ } @{$self->{metrics}{$name}{$_}};
  163         428  
  106         192  
159 106 100       588 $_ ?
160             join '', $name, '{', $_, '} ', $v, "\n" :
161             join '', $name, ' ', $v, "\n"
162             } sort {
163             $name =~ m/_bucket$/ ?
164 111 100       246 do {
165 97         136 my $t_a = $a; $t_a =~ s/le="([^"]+)"//; my $le_a = $1;
  97         243  
  97         174  
166 97         128 my $t_b = $b; $t_b =~ s/le="([^"]+)"//; my $le_b = $1;
  97         229  
  97         170  
167             $t_a eq $t_b ?
168 97 50       144 do {
169 97 100       207 $le_a eq '+Inf' ? 1 :
    100          
170             $le_b eq '+Inf' ? -1 :
171             ($a cmp $b)
172             } :
173             ($a cmp $b)
174             } :
175             ($a cmp $b)
176 71 100       277 } keys %{$self->{metrics}{$name}}),
  71 100       240  
177             )
178             } sort keys %names;
179             }
180              
181             sub psgi {
182 1     1 1 6 my ($self) = @_;
183             return sub {
184 1     1   35184 my ($env) = @_;
185 1 50       6 return [ 405, [], [] ] unless $env->{REQUEST_METHOD} eq 'GET';
186 1         9 return [ 200, [ 'Content-Type' => 'text/plain' ], [ $self->format ] ];
187 1         13 };
188             }
189              
190             1;
191              
192             __END__