29declare( strict_types=1 );
33use Psr\Log\LoggerInterface;
43 private const SUPPORTED_OUTPUT_FORMATS = [
'statsd',
'dogstatsd',
'null' ];
46 private const NAME_DELIMITER =
'.';
49 private const DEFAULT_METRIC_CONFIG = [
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 ===
'' ) {
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 )
114 $config = $this->getValidConfig( $config );
115 $name = self::getFormattedName( $config[
'name'], $config[
'extension'] );
117 $metric = $this->getCachedMetric( $name, CounterMetric::class );
118 }
catch ( TypeError $ex ) {
123 $metric->validateLabels( $config[
'labels'] );
127 return $this->cache[$name];
142 $config = $this->getValidConfig( $config );
143 $name = self::getFormattedName( $config[
'name'], $config[
'extension'] );
145 $metric = $this->getCachedMetric( $name, GaugeMetric::class );
146 }
catch ( TypeError $ex ) {
151 $metric->validateLabels( $config[
'labels'] );
155 return $this->cache[$name];
171 $config = $this->getValidConfig( $config );
172 $name = self::getFormattedName( $config[
'name'], $config[
'extension'] );
174 $metric = $this->getCachedMetric( $name, TimingMetric::class );
175 }
catch ( TypeError $ex ) {
179 $metric->validateLabels( $config[
'labels'] );
183 return $this->cache[$name];
190 if ( $this->format !==
'null' && $this->target ) {
191 $this->
send( UDPTransport::newFromString( $this->target ) );
202 private function getRenderedSamples( array
$cache ): array {
203 $renderedSamples = [];
204 foreach (
$cache as $metric ) {
205 foreach ( $metric->render() as $rendered ) {
206 $renderedSamples[] = $rendered;
209 return $renderedSamples;
224 private function getCachedMetric(
string $name,
string $requested_type ) {
225 if ( !array_key_exists( $name, $this->cache ) ) {
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 );
246 protected function send( UDPTransport $transport ): void {
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";
253 // Send this payload and make a new one
254 $transport->emit( $payload );
258 // Send what is left in the payload
259 if ( strlen( $payload ) > 0 ) {
260 $transport->emit( $payload );
274 private function getFormattedName( string $name, string $extension ): string {
276 self::NAME_DELIMITER,
277 [ $this->prefix, $extension, self::normalizeString( $name ) ]
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.'
302 if ( !isset( $config[
'extension'] ) ) {
304 '\'extension\
' configuration option is required and cannot be empty.'
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'] ?? [] );
314 return $config + self::DEFAULT_METRIC_CONFIG;
328 $entity = preg_replace(
'/[^a-z0-9]/i',
'_', $entity );
329 $entity = preg_replace(
'/_+/',
'_', $entity );
330 return trim( $entity,
'_' );
340 $normalizedEntities = [];
341 foreach ( $entities as $entity ) {
342 $normalizedEntities[] = self::normalizeString( $entity );
344 return $normalizedEntities;
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
A generic class to send a message over UDP.