Skip to content
Draft
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
144 changes: 2 additions & 142 deletions src/State/Hub.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
use Sentry\MonitorConfig;
use Sentry\NoOpClient;
use Sentry\Severity;
use Sentry\Tracing\SamplingContext;
use Sentry\Tracing\Span;
use Sentry\Tracing\Transaction;
use Sentry\Tracing\TransactionContext;
use Sentry\Tracing\TransactionSampler;

/**
* This class is a basic implementation of the {@see HubInterface} interface.
Expand Down Expand Up @@ -237,103 +237,8 @@ public function getIntegration(string $className): ?IntegrationInterface
public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction
{
$transaction = new Transaction($context, $this);
$options = $this->getClient()->getOptions();
$logger = $options->getLoggerOrNullLogger();

if (!$options->isTracingEnabled()) {
$transaction->setSampled(false);

$logger->warning(\sprintf('Transaction [%s] was started but tracing is not enabled.', (string) $transaction->getTraceId()), ['context' => $context]);

return $transaction;
}

$samplingContext = SamplingContext::getDefault($context);
$samplingContext->setAdditionalContext($customSamplingContext);

$sampleSource = 'context';
$sampleRand = $context->getMetadata()->getSampleRand();

if ($transaction->getSampled() === null) {
$tracesSampler = $options->getTracesSampler();

if ($tracesSampler !== null) {
$sampleRate = $tracesSampler($samplingContext);
$sampleSource = 'config:traces_sampler';
} else {
$parentSampleRate = $context->getMetadata()->getParentSamplingRate();
if ($parentSampleRate !== null) {
$sampleRate = $parentSampleRate;
$sampleSource = 'parent:sample_rate';
} else {
$sampleRate = $this->getSampleRate(
$samplingContext->getParentSampled(),
$options->getTracesSampleRate() ?? 0
);
$sampleSource = $samplingContext->getParentSampled() !== null ? 'parent:sampling_decision' : 'config:traces_sample_rate';
}
}

if (!$this->isValidSampleRate($sampleRate)) {
$transaction->setSampled(false);

$logger->warning(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]);

return $transaction;
}

$transaction->getMetadata()->setSamplingRate($sampleRate);

// Always overwrite the sample_rate in the DSC
$dynamicSamplingContext = $context->getMetadata()->getDynamicSamplingContext();
if ($dynamicSamplingContext !== null) {
$dynamicSamplingContext->set('sample_rate', (string) $sampleRate, true);
}

if ($sampleRate === 0.0) {
$transaction->setSampled(false);

$logger->info(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is %s.', (string) $transaction->getTraceId(), $sampleSource, $sampleRate), ['context' => $context]);

return $transaction;
}

$transaction->setSampled($sampleRand < $sampleRate);
}

if (!$transaction->getSampled()) {
$logger->info(\sprintf('Transaction [%s] was started but not sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]);

return $transaction;
}

$logger->info(\sprintf('Transaction [%s] was started and sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]);

$transaction->initSpanRecorder();

$profilesSampleSource = 'config:profiles_sample_rate';
$profilesSampler = $options->getProfilesSampler();

if ($profilesSampler !== null) {
$profilesSampleRate = $profilesSampler($samplingContext);
$profilesSampleSource = 'config:profiles_sampler';
} else {
$profilesSampleRate = $options->getProfilesSampleRate();
}

if ($profilesSampleRate === null) {
$logger->info(\sprintf('Transaction [%s] is not profiling because neither `profiles_sample_rate` nor `profiles_sampler` option is set.', (string) $transaction->getTraceId()));
} elseif (!$this->isValidSampleRate($profilesSampleRate)) {
$logger->warning(\sprintf('Transaction [%s] is not profiling because profile sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $profilesSampleSource));
} elseif ($this->sample($profilesSampleRate)) {
$logger->info(\sprintf('Transaction [%s] started profiling because it was sampled.', (string) $transaction->getTraceId()));

$transaction->initProfiler()->start();
} else {
$logger->info(\sprintf('Transaction [%s] is not profiling because it was not sampled.', (string) $transaction->getTraceId()));
}

return $transaction;
return (new TransactionSampler($this->getClient()->getOptions()))->startTransaction($transaction, $context, $customSamplingContext);
}

/**
Expand Down Expand Up @@ -377,49 +282,4 @@ private function getStackTop(): Layer
{
return $this->stack[\count($this->stack) - 1];
}

private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float
{
if ($hasParentBeenSampled === true) {
return 1.0;
}

if ($hasParentBeenSampled === false) {
return 0.0;
}

return $fallbackSampleRate;
}

/**
* @param mixed $sampleRate
*/
private function sample($sampleRate): bool
{
if ($sampleRate === 0.0 || $sampleRate === null) {
return false;
}

if ($sampleRate === 1.0) {
return true;
}

return mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() < $sampleRate;
}

