Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
BaseMetric
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 17
812
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
 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 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 * @file
18 */
19
20declare( strict_types=1 );
21
22namespace Wikimedia\Stats\Metrics;
23
24use InvalidArgumentException;
25use Wikimedia\Stats\Exceptions\IllegalOperationException;
26use Wikimedia\Stats\Exceptions\InvalidConfigurationException;
27use Wikimedia\Stats\IBufferingStatsdDataFactory;
28use Wikimedia\Stats\Sample;
29use Wikimedia\Stats\StatsUtils;
30
31/**
32 * Base Metric Implementation V1
33 *
34 * Implements shared Metric functionality:
35 *   * Label validation and handling
36 *   * Sample rate validation and handling
37 *   * Sample generation
38 *   * Common properties
39 *   * StatsD transition support
40 *
41 * @author Cole White
42 * @since 1.41
43 */
44class BaseMetric implements BaseMetricInterface {
45
46    private float $sampleRate = StatsUtils::DEFAULT_SAMPLE_RATE;
47    private string $name;
48    private string $component;
49
50    /** @var array<string,string|null> key-value pairs of metric-specific labels */
51    private array $workingLabels = [];
52
53    /** @var Sample[] */
54    private array $samples = [];
55
56    private ?IBufferingStatsdDataFactory $statsdDataFactory = null;
57
58    /** @var string[] */
59    private array $statsdNamespaces = [];
60
61    /** @inheritDoc */
62    public function __construct( string $component, string $name ) {
63        $this->component = $component;
64        $this->name = StatsUtils::normalizeString( $name );
65    }
66
67    /** @inheritDoc */
68    public function addSample( Sample $sample ): void {
69        $this->samples[] = $sample;
70    }
71
72    /** @inheritDoc */
73    public function setSampleRate( float $sampleRate ): void {
74        if ( $this->hasSamples() ) {
75            throw new IllegalOperationException( 'Cannot change sample rate on metric with recorded samples.' );
76        }
77        StatsUtils::validateNewSampleRate( $sampleRate );
78        $this->sampleRate = $sampleRate;
79    }
80
81    /** @inheritDoc */
82    public function getName(): string {
83        return $this->name;
84    }
85
86    /** @inheritDoc */
87    public function getSampleRate(): float {
88        return $this->sampleRate;
89    }
90
91    /** @inheritDoc */
92    public function getSamples(): array {
93        return StatsUtils::getFilteredSamples( $this->sampleRate, $this->samples );
94    }
95
96    /** @inheritDoc */
97    public function getSampleCount(): int {
98        return count( $this->samples );
99    }
100
101    /** @inheritDoc */
102    public function addLabel( string $key, string $value ): void {
103        // Performance optimization: Assume the key is valid and already registered
104        if ( !array_key_exists( $key, $this->workingLabels ) ) {
105            try {
106                StatsUtils::validateLabelKey( $key );
107            } catch ( InvalidConfigurationException $e ) {
108                trigger_error(
109                    "Stats: ($this->name) Non-normalized label keys are deprecated, found '$key'",
110                    E_USER_WARNING );
111                $key = StatsUtils::normalizeString( $key );
112            }
113            if ( $this->hasSamples() && !array_key_exists( $key, $this->workingLabels ) ) {
114                throw new IllegalOperationException( 'Cannot add labels to a metric containing samples' );
115            }
116        }
117
118        StatsUtils::validateLabelValue( $value );
119        $this->workingLabels[$key] = StatsUtils::normalizeString( $value );
120    }
121
122    /** @inheritDoc */
123    public function getStatsdDataFactory() {
124        return $this->statsdDataFactory;
125    }
126
127    /** @inheritDoc */
128    public function withStatsdDataFactory( $statsdDataFactory ): BaseMetricInterface {
129        $this->statsdDataFactory = $statsdDataFactory;
130        return $this;
131    }
132
133    /** @inheritDoc */
134    public function setStatsdNamespaces( $statsdNamespaces ): void {
135        if ( $this->statsdDataFactory === null ) {
136            return;
137        }
138        $statsdNamespaces = is_array( $statsdNamespaces ) ? $statsdNamespaces : [ $statsdNamespaces ];
139
140        foreach ( $statsdNamespaces as $namespace ) {
141            if ( $namespace === '' ) {
142                throw new InvalidArgumentException( 'StatsD namespace cannot be empty.' );
143            }
144            if ( !is_string( $namespace ) ) {
145                throw new InvalidArgumentException( 'StatsD namespace must be a string.' );
146            }
147        }
148        $this->statsdNamespaces = $statsdNamespaces;
149    }
150
151    /** @inheritDoc */
152    public function getStatsdNamespaces(): array {
153        return $this->statsdNamespaces;
154    }
155
156    /** @return string[] */
157    public function getLabelKeys(): array {
158        return array_keys( $this->workingLabels );
159    }
160
161    /**
162     * @return string[]
163     */
164    public function getLabelValues(): array {
165        # make sure all labels are accounted for
166        if ( in_array( null, $this->workingLabels, true ) ) {
167            throw new IllegalOperationException(
168                "Stats: ({$this->getName()}) Cannot associate label keys with label values - "
169                . "Not all initialized labels have an assigned value." );
170        }
171
172        return array_values( $this->workingLabels );
173    }
174
175    /** @inheritDoc */
176    public function clearLabels(): void {
177        $this->workingLabels = array_fill_keys( array_keys( $this->workingLabels ), null );
178    }
179
180    /** @inheritDoc */
181    public function getComponent(): string {
182        return $this->component;
183    }
184
185    private function hasSamples(): bool {
186        return (bool)$this->samples;
187    }
188
189}