File Coverage

blib/lib/Prometheus/Tiny.pm
Criterion Covered Total %
statement 87 87 100.0
branch 34 40 85.0
condition 43 51 84.3
subroutine 16 16 100.0
pod 11 11 100.0
total 191 205 93.1


line stmt bran cond sub pod time code
1             package Prometheus::Tiny;
2             $Prometheus::Tiny::VERSION = '0.009';
3             # ABSTRACT: A tiny Prometheus client
4              
5 13     13   906533 use warnings;
  13         161  
  13         487  
6 13     13   77 use strict;
  13         25  
  13         383  
7              
8 13     13   71 use Carp qw(croak);
  13         60  
  13         21025  
9              
10             my $DEFAULT_BUCKETS = [
11             0.005,
12             0.01, 0.025, 0.05, 0.075,
13             0.1, 0.25, 0.5, 0.75,
14             1.0, 2.5, 5.0, 7.5,
15             10
16             ];
17              
18             sub new {
19 47     47 1 22528 my ($class, %arg) = @_;
20 47 100       179 my %defaults = $arg{default_labels} ? %{$arg{default_labels}} : ();
  3         12  
21 47         272 return bless {
22             metrics => {},
23             meta => {},
24             default_labels => \%defaults,
25             }, $class;
26             }
27              
28             sub _format_labels {
29 191     191   294 my ($self, $labels) = @_;
30              
31             # Avoid copying the labels hash unless we need to add defaults.
32             my $to_format = $self->{default_labels}
33 191 100       354 ? { %{$self->{default_labels}}, %{$labels || {}} }
  191 50       316  
  191         699  
34             : $labels;
35              
36             join ',', map {
37 191         601 my $lv = $to_format->{$_};
  142         207  
38 142         345 $lv =~ s/(["\\])/\\$1/sg;
39 142         260 $lv =~ s/\n/\\n/sg;
40 142         652 qq{$_="$lv"}
41             } sort keys %$to_format;
42             }
43              
44             sub set {
45 57     57 1 303 my ($self, $name, $value, $labels, $timestamp) = @_;
46 57         119 my $f_label = $self->_format_labels($labels);
47 57         176 $self->{metrics}{$name}{$f_label} = [ $value, $timestamp ];
48 57         156 return;
49             }
50              
51             sub add {
52 134     134 1 259 my ($self, $name, $value, $labels) = @_;
53 134         265 $self->{metrics}{$name}{$self->_format_labels($labels)}->[0] += $value;
54 134         311 return;
55             }
56              
57             sub inc {
58 24     24 1 61 my ($self, $name, $labels) = @_;
59 24         54 return $self->add($name, 1, $labels);
60             }
61              
62             sub dec {
63 5     5 1 21 my ($self, $name, $labels) = @_;
64 5         14 return $self->add($name, -1, $labels);
65             }
66              
67             sub clear {
68 1     1 1 3 my ($self, $name) = @_;
69 1         5 $self->{metrics} = {};
70 1         2 return;
71             }
72              
73             sub histogram_observe {
74 9     9 1 44 my ($self, $name, $value, $labels) = @_;
75              
76 9         31 $self->inc($name.'_count', $labels);
77 9         26 $self->add($name.'_sum', $value, $labels);
78              
79 9 100       12 my @buckets = @{$self->{meta}{$name}{buckets} || $DEFAULT_BUCKETS};
  9         38  
80              
81 9         19 my $bucket_metric = $name.'_bucket';
82 9         15 for my $bucket (@buckets) {
83 90 100       183 $self->add($bucket_metric, $value <= $bucket ? 1 : 0, { %{$labels || {}} , le => $bucket });
  90 50       384  
84             }
85 9 50       17 $self->inc($bucket_metric, { %{$labels || {}}, le => '+Inf' });
  9         46  
86              
87 9         27 return;
88             }
89              
90             sub enum_set {
91 2     2 1 14 my ($self, $name, $value, $labels, $timestamp) = @_;
92              
93             my $enum_label = $self->{meta}{$name}{enum} ||
94 2   33     8 croak "enum not declared for '$name'";
95              
96 2 50       3 for my $ev (@{$self->{meta}{$name}{enum_values} || []}) {
  2         7  
97 6 100       15 $self->set($name, $value eq $ev ? 1 : 0, { %{$labels || {}}, $enum_label => $ev }, $timestamp);
  6 100       30  
98             }
99             }
100              
101             sub declare {
102 29     29 1 7767 my ($self, $name, %meta) = @_;
103              
104 29 100       121 if (my $old = $self->{meta}{$name}) {
105 13 100 100     177 if (
      100        
      66        
      33        
      66        
      100        
      100        
      66        
      100        
      100        
      100        
      100        
      100        
      100        
      66        
      100        
106             ((exists $old->{type} ^ exists $meta{type}) ||
107             (exists $old->{type} && $old->{type} ne $meta{type})) ||
108             ((exists $old->{help} ^ exists $meta{help}) ||
109             (exists $old->{help} && $old->{help} ne $meta{help})) ||
110             ((exists $old->{enum} ^ exists $meta{enum}) ||
111             (exists $old->{enum} && $old->{enum} ne $meta{enum})) ||
112             ((exists $old->{buckets} ^ exists $meta{buckets}) ||
113             (exists $old->{buckets} && (
114             @{$old->{buckets}} ne @{$meta{buckets}} ||
115             grep { $old->{buckets}[$_] != $meta{buckets}[$_] } (0 .. $#{$meta{buckets}})
116             ))
117             ) ||
118             ((exists $old->{enum_values} ^ exists $meta{enum_values}) ||
119             (exists $old->{enum_values} && (
120             @{$old->{enum_values}} ne @{$meta{enum_values}} ||
121             grep { $old->{enum_values}[$_] ne $meta{enum_values}[$_] } (0 .. $#{$meta{enum_values}})
122             ))
123             )
124             ) {
125 10         114 croak "redeclaration of '$name' with mismatched meta";
126             }
127             }
128              
129 19         80 $self->{meta}{$name} = { %meta };
130 19         69 return;
131             }
132              
133             sub format {
134 49     49 1 220 my ($self) = @_;
135 49         86 my %names = map { $_ => 1 } (keys %{$self->{metrics}}, keys %{$self->{meta}});
  80         222  
  49         152  
  49         150  
136             return join '', map {
137 49         236 my $name = $_;
  68         109  
138             (
139             (defined $self->{meta}{$name}{help} ?
140             ("# HELP $name $self->{meta}{$name}{help}\n") : ()),
141             (defined $self->{meta}{$name}{type} ?
142             ("# TYPE $name $self->{meta}{$name}{type}\n") : ()),
143             (map {
144 103         191 my $v = join ' ', grep { defined $_ } @{$self->{metrics}{$name}{$_}};
  157         400  
  103         212  
145 103 100       554 $_ ?
146             join '', $name, '{', $_, '} ', $v, "\n" :
147             join '', $name, ' ', $v, "\n"
148             } sort {
149             $name =~ m/_bucket$/ ?
150 110 100       253 do {
151 96         134 my $t_a = $a; $t_a =~ s/le="([^"]+)"//; my $le_a = $1;
  96         255  
  96         170  
152 96         128 my $t_b = $b; $t_b =~ s/le="([^"]+)"//; my $le_b = $1;
  96         228  
  96         163  
153             $t_a eq $t_b ?
154 96 50       151 do {
155 96 100       204 $le_a eq '+Inf' ? 1 :
    100          
156             $le_b eq '+Inf' ? -1 :
157             ($a cmp $b)
158             } :
159             ($a cmp $b)
160             } :
161             ($a cmp $b)
162 68 100       258 } keys %{$self->{metrics}{$name}}),
  68 100       235  
163             )
164             } sort keys %names;
165             }
166              
167             sub psgi {
168 1     1 1 7 my ($self) = @_;
169             return sub {
170 1     1   37768 my ($env) = @_;
171 1 50       7 return [ 405, [], [] ] unless $env->{REQUEST_METHOD} eq 'GET';
172 1         7 return [ 200, [ 'Content-Type' => 'text/plain' ], [ $self->format ] ];
173 1         13 };
174             }
175              
176             1;
177              
178             __END__