File Coverage

blib/lib/Net/Statsd/Server/Metrics.pm
Criterion Covered Total %
statement 105 110 95.4
branch 10 16 62.5
condition 2 6 33.3
subroutine 7 7 100.0
pod 0 3 0.0
total 124 142 87.3


line stmt bran cond sub pod time code
1             # ABSTRACT: Provides metrics abstraction to a running statsd server
2              
3             package Net::Statsd::Server::Metrics;
4             {
5             $Net::Statsd::Server::Metrics::VERSION = '0.17';
6             }
7              
8 5     5   97536 use 5.008;
  5         17  
  5         198  
9 5     5   26 use strict;
  5         10  
  5         358  
10 5     5   36 use Carp ();
  5         10  
  5         82  
11 5     5   2557 use Time::HiRes ();
  5         4753  
  5         15983  
12              
13             sub new {
14 12     12 0 18938 my ($class, $config) = @_;
15 12   33     60 $class = ref $class || $class;
16 12         22 my $g_pref = $config->{prefixStats};
17 12 50       25 if (! $g_pref) {
18 0         0 Carp::croak("prefixStats is empty or invalid! (Metrics.new)");
19             }
20              
21 12         170 my $self = {
22             keyCounter => {},
23             counters => {
24             "${g_pref}.packets_received" => 0,
25             "${g_pref}.bad_lines_seen" => 0,
26             },
27             timers => {},
28             gauges => {},
29             sets => {},
30             counter_rates => {},
31             timer_data => {},
32             pctThreshold => [ 90 ],
33             };
34              
35 12 50 33     49 if (exists $config->{percentThreshold}
36             && ref $config->{percentThreshold} eq "ARRAY") {
37 0         0 $self->{pctThreshold} = $config->{percentThreshold};
38             }
39              
40 12         50 bless $self, $class;
41             }
42              
43             sub process {
44 12     12 0 110 my ($self, $flush_interval) = @_;
45              
46 12         59 my $starttime = [Time::HiRes::gettimeofday];
47 12         16 my $key;
48              
49 12         29 my $metrics = $self->as_hash;
50 12         24 my $counters = $metrics->{counters};
51 12         15 my $timers = $metrics->{timers};
52 12         15 my $pctThreshold = $metrics->{pctThreshold};
53              
54             # Meta metrics added by statsd
55 12         18 my $counter_rates = {};
56 12         20 my $timer_data = {};
57 12         14 my $statsd_metrics = {};
58              
59             # Calculate "per second" rate
60 12         22 $flush_interval /= 1000;
61              
62 12         16 for my $key (keys %{ $counters }) {
  12         37  
63 26         40 my $value = $counters->{$key};
64 26         69 $counter_rates->{$key} = $value / $flush_interval;
65             }
66              
67             # Calculate all requested
68             # percentile values (90%, 95%, ...)
69 12         20 for my $key (keys %{ $timers }) {
  12         32  
70              
71 7         11 my $current_timer_data = {};
72              
73 7 100       12 if (@{ $timers->{$key} } > 0) {
  7         20  
74              
75             # Sort timer samples by value
76 6         8 my @values = @{ $timers->{$key} };
  6         16  
77 6         19 @values = sort { $a <=> $b } @values;
  9         14  
78              
79 6         9 my $count = @values;
80 6         9 my $min = $values[0];
81 6         9 my $max = $values[$#values];
82              
83             # We don't want to iterate at all if there's just 1 value
84 6         25 my $cumulativeValues = [ $min ];
85 6         15 my $cumulSumSquaresValues = [ $min * $min ];
86              
87 6         19 for (my $i = 1; $i < $count; $i++) {
88 6         13 my $cmlVal = $values[$i] + $cumulativeValues->[$i - 1];
89 6         9 push @{ $cumulativeValues }, $values[$i] + $cumulativeValues->[$i - 1];
  6         13  
90 6         7 push @{ $cumulSumSquaresValues }, ($values[$i] * $values[$i])
  6         21  
91             + $cumulSumSquaresValues->[$i - 1];
92             }
93              
94 6         10 my $sum = my $mean = $min;
95 6         7 my $sumSquares = $min * $min;
96 6         8 my $maxAtThreshold = $max;
97              
98 6         9 for my $pct (@{ $pctThreshold }) {
  6         20  
99              
100 8         11 my $numInThreshold = $count;
101              
102 8 100       17 if ($count > 1) {
103             # Pay attention to the rounding: should behave the same
104             # as etsy's statsd, that's using a Math.round(x).
105             # int(x + 0.5) does this.
106 4         12 $numInThreshold = int(($pct / 100 * $count) + 0.5);
107 4 50       9 next if $numInThreshold == 0;
108              
109 4 50       10 if ($pct > 0) {
110 4         6 $maxAtThreshold = $values[$numInThreshold - 1];
111 4         6 $sum = $cumulativeValues->[$numInThreshold - 1];
112 4         7 $sumSquares = $cumulSumSquaresValues->[$numInThreshold - 1];
113             }
114             else {
115 0         0 $maxAtThreshold = $values[$count - $numInThreshold];
116 0         0 $sum = $cumulativeValues->[$count - 1] - $cumulativeValues->[$count - $numInThreshold - 1];
117 0         0 $sumSquares = $cumulSumSquaresValues->[$count - 1] - $cumulSumSquaresValues->[$count - $numInThreshold - 1];
118             }
119 4         6 $mean = $sum / $numInThreshold;
120             }
121              
122 8         14 my $clean_pct = "" . $pct;
123 8         14 $clean_pct =~ s{\.}{_}g;
124 8         11 $clean_pct =~ s{-}{top}g;
125 8         20 $current_timer_data->{"count_${clean_pct}"} = $numInThreshold;
126 8         14 $current_timer_data->{"mean_${clean_pct}"} = $mean;
127 8 50       26 $current_timer_data->{($pct > 0 ? "upper_" : "lower_") . $clean_pct} = $maxAtThreshold;
128 8         12 $current_timer_data->{"sum_${clean_pct}"} = $sum;
129 8         28 $current_timer_data->{"sum_squares_${clean_pct}"} = $sumSquares;
130             }
131              
132 6         12 $sum = $cumulativeValues->[$count - 1];
133 6         7 $sumSquares = $cumulSumSquaresValues->[$count - 1];
134 6         9 $mean = $sum / $count;
135              
136             # Calculate standard deviation
137 6         8 my $sumOfDiffs = 0;
138 6         15 for (0 .. $count - 1) {
139 12         30 $sumOfDiffs += ($values[$_] - $mean) ** 2;
140             }
141 6         18 my $stddev = sqrt($sumOfDiffs / $count);
142 6         10 my $mid = int($count / 2);
143 6 50       15 my $median = $count % 2
144             ? $values[$mid]
145             : ($values[$mid - 1] + $values[$mid]) / 2;
146              
147 6         11 $current_timer_data->{std} = $stddev;
148 6         12 $current_timer_data->{upper} = $max;
149 6         14 $current_timer_data->{lower} = $min;
150 6         10 $current_timer_data->{count} = $count;
151 6         10 $current_timer_data->{count_ps} = $count / $flush_interval;
152 6         11 $current_timer_data->{sum} = $sum;
153 6         10 $current_timer_data->{sum_squares} = $sumSquares;
154 6         8 $current_timer_data->{mean} = $mean;
155 6         18 $current_timer_data->{median} = $median;
156              
157             }
158             else {
159 1         3 $current_timer_data->{count} = 0;
160 1         3 $current_timer_data->{count_ps} = 0;
161             }
162              
163 7         23 $timer_data->{$key} = $current_timer_data;
164             }
165              
166             # This is originally ms in statsd
167 12         44 $statsd_metrics->{processing_time} = Time::HiRes::tv_interval($starttime) * 1000;
168              
169             # Add processed metrics to the metrics_hash
170 12         615 $metrics->{counter_rates} = $counter_rates;
171 12         20 $metrics->{timer_data} = $timer_data;
172 12         16 $metrics->{statsd_metrics} = $statsd_metrics;
173              
174 12         42 return $metrics;
175             }
176              
177             sub as_hash {
178 12     12 0 19 my $self = $_[0];
179              
180 12         61 my %metrics = (
181             counters => $self->{counters},
182             timers => $self->{timers},
183             gauges => $self->{gauges},
184             pctThreshold => $self->{pctThreshold},
185             );
186              
187 12         27 return \%metrics;
188             }
189              
190             1;