Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
70.59% covered (warning)
70.59%
12 / 17
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
TimingMetric
70.59% covered (warning)
70.59%
12 / 17
57.14% covered (warning)
57.14%
4 / 7
12.54
0.00% covered (danger)
0.00%
0 / 1
 start
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 stop
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
2.50
 observeNanoseconds
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 observeSeconds
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 observe
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addSample
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 getTypeIndicator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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 Wikimedia\Stats\Exceptions\IllegalOperationException;
12use Wikimedia\Stats\Sample;
13use Wikimedia\Timestamp\ConvertibleTimestamp;
14
15/**
16 * Timing Metric Implementation
17 *
18 * Timing metrics track duration data which can be broken into histograms.
19 * They are identified by type "ms".
20 *
21 * @author Cole White
22 * @since 1.38
23 */
24class TimingMetric implements MetricInterface {
25    use MetricTrait;
26
27    /**
28     * The StatsD protocol type indicator:
29     * https://github.com/statsd/statsd/blob/v0.9.0/docs/metric_types.md
30     * https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/?tab=metrics
31     */
32    private const TYPE_INDICATOR = "ms";
33
34    private ?float $startTime = null;
35
36    /**
37     * Start the timer.
38     *
39     * Example:
40     *
41     * ```php
42     * $timer = StatsFactory->getTiming( 'example_seconds' )
43     *     ->setLabel( 'foo', 'bar' )
44     *     ->start();
45     * # work to be measured...
46     * $timer->stop();
47     * ```
48     *
49     * Example with an extra label informed by the work:
50     *
51     * ```php
52     * $timer = StatsFactory->getTiming( 'example_seconds' )
53     *     ->start();
54     * # work to be measured...
55     * $timer
56     *     ->setLabel( 'status', $status->isOK() ? 'ok' : 'error' )
57     *     ->stop();
58     * ```
59     *
60     * @return RunningTimer
61     */
62    public function start() {
63        $this->startTime = ConvertibleTimestamp::hrtime();
64        return new RunningTimer( $this->startTime, $this, $this->baseMetric->getLabels() );
65    }
66
67    /**
68     * Stop the running timer.
69     *
70     * @deprecated since 1.45 Call RunningTimer::stop on the object returned by start() instead.
71     */
72    public function stop(): void {
73        if ( $this->startTime === null ) {
74            trigger_error( "Stats: ({$this->getName()}) stop() called before start()", E_USER_WARNING );
75            return;
76        }
77
78        trigger_error( 'Use of shared timer is deprecated. Use the returned start object instead.', E_USER_DEPRECATED );
79        $this->observeNanoseconds( ConvertibleTimestamp::hrtime() - $this->startTime );
80        $this->startTime = null;
81    }
82
83    /**
84     * Record a previously calculated observation in nanoseconds.
85     *
86     * It is recommended to use TimingMetric::start() and RunningTimer::stop() instead.
87     *
88     * Only measure latency yourself if you also need the duration value elsewhere.
89     *
90     * Example:
91     *
92     * ```php
93     * $startTime = ConvertibleTimestamp::hrtime( true );
94     * # work to be measured...
95     * $durationNano = ConvertibleTimestamp::hrtime( true ) - $startTime;
96     * $metric->observeNanoseconds( $durationNano );
97     *
98     * $durationMs = $durationNano / 1e6;
99     * $durationSec = $durationNano / 1e9;
100     * ```
101     *
102     * @param float $nanoseconds
103     * @return void
104     * @since 1.43
105     */
106    public function observeNanoseconds( float $nanoseconds ): void {
107        $this->addSample( $nanoseconds * 1e-6 );
108    }
109
110    /**
111     * Record a previously calculated observation in seconds.
112     *
113     * This method is provided to ease recording of externally-generated time values.
114     * For example, when a service returns a delta in seconds to you, and you are not
115     * measuring or multiplying this value yourself.
116     *
117     * To instrument your own code, it is recommended to use TimingMetric::start()
118     * and RunningTimer::stop() instead. Or, if measuring by hand, use hrtime()
119     * with observeNanoseconds() to guarantee a monotonic clock and not a wall-clock.
120     *
121     * Do not measure latency with time() or microtime(), per T245464.
122     *
123     * NOTE: If you previously used observeSeconds to store non-time values in a histogram,
124     * such as kilobytes or other unrelated quantities, use StatsFactory::getHistogram
125     * instead (T348796, T364240, T383208).
126     *
127     * @param float $seconds
128     * @return void
129     * @since 1.43
130     */
131    public function observeSeconds( float $seconds ): void {
132        $this->addSample( $seconds * 1000 );
133    }
134
135    /**
136     * Record a previously calculated observation in milliseconds.
137     *
138     * NOTE: You MUST pass values converted to milliseconds.
139     *
140     * This method is discouraged in new code, because PHP does not measure
141     * time in milliseconds. It will be less error-prone if you use start()
142     * and stop(), or pass values from hrtime() directly to observeNanoseconds()
143     * without manual multiplication to another unit.
144     *
145     * @deprecated since 1.45 Use TimingMetric::start instead, or switch to hrtime() and
146     * use TimingMetric::observeNanoseconds.
147     * @param float $milliseconds
148     * @return void
149     */
150    public function observe( float $milliseconds ): void {
151        $this->addSample( $milliseconds );
152    }
153
154    private function addSample( float $milliseconds ): void {
155        foreach ( $this->baseMetric->getStatsdNamespaces() as $namespace ) {
156            $this->baseMetric->getStatsdDataFactory()->timing( $namespace, $milliseconds );
157        }
158
159        try {
160            $this->baseMetric->addSample( new Sample( $this->baseMetric->getLabelValues(), $milliseconds ) );
161        } catch ( IllegalOperationException $ex ) {
162            // Log the condition and give the caller something that will absorb calls.
163            trigger_error( "Stats: ({$this->getName()}): {$ex->getMessage()}", E_USER_WARNING );
164        }
165    }
166
167    /** @inheritDoc */
168    public function getTypeIndicator(): string {
169        return self::TYPE_INDICATOR;
170    }
171}