Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 49
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 / 49
0.00% covered (danger)
0.00%
0 / 18
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 / 5
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 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 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
 addLabelKey
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 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 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 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 IBufferingStatsdDataFactory;
25use InvalidArgumentException;
26use Wikimedia\Stats\Exceptions\IllegalOperationException;
27use Wikimedia\Stats\Sample;
28use Wikimedia\Stats\StatsUtils;
29
30/**
31 * Base Metric Implementation V1
32 *
33 * Implements shared Metric functionality:
34 *   * Label validation and handling
35 *   * Sample rate validation and handling
36 *   * Sample generation
37 *   * Common properties
38 *   * StatsD transition support
39 *
40 * @author Cole White
41 * @since 1.41
42 */
43class BaseMetric implements BaseMetricInterface {
44
45    /** @var float */
46    private float $sampleRate = StatsUtils::DEFAULT_SAMPLE_RATE;
47
48    /** @var string */
49    private string $name;
50
51    /** @var string */
52    private string $component;
53
54    /** @var array key-value pairs of metric-specific labels */
55    private array $workingLabels = [];
56
57    /** @var string[] ordered array of label keys */
58    private array $labelKeys = [];
59
60    /** @var Sample[] */
61    private array $samples = [];
62
63    /** @var IBufferingStatsdDataFactory|null */
64    private ?IBufferingStatsdDataFactory $statsdDataFactory = null;
65
66    /** @var string[] */
67    private array $statsdNamespaces = [];
68
69    /** @inheritDoc */
70    public function __construct( string $component, string $name ) {
71        $this->component = $component;
72        $this->name = StatsUtils::normalizeString( $name );
73    }
74
75    /** @inheritDoc */
76    public function addSample( Sample $sample ): void {
77        $this->samples[] = $sample;
78    }
79
80    /** @inheritDoc */
81    public function setSampleRate( float $sampleRate ): void {
82        if ( $this->hasSamples() ) {
83            throw new IllegalOperationException(
84                "Stats: Cannot change sample rate on metric with recorded samples." );
85        }
86        StatsUtils::validateNewSampleRate( $sampleRate );
87        $this->sampleRate = $sampleRate;
88    }
89
90    /** @inheritDoc */
91    public function getName(): string {
92        return $this->name;
93    }
94
95    /** @inheritDoc */
96    public function getSampleRate(): float {
97        return $this->sampleRate;
98    }
99
100    /** @inheritDoc */
101    public function getSamples(): array {
102        return StatsUtils::getFilteredSamples( $this->sampleRate, $this->samples );
103    }
104
105    /** @inheritDoc */
106    public function getSampleCount(): int {
107        return count( $this->samples );
108    }
109
110    /** @inheritDoc */
111    public function addLabel( string $key, string $value ): void {
112        StatsUtils::validateLabelValue( $value );
113        $key = StatsUtils::normalizeString( $key );
114        StatsUtils::validateLabelKey( $key );
115        $this->addLabelKey( $key );
116        $this->workingLabels[$key] = StatsUtils::normalizeString( $value );
117    }
118
119    /** @inheritDoc */
120    public function getStatsdDataFactory() {
121        return $this->statsdDataFactory;
122    }
123
124    /** @inheritDoc */
125    public function withStatsdDataFactory( $statsdDataFactory ): BaseMetric {
126        $this->statsdDataFactory = $statsdDataFactory;
127        return $this;
128    }
129
130    /** @inheritDoc */
131    public function setStatsdNamespaces( $statsdNamespaces ): void {
132        if ( $this->statsdDataFactory === null ) {
133            return;
134        }
135        $statsdNamespaces = is_array( $statsdNamespaces ) ? $statsdNamespaces : [ $statsdNamespaces ];
136
137        foreach ( $statsdNamespaces as $namespace ) {
138            if ( $namespace === '' ) {
139                throw new InvalidArgumentException( "Stats: StatsD namespace cannot be empty." );
140            }
141            if ( !is_string( $namespace ) ) {
142                throw new InvalidArgumentException( "Stats: StatsD namespace must be a string." );
143            }
144        }
145        $this->statsdNamespaces = $statsdNamespaces;
146    }
147
148    /** @inheritDoc */
149    public function getStatsdNamespaces(): array {
150        return $this->statsdNamespaces;
151    }
152
153    /**
154     * Registers a label key
155     *
156     * @param string $key
157     * @return void
158     */
159    private function addLabelKey( string $key ): void {
160        if ( in_array( $key, $this->labelKeys, true ) ) {
161            return;  // key already exists
162        }
163        if ( $this->hasSamples() ) {
164            throw new IllegalOperationException(
165                "Stats: Cannot add labels to a metric containing samples for '" . $this->name . "'"
166            );
167        }
168        $this->labelKeys[] = $key;
169    }
170
171    /** @return string[] */
172    public function getLabelKeys(): array {
173        return $this->labelKeys;
174    }
175
176    /**
177     * Get label values in the order of labelKeys.
178     *
179     * @return string[]
180     */
181    public function getLabelValues(): array {
182        $output = [];
183        # make sure all labels are accounted for
184        if ( array_diff( $this->labelKeys, array_keys( $this->workingLabels ) ) ) {
185            throw new IllegalOperationException(
186                "Stats: Cannot associate label keys with label values: "
187                . "Not all initialized labels have an assigned value." );
188        }
189
190        foreach ( $this->labelKeys as $labelKey ) {
191            $output[] = $this->workingLabels[$labelKey];
192        }
193        return $output;
194    }
195
196    /** @inheritDoc */
197    public function clearLabels(): void {
198        $this->workingLabels = [];
199    }
200
201    /** @inheritDoc */
202    public function getComponent(): string {
203        return $this->component;
204    }
205
206    private function hasSamples(): bool {
207        return $this->samples !== [];
208    }
209
210}