Skip to content

Commit f23a466

Browse files
fulloclaude
andcommitted
Add PSR-3/PSR-20 compliance, TrendReporter, and script_filename tracking
PSR compliance: - PSR-3: LogReporter now type-hints Psr\Log\LoggerInterface instead of duck-typing with ?object. psr/log moved to require (from require-dev). - PSR-20: TimeCollector accepts optional Psr\Clock\ClockInterface for testable timing. Falls back to hrtime() when no clock is provided. psr/clock added to require. New features: - TrendReporter: generates sci-trend.txt with per-script SCI trajectories, ASCII sparklines, trend indicators (improved/stable/worse), and a chronological history table with delta markers. - RequestCollector now captures request.script_filename ($_SERVER['SCRIPT_FILENAME'] or argv[0]) for tracking which PHP file is being profiled. Both bootstrap.php and phar stub register the trend reporter. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6214d1a commit f23a466

9 files changed

Lines changed: 363 additions & 29 deletions

File tree

.claude/CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ Before every push to the repository:
4444
- All classes are `final` with `declare(strict_types=1)`
4545
- No dependencies in production code (only dev: phpunit, phpstan, php-cs-fixer)
4646
- Reporter errors are always caught silently — never break the host application
47+
- PSR-1: Basic Coding Standard
48+
- PSR-3: Logger Interface (accetteremo un PSR-3 logger opzionale)
49+
- PSR-20: Clock interface (per testabilità del timing)
4750

4851
## The phar
4952

