Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
77.55% |
38 / 49 |
|
40.00% |
2 / 5 |
CRAP | |
0.00% |
0 / 1 |
SamplingStatsdClient | |
77.55% |
38 / 49 |
|
40.00% |
2 / 5 |
32.07 | |
0.00% |
0 / 1 |
setSamplingRates | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
appendSampleRate | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
send | |
68.00% |
17 / 25 |
|
0.00% |
0 / 1 |
13.28 | |||
sampleData | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
6.04 | |||
throwException | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | /** |
3 | * Copyright 2015 |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | */ |
22 | |
23 | use Liuggio\StatsdClient\Entity\StatsdData; |
24 | use Liuggio\StatsdClient\Entity\StatsdDataInterface; |
25 | use Liuggio\StatsdClient\StatsdClient; |
26 | use Wikimedia\RequestTimeout\TimeoutException; |
27 | |
28 | /** |
29 | * A statsd client that applies the sampling rate to the data items before sending them. |
30 | * |
31 | * @since 1.26 |
32 | */ |
33 | class SamplingStatsdClient extends StatsdClient { |
34 | protected $samplingRates = []; |
35 | |
36 | /** |
37 | * Sampling rates as an associative array of patterns and rates. |
38 | * Patterns are Unix shell patterns (e.g. 'MediaWiki.api.*'). |
39 | * Rates are sampling probabilities (e.g. 0.1 means 1 in 10 events are sampled). |
40 | * @param array $samplingRates |
41 | * @since 1.28 |
42 | */ |
43 | public function setSamplingRates( array $samplingRates ) { |
44 | $this->samplingRates = $samplingRates; |
45 | } |
46 | |
47 | /** |
48 | * Sets sampling rate for all items in $data. |
49 | * The sample rate specified in a StatsdData entity overrides the sample rate specified here. |
50 | * |
51 | * @inheritDoc |
52 | */ |
53 | public function appendSampleRate( $data, $sampleRate = 1 ) { |
54 | $samplingRates = $this->samplingRates; |
55 | if ( !$samplingRates && $sampleRate !== 1 ) { |
56 | $samplingRates = [ '*' => $sampleRate ]; |
57 | } |
58 | if ( $samplingRates ) { |
59 | array_walk( $data, static function ( $item ) use ( $samplingRates ) { |
60 | /** @var StatsdData $item */ |
61 | foreach ( $samplingRates as $pattern => $rate ) { |
62 | if ( fnmatch( $pattern, $item->getKey(), FNM_NOESCAPE ) ) { |
63 | $item->setSampleRate( $item->getSampleRate() * $rate ); |
64 | break; |
65 | } |
66 | } |
67 | } ); |
68 | } |
69 | |
70 | return $data; |
71 | } |
72 | |
73 | /** |
74 | * Send the metrics over UDP |
75 | * Sample the metrics according to their sample rate and send the remaining ones. |
76 | * |
77 | * @param StatsdDataInterface|StatsdDataInterface[] $data message(s) to sent |
78 | * strings are not allowed here as sampleData requires a StatsdDataInterface |
79 | * @param int $sampleRate |
80 | * |
81 | * @return int the data sent in bytes |
82 | */ |
83 | public function send( $data, $sampleRate = 1 ) { |
84 | if ( !is_array( $data ) ) { |
85 | $data = [ $data ]; |
86 | } |
87 | if ( !$data ) { |
88 | return 0; |
89 | } |
90 | foreach ( $data as $item ) { |
91 | if ( !( $item instanceof StatsdDataInterface ) ) { |
92 | throw new InvalidArgumentException( |
93 | 'SamplingStatsdClient does not accept stringified messages' ); |
94 | } |
95 | } |
96 | |
97 | // add sampling |
98 | $data = $this->appendSampleRate( $data, $sampleRate ); |
99 | $data = $this->sampleData( $data ); |
100 | |
101 | $data = array_map( 'strval', $data ); |
102 | |
103 | // reduce number of packets |
104 | if ( $this->getReducePacket() ) { |
105 | $data = $this->reduceCount( $data ); |
106 | } |
107 | |
108 | // failures in any of this should be silently ignored if .. |
109 | $written = 0; |
110 | try { |
111 | $fp = $this->getSender()->open(); |
112 | if ( !$fp ) { |
113 | return 0; |
114 | } |
115 | foreach ( $data as $message ) { |
116 | $written += $this->getSender()->write( $fp, $message ); |
117 | } |
118 | $this->getSender()->close( $fp ); |
119 | } catch ( TimeoutException $e ) { |
120 | throw $e; |
121 | } catch ( Exception $e ) { |
122 | $this->throwException( $e ); |
123 | } |
124 | |
125 | return $written; |
126 | } |
127 | |
128 | /** |
129 | * Throw away some of the data according to the sample rate. |
130 | * @param StatsdDataInterface[] $data |
131 | * @return StatsdDataInterface[] |
132 | * @throws LogicException |
133 | */ |
134 | protected function sampleData( $data ) { |
135 | $newData = []; |
136 | $mt_rand_max = mt_getrandmax(); |
137 | foreach ( $data as $item ) { |
138 | $samplingRate = $item->getSampleRate(); |
139 | if ( $samplingRate <= 0.0 || $samplingRate > 1.0 ) { |
140 | throw new LogicException( 'Sampling rate shall be within ]0, 1]' ); |
141 | } |
142 | if ( |
143 | $samplingRate === 1 || |
144 | ( mt_rand() / $mt_rand_max <= $samplingRate ) |
145 | ) { |
146 | $newData[] = $item; |
147 | } |
148 | } |
149 | return $newData; |
150 | } |
151 | |
152 | /** |
153 | * Ideally this would override parent::throwException(), but that is private, |
154 | * not protected. |
155 | * |
156 | * @param Exception $exception |
157 | */ |
158 | private function throwException( Exception $exception ) { |
159 | if ( !$this->getFailSilently() ) { |
160 | throw $exception; |
161 | } |
162 | } |
163 | } |