Skip to content

Commit 81b8f96

Browse files
authored
feat(profiling): add profiles_sampler option (#2082)
1 parent 4b0aacc commit 81b8f96

5 files changed

Lines changed: 174 additions & 2 deletions

File tree

src/Options.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,36 @@ public function setProfilesSampleRate(?float $sampleRate): self
293293
return $this;
294294
}
295295

296+
/**
297+
* Gets a callback that will be invoked when we sample a profile.
298+
*
299+
* @phpstan-return null|callable(Tracing\SamplingContext): float
300+
*/
301+
public function getProfilesSampler(): ?callable
302+
{
303+
/** @var callable(Tracing\SamplingContext): float|null $value */
304+
$value = $this->options['profiles_sampler'];
305+
306+
return $value;
307+
}
308+
309+
/**
310+
* Sets a callback that will be invoked when we take the profiling sampling decision.
311+
* Return a number between 0 and 1 to define the sample rate for the provided SamplingContext.
312+
*
313+
* @param ?callable $sampler The sampler
314+
*
315+
* @phpstan-param null|callable(Tracing\SamplingContext): float $sampler
316+
*/
317+
public function setProfilesSampler(?callable $sampler): self
318+
{
319+
$options = array_merge($this->options, ['profiles_sampler' => $sampler]);
320+
321+
$this->options = $this->resolver->resolve($options);
322+
323+
return $this;
324+
}
325+
296326
/**
297327
* Gets whether tracing is enabled or not. The feature is enabled when at
298328
* least one of the `traces_sample_rate` and `traces_sampler` options is
@@ -1395,6 +1425,7 @@ private function configureOptions(OptionsResolver $resolver): void
13951425
'traces_sample_rate' => null,
13961426
'traces_sampler' => null,
13971427
'profiles_sample_rate' => null,
1428+
'profiles_sampler' => null,
13981429
'attach_stacktrace' => false,
13991430
/**
14001431
* @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x.
@@ -1474,6 +1505,7 @@ private function configureOptions(OptionsResolver $resolver): void
14741505
$resolver->setAllowedTypes('traces_sample_rate', ['null', 'int', 'float']);
14751506
$resolver->setAllowedTypes('traces_sampler', ['null', 'callable']);
14761507
$resolver->setAllowedTypes('profiles_sample_rate', ['null', 'int', 'float']);
1508+
$resolver->setAllowedTypes('profiles_sampler', ['null', 'callable']);
14771509
$resolver->setAllowedTypes('attach_stacktrace', 'bool');
14781510
$resolver->setAllowedTypes('attach_metric_code_locations', 'bool');
14791511
$resolver->setAllowedTypes('context_lines', ['null', 'int']);

src/State/Hub.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,20 @@ public function startTransaction(TransactionContext $context, array $customSampl
328328

329329
$transaction->initSpanRecorder();
330330

331-
$profilesSampleRate = $options->getProfilesSampleRate();
331+
$profilesSampleSource = 'config:profiles_sample_rate';
332+
$profilesSampler = $options->getProfilesSampler();
333+
334+
if ($profilesSampler !== null) {
335+
$profilesSampleRate = $profilesSampler($samplingContext);
336+
$profilesSampleSource = 'config:profiles_sampler';
337+
} else {
338+
$profilesSampleRate = $options->getProfilesSampleRate();
339+
}
340+
332341
if ($profilesSampleRate === null) {
333-
$logger->info(\sprintf('Transaction [%s] is not profiling because `profiles_sample_rate` option is not set.', (string) $transaction->getTraceId()));
342+
$logger->info(\sprintf('Transaction [%s] is not profiling because neither `profiles_sample_rate` nor `profiles_sampler` option is set.', (string) $transaction->getTraceId()));
343+
} elseif (!$this->isValidSampleRate($profilesSampleRate)) {
344+
$logger->warning(\sprintf('Transaction [%s] is not profiling because profile sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $profilesSampleSource));
334345
} elseif ($this->sample($profilesSampleRate)) {
335346
$logger->info(\sprintf('Transaction [%s] started profiling because it was sampled.', (string) $transaction->getTraceId()));
336347

src/functions.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
* org_id?: int|null,
5959
* prefixes?: array<string>,
6060
* profiles_sample_rate?: int|float|null,
61+
* profiles_sampler?: callable|null,
6162
* release?: string|null,
6263
* sample_rate?: float|int,
6364
* send_attempts?: int,

tests/OptionsTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,13 @@ static function (): void {},
163163
'setProfilesSampleRate',
164164
];
165165

166+
yield [
167+
'profiles_sampler',
168+
static function (): void {},
169+
'getProfilesSampler',
170+
'setProfilesSampler',
171+
];
172+
166173
yield [
167174
'attach_stacktrace',
168175
false,

tests/State/HubTest.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,127 @@ public function testStartTransactionWithCustomSamplingContext(): void
834834
$hub->startTransaction(new TransactionContext(), $customSamplingContext);
835835
}
836836

837+
public function testStartTransactionStartsProfilerWithProfilesSampler(): void
838+
{
839+
$client = $this->createMock(ClientInterface::class);
840+
$client->expects($this->exactly(2))
841+
->method('getOptions')
842+
->willReturn(new Options([
843+
'traces_sample_rate' => 1.0,
844+
'profiles_sampler' => static function (): float {
845+
return 1.0;
846+
},
847+
]));
848+
849+
$hub = new Hub($client);
850+
$transaction = $hub->startTransaction(new TransactionContext());
851+
852+
$this->assertTrue($transaction->getSampled());
853+
$this->assertNotNull($transaction->getProfiler());
854+
}
855+
856+
public function testStartTransactionDoesNotStartProfilerWhenProfilesSamplerReturnsZero(): void
857+
{
858+
$client = $this->createMock(ClientInterface::class);
859+
$client->expects($this->once())
860+
->method('getOptions')
861+
->willReturn(new Options([
862+
'traces_sample_rate' => 1.0,
863+
'profiles_sampler' => static function (): float {
864+
return 0.0;
865+
},
866+
]));
867+
868+
$hub = new Hub($client);
869+
$transaction = $hub->startTransaction(new TransactionContext());
870+
871+
$this->assertTrue($transaction->getSampled());
872+
$this->assertNull($transaction->getProfiler());
873+
}
874+
875+
public function testStartTransactionPrefersProfilesSamplerOverProfilesSampleRate(): void
876+
{
877+
$client = $this->createMock(ClientInterface::class);
878+
$client->expects($this->once())
879+
->method('getOptions')
880+
->willReturn(new Options([
881+
'traces_sample_rate' => 1.0,
882+
'profiles_sample_rate' => 1.0,
883+
'profiles_sampler' => static function (): float {
884+
return 0.0;
885+
},
886+
]));
887+
888+
$hub = new Hub($client);
889+
$transaction = $hub->startTransaction(new TransactionContext());
890+
891+
$this->assertTrue($transaction->getSampled());
892+
$this->assertNull($transaction->getProfiler());
893+
}
894+
895+
public function testStartTransactionWithProfilesSamplerReceivesCustomSamplingContext(): void
896+
{
897+
$customSamplingContext = ['a' => 'b'];
898+
899+
$client = $this->createMock(ClientInterface::class);
900+
$client->expects($this->once())
901+
->method('getOptions')
902+
->willReturn(new Options([
903+
'traces_sample_rate' => 1.0,
904+
'profiles_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext): float {
905+
$this->assertSame($samplingContext->getAdditionalContext(), $customSamplingContext);
906+
907+
return 0.0;
908+
},
909+
]));
910+
911+
$hub = new Hub($client);
912+
$hub->startTransaction(new TransactionContext(), $customSamplingContext);
913+
}
914+
915+
public function testStartTransactionDoesNotStartProfilerWhenProfilesSamplerReturnsInvalidValue(): void
916+
{
917+
$client = $this->createMock(ClientInterface::class);
918+
$client->expects($this->once())
919+
->method('getOptions')
920+
->willReturn(new Options([
921+
'traces_sample_rate' => 1.0,
922+
'profiles_sampler' => static function (): string {
923+
return 'foo';
924+
},
925+
]));
926+
927+
$hub = new Hub($client);
928+
$transaction = $hub->startTransaction(new TransactionContext());
929+
930+
$this->assertTrue($transaction->getSampled());
931+
$this->assertNull($transaction->getProfiler());
932+
}
933+
934+
public function testStartTransactionDoesNotCallProfilesSamplerWhenTransactionIsNotSampled(): void
935+
{
936+
$profilesSamplerInvoked = false;
937+
938+
$client = $this->createMock(ClientInterface::class);
939+
$client->expects($this->once())
940+
->method('getOptions')
941+
->willReturn(new Options([
942+
'traces_sample_rate' => 0.0,
943+
'profiles_sampler' => static function () use (&$profilesSamplerInvoked): float {
944+
$profilesSamplerInvoked = true;
945+
946+
return 1.0;
947+
},
948+
]));
949+
950+
$hub = new Hub($client);
951+
$transaction = $hub->startTransaction(new TransactionContext());
952+
953+
$this->assertFalse($transaction->getSampled());
954+
$this->assertFalse($profilesSamplerInvoked);
955+
$this->assertNull($transaction->getProfiler());
956+
}
957+
837958
public function testStartTransactionUpdatesTheDscSampleRate(): void
838959
{
839960
$client = $this->createMock(ClientInterface::class);

0 commit comments

Comments
 (0)