1010use Nyholm \Psr7 \ServerRequest ;
1111use OpenTelemetry \API \Instrumentation \Configurator ;
1212use OpenTelemetry \Context \ScopeInterface ;
13+ use OpenTelemetry \Contrib \Instrumentation \Slim \PsrServerRequestMetrics ;
14+ use OpenTelemetry \SDK \Metrics \Data \HistogramDataPoint ;
15+ use OpenTelemetry \SDK \Metrics \MeterProvider ;
16+ use OpenTelemetry \SDK \Metrics \MetricExporter \InMemoryExporter as InMemoryMetricsExporter ;
17+ use OpenTelemetry \SDK \Metrics \MetricReader \ExportingReader ;
1318use OpenTelemetry \SDK \Trace \SpanExporter \InMemoryExporter ;
1419use OpenTelemetry \SDK \Trace \SpanProcessor \SimpleSpanProcessor ;
1520use OpenTelemetry \SDK \Trace \TracerProvider ;
21+ use OpenTelemetry \SemConv \TraceAttributes ;
1622use PHPUnit \Framework \TestCase ;
1723use Psr \Http \Message \ResponseInterface ;
1824use Psr \Http \Message \ServerRequestInterface ;
2834class SlimInstrumentationTest extends TestCase
2935{
3036 private ScopeInterface $ scope ;
31- private ArrayObject $ storage ;
37+ private ArrayObject $ traces ;
38+ private ArrayObject $ metrics ;
39+ private ExportingReader $ reader ;
3240
3341 public function setUp (): void
3442 {
35- $ this ->storage = new ArrayObject ();
43+ $ this ->traces = new ArrayObject ();
44+ $ this ->metrics = new ArrayObject ();
3645 $ tracerProvider = new TracerProvider (
3746 new SimpleSpanProcessor (
38- new InMemoryExporter ($ this ->storage )
47+ new InMemoryExporter ($ this ->traces )
3948 )
4049 );
50+ $ this ->reader = new ExportingReader (new InMemoryMetricsExporter ($ this ->metrics ));
51+ $ meterProvider = MeterProvider::builder ()->addReader ($ this ->reader )->build ();
4152
4253 $ this ->scope = Configurator::create ()
4354 ->withTracerProvider ($ tracerProvider )
55+ ->withMeterProvider ($ meterProvider )
4456 ->activate ();
4557 }
4658
4759 public function tearDown (): void
4860 {
4961 $ this ->scope ->detach ();
62+ PsrServerRequestMetrics::reset ();
5063 }
5164
5265 /**
@@ -67,8 +80,8 @@ public function performRouting(ServerRequestInterface $request): ServerRequestIn
6780 $ routingMiddleware
6881 );
6982 $ app ->handle ($ request ->withAttribute (RouteContext::ROUTE , $ route ));
70- $ this ->assertCount (1 , $ this ->storage );
71- $ span = $ this ->storage ->offsetGet (0 ); // @var ImmutableSpan $span
83+ $ this ->assertCount (1 , $ this ->traces );
84+ $ span = $ this ->traces ->offsetGet (0 ); // @var ImmutableSpan $span
7285 $ this ->assertSame ($ expected , $ span ->getName ());
7386 }
7487
@@ -97,7 +110,7 @@ public function routeProvider(): array
97110 public function test_invocation_strategy (): void
98111 {
99112 $ strategy = $ this ->createMockStrategy ();
100- $ this ->assertCount (0 , $ this ->storage );
113+ $ this ->assertCount (0 , $ this ->traces );
101114 $ strategy ->__invoke (
102115 function (): ResponseInterface {
103116 return new Response ();
@@ -106,7 +119,7 @@ function (): ResponseInterface {
106119 $ this ->createMock (ResponseInterface::class),
107120 []
108121 );
109- $ this ->assertCount (1 , $ this ->storage );
122+ $ this ->assertCount (1 , $ this ->traces );
110123 }
111124
112125 public function test_routing_exception (): void
@@ -129,8 +142,8 @@ public function performRouting(ServerRequestInterface $request): ServerRequestIn
129142 } catch (\Exception $ e ) {
130143 $ this ->assertSame ('routing failed ' , $ e ->getMessage ());
131144 }
132- $ this ->assertCount (1 , $ this ->storage );
133- $ span = $ this ->storage ->offsetGet (0 ); // @var ImmutableSpan $span
145+ $ this ->assertCount (1 , $ this ->traces );
146+ $ span = $ this ->traces ->offsetGet (0 ); // @var ImmutableSpan $span
134147 $ this ->assertSame ('GET ' , $ span ->getName (), 'span name was not updated because routing failed ' );
135148 }
136149
@@ -143,13 +156,63 @@ public function test_response_propagation(): void
143156 $ request = (new ServerRequest ('GET ' , 'https://example.com/foo ' ));
144157 $ app = $ this ->createMockApp (new Response (200 , ['X-Foo ' => 'foo ' ]));
145158 $ response = $ app ->handle ($ request );
146- $ this ->assertCount (1 , $ this ->storage );
159+ $ this ->assertCount (1 , $ this ->traces );
147160 $ this ->assertArrayHasKey ('X-Foo ' , $ response ->getHeaders ());
148161 $ this ->assertArrayHasKey ('server-timing ' , $ response ->getHeaders ());
149162 $ this ->assertStringStartsWith ('traceparent;desc= ' , $ response ->getHeaderLine ('server-timing ' ));
150163 $ this ->assertArrayHasKey ('traceresponse ' , $ response ->getHeaders ());
151164 }
152165
166+ /**
167+ * @psalm-suppress NoInterfaceProperties
168+ */
169+ public function test_generate_metrics (): void
170+ {
171+ $ request = (new ServerRequest (
172+ method: 'GET ' ,
173+ uri: 'http://example.com/foo ' ,
174+ serverParams: [
175+ 'REQUEST_TIME_FLOAT ' => microtime (true ),
176+ ],
177+ ))->withHeader ('Content-Length ' , '999 ' );
178+ $ route = Mockery::mock (RouteInterface::class)->allows ([
179+ 'getName ' => 'route.name ' ,
180+ 'getPattern ' => '/foo ' ,
181+ ]);
182+
183+ $ routingMiddleware = new class ($ this ->createMock (RouteResolverInterface::class), $ this ->createMock (RouteParserInterface::class)) extends RoutingMiddleware {
184+ public function performRouting (ServerRequestInterface $ request ): ServerRequestInterface
185+ {
186+ return $ request ;
187+ }
188+ };
189+ $ app = $ this ->createMockApp (
190+ (new Response ())->withHeader ('Content-Length ' , '999 ' ),
191+ $ routingMiddleware
192+ );
193+ //execute twice to generate 2 data point values
194+ $ app ->handle ($ request ->withAttribute (RouteContext::ROUTE , $ route ));
195+ $ app ->handle ($ request ->withAttribute (RouteContext::ROUTE , $ route ));
196+ $ this ->assertCount (0 , $ this ->metrics );
197+ $ this ->reader ->collect ();
198+ $ this ->assertCount (1 , $ this ->metrics );
199+ $ metric = $ this ->metrics ->offsetGet (0 );
200+ assert ($ metric instanceof \OpenTelemetry \SDK \Metrics \Data \Metric);
201+ $ this ->assertSame ('http.server.request.duration ' , $ metric ->name );
202+ $ this ->assertCount (1 , $ metric ->data ->dataPoints );
203+ $ dataPoint = $ metric ->data ->dataPoints [0 ];
204+ assert ($ dataPoint instanceof HistogramDataPoint);
205+ $ this ->assertSame (2 , $ dataPoint ->count );
206+ $ attributes = $ dataPoint ->attributes ->toArray ();
207+ $ this ->assertEqualsCanonicalizing ([
208+ TraceAttributes::HTTP_REQUEST_METHOD => 'GET ' ,
209+ TraceAttributes::URL_SCHEME => 'http ' ,
210+ TraceAttributes::HTTP_RESPONSE_BODY_SIZE => 999 ,
211+ TraceAttributes::NETWORK_PROTOCOL_VERSION => '1.1 ' ,
212+ TraceAttributes::HTTP_RESPONSE_STATUS_CODE => 200 ,
213+ ], $ attributes );
214+ }
215+
153216 public function createMockStrategy (): InvocationStrategyInterface
154217 {
155218 return new class () implements InvocationStrategyInterface {
0 commit comments