Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Command/Inspector/GetTraceCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ function () use (
$eg_address,
$sg_address,
$get_trace_settings->depth,
$trace_cache
$trace_cache,
$get_trace_settings->start_with_trigger,
);
if (!is_null($call_trace)) {
$trace_output->output($call_trace);
Expand Down
3 changes: 2 additions & 1 deletion src/Inspector/Daemon/Reader/Worker/PhpReaderTraceLoop.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ function () use (
$target_process_descriptor->eg_address,
$target_process_descriptor->sg_address,
$get_trace_settings->depth,
$trace_cache
$trace_cache,
$get_trace_settings->start_with_trigger,
);
if (is_null($call_trace)) {
return;
Expand Down
3 changes: 2 additions & 1 deletion src/Inspector/Settings/GetTraceSettings/GetTraceSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
final class GetTraceSettings
{
public function __construct(
public int $depth
public int $depth,
public bool $start_with_trigger,
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace Reli\Inspector\Settings\GetTraceSettings;

use PhpCast\Cast;
use PhpCast\NullableCast;
use Reli\Inspector\Settings\InspectorSettingsException;
use Symfony\Component\Console\Command\Command;
Expand All @@ -37,6 +38,16 @@ public function setOptions(Command $command): void
'max depth'
)
;

$command
->addOption(
'start-with-trigger',
null,
InputOption::VALUE_NEGATABLE,
'Profile only requests containing trigger query parameter',
false
)
;
}

/**
Expand All @@ -45,13 +56,14 @@ public function setOptions(Command $command): void
public function createSettings(InputInterface $input): GetTraceSettings
{
$depth = NullableCast::toString($input->getOption('depth'));
$start_with_trigger = Cast::toBool($input->getOption('start-with-trigger'));
if (is_null($depth)) {
$depth = PHP_INT_MAX;
}
$depth = filter_var($depth, FILTER_VALIDATE_INT);
if ($depth === false) {
throw GetTraceSettingsException::create(GetTraceSettingsException::DEPTH_IS_NOT_INTEGER);
}
return new GetTraceSettings($depth);
return new GetTraceSettings($depth, $start_with_trigger);
}
}
49 changes: 49 additions & 0 deletions src/Lib/PhpInternals/Types/C/RawChar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/**
* This file is part of the reliforp/reli-prof package.
*
* (c) sji <sji@sj-i.dev>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Reli\Lib\PhpInternals\Types\C;

use Reli\Lib\PhpInternals\CastedCData;
use Reli\Lib\Process\Pointer\Dereferencable;
use Reli\Lib\Process\Pointer\Pointer;

final class RawChar implements Dereferencable
{
public string $value;

/** @param CastedCData<\FFI\CChar> $casted_cdata */
public function __construct(
private CastedCData $casted_cdata,
private Pointer $pointer,
) {
$this->value = $this->casted_cdata->casted->cdata;
}

public static function getCTypeName(): string
{
return 'char';
}

public static function fromCastedCData(
CastedCData $casted_cdata,
Pointer $pointer
): static {
/** @var CastedCData<\FFI\CChar> $casted_cdata */
return new self($casted_cdata, $pointer);
}

public function getPointer(): Pointer
{
return $this->pointer;
}
}
62 changes: 60 additions & 2 deletions src/Lib/PhpProcessReader/CallTraceReader/CallTraceReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
namespace Reli\Lib\PhpProcessReader\CallTraceReader;

use Reli\Lib\PhpInternals\Opcodes\OpcodeFactory;
use Reli\Lib\PhpInternals\Types\C\RawChar;
use Reli\Lib\PhpInternals\Types\C\RawDouble;
use Reli\Lib\PhpInternals\Types\C\RawInt64;
use Reli\Lib\PhpInternals\Types\Zend\Opline;
use Reli\Lib\PhpInternals\Types\Zend\ZendCastedTypeProvider;
use Reli\Lib\PhpInternals\Types\Zend\ZendExecuteData;
use Reli\Lib\PhpInternals\Types\Zend\ZendExecutorGlobals;
use Reli\Lib\PhpInternals\Types\Zend\ZendFunction;
use Reli\Lib\PhpInternals\Types\Zend\ZendOp;
use Reli\Lib\PhpInternals\ZendTypeReader;
use Reli\Lib\PhpInternals\ZendTypeReaderCreator;
Expand All @@ -32,6 +32,8 @@

final class CallTraceReader
{
private const TRIGGER_QUERY_PARAM = 'RELI_PROFILE';

private ?ZendTypeReader $zend_type_reader = null;

public function __construct(
Expand Down Expand Up @@ -105,6 +107,53 @@ public function getGlobalRequestTime(
return $dereferencer->deref($pointer)->value;
}

/**
* @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
*/
public function getQueryString(
int $sg_address,
string $php_version,
Dereferencer $dereferencer,
): string {
$zend_type_reader = $this->getTypeReader($php_version);
[$request_info_offset, ] = $zend_type_reader->getOffsetAndSizeOfMember(
'sapi_globals_struct',
'request_info'
);

[$query_string_offset, ] = $zend_type_reader->getOffsetAndSizeOfMember(
'sapi_request_info',
'query_string'
);

$query_string_pointer_pointer = new Pointer(
RawInt64::class,
$sg_address + $request_info_offset + $query_string_offset,
8
);

$query_string_pointer = new Pointer(
RawChar::class,
$dereferencer->deref($query_string_pointer_pointer)->value,
1,
);

$pointer_index = 0;
$query_string = '';

while (true) {
$query_char = $dereferencer->deref($query_string_pointer->indexedAt($pointer_index++))->value;

if ($query_char === "\x00") {
break;
}

$query_string .= $query_char;
}

return $query_string;
}

/**
* @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
* @throws MemoryReaderException
Expand All @@ -116,13 +165,22 @@ public function readCallTrace(
int $sapi_globals_address,
int $depth,
TraceCache $trace_cache,
bool $start_with_trigger,
): ?CallTrace {
$dereferencer = $this->getDereferencer($pid, $php_version);
$eg = $this->getExecutorGlobals($executor_globals_address, $php_version, $dereferencer);
if (is_null($eg->current_execute_data)) {
return null;
}

if ($start_with_trigger) {
$query_string = $this->getQueryString($sapi_globals_address, $php_version, $dereferencer);

if (!str_contains($query_string, self::TRIGGER_QUERY_PARAM)) {
return null;
}
}

$trace_cache->updateCacheKey($this->getGlobalRequestTime($sapi_globals_address, $php_version, $dereferencer));
$cached_dereferencer = $trace_cache->getDereferencer($dereferencer);

Expand Down
2 changes: 1 addition & 1 deletion tests/Inspector/Daemon/Dispatcher/WorkerPoolTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class WorkerPoolTest extends BaseTestCase
public function testCreate()
{
$trace_settings = new TraceLoopSettings(1, 'q', 1, false);
$get_trace_settings = new GetTraceSettings(1);
$get_trace_settings = new GetTraceSettings(1, false);

$reader_context = Mockery::mock(PhpReaderControllerInterface::class);
$reader_context->expects()->start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function testIsRunning(): void
public function testSendSettings(): void
{
$trace_loop_settings = new TraceLoopSettings(1, 'q', 1, false);
$get_trace_settings = new GetTraceSettings(1);
$get_trace_settings = new GetTraceSettings(1, false);

$expected = new SetSettingsMessage(
$trace_loop_settings,
Expand Down Expand Up @@ -184,7 +184,7 @@ public function testNothingIsSentOnRecoverIfSettingsHaveNotSent()
public function testSettingsIsResentOnRecoverIfOnceSent()
{
$trace_loop_settings = new TraceLoopSettings(1, 'q', 1, false);
$get_trace_settings = new GetTraceSettings(1);
$get_trace_settings = new GetTraceSettings(1, false);

$expected = new SetSettingsMessage(
$trace_loop_settings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function testRun(): void
{
$settings = new SetSettingsMessage(
new TraceLoopSettings(1, 'q', 10, false),
new GetTraceSettings(PHP_INT_MAX)
new GetTraceSettings(PHP_INT_MAX, false)
);
$attach = new AttachMessage(
new TargetProcessDescriptor(
Expand All @@ -60,7 +60,7 @@ function (
[
new TraceLoopSettings(1, 'q', 10, false),
new TargetProcessDescriptor(123, 0, 0, ZendTypeReader::V80),
new GetTraceSettings(PHP_INT_MAX),
new GetTraceSettings(PHP_INT_MAX, false),
],
[
$trace_loop_serrings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public function testFromConsoleInput(): void
{
$input = Mockery::mock(InputInterface::class);
$input->expects()->getOption('depth')->andReturns(10);
$input->expects()->getOption('start-with-trigger')->andReturns(false);

$settings = (new GetTraceSettingsFromConsoleInput())->createSettings($input);

Expand All @@ -33,6 +34,7 @@ public function testFromConsoleInputDefault(): void
{
$input = Mockery::mock(InputInterface::class);
$input->expects()->getOption('depth')->andReturns(null);
$input->expects()->getOption('start-with-trigger')->andReturns(false);
$settings = (new GetTraceSettingsFromConsoleInput())->createSettings($input);
$this->assertSame(PHP_INT_MAX, $settings->depth);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public function wait() {
$sapi_globals_address,
PHP_INT_MAX,
new TraceCache(),
false,
);
$this->assertCount(3, $call_trace->call_frames);
$this->assertSame(
Expand Down
6 changes: 6 additions & 0 deletions tools/stubs/ffi/scalar.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ class CPointer extends CData
public int $cdata;
}

/** @extends CData<int> */
class CChar extends CData
{
public string $cdata;
}

/**
* Class CArray
*
Expand Down