File Coverage

blib/lib/Net/Statsd/Server/Metrics.pm
Criterion Covered Total %
statement 104 109 95.4
branch 10 16 62.5
condition 2 6 33.3
subroutine 7 7 100.0
pod 0 3 0.0
total 123 141 87.2


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