diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000..6cd52b0 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,113 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +name: "Benchmark" + +on: + pull_request: + +jobs: + phpbench: + name: "Benchmark" + + runs-on: ${{ matrix.operating-system }} + + strategy: + matrix: + dependencies: + - "locked" + php-version: + - "8.2" + operating-system: + - "ubuntu-latest" + + steps: + - name: "Install PHP" + uses: "shivammathur/setup-php@2.25.5" + with: + coverage: "pcov" + php-version: "${{ matrix.php-version }}" + ini-values: memory_limit=-1 + extensions: pdo_sqlite + + - name: "Checkout base" + uses: actions/checkout@v3 + with: + ref: ${{ github.base_ref }} + + - uses: ramsey/composer-install@2.2.0 + with: + dependency-versions: ${{ matrix.dependencies }} + + - name: "phpbench on base" + run: "vendor/bin/phpbench run tests/Benchmark --progress=none --report=default --tag=base" + + - name: "Checkout" + uses: actions/checkout@v3 + with: + clean: false + + - uses: ramsey/composer-install@2.2.0 + with: + dependency-versions: ${{ matrix.dependencies }} + + - name: "phpbench diff" + run: "vendor/bin/phpbench run tests/Benchmark --progress=none --report=diff --ref=base > bench.txt" + + - name: "Get Bench Result" + id: phpbench + run: | + echo 'BENCH_RESULT<> $GITHUB_ENV + cat bench.txt >> $GITHUB_ENV + echo 'EOF' >> $GITHUB_ENV + + - uses: actions/github-script@v6 + with: + script: | + // Get the existing comments. + const {data: comments} = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.number, + }) + + // Find any comment already made by the bot. + const botComment = comments.find(comment => comment.user.id === 41898282) + const commentBody = ` + + Hello :wave: + +
+ here is the most recent benchmark result: + +

+ + \`\`\` + ${{ env.BENCH_RESULT }} + \`\`\` + +

+
+ + This comment gets update everytime a new commit comes in! + + `; + + if (context.payload.pull_request.head.repo.full_name !== 'patchlevel/event-sourcing') { + console.log('Not attempting to write comment on PR from fork'); + } else { + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: commentBody + }) + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.number, + body: commentBody + }) + } + } diff --git a/Makefile b/Makefile index d90b09d..a15b772 100644 --- a/Makefile +++ b/Makefile @@ -41,5 +41,15 @@ static: psalm phpstan phpcs-check test: phpunit ## run tests +.PHONY: benchmark +benchmark: vendor ## run benchmarks + vendor/bin/phpbench run tests/Benchmark --report=default + +.PHONY: benchmark-diff-test +benchmark-diff-test: vendor ## run benchmarks + vendor/bin/phpbench run tests/Benchmark --revs=1 --report=default --progress=none --tag=base + vendor/bin/phpbench run tests/Benchmark --revs=1 --report=diff --progress=none --ref=base + + .PHONY: dev dev: static test ## run dev tools diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 0000000..729b39d --- /dev/null +++ b/phpbench.json @@ -0,0 +1,54 @@ +{ + "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "vendor/autoload.php", + "runner.file_pattern": "*Bench.php", + "report.generators": { + "diff": { + "generator": "component", + "partition": ["benchmark_name"], + "components": [ + { + "component": "section", + "title": "{{ first(frame[\"benchmark_name\"]) }}", + "components": [ + { + "component": "table_aggregate", + "partition": ["subject_name", "variant_name"], + "groups": + { + "time (kde mode)": + { + "cols": ["time"] + }, + "memory": + { + "cols": ["memory"] + } + }, + "row": + { + "subject": "first(partition[\"subject_name\"]) ~ \" (\" ~ first(partition[\"variant_name\"]) ~ \")\"", + "time": + { + "type": "expand", + "partition": "suite_tag", + "cols": + { + "Tag: {{ key }}": "mode(partition[\"result_time_avg\"]) as time ~ ' (' ~ rstdev(partition['result_time_avg']) ~ ')'" + } + }, + "memory": + { + "type": "expand", + "partition": "suite_tag", + "cols": + { + "Tag: {{ key }} ": "mode(partition[\"result_mem_peak\"]) as memory" + } + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/tests/Benchmark/Fixture/NameChanged.php b/tests/Benchmark/Fixture/NameChanged.php new file mode 100644 index 0000000..13dcfb6 --- /dev/null +++ b/tests/Benchmark/Fixture/NameChanged.php @@ -0,0 +1,13 @@ +id; + } +} diff --git a/tests/Benchmark/Fixture/ProfileIdNormalizer.php b/tests/Benchmark/Fixture/ProfileIdNormalizer.php new file mode 100644 index 0000000..f867ebf --- /dev/null +++ b/tests/Benchmark/Fixture/ProfileIdNormalizer.php @@ -0,0 +1,37 @@ +toString(); + } + + public function denormalize(mixed $value): ProfileId|null + { + if ($value === null) { + return null; + } + + if (!is_string($value)) { + throw new InvalidArgumentException(); + } + + return ProfileId::fromString($value); + } +} diff --git a/tests/Benchmark/HydratorBench.php b/tests/Benchmark/HydratorBench.php new file mode 100644 index 0000000..4e8ae69 --- /dev/null +++ b/tests/Benchmark/HydratorBench.php @@ -0,0 +1,88 @@ +hydrator = new MetadataHydrator(); + + $object = $this->hydrator->hydrate(ProfileCreated::class, [ + 'profileId' => '1', + 'name' => 'foo', + ]); + + $this->hydrator->extract($object); + } + + #[Bench\Revs(10)] + public function benchHydrate1Object(): void + { + $this->hydrator->hydrate(ProfileCreated::class, [ + 'profileId' => '1', + 'name' => 'foo', + ]); + } + + #[Bench\Revs(10)] + public function benchExtract1Object(): void + { + $object = new ProfileCreated(ProfileId::fromString('1'), 'foo'); + + $this->hydrator->extract($object); + } + + #[Bench\Revs(10)] + public function benchHydrate1_000Objects(): void + { + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->hydrate(ProfileCreated::class, [ + 'profileId' => '1', + 'name' => 'foo', + ]); + } + } + + #[Bench\Revs(10)] + public function benchExtract1_000Objects(): void + { + $object = new ProfileCreated(ProfileId::fromString('1'), 'foo'); + + for ($i = 0; $i < 1_000; $i++) { + $this->hydrator->extract($object); + } + } + + #[Bench\Revs(10)] + public function benchHydrate1_000_000Objects(): void + { + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->hydrate(ProfileCreated::class, [ + 'profileId' => '1', + 'name' => 'foo', + ]); + } + } + + #[Bench\Revs(10)] + public function benchExtract1_000_000Objects(): void + { + $object = new ProfileCreated(ProfileId::fromString('1'), 'foo'); + + for ($i = 0; $i < 1_000_000; $i++) { + $this->hydrator->extract($object); + } + } +}