From b37fff1cdc305b68dfcfb787d8d864723ad94d40 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Wed, 14 Oct 2026 14:55:42 +0200 Subject: [PATCH] feat(profiling): add flush to profiler without stopping it --- analysis-baseline.toml | 2 +- src/Profiling/ContinuousProfiler.php | 9 ++++++ src/Profiling/Profiles.php | 8 +++++ src/SentrySdk.php | 2 ++ tests/SentrySdkTest.php | 45 ++++++++++++++++++++++++++++ 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/analysis-baseline.toml b/analysis-baseline.toml index 44e216a21..44e5141a9 100644 --- a/analysis-baseline.toml +++ b/analysis-baseline.toml @@ -1066,7 +1066,7 @@ count = 2 file = "src/Profiling/ContinuousProfiler.php" code = "invalid-method-access" message = "Attempting to access a method on a non-object type (`unknown-ref(ExcimerProfiler)`)." -count = 2 +count = 3 [[issues]] file = "src/Profiling/ContinuousProfiler.php" diff --git a/src/Profiling/ContinuousProfiler.php b/src/Profiling/ContinuousProfiler.php index a98d8a535..0c81a53d3 100644 --- a/src/Profiling/ContinuousProfiler.php +++ b/src/Profiling/ContinuousProfiler.php @@ -179,6 +179,15 @@ public function stop(): void $this->activeRootSpans = []; } + public function flush(): void + { + if ($this->profiler === null) { + return; + } + + $this->profiler->flush(); + } + public function isRunning(): bool { return $this->profiler !== null; diff --git a/src/Profiling/Profiles.php b/src/Profiling/Profiles.php index 167e466a8..40f263f91 100644 --- a/src/Profiling/Profiles.php +++ b/src/Profiling/Profiles.php @@ -58,6 +58,14 @@ public function stop(): void $this->continuousProfiler->stop(); } + /** + * Sends the samples collected so far to Sentry, the profiler keeps running. + */ + public function flush(): void + { + $this->continuousProfiler->flush(); + } + /** * Get the continuous profiler. * diff --git a/src/SentrySdk.php b/src/SentrySdk.php index 86f2b79ee..e0410a1b7 100644 --- a/src/SentrySdk.php +++ b/src/SentrySdk.php @@ -6,6 +6,7 @@ use Sentry\Logs\Logs; use Sentry\Metrics\TraceMetrics; +use Sentry\Profiling\Profiles; use Sentry\State\Hub; use Sentry\State\HubInterface; use Sentry\State\RuntimeContext; @@ -187,6 +188,7 @@ public static function flush(): void { Logs::getInstance()->flush(); TraceMetrics::getInstance()->flush(); + Profiles::getInstance()->flush(); $client = self::getCurrentHub()->getClient(); diff --git a/tests/SentrySdkTest.php b/tests/SentrySdkTest.php index f9d5cdb75..ebe2f75ad 100644 --- a/tests/SentrySdkTest.php +++ b/tests/SentrySdkTest.php @@ -6,11 +6,13 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Sentry\Client; use Sentry\ClientInterface; use Sentry\Event; use Sentry\Logs\Logs; use Sentry\Metrics\TraceMetrics; use Sentry\Options; +use Sentry\Profiling\Profiles; use Sentry\SentrySdk; use Sentry\State\Hub; use Sentry\State\Scope; @@ -406,6 +408,40 @@ public function testEndContextFlushesClientTransportWithOptionalTimeout(): void SentrySdk::endContext(12); } + /** + * @requires extension excimer + */ + public function testFlushSendsCollectedProfileSamplesAndKeepsProfiling(): void + { + StubTransport::$events = []; + + SentrySdk::init()->bindClient(new Client(new Options(['profile_session_sample_rate' => 1.0]), StubTransport::getInstance())); + + $profiler = Profiles::getInstance()->continuousProfiler(); + $profiler->start(); + + $profilerId = $profiler->getProfilerId(); + + self::busyWait(0.05); + + SentrySdk::flush(); + + $this->assertTrue($profiler->isRunning()); + $this->assertSame($profilerId, $profiler->getProfilerId()); + $this->assertCount(1, StubTransport::$events); + + $profileChunk = StubTransport::$events[0]->getProfileChunk(); + + $this->assertNotNull($profileChunk); + $this->assertSame($profilerId, $profileChunk->getProfilerId()); + + self::busyWait(0.05); + + $profiler->stop(); + + $this->assertCount(2, StubTransport::$events); + } + public function testFlushFlushesClientTransport(): void { /** @var ClientInterface&MockObject $client */ @@ -565,4 +601,13 @@ private function getCurrentScopeTag(string $key): ?string return $value; } + + private static function busyWait(float $seconds): void + { + $end = microtime(true) + $seconds; + + while (microtime(true) < $end) { + // Burn CPU so that Excimer collects samples + } + } }