Completed
Branch master (7e350b)
by
unknown
30:36
created

SamplingStatsdClient::send()   D

Complexity

Conditions 9
Paths 68

Size

Total Lines 42
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 25
nc 68
nop 2
dl 0
loc 42
rs 4.909
c 0
b 0
f 0
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\StatsdClient;
24
use Liuggio\StatsdClient\Entity\StatsdData;
25
use Liuggio\StatsdClient\Entity\StatsdDataInterface;
26
27
/**
28
 * A statsd client that applies the sampling rate to the data items before sending them.
29
 *
30
 * @since 1.26
31
 */
32
class SamplingStatsdClient extends StatsdClient {
33
	protected $samplingRates = [];
34
35
	/**
36
	 * Sampling rates as an associative array of patterns and rates.
37
	 * Patterns are Unix shell patterns (e.g. 'MediaWiki.api.*').
38
	 * Rates are sampling probabilities (e.g. 0.1 means 1 in 10 events are sampled).
39
	 * @param array $samplingRates
40
	 * @since 1.28
41
	 */
42
	public function setSamplingRates( array $samplingRates ) {
43
		$this->samplingRates = $samplingRates;
44
	}
45
46
	/**
47
	 * Sets sampling rate for all items in $data.
48
	 * The sample rate specified in a StatsdData entity overrides the sample rate specified here.
49
	 *
50
	 * {@inheritDoc}
51
	 */
52
	public function appendSampleRate( $data, $sampleRate = 1 ) {
53
		$samplingRates = $this->samplingRates;
54
		if ( !$samplingRates && $sampleRate !== 1 ) {
55
			$samplingRates = [ '*' => $sampleRate ];
56
		}
57
		if ( $samplingRates ) {
58
			array_walk( $data, function( $item ) use ( $samplingRates ) {
59
				/** @var $item StatsdData */
60
				foreach ( $samplingRates as $pattern => $rate ) {
61
					if ( fnmatch( $pattern, $item->getKey(), FNM_NOESCAPE ) ) {
62
						$item->setSampleRate( $item->getSampleRate() * $rate );
63
						break;
64
					}
65
				}
66
			} );
67
		}
68
69
		return $data;
70
	}
71
72
	/*
73
	 * Send the metrics over UDP
74
	 * Sample the metrics according to their sample rate and send the remaining ones.
75
	 *
76
	 * @param StatsdDataInterface|StatsdDataInterface[] $data message(s) to sent
77
	 *        strings are not allowed here as sampleData requires a StatsdDataInterface
78
	 * @param int $sampleRate
79
	 *
80
	 * @return integer the data sent in bytes
81
	 */
82
	public function send( $data, $sampleRate = 1 ) {
83
		if ( !is_array( $data ) ) {
84
			$data = [ $data ];
85
		}
86
		if ( !$data ) {
87
			return;
88
		}
89
		foreach ( $data as $item ) {
90
			if ( !( $item instanceof StatsdDataInterface ) ) {
91
				throw new InvalidArgumentException(
92
					'SamplingStatsdClient does not accept stringified messages' );
93
			}
94
		}
95
96
		// add sampling
97
		$data = $this->appendSampleRate( $data, $sampleRate );
98
		$data = $this->sampleData( $data );
99
100
		$data = array_map( 'strval', $data );
101
102
		// reduce number of packets
103
		if ( $this->getReducePacket() ) {
104
			$data = $this->reduceCount( $data );
105
		}
106
107
		// failures in any of this should be silently ignored if ..
108
		$written = 0;
109
		try {
110
			$fp = $this->getSender()->open();
111
			if ( !$fp ) {
112
				return;
113
			}
114
			foreach ( $data as $message ) {
115
				$written += $this->getSender()->write( $fp, $message );
116
			}
117
			$this->getSender()->close( $fp );
118
		} catch ( Exception $e ) {
119
			$this->throwException( $e );
120
		}
121
122
		return $written;
123
	}
124
125
	/**
126
	 * Throw away some of the data according to the sample rate.
127
	 * @param StatsdDataInterface[] $data
128
	 * @return StatsdDataInterface[]
129
	 * @throws LogicException
130
	 */
131
	protected function sampleData( $data ) {
132
		$newData = [];
133
		$mt_rand_max = mt_getrandmax();
134
		foreach ( $data as $item ) {
135
			$samplingRate = $item->getSampleRate();
136
			if ( $samplingRate <= 0.0 || $samplingRate > 1.0 ) {
137
				throw new LogicException( 'Sampling rate shall be within ]0, 1]' );
138
			}
139
			if (
140
				$samplingRate === 1 ||
141
				( mt_rand() / $mt_rand_max <= $samplingRate )
142
			) {
143
				$newData[] = $item;
144
			}
145
		}
146
		return $newData;
147
	}
148
149
	/**
150
	 * {@inheritDoc}
151
	 */
152
	protected function throwException( Exception $exception ) {
153
		if ( !$this->getFailSilently() ) {
154
			throw $exception;
155
		}
156
	}
157
}
158