diff --git a/composer.json b/composer.json index c59f8ca9..664f5853 100644 --- a/composer.json +++ b/composer.json @@ -46,6 +46,7 @@ }, "require-dev": { "maglnet/composer-require-checker": "^4.7", + "phpbench/phpbench": "^1.3", "phpunit/phpunit": "^10.5", "rector/rector": "^1.0.0", "roave/infection-static-analysis-plugin": "^1.34", diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 00000000..04436028 --- /dev/null +++ b/phpbench.json @@ -0,0 +1,8 @@ +{ + "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "vendor/autoload.php", + "runner.path": "tests/Benchmark", + "runner.revs": 100000, + "runner.iterations": 5, + "runner.warmup": 5 +} diff --git a/tests/Benchmark/MetadataBench.php b/tests/Benchmark/MetadataBench.php new file mode 100644 index 00000000..459cd53b --- /dev/null +++ b/tests/Benchmark/MetadataBench.php @@ -0,0 +1,109 @@ + 1]); + $id = $message->getMetadata()['id']; + } + + /** + * Create metadata as object and read its value immediately + */ + #[Tag('metadata_read')] + public function benchEnvelopeRead(): void + { + $message = new IdEnvelope(new Message('foo', 'bar'), 1); + $id = $message->getId(); + } + + /** + * Create metadata as array and read its value from an envelope object + */ + #[Tag('metadata_read')] + public function benchEnvelopeReadRestored(): void + { + $message = IdEnvelope::fromMessage(new Message('foo', 'bar', ['id' => 1])); + $id = $message->getId(); + } + + public function provideEnvelopeStack(): Generator + { + $config = [1 => 'one', 5 => 'three', 15 => 'fifteen']; + $message = new IdEnvelope(new Message('foo', 'bar'), 1); + + for ($i = 1; $i <= max(...array_keys($config)); $i++) { + $message = new FailureEnvelope($message, ["fail$i" => "fail$i"]); + if (isset($config[$i])) { + yield $config[$i] => ['message' => $message]; + } + } + } + + /** + * Read metadata value from an envelope object restored from an envelope stacks of different depth + * + * @psalm-param array{message: MessageInterface} $params + */ + #[ParamProviders('provideEnvelopeStack')] + #[Tag('metadata_read')] + public function benchEnvelopeReadFromStack(array $params): void + { + $id = IdEnvelope::fromMessage($params['message'])->getId(); + } + + public function provideEnvelopeStackCounts(): Generator + { + yield 'one' => [1]; + yield 'three' => [3]; + yield 'fifteen' => [15]; + } + + /** + * Create envelope stack with the given depth + * + * @psalm-param array{0: int} $params + */ + #[ParamProviders('provideEnvelopeStackCounts')] + #[Tag('metadata_create')] + public function benchEnvelopeStackCreation(array $params): void + { + $message = new Message('foo', 'bar'); + for ($i = 0; $i < $params[0]; $i++) { + $message = new FailureEnvelope($message, ["fail$i" => "fail$i"]); + } + } + + /** + * Create metadata array with the given elements count + * + * @psalm-param array{0: int} $params + */ + #[ParamProviders('provideEnvelopeStackCounts')] + #[Tag('metadata_create')] + public function benchMetadataArrayCreation(array $params): void + { + $metadata = ['failure-meta' => []]; + for ($i = 0; $i < $params[0]; $i++) { + $metadata['failure-meta']["fail$i"] = "fail$i"; + } + $message = new Message('foo', 'bar', $metadata); + } +} diff --git a/tests/Benchmark/QueueBench.php b/tests/Benchmark/QueueBench.php new file mode 100644 index 00000000..76480523 --- /dev/null +++ b/tests/Benchmark/QueueBench.php @@ -0,0 +1,112 @@ + static function (): void { + }, + ], + $logger, + new Injector($container), + $container, + new ConsumeMiddlewareDispatcher(new MiddlewareFactoryConsume($container, $callableFactory)), + new FailureMiddlewareDispatcher( + new MiddlewareFactoryFailure($container, $callableFactory), + [], + ), + ); + $this->serializer = new JsonMessageSerializer(); + $this->adapter = new VoidAdapter($this->serializer); + + $this->queue = new Queue( + $worker, + new SimpleLoop(0), + $logger, + new PushMiddlewareDispatcher(new MiddlewareFactoryPush($container, $callableFactory)), + $this->adapter, + ); + } + + public function providePush(): Generator + { + yield 'simple' => ['message' => new Message('foo', 'bar')]; + yield 'with envelopes' => [ + 'message' => new FailureEnvelope( + new IdEnvelope( + new Message('foo', 'bar'), + 'test id', + ), + ['failure-1' => ['a', 'b', 'c']], + ), + ]; + } + + #[ParamProviders('providePush')] + #[Tag('queue_push')] + public function benchPush(array $params): void + { + $this->queue->push($params['message']); + } + + public function provideConsume(): Generator + { + yield 'simple mapping' => ['message' => $this->serializer->serialize(new Message('foo', 'bar'))]; + yield 'with envelopes mapping' => [ + 'message' => $this->serializer->serialize( + new FailureEnvelope( + new IdEnvelope( + new Message('foo', 'bar'), + 'test id', + ), + ['failure-1' => ['a', 'b', 'c']], + ), + ), + ]; + } + + #[ParamProviders('provideConsume')] + #[Tag('queue_consume')] + public function benchConsume(array $params): void + { + $this->adapter->message = $params['message']; + $this->queue->run(); + } +} diff --git a/tests/Benchmark/Support/VoidAdapter.php b/tests/Benchmark/Support/VoidAdapter.php new file mode 100644 index 00000000..edd927aa --- /dev/null +++ b/tests/Benchmark/Support/VoidAdapter.php @@ -0,0 +1,52 @@ +serializer->unserialize($this->message)); + } + + public function status(int|string $id): JobStatus + { + throw new InvalidArgumentException(); + } + + public function push(MessageInterface $message): MessageInterface + { + $this->serializer->serialize($message); + + return new IdEnvelope($message, 1); + } + + public function subscribe(callable $handlerCallback): void + { + throw new RuntimeException('Method is not implemented'); + } + + public function withChannel(string $channel): AdapterInterface + { + throw new RuntimeException('Method is not implemented'); + } +} diff --git a/tests/docker/php/Dockerfile b/tests/docker/php/Dockerfile index a2051c02..cd3e6270 100644 --- a/tests/docker/php/Dockerfile +++ b/tests/docker/php/Dockerfile @@ -1,17 +1,19 @@ # Important! Do not use this image in production! ARG PHP_VERSION -FROM --platform=linux/amd64 php:${PHP_VERSION}-cli-alpine +FROM php:${PHP_VERSION}-cli-alpine -RUN apk add git autoconf g++ make linux-headers +RUN apk add git autoconf g++ make linux-headers && \ + docker-php-ext-install pcntl && \ + pecl install xdebug pcov && \ + docker-php-ext-enable xdebug pcov -RUN docker-php-ext-install pcntl -RUN pecl install xdebug pcov -RUN docker-php-ext-enable xdebug pcov +ADD ./tests/docker/php/php.ini /usr/local/etc/php/conf.d/40-custom.ini COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer ENV COMPOSER_ALLOW_SUPERUSER 1 WORKDIR /app +RUN git config --global --add safe.directory /app ENTRYPOINT ["sh", "tests/docker/php/entrypoint.sh"] CMD ["sleep", "infinity"] diff --git a/tests/docker/php/php.ini b/tests/docker/php/php.ini new file mode 100644 index 00000000..18fdd3e5 --- /dev/null +++ b/tests/docker/php/php.ini @@ -0,0 +1,2 @@ +opcache.enable=1 +opcache.enable_cli=1