Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
74.07% covered (warning)
74.07%
20 / 27
80.00% covered (warning)
80.00%
8 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
TracerState
74.07% covered (warning)
74.07%
20 / 27
80.00% covered (warning)
80.00%
8 / 10
15.95
0.00% covered (danger)
0.00%
0 / 1
 getInstance
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 destroyInstance
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 addSpanContext
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSpanContexts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 clearSpanContexts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 activateSpan
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 deactivateSpan
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getActiveSpanContext
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRootSpan
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 endRootSpan
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2namespace Wikimedia\Telemetry;
3
4use Wikimedia\Assert\Assert;
5use Wikimedia\Assert\PreconditionException;
6
7/**
8 * Holds shared telemetry state, such as finished span data buffered for export.
9 *
10 * Since this data is tied to the lifetime of a given web request or process, this class is a singleton to
11 * avoid discarding data in the case of MediaWiki service container resets.
12 *
13 * @since 1.43
14 * @internal
15 */
16class TracerState {
17    /**
18     * Shared tracer state for the current process or web request, `null` if uninitialized.
19     * @var TracerState|null
20     */
21    private static ?TracerState $instance = null;
22
23    /**
24     * List of already finished spans to be exported.
25     * @var SpanContext[]
26     */
27    private array $finishedSpanContexts = [];
28
29    /**
30     * Stack holding contexts for activated spans.
31     * @var SpanContext[]
32     */
33    private array $activeSpanContextStack = [];
34
35    /**
36     * The root span of this process, or `null` if not initialized yet.
37     * @var SpanInterface|null
38     */
39    private ?SpanInterface $rootSpan = null;
40
41    /**
42     * Get or initialize the shared tracer state for the current process or web request.
43     */
44    public static function getInstance(): TracerState {
45        self::$instance ??= new self();
46        return self::$instance;
47    }
48
49    /**
50     * Reset shared tracer state. Useful for testing.
51     */
52    public static function destroyInstance(): void {
53        Assert::precondition(
54            defined( 'MW_PHPUNIT_TEST' ),
55            'This function can only be called in tests'
56        );
57
58        self::$instance = null;
59    }
60
61    /**
62     * Add the given span to the list of finished spans.
63     * @param SpanContext $context
64     * @return void
65     */
66    public function addSpanContext( SpanContext $context ): void {
67        $this->finishedSpanContexts[] = $context;
68    }
69
70    /**
71     * Get the list of finished spans.
72     * @return SpanContext[]
73     */
74    public function getSpanContexts(): array {
75        return $this->finishedSpanContexts;
76    }
77
78    /**
79     * Clear the list of finished spans.
80     */
81    public function clearSpanContexts(): void {
82        $this->finishedSpanContexts = [];
83    }
84
85    /**
86     * Make the given span the active span.
87     * @param SpanContext $spanContext Context of the span to activate
88     * @return void
89     */
90    public function activateSpan( SpanContext $spanContext ): void {
91        $this->activeSpanContextStack[] = $spanContext;
92    }
93
94    /**
95     * Deactivate the given span, if it was the active span.
96     * @param SpanContext $spanContext Context of the span to deactivate
97     * @return void
98     */
99    public function deactivateSpan( SpanContext $spanContext ): void {
100        $activeSpanContext = $this->getActiveSpanContext();
101
102        Assert::invariant(
103            $activeSpanContext !== null && $activeSpanContext->getSpanId() === $spanContext->getSpanId(),
104            'Attempted to deactivate a span which is not the active span.'
105        );
106
107        array_pop( $this->activeSpanContextStack );
108    }
109
110    /**
111     * Get the context of the currently active span, or `null` if no span is active.
112     * @return SpanContext|null
113     */
114    public function getActiveSpanContext(): ?SpanContext {
115        return $this->activeSpanContextStack[count( $this->activeSpanContextStack ) - 1] ?? null;
116    }
117
118    /**
119     * Set the root span associated with the current request or process.
120     *
121     * @since 1.44
122     * @param SpanInterface $rootSpan The root span to set.
123     * @throws PreconditionException If a root span was already initialized for this request or process.
124     */
125    public function setRootSpan( SpanInterface $rootSpan ): void {
126        Assert::precondition(
127            $this->rootSpan === null,
128            'Attempted to set a new root span while one was already initialized.'
129        );
130        $this->rootSpan = $rootSpan;
131    }
132
133    /**
134     * End the root span associated with the current request or process.
135     *
136     * @since 1.44
137     * @param int $spanStatus The status of the root span. One of the SpanInterface::SPAN_STATUS_** constants.
138     */
139    public function endRootSpan( int $spanStatus = SpanInterface::SPAN_STATUS_UNSET ): void {
140        if ( $this->rootSpan !== null ) {
141            if ( $spanStatus !== SpanInterface::SPAN_STATUS_UNSET ) {
142                $this->rootSpan->setSpanStatus( $spanStatus );
143            }
144            $this->rootSpan->end();
145        }
146    }
147}