Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
5.00% covered (danger)
5.00%
1 / 20
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Timing
5.00% covered (danger)
5.00%
1 / 20
20.00% covered (danger)
20.00%
1 / 5
95.74
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 millis
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 end
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 fakeTiming
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 start
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Utils;
5
6use Wikimedia\Assert\Assert;
7use Wikimedia\Parsoid\Config\SiteConfig;
8
9/**
10 * A helper class to make it easier to compute timing metrics.
11 */
12class Timing {
13    /**
14     * This is typically a StatsdDataFactoryInterface, but really could be
15     * anything which has a `timing()` method.  Set it to `null` to disable
16     * metrics.
17     *
18     * @var ?object
19     */
20    private ?object $metrics;
21
22    /**
23     * @var float
24     */
25    private float $startTime;
26
27    /**
28     * @var ?SiteConfig
29     */
30    private ?SiteConfig $siteConfig;
31
32    private ?float $elapsed;
33    private bool $isTime;
34
35    private function __construct( ?object $configOrMetrics, ?float $elapsed = null, bool $isTime = true ) {
36        if ( $configOrMetrics instanceof SiteConfig ) {
37            $this->siteConfig = $configOrMetrics;
38            $this->metrics = $configOrMetrics->metrics();
39        } else {
40            $this->siteConfig = null;
41            $this->metrics = $configOrMetrics;
42        }
43        $this->startTime = self::millis();
44        $this->elapsed = $elapsed;
45        $this->isTime = $isTime;
46    }
47
48    /**
49     * Return the current number of milliseconds since the epoch, as a float.
50     */
51    public static function millis(): float {
52        return 1000 * microtime( true );
53    }
54
55    /**
56     * End this timing measurement, reporting it under the given `name`.
57     * @param ?string $statsdCompat
58     * @param ?string $name
59     * @param ?array $labels
60     * @return float Number of milliseconds reported
61     */
62    public function end(
63        ?string $statsdCompat = null,
64        ?string $name = null,
65        ?array $labels = []
66    ): float {
67        if ( $this->elapsed === null ) {
68            $this->elapsed = self::millis() - $this->startTime;
69        }
70        if ( $this->metrics ) {
71            Assert::invariant( $statsdCompat !== null, 'Recording metric without a key.' );
72            $this->metrics->timing( $statsdCompat, $this->elapsed );
73        }
74        if ( $this->siteConfig ) {
75            // Note that observeTiming takes a value in *milliseconds*
76            // despite the name of the metric ending in `_seconds`.
77
78            // TODO Replace this with histogram when metric type is available
79            // If metric doesn't observe time, scale by 1000 to work around the internal
80            // scaling of statslib to convert ms to s.
81
82            $observedValue = $this->isTime ? $this->elapsed : 1000 * $this->elapsed;
83            $this->siteConfig->observeTiming( $name, $observedValue, $labels );
84        }
85        return $this->elapsed;
86    }
87
88    /**
89     * Override elapsed time of a timing instance
90     * @param SiteConfig $siteConfig
91     * @param float $value Value to measure in the metrics (milliseconds if timing)
92     * @param bool $isTime Flag if fake timing measures time (workaround for histogram)
93     * @return Timing
94     */
95    public static function fakeTiming( SiteConfig $siteConfig, float $value, bool $isTime = true ): Timing {
96        return new Timing( $siteConfig, $value, $isTime );
97    }
98
99    /**
100     * Start a timing measurement, logging it to the given `$metrics` object
101     * (which just needs to have a `timing()` method).
102     * @param ?object $configOrMetrics
103     * @return Timing
104     */
105    public static function start( ?object $configOrMetrics = null ): Timing {
106        return new Timing( $configOrMetrics );
107    }
108}