Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.84% covered (warning)
86.84%
33 / 38
80.00% covered (warning)
80.00%
12 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
TimingMetric
86.84% covered (warning)
86.84%
33 / 38
80.00% covered (warning)
80.00%
12 / 15
22.00
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 start
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 stop
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 observe
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getComponent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTypeIndicator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSamples
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSampleCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSampleRate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSampleRate
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 getLabelKeys
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setLabel
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 copyToStatsdAt
40.00% covered (danger)
40.00%
2 / 5
0.00% covered (danger)
0.00%
0 / 1
2.86
 fresh
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
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 Psr\Log\LoggerInterface;
26use Wikimedia\Stats\Exceptions\IllegalOperationException;
27use Wikimedia\Stats\Sample;
28
29/**
30 * Timing Metric Implementation
31 *
32 * Timing metrics track duration data which can be broken into histograms.
33 * They are identified by type "ms".
34 *
35 * @author Cole White
36 * @since 1.38
37 */
38class TimingMetric implements MetricInterface {
39
40    /**
41     * The StatsD protocol type indicator:
42     * https://github.com/statsd/statsd/blob/v0.9.0/docs/metric_types.md
43     * https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/?tab=metrics
44     *
45     * @var string
46     */
47    private const TYPE_INDICATOR = "ms";
48
49    /** @var BaseMetricInterface */
50    private BaseMetricInterface $baseMetric;
51
52    /** @var LoggerInterface */
53    private LoggerInterface $logger;
54
55    /** @var float|null */
56    private ?float $startTime = null;
57
58    /** @inheritDoc */
59    public function __construct( $baseMetric, $logger ) {
60        $this->baseMetric = $baseMetric;
61        $this->logger = $logger;
62    }
63
64    /**
65     * Starts a timer.
66     *
67     * @return void
68     */
69    public function start(): void {
70        $this->startTime = hrtime( true );
71    }
72
73    /**
74     * Stops a running timer.
75     *
76     * @return void
77     */
78    public function stop(): void {
79        if ( $this->startTime === null ) {
80            trigger_error( "Stats: stop() called before start() for metric '{$this->getName()}'", E_USER_WARNING );
81            return;
82        }
83        $value = ( hrtime( true ) - $this->startTime ) * 1e-6; // convert nanoseconds to milliseconds
84        $this->observe( $value );
85        $this->startTime = null;
86    }
87
88    /**
89     * Records a previously calculated observation.
90     *
91     * Expects values in milliseconds.
92     *
93     * @param float $value milliseconds
94     * @return void
95     */
96    public function observe( float $value ): void {
97        foreach ( $this->baseMetric->getStatsdNamespaces() as $namespace ) {
98            $this->baseMetric->getStatsdDataFactory()->timing( $namespace, $value );
99        }
100
101        try {
102            $this->baseMetric->addSample( new Sample( $this->baseMetric->getLabelValues(), $value ) );
103        } catch ( IllegalOperationException $ex ) {
104            // Log the condition and give the caller something that will absorb calls.
105            trigger_error( $ex->getMessage(), E_USER_WARNING );
106        }
107    }
108
109    /** @inheritDoc */
110    public function getName(): string {
111        return $this->baseMetric->getName();
112    }
113
114    /** @inheritDoc */
115    public function getComponent(): string {
116        return $this->baseMetric->getComponent();
117    }
118
119    /** @inheritDoc */
120    public function getTypeIndicator(): string {
121        return self::TYPE_INDICATOR;
122    }
123
124    /** @inheritDoc */
125    public function getSamples(): array {
126        return $this->baseMetric->getSamples();
127    }
128
129    /** @inheritDoc */
130    public function getSampleCount(): int {
131        return $this->baseMetric->getSampleCount();
132    }
133
134    /** @inheritDoc */
135    public function getSampleRate(): float {
136        return $this->baseMetric->getSampleRate();
137    }
138
139    /** @inheritDoc */
140    public function setSampleRate( float $sampleRate ) {
141        try {
142            $this->baseMetric->setSampleRate( $sampleRate );
143        } catch ( IllegalOperationException | InvalidArgumentException $ex ) {
144            // Log the condition and give the caller something that will absorb calls.
145            trigger_error( $ex->getMessage(), E_USER_WARNING );
146            return new NullMetric;
147        }
148        return $this;
149    }
150
151    /** @inheritDoc */
152    public function getLabelKeys(): array {
153        return $this->baseMetric->getLabelKeys();
154    }
155
156    /** @inheritDoc */
157    public function setLabel( string $key, string $value ) {
158        try {
159            $this->baseMetric->addLabel( $key, $value );
160        } catch ( IllegalOperationException | InvalidArgumentException $ex ) {
161            // Log the condition and give the caller something that will absorb calls.
162            trigger_error( $ex->getMessage(), E_USER_WARNING );
163            return new NullMetric;
164        }
165        return $this;
166    }
167
168    /** @inheritDoc */
169    public function copyToStatsdAt( $statsdNamespaces ) {
170        try {
171            $this->baseMetric->setStatsdNamespaces( $statsdNamespaces );
172        } catch ( InvalidArgumentException $ex ) {
173            // Log the condition and give the caller something that will absorb calls.
174            trigger_error( $ex->getMessage(), E_USER_WARNING );
175            return new NullMetric;
176        }
177        return $this;
178    }
179
180    /** @inheritDoc */
181    public function fresh(): TimingMetric {
182        $this->baseMetric->clearLabels();
183        return $this;
184    }
185}