Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
BaseMetric
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 18
870
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addSample
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setSampleRate
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSampleRate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSamples
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSampleCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addLabel
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 getStatsdDataFactory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 withStatsdDataFactory
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setStatsdNamespaces
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
 getStatsdNamespaces
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLabels
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLabelKeys
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLabelValues
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 clearLabels
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getComponent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasSamples
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7declare( strict_types=1 );
8
9namespace Wikimedia\Stats\Metrics;
10
11use InvalidArgumentException;
12use Wikimedia\Stats\Exceptions\IllegalOperationException;
13use Wikimedia\Stats\Exceptions\InvalidConfigurationException;
14use Wikimedia\Stats\IBufferingStatsdDataFactory;
15use Wikimedia\Stats\Sample;
16use Wikimedia\Stats\StatsUtils;
17
18/**
19 * Base Metric Implementation V1
20 *
21 * Implements shared Metric functionality:
22 *   * Label validation and handling
23 *   * Sample rate validation and handling
24 *   * Sample generation
25 *   * Common properties
26 *   * StatsD transition support
27 *
28 * @author Cole White
29 * @since 1.41
30 */
31class BaseMetric implements BaseMetricInterface {
32
33    private float $sampleRate = StatsUtils::DEFAULT_SAMPLE_RATE;
34    private string $name;
35    private string $component;
36
37    /** @var array<string,string|null> key-value pairs of metric-specific labels */
38    private array $workingLabels = [];
39
40    /** @var Sample[] */
41    private array $samples = [];
42
43    private ?IBufferingStatsdDataFactory $statsdDataFactory = null;
44
45    /** @var string[] */
46    private array $statsdNamespaces = [];
47
48    /** @inheritDoc */
49    public function __construct( string $component, string $name ) {
50        $this->component = $component;
51        $this->name = StatsUtils::normalizeString( $name );
52    }
53
54    /** @inheritDoc */
55    public function addSample( Sample $sample ): void {
56        $this->samples[] = $sample;
57    }
58
59    /** @inheritDoc */
60    public function setSampleRate( float $sampleRate ): void {
61        if ( $this->hasSamples() ) {
62            throw new IllegalOperationException( 'Cannot change sample rate on metric with recorded samples.' );
63        }
64        StatsUtils::validateNewSampleRate( $sampleRate );
65        $this->sampleRate = $sampleRate;
66    }
67
68    /** @inheritDoc */
69    public function getName(): string {
70        return $this->name;
71    }
72
73    /** @inheritDoc */
74    public function getSampleRate(): float {
75        return $this->sampleRate;
76    }
77
78    /** @inheritDoc */
79    public function getSamples(): array {
80        return StatsUtils::getFilteredSamples( $this->sampleRate, $this->samples );
81    }
82
83    /** @inheritDoc */
84    public function getSampleCount(): int {
85        return count( $this->samples );
86    }
87
88    /** @inheritDoc */
89    public function addLabel( string $key, string $value ): void {
90        // Performance optimization: Assume the key is valid and already registered
91        if ( !array_key_exists( $key, $this->workingLabels ) ) {
92            try {
93                StatsUtils::validateLabelKey( $key );
94            } catch ( InvalidConfigurationException ) {
95                trigger_error(
96                    "Stats: ($this->name) Non-normalized label keys are deprecated, found '$key'",
97                    E_USER_WARNING );
98                $key = StatsUtils::normalizeString( $key );
99            }
100            if ( $this->hasSamples() && !array_key_exists( $key, $this->workingLabels ) ) {
101                throw new IllegalOperationException( 'Cannot add labels to a metric containing samples' );
102            }
103        }
104
105        StatsUtils::validateLabelValue( $value );
106        $this->workingLabels[$key] = StatsUtils::normalizeString( $value );
107    }
108
109    /** @inheritDoc */
110    public function getStatsdDataFactory() {
111        return $this->statsdDataFactory;
112    }
113
114    /** @inheritDoc */
115    public function withStatsdDataFactory( $statsdDataFactory ): BaseMetricInterface {
116        $this->statsdDataFactory = $statsdDataFactory;
117        return $this;
118    }
119
120    /** @inheritDoc */
121    public function setStatsdNamespaces( $statsdNamespaces ): void {
122        if ( $this->statsdDataFactory === null ) {
123            return;
124        }
125        $statsdNamespaces = is_array( $statsdNamespaces ) ? $statsdNamespaces : [ $statsdNamespaces ];
126
127        foreach ( $statsdNamespaces as $namespace ) {
128            if ( $namespace === '' ) {
129                throw new InvalidArgumentException( 'StatsD namespace cannot be empty.' );
130            }
131            if ( !is_string( $namespace ) ) {
132                throw new InvalidArgumentException( 'StatsD namespace must be a string.' );
133            }
134        }
135        $this->statsdNamespaces = $statsdNamespaces;
136    }
137
138    /** @inheritDoc */
139    public function getStatsdNamespaces(): array {
140        return $this->statsdNamespaces;
141    }
142
143    /** @inheritDoc */
144    public function getLabels(): array {
145        return $this->workingLabels;
146    }
147
148    /** @inheritDoc */
149    public function getLabelKeys(): array {
150        return array_keys( $this->workingLabels );
151    }
152
153    /** @inheritDoc */
154    public function getLabelValues(): array {
155        # make sure all labels are accounted for
156        if ( in_array( null, $this->workingLabels, true ) ) {
157            throw new IllegalOperationException(
158                "Stats: ({$this->getName()}) Cannot associate label keys with label values - "
159                . "Not all initialized labels have an assigned value." );
160        }
161
162        return array_values( $this->workingLabels );
163    }
164
165    /** @inheritDoc */
166    public function clearLabels(): void {
167        $this->workingLabels = array_fill_keys( array_keys( $this->workingLabels ), null );
168    }
169
170    /** @inheritDoc */
171    public function getComponent(): string {
172        return $this->component;
173    }
174
175    private function hasSamples(): bool {
176        return (bool)$this->samples;
177    }
178
179}