diff --git a/config/Middlewares.php b/config/Middlewares.php index 26478a3e..b10840d9 100644 --- a/config/Middlewares.php +++ b/config/Middlewares.php @@ -6,12 +6,10 @@ use App\Libs\Middlewares\{AddCorsMiddleware, AddTimingMiddleware, APIKeyRequiredMiddleware, NoAccessLogMiddleware, - ParseJsonBodyMiddleware, - ProfilerMiddleware}; + ParseJsonBodyMiddleware}; return static fn(): array => [ fn() => new AddTimingMiddleware(), - fn() => new ProfilerMiddleware(), fn() => new APIKeyRequiredMiddleware(), fn() => new ParseJsonBodyMiddleware(), fn() => new NoAccessLogMiddleware(), diff --git a/config/config.php b/config/config.php index 43c76f4f..71a34aef 100644 --- a/config/config.php +++ b/config/config.php @@ -151,12 +151,9 @@ return (function () { ]; $config['profiler'] = [ - 'sampler' => (int)env('WS_PROFILER_SAMPLER', 3000), 'save' => (bool)env('WS_PROFILER_SAVE', true), 'path' => env('WS_PROFILER_PATH', fn() => ag($config, 'tmpDir') . '/profiler'), 'collector' => env('WS_PROFILER_COLLECTOR', null), - 'flags' => ['PROFILER_CPU_PROFILING', 'PROFILER_MEMORY_PROFILING'], - 'config' => [] ]; $config['cache'] = [ diff --git a/config/env.spec.php b/config/env.spec.php index fd98c00f..6fa28bcf 100644 --- a/config/env.spec.php +++ b/config/env.spec.php @@ -176,11 +176,6 @@ return (function () { 'description' => 'The XHProf data collector URL to send the profiler data to.', 'type' => 'string', ], - [ - 'key' => 'WS_PROFILER_SAMPLER', - 'description' => 'The XHProf sampler value.', - 'type' => 'int', - ], [ 'key' => 'WS_PROFILER_SAVE', 'description' => 'Save the profiler data to disk.', diff --git a/public/index.php b/public/index.php index 56553482..b7066451 100644 --- a/public/index.php +++ b/public/index.php @@ -5,6 +5,10 @@ declare(strict_types=1); use App\Command; use App\Libs\Emitter; use App\Libs\Enums\Http\Status; +use App\Libs\Profiler; +use App\Listeners\ProcessProfileEvent; +use Nyholm\Psr7\Factory\Psr17Factory; +use Nyholm\Psr7Server\ServerRequestCreator; error_reporting(E_ALL); ini_set('error_reporting', 'On'); @@ -51,56 +55,66 @@ set_exception_handler(function (Throwable $e) { exit(Command::FAILURE); }); -try { - // -- In case the frontend proxy does not generate request unique id. - if (!isset($_SERVER['X_REQUEST_ID'])) { - $_SERVER['X_REQUEST_ID'] = bin2hex(random_bytes(16)); +$factory = new Psr17Factory(); +$request = new ServerRequestCreator($factory, $factory, $factory, $factory)->fromGlobals(); +$profiler = new Profiler(callback: fn(array $data) => queueEvent(ProcessProfileEvent::NAME, $data)); + +$exitCode = $profiler->process(function () use ($request) { + try { + // -- In case the frontend proxy does not generate request unique id. + if (!isset($_SERVER['X_REQUEST_ID'])) { + $_SERVER['X_REQUEST_ID'] = bin2hex(random_bytes(16)); + } + + $app = new App\Libs\Initializer()->boot(); + } catch (Throwable $e) { + $out = fn($message) => inContainer() ? fwrite(STDERR, $message) : syslog(LOG_ERR, $message); + + $out( + r( + text: "HTTP: Exception '{kind}' was thrown unhandled during HTTP boot context. Error '{message} @ {file}:{line}'.", + context: [ + 'kind' => $e::class, + 'line' => $e->getLine(), + 'message' => $e->getMessage(), + 'file' => after($e->getFile(), ROOT_PATH), + ] + ) + ); + + if (!headers_sent()) { + http_response_code(Status::SERVICE_UNAVAILABLE->value); + } + + return Command::FAILURE; } - $app = new App\Libs\Initializer()->boot(); -} catch (Throwable $e) { - $out = fn($message) => inContainer() ? fwrite(STDERR, $message) : syslog(LOG_ERR, $message); + try { + new Emitter()($app->http($request)); + } catch (Throwable $e) { + $out = fn($message) => inContainer() ? fwrite(STDERR, $message) : syslog(LOG_ERR, $message); - $out( - r( - text: "HTTP: Exception '{kind}' was thrown unhandled during HTTP boot context. Error '{message} @ {file}:{line}'.", - context: [ - 'kind' => $e::class, - 'line' => $e->getLine(), - 'message' => $e->getMessage(), - 'file' => after($e->getFile(), ROOT_PATH), - ] - ) - ); + $out( + r( + text: "HTTP: Exception '{kind}' was thrown unhandled during response context. Error '{message} @ {file}:{line}'.", + context: [ + 'kind' => $e::class, + 'line' => $e->getLine(), + 'message' => $e->getMessage(), + 'file' => after($e->getFile(), ROOT_PATH), + ] + ) + ); - if (!headers_sent()) { - http_response_code(Status::SERVICE_UNAVAILABLE->value); + if (!headers_sent()) { + http_response_code(Status::SERVICE_UNAVAILABLE->value); + } + return Command::FAILURE; } - exit(Command::FAILURE); + return Command::SUCCESS; +}, $request); + +if (Command::SUCCESS !== $exitCode) { + exit($exitCode); } - -try { - new Emitter()($app->http()); -} catch (Throwable $e) { - $out = fn($message) => inContainer() ? fwrite(STDERR, $message) : syslog(LOG_ERR, $message); - - $out( - r( - text: "HTTP: Exception '{kind}' was thrown unhandled during response context. Error '{message} @ {file}:{line}'.", - context: [ - 'kind' => $e::class, - 'line' => $e->getLine(), - 'message' => $e->getMessage(), - 'file' => after($e->getFile(), ROOT_PATH), - ] - ) - ); - - if (!headers_sent()) { - http_response_code(Status::SERVICE_UNAVAILABLE->value); - } - - exit(Command::FAILURE); -} - diff --git a/src/Libs/Middlewares/ProfilerMiddleware.php b/src/Libs/Middlewares/ProfilerMiddleware.php deleted file mode 100644 index 180f8c9c..00000000 --- a/src/Libs/Middlewares/ProfilerMiddleware.php +++ /dev/null @@ -1,84 +0,0 @@ -handle($request); - } - - if (Method::GET !== Method::tryFrom($request->getMethod())) { - return $handler->handle($request); - } - - $config = Config::get('profiler', []); - - if (false === $this->sample($request, $config)) { - return $handler->handle($request->withHeader('X-Profiled', 'No')); - } - - try { - $profiler = new \Xhgui\Profiler\Profiler(ag($config, 'config', [])); - $profiler->enable(ag($config, 'flags', [])); - } catch (Throwable) { - return $handler->handle($request); - } - - $response = $handler->handle($request); - - try { - $data = $profiler->disable(); - } catch (Throwable) { - $data = []; - } - - if (empty($data)) { - return $response; - } - - $data = ag_set( - $data, - 'meta.id', - ag($request->getServerParams(), 'X_REQUEST_ID', fn() => generateUUID()) - ); - - queueEvent(ProcessProfileEvent::NAME, $data); - return $response->withHeader('X-Profiled', 'Yes'); - } - - private function sample(iRequest $request, array $config): bool - { - if (true === $request->hasHeader(self::HEADER_NAME)) { - return true; - } - - if (true === ag_exists($request->getQueryParams(), self::QUERY_NAME)) { - return true; - } - - $max = (int)ag($config, 'sampler', 1000); - try { - return 1 === random_int(1, $max); - } catch (RandomException) { - return 1 === rand(1, $max); - } - } -} diff --git a/src/Libs/Profiler.php b/src/Libs/Profiler.php new file mode 100644 index 00000000..1ba72065 --- /dev/null +++ b/src/Libs/Profiler.php @@ -0,0 +1,116 @@ +fromGlobals(); + } + + if (Method::GET !== Method::tryFrom($request->getMethod())) { + return $func(); + } + + if (false === $this->sample($request)) { + return $func(); + } + + try { + $profiler = new \Xhgui\Profiler\Profiler($this->config); + $profiler->enable($this->flags); + } catch (Throwable) { + return $func(); + } + + $response = $func(); + + try { + $data = $profiler->disable(); + } catch (Throwable) { + $data = []; + } + + if (empty($data)) { + return $response; + } + + $data = ag_set( + $data, + 'meta.id', + ag($request->getServerParams(), 'X_REQUEST_ID', fn() => generateUUID()) + ); + + ($this->callback)($data, $response); + + return $response; + } + + /** + * Check if the request should be profiled. + * + * @param iRequest $request The request object. + * + * @return bool True if the request should be profiled, false otherwise. + */ + private function sample(iRequest $request): bool + { + if (true === $request->hasHeader(self::HEADER_NAME)) { + return true; + } + + if (true === ag_exists($request->getQueryParams(), self::QUERY_NAME)) { + return true; + } + + try { + return 1 === random_int(1, $this->sample); + } catch (RandomException) { + return 1 === rand(1, $this->sample); + } + } +} diff --git a/src/Listeners/ProcessProfileEvent.php b/src/Listeners/ProcessProfileEvent.php index e5832467..5bfed064 100644 --- a/src/Listeners/ProcessProfileEvent.php +++ b/src/Listeners/ProcessProfileEvent.php @@ -8,7 +8,7 @@ use App\Libs\Config; use App\Libs\Enums\Http\Method; use App\Libs\Enums\Http\Status; use App\libs\Events\DataEvent; -use App\Libs\Middlewares\ProfilerMiddleware; +use App\Libs\Profiler; use App\Libs\Stream; use App\Libs\Uri; use App\Model\Events\EventListener; @@ -113,7 +113,7 @@ final readonly class ProcessProfileEvent 'meta.SERVER.REMOTE_USER' => true, 'meta.SERVER.UNIQUE_ID' => true, 'meta.get.apikey' => true, - 'meta.get.' . ProfilerMiddleware::QUERY_NAME => false, + 'meta.get.' . Profiler::QUERY_NAME => false, ]; foreach ($maskKeys as $key => $mask) {