Skip to content

Commit e5897d5

Browse files
CopilotThavarshan
andcommitted
Add DebugInfo and FetchProfiler classes with debugging and profiling support
Co-authored-by: Thavarshan <10804999+Thavarshan@users.noreply.github.com>
1 parent fc94e01 commit e5897d5

9 files changed

Lines changed: 1413 additions & 1 deletion

File tree

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Fetch\Concerns;
6+
7+
use Fetch\Support\DebugInfo;
8+
use Fetch\Support\FetchProfiler;
9+
10+
/**
11+
* Trait ManagesDebugAndProfiling
12+
*
13+
* Provides debugging and profiling capabilities for HTTP requests.
14+
*/
15+
trait ManagesDebugAndProfiling
16+
{
17+
/**
18+
* Whether debug mode is enabled.
19+
*/
20+
protected bool $debugEnabled = false;
21+
22+
/**
23+
* Debug options configuration.
24+
*
25+
* @var array<string, mixed>
26+
*/
27+
protected array $debugOptions = [];
28+
29+
/**
30+
* The profiler instance for performance tracking.
31+
*/
32+
protected ?FetchProfiler $profiler = null;
33+
34+
/**
35+
* The last debug info from the most recent request.
36+
*/
37+
protected ?DebugInfo $lastDebugInfo = null;
38+
39+
/**
40+
* Enable debug mode with specified options.
41+
*
42+
* @param array<string, mixed>|bool $options Debug options or true to enable all
43+
* @return $this
44+
*/
45+
public function withDebug(array|bool $options = true): static
46+
{
47+
$this->debugEnabled = $options !== false;
48+
49+
if (is_array($options)) {
50+
$this->debugOptions = array_merge(DebugInfo::getDefaultOptions(), $options);
51+
} else {
52+
$this->debugOptions = DebugInfo::getDefaultOptions();
53+
}
54+
55+
return $this;
56+
}
57+
58+
/**
59+
* Set a profiler for performance tracking.
60+
*
61+
* @param FetchProfiler $profiler The profiler instance
62+
* @return $this
63+
*/
64+
public function withProfiler(FetchProfiler $profiler): static
65+
{
66+
$this->profiler = $profiler;
67+
68+
return $this;
69+
}
70+
71+
/**
72+
* Get the profiler instance if set.
73+
*/
74+
public function getProfiler(): ?FetchProfiler
75+
{
76+
return $this->profiler;
77+
}
78+
79+
/**
80+
* Check if debug mode is enabled.
81+
*/
82+
public function isDebugEnabled(): bool
83+
{
84+
return $this->debugEnabled;
85+
}
86+
87+
/**
88+
* Get the debug options.
89+
*
90+
* @return array<string, mixed>
91+
*/
92+
public function getDebugOptions(): array
93+
{
94+
return $this->debugOptions;
95+
}
96+
97+
/**
98+
* Get the last debug info from the most recent request.
99+
*/
100+
public function getLastDebugInfo(): ?DebugInfo
101+
{
102+
return $this->lastDebugInfo;
103+
}
104+
105+
/**
106+
* Create debug info for the current request.
107+
*
108+
* @param string $method HTTP method
109+
* @param string $uri Request URI
110+
* @param array<string, mixed> $options Request options
111+
* @param \Psr\Http\Message\ResponseInterface|null $response The response
112+
* @param array<string, float> $timings Timing information
113+
* @param int $memoryUsage Memory usage in bytes
114+
*/
115+
protected function createDebugInfo(
116+
string $method,
117+
string $uri,
118+
array $options,
119+
?\Psr\Http\Message\ResponseInterface $response = null,
120+
array $timings = [],
121+
int $memoryUsage = 0
122+
): DebugInfo {
123+
$this->lastDebugInfo = DebugInfo::create(
124+
$method,
125+
$uri,
126+
$options,
127+
$response,
128+
$timings,
129+
$memoryUsage
130+
);
131+
132+
return $this->lastDebugInfo;
133+
}
134+
135+
/**
136+
* Start profiling for a request.
137+
*
138+
* @param string $method HTTP method
139+
* @param string $uri Request URI
140+
* @return string|null The request ID if profiling is enabled
141+
*/
142+
protected function startProfiling(string $method, string $uri): ?string
143+
{
144+
if ($this->profiler === null || ! $this->profiler->isEnabled()) {
145+
return null;
146+
}
147+
148+
$requestId = FetchProfiler::generateRequestId($method, $uri);
149+
$this->profiler->startProfile($requestId);
150+
151+
return $requestId;
152+
}
153+
154+
/**
155+
* Record a profiling event.
156+
*
157+
* @param string|null $requestId The request ID
158+
* @param string $event The event name
159+
*/
160+
protected function recordProfilingEvent(?string $requestId, string $event): void
161+
{
162+
if ($requestId === null || $this->profiler === null) {
163+
return;
164+
}
165+
166+
$this->profiler->recordEvent($requestId, $event);
167+
}
168+
169+
/**
170+
* End profiling for a request.
171+
*
172+
* @param string|null $requestId The request ID
173+
* @param int|null $statusCode HTTP status code
174+
*/
175+
protected function endProfiling(?string $requestId, ?int $statusCode = null): void
176+
{
177+
if ($requestId === null || $this->profiler === null) {
178+
return;
179+
}
180+
181+
$this->profiler->endProfile($requestId, $statusCode);
182+
}
183+
}

