diff --git a/.gitignore b/.gitignore index 1586ec275..7b4e7cd3b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ vendor/ coverage.clover tests/coverage .php-cs-fixer.cache +.phpbench !tests/coverage/html/.gitkeep # IntelliJ IDEA diff --git a/Makefile b/Makefile index 9a3fd2353..2cfce5297 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,8 @@ psalm-info: $(DC_RUN_PHP) env XDEBUG_MODE=off vendor/bin/psalm --show-info=true --threads=1 phpstan: $(DC_RUN_PHP) env XDEBUG_MODE=off vendor/bin/phpstan analyse +benchmark: + $(DC_RUN_PHP) env XDEBUG_MODE=off vendor/bin/phpbench run --report=default trace examples: FORCE docker-compose up -d --remove-orphans $(DC_RUN_PHP) php ./examples/AlwaysOnZipkinExample.php diff --git a/composer.json b/composer.json index c262a4fbb..46c0141cc 100644 --- a/composer.json +++ b/composer.json @@ -64,6 +64,7 @@ "nyholm/psr7": "^1.4", "phan/phan": "^5.0", "php-http/mock-client": "^1.5", + "phpbench/phpbench": "^1.2", "phpstan/phpstan": "^1.1", "phpstan/phpstan-mockery": "^1.0", "phpstan/phpstan-phpunit": "^1.0", diff --git a/phpbench.json.dist b/phpbench.json.dist new file mode 100644 index 000000000..9d4272fc3 --- /dev/null +++ b/phpbench.json.dist @@ -0,0 +1,6 @@ +{ + "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "vendor/autoload.php", + "runner.path": "tests/Benchmark", + "runner.file_pattern": "*Bench.php" +} diff --git a/tests/Benchmark/OtlpBench.php b/tests/Benchmark/OtlpBench.php new file mode 100644 index 000000000..ba594fa3e --- /dev/null +++ b/tests/Benchmark/OtlpBench.php @@ -0,0 +1,187 @@ +sampler = new AlwaysOnSampler(); + $this->resource = ResourceInfo::create(new Attributes([ + 'service.name' => 'A123456789', + 'service.version' => '1.34567890', + 'service.instance.id' => '123ab456-a123-12ab-12ab-12340a1abc12', + ])); + } + + public function setUpNoExporter(): void + { + $processor = new SimpleSpanProcessor(); + $provider = new TracerProvider($processor, $this->sampler, $this->resource); + $this->tracer = $provider->getTracer(); + } + + public function setUpGrpc(): void + { + $client = $this->createMockTraceServiceClient(); + $exporter = new GrpcExporter('foo:4317', true, '', '', false, 10, $client); + $processor = new SimpleSpanProcessor($exporter); + $provider = new TracerProvider($processor, $this->sampler, $this->resource); + $this->tracer = $provider->getTracer(); + } + + /** + * @psalm-suppress UndefinedMagicMethod + * @psalm-suppress InvalidArgument + * @psalm-suppress PossiblyUndefinedMethod + */ + public function setUpGrpcHttp(): void + { + $response = Mockery::mock(ResponseInterface::class) + ->allows(['getStatusCode' => 200]); + $stream = Mockery::mock(StreamInterface::class); + $request = Mockery::mock(RequestInterface::class); + $request->allows('withBody')->andReturnSelf(); + $request->allows('withHeader')->andReturnSelf(); + $client = Mockery::mock(ClientInterface::class) + ->allows(['sendRequest' => $response]); + $requestFactory = Mockery::mock(RequestFactoryInterface::class) + ->allows(['createRequest' => $request]); + $streamFactory = Mockery::mock(StreamFactoryInterface::class) + ->allows(['createStream' => $stream]); + // @phpstan-ignore-next-line + $exporter = new HttpExporter($client, $requestFactory, $streamFactory); + $processor = new SimpleSpanProcessor($exporter); + $provider = new TracerProvider($processor, $this->sampler, $this->resource); + $this->tracer = $provider->getTracer(); + } + + /** + * @BeforeMethods("setUpNoExporter") + * @Revs(1000) + * @Iterations(10) + * @OutputTimeUnit("microseconds") + */ + public function benchCreateSpans(): void + { + $span = $this->tracer->spanBuilder('foo') + ->setAttribute('foo', PHP_INT_MAX) + ->startSpan(); + $span->addEvent('my_event'); + $span->end(); + } + + /** + * @BeforeMethods("setUpNoExporter") + * @Revs(1000) + * @Iterations(10) + * @OutputTimeUnit("microseconds") + */ + public function benchCreateSpansWithStackTrace(): void + { + $span = $this->tracer->spanBuilder('foo') + ->setAttribute('foo', PHP_INT_MAX) + ->startSpan(); + $span->recordException(new \Exception('foo')); + $span->end(); + } + + /** + * @BeforeMethods("setUpNoExporter") + * @ParamProviders("provideEventCounts") + * @Revs(1000) + * @Iterations(5) + * @OutputTimeUnit("microseconds") + */ + public function benchCreateSpansWithMultipleEvents(array $params): void + { + $span = $this->tracer->spanBuilder('foo') + ->setAttribute('foo', PHP_INT_MAX) + ->startSpan(); + for ($i=0; $i < $params[0]; $i++) { + $span->addEvent('event-' . $i); + } + $span->end(); + } + + public function provideEventCounts(): \Generator + { + yield [1]; + yield [4]; + yield [16]; + yield [256]; + } + + /** + * @BeforeMethods("setUpGrpc") + * @Revs(1000) + * @Iterations(10) + * @OutputTimeUnit("microseconds") + */ + public function benchExportSpans_OltpGrpc(): void + { + $span = $this->tracer->spanBuilder('foo') + ->setAttribute('foo', PHP_INT_MAX) + ->startSpan(); + $span->addEvent('my_event'); + $span->end(); + } + + /** + * @BeforeMethods("setUpGrpcHttp") + * @Revs(1000) + * @Iterations(10) + * @OutputTimeUnit("microseconds") + */ + public function benchExportSpans_OtlpHttp(): void + { + $span = $this->tracer->spanBuilder('foo') + ->setAttribute('foo', PHP_INT_MAX) + ->startSpan(); + $span->addEvent('my_event'); + $span->end(); + } + + private function createMockTraceServiceClient() + { + // @var MockInterface&TraceServiceClient + $unaryCall = Mockery::mock(UnaryCall::class) + ->allows(['wait' => [ + 'unused response data', + (object) ['code' => \Grpc\STATUS_OK], + ]]); + $mockClient = Mockery::mock(TraceServiceClient::class) + ->allows(['Export'=> $unaryCall]); + + return $mockClient; + } +}