bin/build-phar.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,10 @@
122122
$__sciProfiler->addCollector(new \SciProfiler\Collector\RequestCollector());
123123
124124
$__sciReporterMap = [
125-
'json' => static fn () => new \SciProfiler\Reporter\JsonReporter(),
126-
'log' => static fn () => new \SciProfiler\Reporter\LogReporter(),
127-
'html' => static fn () => new \SciProfiler\Reporter\HtmlReporter(),
125+
'json' => static fn () => new \SciProfiler\Reporter\JsonReporter(),
126+
'log' => static fn () => new \SciProfiler\Reporter\LogReporter(),
127+
'html' => static fn () => new \SciProfiler\Reporter\HtmlReporter(),
128+
'trend' => static fn () => new \SciProfiler\Reporter\TrendReporter(),
128129
];
129130
foreach ($__sciConfig->getReporters() as $__rName) {
130131
$__rName = trim($__rName);

bin/sci-profiler.phar

10.3 KB
Binary file not shown.

composer.json

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,14 @@
1111
}
1212
],
1313
"require": {
14-
"php": ">=8.1"
14+
"php": ">=8.1",
15+
"psr/clock": "^1.0",
16+
"psr/log": "^2.0|^3.0"
1517
},
1618
"require-dev": {
17-
"phpunit/phpunit": "^10.0|^11.0",
18-
"phpstan/phpstan": "^1.10",
1919
"friendsofphp/php-cs-fixer": "^3.0",
20-
"psr/log": "^3.0",
21-
"psr/clock": "^1.0"
22-
},
23-
"suggest": {
24-
"psr/log": "For PSR-3 compatible logging of SCI results",
25-
"psr/clock": "For PSR-20 compatible clock (testing)"
20+
"phpstan/phpstan": "^1.10",
21+
"phpunit/phpunit": "^10.0|^11.0"
2622
},
2723
"autoload": {
2824
"psr-4": {

src/Collector/RequestCollector.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ final class RequestCollector implements CollectorInterface
1414
{
1515
private string $method = '';
1616
private string $uri = '';
17+
private string $scriptFilename = '';
1718
private int $inputBytes = 0;
1819
private int $outputBytes = 0;
1920
private int $responseCode = 0;
@@ -22,6 +23,7 @@ public function start(): void
2223
{
2324
$this->method = $_SERVER['REQUEST_METHOD'] ?? 'CLI';
2425
$this->uri = $_SERVER['REQUEST_URI'] ?? ($_SERVER['SCRIPT_FILENAME'] ?? 'unknown');
26+
$this->scriptFilename = $_SERVER['SCRIPT_FILENAME'] ?? ($_SERVER['argv'][0] ?? 'unknown');
2527

2628
// Use CONTENT_LENGTH header when available to avoid reading the entire
2729
// request body into memory (file uploads, large JSON payloads, etc.).
@@ -48,6 +50,7 @@ public function getMetrics(): array
4850
return [
4951
'method' => $this->method,
5052
'uri' => $this->uri,
53+
'script_filename' => $this->scriptFilename,
5154
'response_code' => $this->responseCode,
5255
'input_bytes' => $this->inputBytes,
5356
'output_bytes' => $this->outputBytes,

src/Collector/TimeCollector.php

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,52 @@
44

55
namespace SciProfiler\Collector;
66

7+
use Psr\Clock\ClockInterface;
8+
79
/**
810
* Collects wall time and CPU time metrics.
911
*
10-
* Uses hrtime() for nanosecond-precision wall time
11-
* and getrusage() for user/system CPU time on supported platforms.
12+
* Uses hrtime() for nanosecond-precision wall time by default.
13+
* Accepts an optional PSR-20 ClockInterface for testable timing.
14+
* Uses getrusage() for user/system CPU time on supported platforms.
15+
*
16+
* @see https://www.php-fig.org/psr/psr-20/
1217
*/
1318
final class TimeCollector implements CollectorInterface
1419
{
15-
/** Whether getrusage() is available — checked once, not per call. */
20+
/** Whether getrusage() is available — checked once per process. */
1621
private static bool $hasRusage;
1722

1823
private int $startHrtime = 0;
1924
private int $stopHrtime = 0;
2025

26+
private ?\DateTimeImmutable $startTime = null;
27+
private ?\DateTimeImmutable $stopTime = null;
28+
2129
/** @var array{ru_utime.tv_sec: int, ru_utime.tv_usec: int, ru_stime.tv_sec: int, ru_stime.tv_usec: int}|null */
2230
private ?array $startRusage = null;
2331

2432
/** @var array{ru_utime.tv_sec: int, ru_utime.tv_usec: int, ru_stime.tv_sec: int, ru_stime.tv_usec: int}|null */
2533
private ?array $stopRusage = null;
2634

27-
public function __construct()
28-
{
29-
// Cache function availability once per process, not per start()/stop() call.
35+
/**
36+
* @param ClockInterface|null $clock Optional PSR-20 clock for testability.
37+
* When null, uses hrtime() for nanosecond precision.
38+
*/
39+
public function __construct(
40+
private readonly ?ClockInterface $clock = null,
41+
) {
3042
self::$hasRusage ??= function_exists('getrusage');
3143
}
3244

3345
public function start(): void
3446
{
3547
$this->startHrtime = hrtime(true);
3648

49+
if ($this->clock !== null) {
50+
$this->startTime = $this->clock->now();
51+
}
52+
3753
if (self::$hasRusage) {
3854
$this->startRusage = getrusage();
3955
}
@@ -43,16 +59,29 @@ public function stop(): void
4359
{
4460
$this->stopHrtime = hrtime(true);
4561

62+
if ($this->clock !== null) {
63+
$this->stopTime = $this->clock->now();
64+
}
65+
4666
if (self::$hasRusage) {
4767
$this->stopRusage = getrusage();
4868
}
4969
}
5070

5171
public function getMetrics(): array
5272
{
53-
$wallTimeNs = $this->stopHrtime - $this->startHrtime;
54-
$wallTimeMs = $wallTimeNs / 1_000_000;
55-
$wallTimeSec = $wallTimeNs / 1_000_000_000;
73+
// When a PSR-20 clock is provided and both timestamps exist,
74+
// use the clock for wall time (enables deterministic testing).
75+
if ($this->startTime !== null && $this->stopTime !== null) {
76+
$wallTimeSec = (float) $this->stopTime->format('U.u')
77+
- (float) $this->startTime->format('U.u');
78+
$wallTimeNs = (int) ($wallTimeSec * 1_000_000_000);
79+
$wallTimeMs = $wallTimeSec * 1_000;
80+
} else {
81+
$wallTimeNs = $this->stopHrtime - $this->startHrtime;
82+
$wallTimeMs = $wallTimeNs / 1_000_000;
83+
$wallTimeSec = $wallTimeNs / 1_000_000_000;
84+
}
5685

5786
$metrics = [
5887
'wall_time_ns' => $wallTimeNs,

src/Reporter/LogReporter.php

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,24 @@
44

55
namespace SciProfiler\Reporter;
66

7+
use Psr\Log\LoggerInterface;
78
use SciProfiler\Config;
89
use SciProfiler\ProfileResult;
910

1011
/**
1112
* Writes a human-readable log line per request.
1213
*
13-
* If a PSR-3 logger is provided, uses it. Otherwise writes to a plain file.
14+
* If a PSR-3 LoggerInterface is provided, uses it. Otherwise writes to a plain file.
15+
*
16+
* @see https://www.php-fig.org/psr/psr-3/
1417
*/
1518
final class LogReporter implements ReporterInterface
1619
{
1720
use EnsuresOutputDirectory;
1821

19-
/** @var \Psr\Log\LoggerInterface|null */
20-
private ?object $logger;
22+
private ?LoggerInterface $logger;
2123

22-
/**
23-
* @param object|null $logger Optional PSR-3 logger instance
24-
*/
25-
public function __construct(?object $logger = null)
24+
public function __construct(?LoggerInterface $logger = null)
2625
{
2726
$this->logger = $logger;
2827
}
@@ -40,7 +39,7 @@ public function report(ProfileResult $result, Config $config): void
4039
$data['memory.memory_peak_mb'] ?? '?',
4140
);
4241

43-
if ($this->logger !== null && method_exists($this->logger, 'info')) {
42+
if ($this->logger !== null) {
4443
$this->logger->info($line, $result->toArray());
4544
return;
4645
}

0 commit comments

Comments
 (0)