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
77 changes: 77 additions & 0 deletions src/Profiling/Profiles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace Sentry\Profiling;

use Sentry\SentrySdk;

class Profiles
{
/**
* @var self|null
*/
private static $instance;

/**
* @var ContinuousProfiler
*/
private $continuousProfiler;

private function __construct()
{
$this->continuousProfiler = new ContinuousProfiler();
}

public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}

return self::$instance;
}

/**
* Starts the continuous profiler if the "profile_lifecycle" option is set to "manual"
* and the profiling session is sampled.
*/
public function start(): void
{
if (!$this->isManualLifecycle()) {
return;
}

$this->continuousProfiler->start();
}

/**
* Stops the continuous profiler if the "profile_lifecycle" option is set to "manual"
* and sends the collected profile to Sentry.
*/
public function stop(): void
{
if (!$this->isManualLifecycle()) {
return;
}

$this->continuousProfiler->stop();
}

/**
* Get the continuous profiler.
*
* @internal
*/
public function continuousProfiler(): ContinuousProfiler
{
return $this->continuousProfiler;
}

private function isManualLifecycle(): bool
{
$client = SentrySdk::getCurrentHub()->getClient();

return $client !== null && $client->getOptions()->getProfileLifecycle() === 'manual';
}
}
9 changes: 9 additions & 0 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Sentry\Logs\Logs;
use Sentry\Metrics\Metrics;
use Sentry\Metrics\TraceMetrics;
use Sentry\Profiling\Profiles;
use Sentry\State\HubInterface;
use Sentry\State\Scope;
use Sentry\Tracing\PropagationContext;
Expand Down Expand Up @@ -518,6 +519,14 @@ function traceMetrics(): TraceMetrics
return TraceMetrics::getInstance();
}

/**
* Get the Sentry Profiles client.
*/
function profiler(): Profiles
{
return Profiles::getInstance();
}

/**
* @deprecated use `traceMetrics` instead
*/
Expand Down
75 changes: 75 additions & 0 deletions tests/FunctionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
use Sentry\Event;
use Sentry\EventHint;
use Sentry\EventId;
use Sentry\EventType;
use Sentry\Integration\OTLPIntegration;
use Sentry\MonitorConfig;
use Sentry\MonitorSchedule;
use Sentry\Options;
use Sentry\Profiling\Profiles;
use Sentry\SentrySdk;
use Sentry\Severity;
use Sentry\State\Hub;
Expand Down Expand Up @@ -47,6 +49,7 @@
use function Sentry\getOtlpTracesEndpointUrl;
use function Sentry\getTraceparent;
use function Sentry\init;
use function Sentry\profiler;
use function Sentry\startContext;
use function Sentry\startTransaction;
use function Sentry\trace;
Expand Down Expand Up @@ -859,4 +862,76 @@ public function testContinueTraceWhenOrgMatch(): void
$this->assertSame('1', $dynamicSamplingContext->get('org_id'));
});
}

/**
* @requires extension excimer
*/
public function testProfilerStartAndStopCaptureProfileChunk(): void
{
$client = $this->createMock(ClientInterface::class);
$client->method('getOptions')
->willReturn(new Options([
'profile_lifecycle' => 'manual',
'profile_session_sample_rate' => 1.0,
]));

$client->expects($this->once())
->method('captureEvent')
->with($this->callback(static function (Event $event): bool {
return $event->getType() === EventType::profileChunk() && $event->getProfileChunk() !== null;
}));

SentrySdk::setCurrentHub(new Hub($client));

profiler()->start();

$this->assertTrue(Profiles::getInstance()->continuousProfiler()->isRunning());

self::busyWait(0.05);

profiler()->stop();

$this->assertFalse(Profiles::getInstance()->continuousProfiler()->isRunning());
}

/**
* @requires extension excimer
*/
public function testProfilerStopDoesNotStopTraceLifecycleProfiler(): void
{
$client = $this->createMock(ClientInterface::class);
$client->method('getOptions')
->willReturn(new Options([
'profile_lifecycle' => 'trace',
'profile_session_sample_rate' => 1.0,
]));

$client->expects($this->once())
->method('captureEvent')
->with($this->callback(static function (Event $event): bool {
return $event->getType() === EventType::profileChunk() && $event->getProfileChunk() !== null;
}));

SentrySdk::setCurrentHub(new Hub($client));

$profiler = Profiles::getInstance()->continuousProfiler();
$profiler->start();

self::busyWait(0.05);

profiler()->stop();

$this->assertTrue($profiler->isRunning());

$profiler->stop();
}

private static function busyWait(float $seconds): void
{
$end = microtime(true) + $seconds;

while (microtime(true) < $end) {
// Burn CPU so that Excimer collects samples
}
}
}
Loading