diff --git a/Common/Export/Http/PsrTransportFactory.php b/Common/Export/Http/PsrTransportFactory.php index 433ef7c..23e5dca 100644 --- a/Common/Export/Http/PsrTransportFactory.php +++ b/Common/Export/Http/PsrTransportFactory.php @@ -10,6 +10,7 @@ use Http\Discovery\Psr18ClientDiscovery; use InvalidArgumentException; use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface; +use OpenTelemetry\SDK\Common\Export\TransportInterface; use OpenTelemetry\SDK\Common\Http\Psr\Client\Discovery; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; @@ -38,7 +39,7 @@ public function create( ?string $cacert = null, ?string $cert = null, ?string $key = null, - ): PsrTransport { + ): TransportInterface { if (!filter_var($endpoint, FILTER_VALIDATE_URL)) { throw new InvalidArgumentException(sprintf('Invalid endpoint url "%s"', $endpoint)); } diff --git a/Trace/Sampler/TraceIdRatioBasedSampler.php b/Trace/Sampler/TraceIdRatioBasedSampler.php index 4f49e58..10d58ee 100644 --- a/Trace/Sampler/TraceIdRatioBasedSampler.php +++ b/Trace/Sampler/TraceIdRatioBasedSampler.php @@ -4,12 +4,20 @@ namespace OpenTelemetry\SDK\Trace\Sampler; +use function assert; +use function bin2hex; use InvalidArgumentException; use OpenTelemetry\Context\ContextInterface; use OpenTelemetry\SDK\Common\Attribute\AttributesInterface; use OpenTelemetry\SDK\Trace\SamplerInterface; use OpenTelemetry\SDK\Trace\SamplingResult; use OpenTelemetry\SDK\Trace\Span; +use function pack; +use function rtrim; +use function sprintf; +use function substr; +use function substr_compare; +use function unpack; /** * This implementation of the SamplerInterface records with given probability. @@ -22,22 +30,22 @@ class TraceIdRatioBasedSampler implements SamplerInterface { private readonly float $probability; + private readonly string $tv; /** * @param float $probability Probability float value between 0.0 and 1.0. + * @param int<1, 14> $precision threshold precision in hexadecimal digits */ - public function __construct(float $probability) + public function __construct(float $probability, int $precision = 4) { - if ($probability < 0.0 || $probability > 1.0) { + if (!($probability >= 0 && $probability <= 1)) { throw new InvalidArgumentException('probability should be be between 0.0 and 1.0.'); } + $this->probability = $probability; + $this->tv = rtrim(bin2hex(substr(pack('J', self::computeTValue($probability, $precision, 4)), 1)), '0') ?: '0'; } - /** - * Returns `SamplingResult` based on probability. Respects the parent `SampleFlag` - * {@inheritdoc} - */ public function shouldSample( ContextInterface $parentContext, string $traceId, @@ -46,22 +54,62 @@ public function shouldSample( AttributesInterface $attributes, array $links, ): SamplingResult { - // TODO: Add config to adjust which spans get sampled (only default from specification is implemented) - $parentSpan = Span::fromContext($parentContext); - $parentSpanContext = $parentSpan->getContext(); - $traceState = $parentSpanContext->getTraceState(); + $traceState = Span::fromContext($parentContext)->getContext()->getTraceState(); - /** - * Since php can only store up to 63 bit positive integers - */ - $traceIdLimit = (1 << 60) - 1; - $lowerOrderBytes = hexdec(substr($traceId, strlen($traceId) - 15, 15)); - $traceIdCondition = $lowerOrderBytes < round($this->probability * $traceIdLimit); - $decision = $traceIdCondition ? SamplingResult::RECORD_AND_SAMPLE : SamplingResult::DROP; + $decision = $this->probability >= 2 ** -56 && substr_compare($traceId, $this->tv, -14) >= 0 + ? SamplingResult::RECORD_AND_SAMPLE + : SamplingResult::DROP; return new SamplingResult($decision, [], $traceState); } + /** + * Computes the 56-bit rejection threshold (T-value) for a given probability. + * + * The T-value is computed as `2**56*(1-$probability)` with a precision of + * `2**-($wordSize*⌈-log2($probability)/$wordSize+$precision-1⌉)`. + * + * Values below `2**-56` will return `0`. + * + * ``` + * 1/3 w/ precision=3, wordSize=4 + * => 1 - 1/3 + * => 2/3 + * => 2730.666../4096 + * => 2731/4096 + * => 0xaab + * ``` + * + * Converting the result into `th` hexadecimal value: + * ``` + * $th = rtrim(bin2hex(substr(pack('J', $t), 1)), '0') ?: '0'; + * ``` + * + * @param float $probability sampling probability, must be between 0 and 1 + * @param int $precision precision in words + * @param int $wordSize word size to use, must be a power of two + * @return int 56bit T-value + * + * @internal + */ + public static function computeTValue(float $probability, int $precision, int $wordSize = 1): int + { + assert($probability >= 0 && $probability <= 1); + assert($precision >= 1); + assert($wordSize >= 1 && ($wordSize & $wordSize - 1) === 0); + + $b = unpack('J', pack('E', $probability))[1]; + $e = $b >> 52 & (1 << 11) - 1; + $f = $b & (1 << 52) - 1 | ($e ? 1 << 52 : 0); + + // 56+1bit for rounding + $s = $e - 1023 - 52 + 57; + $t = (1 << 57) - ($s < 0 ? $f >> -$s : $f << $s); + $m = -1 << 56 >> (-($e - 1023 + 1) + $precision * $wordSize & -$wordSize); + + return $t - $m >> 1 & $m; + } + public function getDescription(): string { return sprintf('%s{%.6F}', 'TraceIdRatioBasedSampler', $this->probability);