Skip to content

Commit 97712eb

Browse files
authored
Refactor for PHP 8.4 and above (#36)
* Add dev tooling and update dependencies for PHP 8.4+ Upgrade to PHP-DI 7, Symfony 7, adbario 3. Add PHPStan (level 8), PHP-CS-Fixer (PER-CS2.0), and GitHub Actions CI workflow. * Modernize core utilities and config for PHP 8.4+ Typed properties, constructor promotion, strict_types, readonly. Replace opendir/readdir with RecursiveDirectoryIterator. Update Symfony 7 FileLoader signatures. Remove Table and Visual classes. * Add Result DTOs for benchmark data Readonly DTOs implementing JsonSerializable for PHP, CPU, Disk, MySQL, and aggregate BenchmarkResult. * Refactor modules to return typed Result objects Modules no longer render output. Each implements BenchmarkModuleInterface with isEnabled() and a typed run() method. Add OutputMode enum for CLI/HTML detection. * Add CLI and HTML renderers CliRenderer with ANSI colors and Unicode box-drawing tables. HtmlRenderer with standalone styled HTML dashboard and responsive CSS grid layout. * Add JSON report system Save timestamped JSON reports to reports/ on each benchmark run. * Rewrite orchestrator and entry point Benchmark.php orchestrates modules, builds results, saves reports, and dispatches to the appropriate renderer. Auto-detects output mode from SAPI with config override support. * Update README for new architecture Document output modes, report system, and configuration options. * Fix Disk I/O card not spanning full width in HTML output * Add Memory benchmark module Tests allocation speed for array fill, array sort, string concat, object creation, array map, and array merge. Reports both execution time and peak memory usage per operation. * Fix memory benchmark reporting 0 bytes for most operations Use memory_reset_peak_usage() and memory_get_peak_usage() instead of memory_get_usage(true) which reports OS-level allocations in large chunks and misses reused memory. * Add Result DTOs for Network, JSON, Regex, and File Read modules * Add Network, JSON, Regex, and File Read benchmark modules Network: DNS resolution and TCP connection latency to configurable hosts. JSON: encode/decode across small, medium, and large payloads. Regex: match and replace across 10 pattern types. File Read: sequential reads across 1KB to 4MB file sizes. * Add rendering for new modules and switch all timings to milliseconds * Wire new modules into orchestrator and config * Add Result DTOs for Serialization, Hashing, Encryption, Function Overhead, Type Casting, Autoloading, and Compression * Add Serialization, Hashing, Encryption, Function Overhead, Type Casting, Object Instantiation, and Compression modules Serialization: serialize vs json_encode vs var_export, with igbinary/msgpack if available. Hashing: md5, sha1, sha256, sha512, xxh3, crc32, hmac-sha256, bcrypt, argon2id. Encryption: AES-128/256-CBC/GCM, ChaCha20-Poly1305 encrypt and decrypt. Function Overhead: named function, closure, first-class callable, static/instance method, arrow function. Type Casting: int/string/float/bool conversions and intval(). Object Instantiation: stdClass, DateTime, DateTimeImmutable, SplStack, SplPriorityQueue, ArrayObject. Compression: gzip, zlib, deflate with brotli/lzf if available. Reports time and compression ratio. Also reduces network connection timeout from 5s to 2s. * Add rendering for new benchmark modules in CLI and HTML * Wire new modules into orchestrator and config * Update README with full module list, example output, and config reference * Add CLI progress indicator during benchmark execution Shows [1/15] Running Module... on a single updating line so the user knows work is in progress. Clears the line before rendering results. Only displayed in CLI mode. * Add color to CLI progress indicator
1 parent a5d2c68 commit 97712eb

59 files changed

Lines changed: 3471 additions & 936 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
jobs:
10+
analyse:
11+
name: Static Analysis & Code Style
12+
runs-on: ubuntu-latest
13+
14+
strategy:
15+
matrix:
16+
php-version:
17+
- 8.4
18+
- 8.5
19+
20+
steps:
21+
- uses: actions/checkout@v6
22+
23+
- name: Setup PHP
24+
uses: shivammathur/setup-php@v2
25+
with:
26+
php-version: ${{ matrix.php-version }}
27+
extensions: mysqli
28+
tools: composer
29+
30+
- name: Install dependencies
31+
run: composer install --no-interaction --prefer-dist
32+
33+
- name: PHPStan
34+
run: composer analyse
35+
36+
- name: PHP-CS-Fixer
37+
run: composer cs-check

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
/vendor/
22
/.idea/
33
/config/*.yml
4-
composer.lock
4+
composer.lock
5+
.php-cs-fixer.cache
6+
reports/*.json
7+
CLAUDE.md
8+
/.claude

.php-cs-fixer.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
return (new PhpCsFixer\Config())
6+
->setRules([
7+
'@PER-CS2.0' => true,
8+
'strict_param' => true,
9+
'declare_strict_types' => true,
10+
])
11+
->setFinder(
12+
PhpCsFixer\Finder::create()->in(__DIR__ . '/src')
13+
);

README.md

Lines changed: 160 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,178 @@
11
# PHP benchmark
2-
[![Build Status](https://scrutinizer-ci.com/g/hyperized/benchmark/badges/build.png?b=master)](https://scrutinizer-ci.com/g/hyperized/benchmark/build-status/master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/hyperized/benchmark/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/hyperized/benchmark/?branch=master)
3-
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fhyperized%2Fbenchmark.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fhyperized%2Fbenchmark?ref=badge_shield)
42

5-
Simple PHP server benchmarking.
3+
[![CI](https://github.com/hyperized/benchmark/actions/workflows/ci.yml/badge.svg)](https://github.com/hyperized/benchmark/actions/workflows/ci.yml)
64

7-
This tool can help you determine if a hosting environment is suited for your projects in terms of:
8-
- PHP.ini settings that affect uploads;
9-
- CPU speed available to your PHP instance;
10-
- Disk IOPS available to your PHP instance;
11-
- MySQL query speed;
5+
Comprehensive PHP server benchmarking utility. Tests your hosting environment across 15 benchmark modules and reports results in milliseconds.
126

13-
## How to install:
14-
composer create-project hyperized/benchmark:dev-master
15-
16-
Copy the `/config/config.yml.example` to `/config/config.yml` and adjust to your preferences.
17-
18-
## How to run:
7+
Auto-detects CLI vs web and renders with the appropriate output mode:
8+
- **CLI** — ANSI colors with Unicode box-drawing tables
9+
- **Web** — styled HTML dashboard with responsive CSS grid layout
1910

20-
### Locally with CLI
21-
php benchmark.php
11+
Each run saves a JSON report with a timestamp to `reports/`.
2212

23-
### Locally with development server
13+
## Benchmark Modules
2414

25-
php -S localhost:8000 benchmark.php
26-
27-
### Remotely
28-
Install on the server by running composer and visiting the `/benchmark.php` page of the directory the project is installed at.
15+
| Category | Module | What it measures |
16+
|----------|--------|-----------------|
17+
| **Environment** | PHP | Version, ini settings, memory limits, loaded extensions |
18+
| **I/O** | Disk I/O | File creation across 8 block sizes (512B–64KB) |
19+
| | File Read | Sequential reads across file sizes (1KB–4MB) |
20+
| | Network | DNS resolution and TCP connection latency |
21+
| **Compute** | CPU | Math functions, string operations, loops, conditionals |
22+
| | Memory | Allocation speed and peak memory per operation |
23+
| | Regex | `preg_match` and `preg_replace` across 10 pattern types |
24+
| | JSON | Encode/decode with small, medium, and large payloads |
25+
| | Serialization | `serialize` vs `json_encode` vs `var_export` (+ igbinary/msgpack if available) |
26+
| | Hashing | md5, sha1, sha256, sha512, xxh3, crc32, hmac-sha256, bcrypt, argon2id |
27+
| | Encryption | AES-128/256-CBC/GCM, ChaCha20-Poly1305 encrypt and decrypt |
28+
| | Compression | gzip, zlib, deflate (+ brotli/lzf if available) with compression ratios |
29+
| **Runtime** | Function Overhead | Named function, closure, first-class callable, static/instance method, arrow function |
30+
| | Type Casting | int/string/float/bool conversions |
31+
| | Object Instantiation | stdClass, DateTime, SplStack, SplPriorityQueue, ArrayObject |
32+
| **Database** | MySQL | Query performance (requires ext-mysqli) |
33+
34+
All modules can be individually enabled/disabled and configured in `config/config.yml`.
35+
36+
## Requirements
37+
38+
- PHP >= 8.4
39+
- ext-mysqli (for MySQL benchmarks, optional)
40+
41+
## Installation
42+
43+
```bash
44+
composer create-project hyperized/benchmark:dev-master
45+
cp config/config.yml.example config/config.yml
46+
```
47+
48+
Adjust `config/config.yml` to your preferences. Set module toggles, cycle counts, MySQL credentials, and optionally override the output format.
49+
50+
## Usage
51+
52+
### CLI
53+
54+
```bash
55+
php benchmark.php
56+
```
57+
58+
Example output:
59+
60+
```
61+
╔════════════════════════════════════════════════╗
62+
║ PHP Benchmark Report ║
63+
║ 2026-04-11 14:30:22 ║
64+
╚════════════════════════════════════════════════╝
65+
66+
── PHP ───────────────────────────────────────────
67+
68+
PHP Version 8.5.4
69+
Server CLI
70+
Max Memory 128.00 MB
71+
Max Upload 2.00 MB
72+
Max Exec Time 0 seconds
73+
Extensions 67 loaded
74+
75+
── Disk I/O ──────────────────────────────────────
76+
77+
Cycles: 100
78+
ms (lower is better)
79+
80+
┌──────────┬──────────┬──────────┬──────────┐
81+
│ 512.00 B │ 1.00 KB │ 2.00 KB │ 4.00 KB │ ...
82+
├──────────┼──────────┼──────────┼──────────┤
83+
│ 9.55ms │ 9.78ms │ 9.46ms │ 10.97ms │ ...
84+
└──────────┴──────────┴──────────┴──────────┘
85+
86+
── Hashing ───────────────────────────────────────
87+
88+
Hash Functions (99999 cycles, ms, lower is better)
89+
90+
┌──────────┬──────────┬──────────┬──────────┬──────────┐
91+
│ md5 │ sha1 │ sha256 │ sha512 │ xxh3 │ ...
92+
├──────────┼──────────┼──────────┼──────────┼──────────┤
93+
│ 143.00ms │ 155.71ms │ 387.45ms │ 288.95ms │ 16.35ms │ ...
94+
└──────────┴──────────┴──────────┴──────────┴──────────┘
95+
96+
...
97+
98+
──────────────────────────────────────────────────
99+
Total Duration 18.657s
100+
Report saved reports/2026-04-11_143022.json
101+
──────────────────────────────────────────────────
102+
```
103+
104+
### Web
105+
106+
```bash
107+
php -S localhost:8000 benchmark.php
108+
```
109+
110+
Open `http://localhost:8000` for a styled HTML dashboard with cards for each module.
111+
112+
### Remote
113+
114+
Install on the server via Composer and visit `/benchmark.php` in the browser.
115+
116+
### Reports
117+
118+
JSON reports are saved to `reports/` with timestamped filenames (e.g. `2026-04-11_143022.json`). Disable via `benchmark.output.report: false` in config.
119+
120+
### Configuration
121+
122+
All modules support `enabled: true/false` and configurable cycle counts:
123+
124+
```yaml
125+
benchmark:
126+
output:
127+
format: ~ # null = auto-detect, 'cli', or 'html'
128+
report: true # save JSON report to reports/
129+
php:
130+
enabled: true
131+
disk:
132+
enabled: true
133+
cycles: 100
134+
memory:
135+
enabled: true
136+
count: 9999
137+
network:
138+
enabled: true
139+
cycles: 3
140+
hosts:
141+
- google.com
142+
- cloudflare.com
143+
- github.com
144+
hashing:
145+
enabled: true
146+
hashCount: 99999
147+
passwordCount: 10
148+
# ... see config/config.yml.example for all options
149+
```
29150

30151
### Security
31-
Note that you might want to add additional security to your server to not expose the config.yml file to your webtraffic.
32152

33-
For Apache with `mod_rewrite` you can use something like this in your `.htaccess` file:
153+
If deploying to a web server, ensure `config.yml` is not publicly accessible. For Apache with `mod_rewrite`:
34154

35-
<Files "config.yml">
36-
deny from all
37-
</Files>
155+
```apache
156+
<Files "config.yml">
157+
deny from all
158+
</Files>
159+
```
38160

39-
## Contribution
40-
I'm open to improvements and new benchmarks via [pull requests](https://github.com/hyperized/benchmark/pulls)
161+
## Development
41162

42-
Issues can be reported through [Issues](https://github.com/hyperized/benchmark/issues).
43-
Please include the full output of the script and your config file without the password.
163+
```bash
164+
composer analyse # Run PHPStan (level 8)
165+
composer cs-fix # Auto-fix code style (PER-CS2.0)
166+
composer cs-check # Check code style (dry run)
167+
```
44168

45-
## Credit
46-
Credit where credit is due:
169+
## Contribution
47170

48-
- https://github.com/odan/benchmark-php
49-
- https://gist.github.com/RamadhanAmizudin/ca87f7be83c6237bb070
50-
- https://stackoverflow.com/a/25370978/1757763
51-
- http://php.net/manual/en/function.rmdir.php#119949
171+
I'm open to improvements and new benchmarks via [pull requests](https://github.com/hyperized/benchmark/pulls).
52172

173+
Issues can be reported through [Issues](https://github.com/hyperized/benchmark/issues).
174+
Please include the full output of the script and your config file without the password.
53175

54176
## License
55-
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fhyperized%2Fbenchmark.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fhyperized%2Fbenchmark?ref=badge_large)
177+
178+
MIT

autoload.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
<?php
22

3-
require 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
3+
declare(strict_types=1);
4+
5+
require 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';

benchmark.php

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
use DI\ContainerBuilder;
4-
use DI\DependencyException;
5-
use DI\NotFoundException;
66
use Hyperized\Benchmark\Benchmark;
7+
use Hyperized\Benchmark\Report\ReportWriter;
78

89
require __DIR__ . DIRECTORY_SEPARATOR . 'autoload.php';
910

1011
$builder = new ContainerBuilder();
12+
$builder->addDefinitions([
13+
ReportWriter::class => \DI\create(ReportWriter::class)
14+
->constructor(__DIR__ . DIRECTORY_SEPARATOR . 'reports'),
15+
]);
1116
$container = $builder->build();
1217

13-
echo '<pre>';
1418
try {
1519
$benchmark = $container->get(Benchmark::class);
16-
} catch (DependencyException $e) {
17-
\print_r($e);
18-
} catch (NotFoundException $e) {
19-
\print_r($e);
20+
$benchmark->execute();
21+
} catch (\Throwable $e) {
22+
if (\php_sapi_name() === 'cli') {
23+
\fwrite(STDERR, $e->getMessage() . "\n");
24+
exit(1);
25+
}
26+
echo '<pre>' . \htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5) . '</pre>';
2027
}
21-
echo '</pre>';
22-

composer.json

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,26 @@
1616
}
1717
],
1818
"require": {
19+
"php": ">=8.4",
1920
"roave/security-advisories": "dev-master",
20-
"php": ">=7.1",
21-
"symfony/config": "^4.1",
22-
"symfony/yaml": "^4.1",
23-
"php-di/php-di": "^6.0",
24-
"adbario/php-dot-notation": "2.2.0",
25-
"ext-mysqli": "^7.1"
21+
"symfony/config": "^7.0",
22+
"symfony/yaml": "^7.0",
23+
"php-di/php-di": "^7.0",
24+
"adbario/php-dot-notation": "^3.0",
25+
"ext-mysqli": "*"
26+
},
27+
"require-dev": {
28+
"phpstan/phpstan": "^2.0",
29+
"friendsofphp/php-cs-fixer": "^3.0"
2630
},
2731
"autoload": {
2832
"psr-4": {
2933
"Hyperized\\Benchmark\\": "src/"
3034
}
35+
},
36+
"scripts": {
37+
"analyse": "phpstan analyse",
38+
"cs-fix": "php-cs-fixer fix --allow-risky=yes",
39+
"cs-check": "php-cs-fixer fix --dry-run --diff --allow-risky=yes"
3140
}
3241
}

0 commit comments

Comments
 (0)