Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.75% covered (success)
93.75%
15 / 16
85.71% covered (warning)
85.71%
6 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
TimingMetric
93.75% covered (success)
93.75%
15 / 16
85.71% covered (warning)
85.71%
6 / 7
10.02
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
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 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
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 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     * @return RunningTimer
50     */
51    public function start() {
52        $this->startTime = ConvertibleTimestamp::hrtime();
53        return new RunningTimer( $this->startTime, $this, $this->baseMetric->getLabels() );
54    }
55
56    /**
57     * Stop the running timer.
58     */
59    public function stop(): void {
60        if ( $this->startTime === null ) {
61            trigger_error( "Stats: ({$this->getName()}) stop() called before start()", E_USER_WARNING );
62            return;
63        }
64        $this->observeNanoseconds( ConvertibleTimestamp::hrtime() - $this->startTime );
65        $this->startTime = null;
66    }
67
68    /**
69     * Record a previously calculated observation in nanoseconds.
70     *
71     * Example:
72     *
73     * ```php
74     * $startTime = hrtime( true )
75     * # work to be measured...
76     * $metric->observeNanoseconds( hrtime( true ) - $startTime )
77     * ```
78     *
79     * @param float $nanoseconds
80     * @return void
81     * @since 1.43
82     */
83    public function observeNanoseconds( float $nanoseconds ): void {
84        $this->addSample( $nanoseconds * 1e-6 );
85    }
86
87    /**
88     * Record a previously calculated observation in seconds.
89     *
90     * This method is provided for tracking externally-generated values, timestamp deltas, and
91     * situations where the expected input value is the expected Prometheus graphed value.
92     *
93     * Performance measurements in process should be done with hrtime() and observeNanoseconds()
94     * to ensure monotonic time is used and not wall-clock time.
95     *
96     * Example:
97     *
98     * ```php
99     * $startTime = microtime( true )
100     * # work to be measured...
101     * $metric->observeSeconds( microtime( true ) - $startTime )
102     * ```
103     *
104     * @param float $seconds
105     * @return void
106     * @since 1.43
107     */
108    public function observeSeconds( float $seconds ): void {
109        $this->addSample( $seconds * 1000 );
110    }
111
112    /**
113     * Record a previously calculated observation in milliseconds.
114     *
115     * NOTE: You MUST pass values converted to milliseconds.
116     *
117     * This method is discouraged in new code, because PHP does not measure
118     * time in milliseconds. It will be less error-prone if you use start()
119     * and stop(), or pass values from hrtime() directly to observeNanoseconds()
120     * without manual multiplication to another unit.
121     *
122     * @param float $milliseconds
123     * @return void
124     */
125    public function observe( float $milliseconds ): void {
126        $this->addSample( $milliseconds );
127    }
128
129    private function addSample( float $milliseconds ): void {
130        foreach ( $this->baseMetric->getStatsdNamespaces() as $namespace ) {
131            $this->baseMetric->getStatsdDataFactory()->timing( $namespace, $milliseconds );
132        }
133
134        try {
135            $this->baseMetric->addSample( new Sample( $this->baseMetric->getLabelValues(), $milliseconds ) );
136        } catch ( IllegalOperationException $ex ) {
137            // Log the condition and give the caller something that will absorb calls.
138            trigger_error( "Stats: ({$this->getName()}): {$ex->getMessage()}", E_USER_WARNING );
139        }
140    }
141
142    /** @inheritDoc */
143    public function getTypeIndicator(): string {
144        return self::TYPE_INDICATOR;
145    }
146}