src/Fetch/Concerns/PerformsHttpRequests.php

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,17 +344,55 @@ protected function executeSyncRequest(
344344
array $options,
345345
float $startTime,
346346
): ResponseInterface {
347-
return $this->retryRequest(function () use ($method, $uri, $options, $startTime): ResponseInterface {
347+
// Start profiling if profiler is available
348+
$requestId = null;
349+
if (method_exists($this, 'startProfiling')) {
350+
$requestId = $this->startProfiling($method, $uri);
351+
}
352+
353+
// Track memory for debugging
354+
$startMemory = memory_get_usage(true);
355+
356+
return $this->retryRequest(function () use ($method, $uri, $options, $startTime, $requestId, $startMemory): ResponseInterface {
348357
try {
358+
// Record request sent event for profiling
359+
if ($requestId !== null && method_exists($this, 'recordProfilingEvent')) {
360+
$this->recordProfilingEvent($requestId, 'request_sent');
361+
}
362+
349363
// Send the request to Guzzle
350364
$psrResponse = $this->getHttpClient()->request($method, $uri, $options);
351365

366+
// Record response received event for profiling
367+
if ($requestId !== null && method_exists($this, 'recordProfilingEvent')) {
368+
$this->recordProfilingEvent($requestId, 'response_start');
369+
}
370+
352371
// Calculate duration
353372
$duration = microtime(true) - $startTime;
354373

355374
// Create our response object
356375
$response = Response::createFromBase($psrResponse);
357376

377+
// End profiling
378+
if ($requestId !== null && method_exists($this, 'endProfiling')) {
379+
$this->endProfiling($requestId, $response->getStatusCode());
380+
}
381+
382+
// Create debug info if debug mode is enabled
383+
if (method_exists($this, 'isDebugEnabled') && $this->isDebugEnabled()) {
384+
$memoryUsage = memory_get_usage(true) - $startMemory;
385+
$timings = [
386+
'total_time' => round($duration * 1000, 3),
387+
'start_time' => $startTime,
388+
'end_time' => microtime(true),
389+
];
390+
391+
if (method_exists($this, 'createDebugInfo')) {
392+
$this->createDebugInfo($method, $uri, $options, $response, $timings, $memoryUsage);
393+
}
394+
}
395+
358396
// Trigger retry on configured retryable status codes
359397
if (in_array($response->getStatusCode(), $this->getRetryableStatusCodes(), true)) {
360398
$psrRequest = new GuzzleRequest($method, $uri, $options['headers'] ?? []);
@@ -369,6 +407,11 @@ protected function executeSyncRequest(
369407

370408
return $response;
371409
} catch (GuzzleException $e) {
410+
// End profiling with error
411+
if ($requestId !== null && method_exists($this, 'endProfiling')) {
412+
$this->endProfiling($requestId, null);
413+
}
414+
372415
// Normalize to Fetch RequestException to participate in retry logic
373416
if ($e instanceof GuzzleRequestException) {
374417
$req = $e->getRequest();

src/Fetch/Http/ClientHandler.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Fetch\Concerns\ConfiguresRequests;
88
use Fetch\Concerns\HandlesMocking;
99
use Fetch\Concerns\HandlesUris;
10+
use Fetch\Concerns\ManagesDebugAndProfiling;
1011
use Fetch\Concerns\ManagesPromises;
1112
use Fetch\Concerns\ManagesRetries;
1213
use Fetch\Concerns\PerformsHttpRequests;
@@ -28,6 +29,7 @@ class ClientHandler implements ClientHandlerInterface
2829
use ConfiguresRequests,
2930
HandlesMocking,
3031
HandlesUris,
32+
ManagesDebugAndProfiling,
3133
ManagesPromises,
3234
ManagesRetries,
3335
PerformsHttpRequests;

src/Fetch/Interfaces/ClientHandler.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,4 +535,42 @@ public function getRetryableExceptions(): array;
535535
* @return static New client handler instance
536536
*/
537537
public function withClonedOptions(array $options): self;
538+
539+
/**
540+
* Enable debug mode with specified options.
541+
*
542+
* @param array<string, mixed>|bool $options Debug options or true to enable all
543+
* @return $this
544+
*/
545+
public function withDebug(array|bool $options = true): self;
546+
547+
/**
548+
* Set a profiler for performance tracking.
549+
*
550+
* @param \Fetch\Support\FetchProfiler $profiler The profiler instance
551+
* @return $this
552+
*/
553+
public function withProfiler(\Fetch\Support\FetchProfiler $profiler): self;
554+
555+
/**
556+
* Get the profiler instance if set.
557+
*/
558+
public function getProfiler(): ?\Fetch\Support\FetchProfiler;
559+
560+
/**
561+
* Check if debug mode is enabled.
562+
*/
563+
public function isDebugEnabled(): bool;
564+
565+
/**
566+
* Get the debug options.
567+
*
568+
* @return array<string, mixed>
569+
*/
570+
public function getDebugOptions(): array;
571+
572+
/**
573+
* Get the last debug info from the most recent request.
574+
*/
575+
public function getLastDebugInfo(): ?\Fetch\Support\DebugInfo;
538576
}

0 commit comments

Comments
 (0)