MediaWiki REL1_39
MetricsFactory.php
Go to the documentation of this file.
1<?php
29declare( strict_types=1 );
30
31namespace Wikimedia\Metrics;
32
33use Psr\Log\LoggerInterface;
34use TypeError;
35use UDPTransport;
39
41
43 private const SUPPORTED_OUTPUT_FORMATS = [ 'statsd', 'dogstatsd', 'null' ];
44
46 private const NAME_DELIMITER = '.';
47
49 private const DEFAULT_METRIC_CONFIG = [
50 // 'name' => required,
51 // 'extension' => required,
52 'labels' => [],
53 'sampleRate' => 1.0,
54 'service' => '',
55 'format' => 'statsd',
56 ];
57
59 private $cache = [];
60
62 private $target;
63
65 private $format;
66
68 private $prefix;
69
71 private $logger;
72
84 public function __construct( array $config, LoggerInterface $logger ) {
85 $this->logger = $logger;
86 $this->target = $config['target'] ?? null;
87 $this->format = $config['format'] ?? 'null';
88 $this->prefix = $config['prefix'] ?? '';
89 if ( $this->prefix === '' ) {
90 throw new UndefinedPrefixException( '\'prefix\' option is required and cannot be empty.' );
91 }
92 $this->prefix = self::normalizeString( $config['prefix'] );
93 if ( !in_array( $this->format, self::SUPPORTED_OUTPUT_FORMATS ) ) {
95 'Format "' . $this->format . '" not supported. Expected one of '
96 . json_encode( self::SUPPORTED_OUTPUT_FORMATS )
97 );
98 }
99 }
100
113 public function getCounter( array $config = [] ) {
114 $config = $this->getValidConfig( $config );
115 $name = self::getFormattedName( $config['name'], $config['extension'] );
116 try {
117 $metric = $this->getCachedMetric( $name, CounterMetric::class );
118 } catch ( TypeError $ex ) {
119 return new NullMetric();
120 }
121
122 if ( $metric ) {
123 $metric->validateLabels( $config['labels'] );
124 return $metric;
125 }
126 $this->cache[$name] = new CounterMetric( $config, new MetricUtils() );
127 return $this->cache[$name];
128 }
129
141 public function getGauge( array $config = [] ) {
142 $config = $this->getValidConfig( $config );
143 $name = self::getFormattedName( $config['name'], $config['extension'] );
144 try {
145 $metric = $this->getCachedMetric( $name, GaugeMetric::class );
146 } catch ( TypeError $ex ) {
147 return new NullMetric();
148 }
149
150 if ( $metric ) {
151 $metric->validateLabels( $config['labels'] );
152 return $metric;
153 }
154 $this->cache[$name] = new GaugeMetric( $config, new MetricUtils() );
155 return $this->cache[$name];
156 }
157
170 public function getTiming( array $config = [] ) {
171 $config = $this->getValidConfig( $config );
172 $name = self::getFormattedName( $config['name'], $config['extension'] );
173 try {
174 $metric = $this->getCachedMetric( $name, TimingMetric::class );
175 } catch ( TypeError $ex ) {
176 return new NullMetric();
177 }
178 if ( $metric ) {
179 $metric->validateLabels( $config['labels'] );
180 return $metric;
181 }
182 $this->cache[$name] = new TimingMetric( $config, new MetricUtils() );
183 return $this->cache[$name];
184 }
185
189 public function flush(): void {
190 if ( $this->format !== 'null' && $this->target ) {
191 $this->send( UDPTransport::newFromString( $this->target ) );
192 }
193 $this->cache = [];
194 }
195
202 private function getRenderedSamples( array $cache ): array {
203 $renderedSamples = [];
204 foreach ( $cache as $metric ) {
205 foreach ( $metric->render() as $rendered ) {
206 $renderedSamples[] = $rendered;
207 }
208 }
209 return $renderedSamples;
210 }
211
224 private function getCachedMetric( string $name, string $requested_type ) {
225 if ( !array_key_exists( $name, $this->cache ) ) {
226 return null;
227 }
228
229 $metric = $this->cache[$name];
230 if ( get_class( $metric ) !== $requested_type ) {
231 $msg = 'Metric name collision detected: \'' . $name . '\' defined as type \'' . get_class( $metric )
232 . '\' but a \'' . $requested_type . '\' was requested.';
233 $this->logger->error( $msg );
234 throw new TypeError( $msg );
235 }
236
237 return $metric;
238 }
239
246 protected function send( UDPTransport $transport ): void {
247 $payload = '';
248 $renderedSamples = $this->getRenderedSamples( $this->cache );
249 foreach ( $renderedSamples as $sample ) {
250 if ( strlen( $payload ) + strlen( $sample ) + 1 < UDPTransport::MAX_PAYLOAD_SIZE ) {
251 $payload .= $sample . "\n";
252 } else {
253 // Send this payload and make a new one
254 $transport->emit( $payload );
255 $payload = '';
256 }
257 }
258 // Send what is left in the payload
259 if ( strlen( $payload ) > 0 ) {
260 $transport->emit( $payload );
261 }
262 }
263
274 private function getFormattedName( string $name, string $extension ): string {
275 return implode(
276 self::NAME_DELIMITER,
277 [ $this->prefix, $extension, self::normalizeString( $name ) ]
278 );
279 }
280
296 private function getValidConfig( array $config = [] ): array {
297 if ( !isset( $config['name'] ) ) {
298 throw new InvalidConfigurationException(
299 '\'name\' configuration option is required and cannot be empty.'
300 );
301 }
302 if ( !isset( $config['extension'] ) ) {
304 '\'extension\' configuration option is required and cannot be empty.'
305 );
306 }
307
308 $config['prefix'] = $this->prefix;
309 $config['format'] = $this->format;
310 $config['name'] = self::normalizeString( $config['name'] );
311 $config['extension'] = self::normalizeString( $config['extension'] );
312 $config['labels'] = self::normalizeArray( $config['labels'] ?? [] );
313
314 return $config + self::DEFAULT_METRIC_CONFIG;
315 }
316
327 public static function normalizeString( string $entity ): string {
328 $entity = preg_replace( '/[^a-z0-9]/i', '_', $entity );
329 $entity = preg_replace( '/_+/', '_', $entity );
330 return trim( $entity, '_' );
331 }
332
339 public static function normalizeArray( array $entities ): array {
340 $normalizedEntities = [];
341 foreach ( $entities as $entity ) {
342 $normalizedEntities[] = self::normalizeString( $entity );
343 }
344 return $normalizedEntities;
345 }
346}
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition WebStart.php:82
A generic class to send a message over UDP.
getCounter(array $config=[])
Makes a new CounterMetric or fetches one from cache.
send(UDPTransport $transport)
Render the buffer of samples, group them into payloads, and send them through the provided UDPTranspo...
getTiming(array $config=[])
Makes a new TimingMetric or fetches one from cache.
static normalizeArray(array $entities)
Normalize an array of strings.
__construct(array $config, LoggerInterface $logger)
MetricsFactory builds, configures, and caches Metrics.
flush()
Send all buffered metrics to the target and destroy the cache.
getGauge(array $config=[])
Makes a new GaugeMetric or fetches one from cache.
static normalizeString(string $entity)
Normalize strings to a metrics-compatible format.
$cache
Definition mcc.php:33