packages/deriver/bench/LaravelBench.php

1<?php
2
3declare(strict_types=1);
4
5namespace Bench;
6
7use Deriver\Deriver;
8use Deriver\Deriver\BranchEvaluators;
9use Deriver\Deriver\Candidate\CandidateCollection;
10use Deriver\Deriver\DeriverConfig;
11use Deriver\Deriver\MicroEvaluator;
12use Deriver\Finder;
13use PhpBench\Attributes as Bench;
14use RuntimeException;
15
16#[Bench\Iterations(5)]
17#[Bench\Revs(1)]
18#[Bench\Warmup(0)]
19#[Bench\OutputTimeUnit('milliseconds')]
20final class LaravelBench
21{
22    private const QUERIES = [
23        'App\Services\PublishingRules::initialState#return',
24        'App\Services\PublishingRules::nextState#return',
25        'App\Services\PublishingRules::escalationHours#return',
26        'App\Services\Publishing::create#return',
27    ];
28
29    /** @var list<string> */
30    private array $files;
31    private DeriverConfig $configuration;
32    private Deriver $prepared;
33    /** @var list<CandidateCollection> */
34    private array $results = [];
35
36    public function locate(): void
37    {
38        $this->locateWithEvaluators([]);
39    }
40
41    public function locateWithBranchPruning(): void
42    {
43        $this->locateWithEvaluators(BranchEvaluators::truth());
44    }
45
46    /** @param list<MicroEvaluator> $evaluators */
47    public function locateWithEvaluators(array $evaluators): void
48    {
49        $root = __DIR__ . '/target/laravel';
50        $this->files = (new Finder())->in($root)
51            ->include('**/*.php', 'artisan')
52            ->exclude('vendor', 'tests', 'storage', 'bootstrap/cache', 'resources/views', 'database/factories', 'database/seeders')
53            ->files();
54        // Include the framework traits used by these application sources.
55        $this->files = [
56            ...$this->files,
57            $root . '/vendor/laravel/framework/src/Illuminate/Bus/Queueable.php',
58            $root . '/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/HasFactory.php',
59            $root . '/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletes.php',
60            $root . '/vendor/laravel/framework/src/Illuminate/Foundation/Bus/Dispatchable.php',
61            $root . '/vendor/laravel/framework/src/Illuminate/Foundation/Events/Dispatchable.php',
62            $root . '/vendor/laravel/framework/src/Illuminate/Foundation/Queue/Queueable.php',
63            $root . '/vendor/laravel/framework/src/Illuminate/Notifications/HasDatabaseNotifications.php',
64            $root . '/vendor/laravel/framework/src/Illuminate/Notifications/Notifiable.php',
65            $root . '/vendor/laravel/framework/src/Illuminate/Notifications/RoutesNotifications.php',
66            $root . '/vendor/laravel/framework/src/Illuminate/Queue/InteractsWithQueue.php',
67            $root . '/vendor/laravel/framework/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php',
68            $root . '/vendor/laravel/framework/src/Illuminate/Queue/SerializesModels.php',
69            $root . '/vendor/laravel/framework/src/Illuminate/Support/InteractsWithTime.php',
70            $root . '/vendor/laravel/sanctum/src/HasApiTokens.php',
71        ];
72        sort($this->files, SORT_STRING);
73        $this->configuration = new DeriverConfig(phpVersion: '8.4', evaluators: $evaluators);
74    }
75
76    public function prepare(): void
77    {
78        $this->locate();
79        $this->prepared = Deriver::prepare($this->files, $this->configuration);
80    }
81
82    public function prepareWithBranchPruning(): void
83    {
84        $this->locateWithBranchPruning();
85        $this->prepared = Deriver::prepare($this->files, $this->configuration);
86    }
87
88    #[Bench\BeforeMethods('locate')]
89    #[Bench\Groups(['preparation'])]
90    public function benchPrepare(): void
91    {
92        $this->prepared = Deriver::prepare($this->files, $this->configuration);
93    }
94
95    #[Bench\BeforeMethods('prepare')]
96    #[Bench\Groups(['first-query'])]
97    #[Bench\AfterMethods('verifyResults')]
98    public function benchFirstQuery(): void
99    {
100        $this->results = [$this->prepared->derive(self::QUERIES[0])];
101    }
102
103    #[Bench\BeforeMethods('prepare')]
104    #[Bench\Groups(['query-batch'])]
105    #[Bench\AfterMethods('verifyResults')]
106    public function benchQueryBatch(): void
107    {
108        $this->results = [];
109        foreach (self::QUERIES as $query) {
110            $this->results[] = $this->prepared->derive($query);
111        }
112    }
113
114    #[Bench\BeforeMethods('locateWithBranchPruning')]
115    #[Bench\Groups(['preparation', 'branch-pruning'])]
116    public function benchPrepareWithBranchPruning(): void
117    {
118        $this->benchPrepare();
119    }
120
121    #[Bench\BeforeMethods('prepareWithBranchPruning')]
122    #[Bench\Groups(['first-query', 'branch-pruning'])]
123    #[Bench\AfterMethods('verifyResults')]
124    public function benchFirstQueryWithBranchPruning(): void
125    {
126        $this->benchFirstQuery();
127    }
128
129    #[Bench\BeforeMethods('prepareWithBranchPruning')]
130    #[Bench\Groups(['query-batch', 'branch-pruning'])]
131    #[Bench\AfterMethods('verifyResults')]
132    public function benchQueryBatchWithBranchPruning(): void
133    {
134        $this->benchQueryBatch();
135    }
136
137    public function verifyResults(): void
138    {
139        foreach ($this->results as $collection) {
140            if (count($collection) === 0) {
141                throw new RuntimeException('A benchmark query returned no candidates.');
142            }
143        }
144    }
145}
146