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 |
|
|
|
|
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
|
|
|
|
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.