Completed
Push — master ( ee70e4...91224a )
by mw
37:03
created

TransientStatsdCollector::recordStats()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 9
nc 2
nop 0
dl 0
loc 23
ccs 5
cts 5
cp 1
crap 2
rs 9.0856
c 2
b 0
f 0
1
<?php
2
3
namespace SMW;
4
5
use Onoi\BlobStore\BlobStore;
6
use RuntimeException;
7
8
/**
9
 * Collect statistics in a provisional schema-free storage that depends on the
10
 * availability of the cache back-end.
11
 *
12
 * @license GNU GPL v2+
13
 * @since 2.5
14
 *
15
 * @author mwjames
16
 */
17
class TransientStatsdCollector {
18
19
	/**
20
	 * Update this version number when the serialization format
21
	 * changes.
22
	 */
23
	const VERSION = '0.2';
24
25
	/**
26
	 * Available operations
27
	 */
28
	const STATS_INIT = 'init';
29
	const STATS_INCR = 'incr';
30
	const STATS_SET = 'set';
31
	const STATS_MEDIAN = 'median';
32
33
	/**
34
	 * Namespace occupied by the BlobStore
35
	 */
36
	const CACHE_NAMESPACE = 'smw:stats:store';
37
38
	/**
39
	 * @var BlobStore
40
	 */
41
	private $blobStore;
42
43
	/**
44
	 * @var string|integer
45
	 */
46
	private $statsdId;
47
48
	/**
49
	 * @var boolean
50
	 */
51
	private $shouldRecord = true;
52
53
	/**
54
	 * @var array
55
	 */
56
	private $stats = array();
57
58
	/**
59
	 * Identifies an update fingerprint to compare invoked deferred updates
60
	 * against each other and filter those with the same print to avoid recording
61
	 * duplicate stats.
62
	 *
63
	 * @var string
64
	 */
65
	private $fingerprint = null;
66
67
	/**
68
	 * @var array
69 184
	 */
70 184
	private $operations = array();
71 184
72 184
	/**
73
	 * @since 2.5
74
	 *
75
	 * @param BlobStore $blobStore
76
	 * @param string $statsdId
77
	 */
78
	public function __construct( BlobStore $blobStore, $statsdId ) {
79 176
		$this->blobStore = $blobStore;
80 176
		$this->statsdId = $statsdId;
81 176
		$this->fingerprint = $statsdId . uniqid();
82
	}
83
84
	/**
85
	 * @since 2.5
86
	 *
87
	 * @param boolean $shouldRecord
88 3
	 */
89
	public function shouldRecord( $shouldRecord ) {
90 3
		$this->shouldRecord = (bool)$shouldRecord;
91 3
	}
92
93
	/**
94 3
	 * @since 2.5
95 3
	 *
96
	 * @return array
97 3
	 */
98 3
	public function getStats() {
99 2
100
		$container = $this->blobStore->read(
101 3
			md5( $this->statsdId . self::VERSION )
102
		);
103
104
		$data = $container->getData();
105 3
		$stats = array();
106
107
		foreach ( $data as $key => $value ) {
108
			if ( strpos( $key, '.' ) !== false ) {
109
				$stats = array_merge_recursive( $stats, $this->stringToArray( $key, $value ) );
110
			} else {
111
				$stats[$key] = $value;
112
			}
113 171
		}
114
115 171
		return $stats;
116 12
	}
117
118
	/**
119 171
	 * @since 2.5
120 171
	 *
121 171
	 * @param string|array $key
122
	 */
123
	public function incr( $key ) {
124
125
		if ( !isset( $this->stats[$key] ) ) {
126
			$this->stats[$key] = 0;
127
		}
128
129 176
		$this->stats[$key]++;
130 176
		$this->operations[$key] = self::STATS_INCR;
131 176
	}
132 176
133
	/**
134
	 * @since 2.5
135
	 *
136
	 * @param string|array $key
137
	 * @param string|integer $default
138
	 */
139
	public function init( $key, $default ) {
140 177
		$this->stats[$key] = $default;
141 177
		$this->operations[$key] = self::STATS_INIT;
142 177
	}
143 177
144
	/**
145
	 * @since 2.5
146
	 *
147
	 * @param string|array $key
148
	 * @param string|integer $value
149
	 */
150
	public function set( $key, $value ) {
151 40
		$this->stats[$key] = $value;
152
		$this->operations[$key] = self::STATS_SET;
153 40
	}
154 40
155
	/**
156 7
	 * @since 2.5
157
	 *
158
	 * @param string|array $key
159 40
	 * @param integer $value
160 40
	 */
161
	public function calcMedian( $key, $value ) {
162
163
		if ( !isset( $this->stats[$key] ) ) {
164 9
			$this->stats[$key] = $value;
165
		} else {
166 9
			$this->stats[$key] = ( $this->stats[$key] + $value ) / 2;
167 9
		}
168
169
		$this->operations[$key] = self::STATS_MEDIAN;
170 9
	}
171
172 4
	/**
173
	 * @since 2.5
174 4
	 */
175
	public function saveStats() {
176
177
		$container = $this->blobStore->read(
178 4
			md5( $this->statsdId . self::VERSION )
179 2
		);
180
181
		foreach ( $this->stats as $key => $value ) {
182
183
			$old = $container->has( $key ) ? $container->get( $key ) : 0;
184
185 4
			if ( $this->operations[$key] === self::STATS_INIT && $old != 0 ) {
186 2
				$value = $old;
187
			}
188
189 4
			if ( $this->operations[$key] === self::STATS_INCR ) {
190
				$value = $old + $value;
191
			}
192 9
193
			// Use as-is
194
			// $this->operations[$key] === self::STATS_SET
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
195 9
196
			if ( $this->operations[$key] === self::STATS_MEDIAN ) {
197
				$value = $old > 0 ? ( $old + $value ) / 2 : $value;
198
			}
199 2
200 2
			$container->set( $key, $value );
201 2
		}
202
203 2
		$this->blobStore->save(
204 2
			$container
205
		);
206
207 2
		$this->stats = array();
208 2
	}
209
210
	/**
211 2
	 * @since 2.5
212
	 */
213
	public function recordStats() {
214 2
215
		if ( $this->shouldRecord === false ) {
216
			return $this->stats = array();
217
		}
218
219
		// #2046
220 9
		// __destruct as event trigger has shown to be unreliable in a MediaWiki
221 9
		// environment therefore rely on the deferred update and any caller
222 9
		// that invokes the recordStats method
223
224 9
		$deferredCallableUpdate = ApplicationFactory::getInstance()->newDeferredCallableUpdate(
225
			function() { $this->saveStats(); }
226
		);
227
228
		$deferredCallableUpdate->setOrigin( __METHOD__ );
229
230
		$deferredCallableUpdate->setFingerprint(
231
			__METHOD__ . $this->fingerprint
232
		);
233
234
		$deferredCallableUpdate->pushToUpdateQueue();
235
	}
236
237
	// http://stackoverflow.com/questions/10123604/multstatsdIdimensional-array-from-string
238
	private function stringToArray( $path, $value ) {
239
240
		$separator = '.';
241
		$pos = strpos( $path, $separator );
242
243
		if ( $pos === false ) {
244
			return array( $path => $value );
245
		}
246
247
		$key = substr( $path, 0, $pos );
248
		$path = substr( $path, $pos + 1 );
249
250
		$result = array(
251
			$key => $this->stringToArray( $path, $value )
252
		);
253
254
		return $result;
255
	}
256
257
}
258