/**
* @param mixed $sampleRate
*/
private function isValidSampleRate($sampleRate): bool
{
if (!\is_float($sampleRate) && !\is_int($sampleRate)) {
return false;
}

if ($sampleRate < 0 || $sampleRate > 1) {
return false;
}

return true;
}
}
177 changes: 177 additions & 0 deletions src/Tracing/TransactionSampler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php

declare(strict_types=1);

namespace Sentry\Tracing;

use Sentry\Options;

/**
* Applies tracing and profiling sampling decisions to transactions.
*
* @internal
*/
final class TransactionSampler
{
/**
* @var Options
*/
private $options;

public function __construct(Options $options)
{
$this->options = $options;
}

/**
* @param array<string, mixed> $customSamplingContext Additional context that will be passed to the {@see SamplingContext}
*/
public function startTransaction(Transaction $transaction, TransactionContext $context, array $customSamplingContext = []): Transaction
{
$logger = $this->options->getLoggerOrNullLogger();

if (!$this->options->isTracingEnabled()) {
$transaction->setSampled(false);

$logger->warning(\sprintf('Transaction [%s] was started but tracing is not enabled.', (string) $transaction->getTraceId()), ['context' => $context]);

return $transaction;
}

$samplingContext = SamplingContext::getDefault($context);
$samplingContext->setAdditionalContext($customSamplingContext);

$sampleSource = 'context';
$sampleRand = $context->getMetadata()->getSampleRand() ?? 0.0;

if ($transaction->getSampled() === null) {
$tracesSampler = $this->options->getTracesSampler();

if ($tracesSampler !== null) {
$sampleRate = $tracesSampler($samplingContext);
$sampleSource = 'config:traces_sampler';
} else {
$parentSampleRate = $context->getMetadata()->getParentSamplingRate();
if ($parentSampleRate !== null) {
$sampleRate = $parentSampleRate;
$sampleSource = 'parent:sample_rate';
} else {
$sampleRate = $this->getSampleRate(
$samplingContext->getParentSampled(),
$this->options->getTracesSampleRate() ?? 0
);
$sampleSource = $samplingContext->getParentSampled() !== null ? 'parent:sampling_decision' : 'config:traces_sample_rate';
}
}

if (!$this->isValidSampleRate($sampleRate)) {
$transaction->setSampled(false);

$logger->warning(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]);

return $transaction;
}

$transaction->getMetadata()->setSamplingRate($sampleRate);

// Always overwrite the sample_rate in the DSC
$dynamicSamplingContext = $context->getMetadata()->getDynamicSamplingContext();
if ($dynamicSamplingContext !== null) {
$dynamicSamplingContext->set('sample_rate', (string) $sampleRate, true);
}

if ($sampleRate === 0.0) {
$transaction->setSampled(false);

$logger->info(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is %s.', (string) $transaction->getTraceId(), $sampleSource, $sampleRate), ['context' => $context]);

return $transaction;
}

$transaction->setSampled($sampleRand < $sampleRate);
}

if (!$transaction->getSampled()) {
$logger->info(\sprintf('Transaction [%s] was started but not sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]);

return $transaction;
}

$logger->info(\sprintf('Transaction [%s] was started and sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]);

$transaction->initSpanRecorder();

$profilesSampleSource = 'config:profiles_sample_rate';
$profilesSampler = $this->options->getProfilesSampler();

if ($profilesSampler !== null) {
$profilesSampleRate = $profilesSampler($samplingContext);
$profilesSampleSource = 'config:profiles_sampler';
} else {
$profilesSampleRate = $this->options->getProfilesSampleRate();
}

if ($profilesSampleRate === null) {
$logger->info(\sprintf('Transaction [%s] is not profiling because neither `profiles_sample_rate` nor `profiles_sampler` option is set.', (string) $transaction->getTraceId()));
} elseif (!$this->isValidSampleRate($profilesSampleRate)) {
$logger->warning(\sprintf('Transaction [%s] is not profiling because profile sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $profilesSampleSource));
} elseif ($this->sampleRate($profilesSampleRate)) {
$logger->info(\sprintf('Transaction [%s] started profiling because it was sampled.', (string) $transaction->getTraceId()));

$transaction->initProfiler()->start();
} else {
$logger->info(\sprintf('Transaction [%s] is not profiling because it was not sampled.', (string) $transaction->getTraceId()));
}

return $transaction;
}

private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float
{
if ($hasParentBeenSampled === true) {
return 1.0;
}

if ($hasParentBeenSampled === false) {
return 0.0;
}

return $fallbackSampleRate;
}

/**
* @param mixed $sampleRate
*/
private function sampleRate($sampleRate): bool
{
if (!\is_float($sampleRate) && !\is_int($sampleRate)) {
return false;
}

if ($sampleRate === 0.0) {
return false;
}

if ($sampleRate === 1.0) {
return true;
}

return mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() < (float) $sampleRate;
}

/**
* @param mixed $sampleRate
*/
private function isValidSampleRate($sampleRate): bool
{
if (!\is_float($sampleRate) && !\is_int($sampleRate)) {
return false;
}

if ($sampleRate < 0 || $sampleRate > 1) {
return false;
}

return true;
}
}
Loading
Loading