diff --git a/src/SDK/Trace/AutoRootSpan.php b/src/SDK/Trace/AutoRootSpan.php index 69790cd61..f675f85bf 100644 --- a/src/SDK/Trace/AutoRootSpan.php +++ b/src/SDK/Trace/AutoRootSpan.php @@ -43,12 +43,12 @@ public static function create(ServerRequestInterface $request): void Version::VERSION_1_25_0->url(), ); $parent = Globals::propagator()->extract($request->getHeaders()); - $startTime = array_key_exists('REQUEST_TIME_FLOAT', $request->getServerParams()) - ? $request->getServerParams()['REQUEST_TIME_FLOAT'] - : (int) microtime(true); + $startTimeInNanos = array_key_exists('REQUEST_TIME_FLOAT', $request->getServerParams()) + ? (int) ((float) $request->getServerParams()['REQUEST_TIME_FLOAT'] * (float) ClockInterface::NANOS_PER_SECOND) + : (int) (microtime(true) * (float) ClockInterface::NANOS_PER_SECOND); $span = $tracer->spanBuilder($request->getMethod()) ->setSpanKind(SpanKind::KIND_SERVER) - ->setStartTimestamp((int) ($startTime*ClockInterface::NANOS_PER_SECOND)) + ->setStartTimestamp($startTimeInNanos) ->setParent($parent) ->setAttribute(TraceAttributes::URL_FULL, (string) $request->getUri()) ->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, $request->getMethod()) diff --git a/tests/Unit/SDK/Trace/AutoRootSpanTest.php b/tests/Unit/SDK/Trace/AutoRootSpanTest.php index a53859f52..0cfe528ea 100644 --- a/tests/Unit/SDK/Trace/AutoRootSpanTest.php +++ b/tests/Unit/SDK/Trace/AutoRootSpanTest.php @@ -5,6 +5,7 @@ namespace OpenTelemetry\Tests\SDK\Trace; use Nyholm\Psr7\ServerRequest; +use OpenTelemetry\API\Common\Time\ClockInterface; use OpenTelemetry\API\Instrumentation\Configurator; use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator; use OpenTelemetry\API\Trace\Span; @@ -133,6 +134,73 @@ public function test_create(): void $scope->detach(); } + public function test_create_converts_request_time_float_to_nanoseconds(): void + { + $requestTimeFloat = 1700000000.5; + $expectedNanos = (int) ($requestTimeFloat * (float) ClockInterface::NANOS_PER_SECOND); + + $request = new ServerRequest( + 'GET', + 'https://example.com/', + [], + null, + '1.1', + ['REQUEST_TIME_FLOAT' => $requestTimeFloat], + ); + + $span = $this->createMock(SpanInterface::class); + $spanBuilder = $this->createMock(SpanBuilderInterface::class); + $spanBuilder->method('setSpanKind')->willReturnSelf(); + $spanBuilder->method('setParent')->willReturnSelf(); + $spanBuilder->method('setAttribute')->willReturnSelf(); + $spanBuilder->method('startSpan')->willReturn($span); + $spanBuilder + ->expects($this->once()) + ->method('setStartTimestamp') + ->with($this->identicalTo($expectedNanos)) + ->willReturnSelf(); + + $this->tracer->method('spanBuilder')->willReturn($spanBuilder); + + AutoRootSpan::create($request); + + Context::storage()->scope()?->detach(); + } + + public function test_create_start_timestamp_preserves_sub_second_precision_without_request_time_float(): void + { + $request = new ServerRequest('GET', 'https://example.com/'); + + $capturedTimestamp = null; + $span = $this->createMock(SpanInterface::class); + $spanBuilder = $this->createMock(SpanBuilderInterface::class); + $spanBuilder->method('setSpanKind')->willReturnSelf(); + $spanBuilder->method('setParent')->willReturnSelf(); + $spanBuilder->method('setAttribute')->willReturnSelf(); + $spanBuilder->method('startSpan')->willReturn($span); + $spanBuilder + ->expects($this->once()) + ->method('setStartTimestamp') + ->with($this->callback(function (int $timestamp) use (&$capturedTimestamp): bool { + $capturedTimestamp = $timestamp; + + return true; + })) + ->willReturnSelf(); + + $this->tracer->method('spanBuilder')->willReturn($spanBuilder); + + $before = (int) (microtime(true) * (float) ClockInterface::NANOS_PER_SECOND); + AutoRootSpan::create($request); + $after = (int) (microtime(true) * (float) ClockInterface::NANOS_PER_SECOND); + + $this->assertNotNull($capturedTimestamp); + $this->assertGreaterThanOrEqual($before, $capturedTimestamp); + $this->assertLessThanOrEqual($after, $capturedTimestamp); + + Context::storage()->scope()?->detach(); + } + public function test_shutdown_handler(): void { $this->setEnvironmentVariable('OTEL_PHP_DEBUG_SCOPES_DISABLED', 'true');