Skip to content

PoC: Implement span suppression strategies #1599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

Nevay
Copy link
Contributor

@Nevay Nevay commented May 17, 2025

Alternative to #1583

Span suppression is implemented in the SDK -> instrumentation does not need to handle suppression.
(Everything besides SemanticConvention and SemanticConventionResolver could be moved to the SDK.)

Provides three span suppression strategies:
Noop: does not suppress anything
SpanKind: suppresses nested spans with the same span kind (except for internal)
SemConv: attempts to resolve the semantic convention based on span attributes, otherwise falls back to span kind suppression

Noop: does not suppress anything
SpanKind: suppresses nested spans with the same span kind (except for internal)
SemConv: attempts to guess and suppress the semantic convention that is represented by the span
Copy link
Contributor

@brettmc brettmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks pretty solid. There's a lot of configuration, it seems like this will need to use declarative configuration, but that can be a follow-up change.

@brettmc
Copy link
Contributor

brettmc commented May 22, 2025

I'm happy to accept this as-is and complete it incrementally, if you don't have much time. If you're happy with that, can you add @experimental tags everywhere, and fix the (hopefully minor) CI issues. I've documented some steps to get this towards readiness in #1579

Copy link

codecov bot commented May 25, 2025

Codecov Report

Attention: Patch coverage is 9.57447% with 85 lines in your changes missing coverage. Please review.

Project coverage is 68.78%. Comparing base (2fcafab) to head (a9b6ab6).
Report is 17 commits behind head on main.

Files with missing lines Patch % Lines
...ppressionStrategy/SemanticConventionSuppressor.php 0.00% 29 Missing ⚠️
...pressionStrategy/SemanticConventionSuppression.php 0.00% 15 Missing ⚠️
...Strategy/SemanticConventionSuppressionStrategy.php 0.00% 8 Missing ⚠️
...SpanKindSuppressionStrategy/SpanKindSuppressor.php 0.00% 8 Missing ⚠️
...panKindSuppressionStrategy/SpanKindSuppression.php 0.00% 6 Missing ⚠️
...ession/NoopSuppressionStrategy/NoopSuppression.php 0.00% 4 Missing ⚠️
src/SDK/Trace/TracerProviderBuilder.php 0.00% 4 Missing ⚠️
...rumentation/SpanSuppression/SemanticConvention.php 0.00% 2 Missing ⚠️
src/SDK/Trace/Span.php 50.00% 2 Missing ⚠️
...oopSuppressionStrategy/NoopSuppressionStrategy.php 0.00% 2 Missing ⚠️
... and 3 more
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main    #1599      +/-   ##
============================================
- Coverage     69.40%   68.78%   -0.62%     
- Complexity     2805     2857      +52     
============================================
  Files           414      424      +10     
  Lines          8585     8685     +100     
============================================
+ Hits           5958     5974      +16     
- Misses         2627     2711      +84     
Flag Coverage Δ
8.1 68.47% <8.51%> (-0.60%) ⬇️
8.2 68.65% <9.57%> (-0.62%) ⬇️
8.3 68.70% <9.57%> (-0.56%) ⬇️
8.4 68.63% <9.57%> (-0.72%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/API/Trace/Span.php 94.44% <100.00%> (ø)
src/SDK/Trace/Tracer.php 89.47% <100.00%> (+0.58%) ⬆️
src/SDK/Trace/TracerProvider.php 100.00% <100.00%> (ø)
src/SDK/Trace/SpanBuilder.php 84.88% <80.00%> (-0.49%) ⬇️
...rumentation/SpanSuppression/SemanticConvention.php 0.00% <0.00%> (ø)
src/SDK/Trace/Span.php 93.42% <50.00%> (-1.21%) ⬇️
...oopSuppressionStrategy/NoopSuppressionStrategy.php 0.00% <0.00%> (ø)
...ression/NoopSuppressionStrategy/NoopSuppressor.php 0.00% <0.00%> (ø)
...uppressionStrategy/SpanKindSuppressionStrategy.php 0.00% <0.00%> (ø)
...ession/NoopSuppressionStrategy/NoopSuppression.php 0.00% <0.00%> (ø)
... and 6 more

... and 9 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2fcafab...a9b6ab6. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +26 to +27
new SemanticConvention('span.http.client', SpanKind::KIND_CLIENT, ['http.request.method', 'server.address', 'server.port', 'url.full'], ['network.peer.address', 'network.peer.port', 'error.type', 'http.request.body.size', 'http.request.header', 'http.request.method_original', 'http.request.resend_count', 'http.request.size', 'http.response.body.size', 'http.response.header', 'http.response.size', 'http.response.status_code', 'network.protocol.name', 'network.protocol.version', 'network.transport', 'user_agent.original', 'user_agent.synthetic.type', 'url.scheme', 'url.template']),
new SemanticConvention('span.http.server', SpanKind::KIND_SERVER, ['http.request.method', 'url.path', 'url.scheme'], ['network.peer.address', 'network.peer.port', 'error.type', 'http.request.body.size', 'http.request.method_original', 'http.request.size', 'http.response.body.size', 'http.response.header', 'http.response.size', 'http.response.status_code', 'network.protocol.name', 'network.protocol.version', 'network.transport', 'user_agent.synthetic.type', 'client.address', 'client.port', 'http.request.header', 'http.route', 'network.local.address', 'network.local.port', 'user_agent.original', 'server.address', 'server.port', 'url.query']),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
new SemanticConvention('span.http.client', SpanKind::KIND_CLIENT, ['http.request.method', 'server.address', 'server.port', 'url.full'], ['network.peer.address', 'network.peer.port', 'error.type', 'http.request.body.size', 'http.request.header', 'http.request.method_original', 'http.request.resend_count', 'http.request.size', 'http.response.body.size', 'http.response.header', 'http.response.size', 'http.response.status_code', 'network.protocol.name', 'network.protocol.version', 'network.transport', 'user_agent.original', 'user_agent.synthetic.type', 'url.scheme', 'url.template']),
new SemanticConvention('span.http.server', SpanKind::KIND_SERVER, ['http.request.method', 'url.path', 'url.scheme'], ['network.peer.address', 'network.peer.port', 'error.type', 'http.request.body.size', 'http.request.method_original', 'http.request.size', 'http.response.body.size', 'http.response.header', 'http.response.size', 'http.response.status_code', 'network.protocol.name', 'network.protocol.version', 'network.transport', 'user_agent.synthetic.type', 'client.address', 'client.port', 'http.request.header', 'http.route', 'network.local.address', 'network.local.port', 'user_agent.original', 'server.address', 'server.port', 'url.query']),
new SemanticConvention('span.http.client', SpanKind::KIND_CLIENT, ['http.request.method', 'server.address', 'server.port', 'url.full'], ['network.peer.address', 'network.peer.port', 'error.type', 'http.request.body.size', 'http.request.header.*', 'http.request.method_original', 'http.request.resend_count', 'http.request.size', 'http.response.body.size', 'http.response.header.*', 'http.response.size', 'http.response.status_code', 'network.protocol.name', 'network.protocol.version', 'network.transport', 'user_agent.original', 'user_agent.synthetic.type', 'url.scheme', 'url.template']),
new SemanticConvention('span.http.server', SpanKind::KIND_SERVER, ['http.request.method', 'url.path', 'url.scheme'], ['network.peer.address', 'network.peer.port', 'error.type', 'http.request.body.size', 'http.request.method_original', 'http.request.size', 'http.response.body.size', 'http.response.header.*', 'http.response.size', 'http.response.status_code', 'network.protocol.name', 'network.protocol.version', 'network.transport', 'user_agent.synthetic.type', 'client.address', 'client.port', 'http.request.header.*', 'http.route', 'network.local.address', 'network.local.port', 'user_agent.original', 'server.address', 'server.port', 'url.query']),

Implementation does not handle template types yet.

*/
interface SpanSuppressionStrategy
{
public function getSuppressor(string $name, ?string $version, ?string $schemaUrl): SpanSuppressor;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could accept InstrumentationScopeInterface instead.

public function resolveSuppression(int $spanKind, array $attributes): SpanSuppression
{
$semanticConventions = [];
foreach ($this->semanticConventions as $entry) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can precompute matching/non-matching semantic conventions for each sampling relevant attribute which allows iterating the sampling relevant attributes and combining the lists of matching/non-matching semantic conventions instead.
Was able to increase performance of SpanBuilder::startSpan() calls by ~7% for empty context, 11 span.http.client attributes and the OpenTelemetry\Example\SemanticConventionResolver. (Code to compute the masks is quite messy, thus not committing :); mentioning it as this seems like a good approach to improve performance for the semantic convention strategy.)

+--------------------------------------------------------+---------+---------+--------+---------+
| subject                                                | memory  | mode    | rstdev | stdev   |
+--------------------------------------------------------+---------+---------+--------+---------+
| benchSpanBuilderStart (noop,sampled,http)              | 1.460mb | 1.707μs | ±1.74% | 0.030μs |
| benchSpanBuilderStart (spanKind,sampled,http)          | 1.461mb | 1.816μs | ±1.38% | 0.025μs |
| benchSpanBuilderStart (semConv,sampled,http)           | 1.465mb | 2.473μs | ±1.47% | 0.036μs |
| benchSpanBuilderStart (semConv-optimized,sampled,http) | 1.475mb | 2.303μs | ±1.75% | 0.040μs |
| benchSpanBuilderStart (noop,dropped,http)              | 1.455mb | 1.052μs | ±1.49% | 0.016μs |
| benchSpanBuilderStart (spanKind,dropped,http)          | 1.456mb | 1.153μs | ±1.73% | 0.020μs |
| benchSpanBuilderStart (semConv,dropped,http)           | 1.460mb | 1.790μs | ±1.68% | 0.030μs |
| benchSpanBuilderStart (semConv-optimized,dropped,http) | 1.471mb | 1.626μs | ±2.06% | 0.033μs |
+--------------------------------------------------------+---------+---------+--------+---------+

Impact on SpanSuppressor::resolveSuppression():

+--------------------------------------------------+---------+---------+--------+---------+
| subject                                          | memory  | mode    | rstdev | stdev   |
+--------------------------------------------------+---------+---------+--------+---------+
| benchResolveSuppression (noop,http)              | 1.410mb | 0.056μs | ±2.40% | 0.001μs |
| benchResolveSuppression (spanKind,http)          | 1.410mb | 0.102μs | ±1.96% | 0.002μs |
| benchResolveSuppression (semConv,http)           | 1.427mb | 0.693μs | ±1.60% | 0.011μs |
| benchResolveSuppression (semConv-optimized,http) | 1.431mb | 0.444μs | ±1.66% | 0.007μs |
+--------------------------------------------------+---------+---------+--------+---------+
SemanticConventionSuppressor using precomputed masks
$mask = 0;
foreach ($this->attributes[$spanKind->name] ?? [] as $attribute) {
    // $attribute->m: semantic conventions that do not contain this attribute
    // $attribute->n: semantic conventions that contain this attribute as sampling relevant attribute
    $mask |= array_key_exists($attribute->name, $attributes) ? $attribute->m : $attribute->n;
}

$semanticConventions = [];
foreach ($this->semanticConventions as $i => $semanticConvention) {
    if ($mask & 1 << $i) {
        continue;
    }

    foreach ($attributes as $attribute => $_) {
        if (array_key_exists($attribute, $semanticConvention->attributes)) {
            continue;
        }
        if (array_key_exists(strrchr($attribute, '.') . '.*', $semanticConvention->attributes)) {
            continue;
        }

        continue 2;
    }

    $semanticConventions[] = $semanticConvention->name;
}
Benchmark code (using local branch of different SDK -> needs some minor changes to work with this PR)
#[RetryThreshold(5)]
#[Warmup(100)]
#[Revs(10000)]
#[Iterations(100)]
#[OutputTimeUnit('seconds')]
#[OutputMode('throughput')]
final class SpanSuppressionStrategyBench {

    private readonly SpanBuilderInterface $builder;
    private readonly SpanSuppressor $suppressor;

    public function setupTracer(array $params): void {
        $tp = (new TracerProviderBuilder())
            ->setSampler($params['sampler'])
            ->setSuppressionStrategy($params['strategy'])
            ->build();
        $tracer = $tp->getTracer('bench', schemaUrl: 'https://opentelemetry.io/schemas/1.33.0');
        $this->builder = $tracer->spanBuilder('bench')
            ->setSpanKind($params['apiKind'])
            ->setAttributes($params['attributes']);
    }

    public function setupSuppressor(array $params): void {
        $this->suppressor = $params['strategy']->getSuppressor(new InstrumentationScope('bench', null, 'https://opentelemetry.io/schemas/1.33.0', new Attributes([])));
    }

    #[BeforeMethods('setupTracer')]
    #[ParamProviders(['suppressionStrategies', 'samplers', 'attributes'])]
    public function benchSpanBuilderStart(): void {
        $this->builder->startSpan();
    }

    #[BeforeMethods('setupSuppressor')]
    #[ParamProviders(['suppressionStrategies', 'attributes'])]
    public function benchResolveSuppression(array $params): SpanSuppression {
        return $this->suppressor->resolveSuppression($params['sdkKind'], $params['attributes']);
    }

    public function suppressionStrategies(): iterable {
        yield 'noop' => ['strategy' => new NoopSuppressionStrategy()];
        yield 'spanKind' => ['strategy' => new SpanKindSuppressionStrategy()];
        yield 'semConv' => ['strategy' => new SemanticConventionSuppressionStrategySimple([new SemConvResolver()])];
        yield 'semConv-optimized' => ['strategy' => new SemanticConventionSuppressionStrategy([new SemConvResolver()])];
    }

    public function samplers(): iterable {
        yield 'sampled' => ['sampler' => new AlwaysOnSampler()];
        yield 'dropped' => ['sampler' => new AlwaysOffSampler()];
    }

    public function attributes(): iterable {
        yield 'http' => ['apiKind' => SpanKind::KIND_CLIENT, 'sdkKind' => Kind::Client, 'attributes' => new Attributes([
            'http.request.method' => 'GET',
            'server.address' => 'https://example.com',
            'server.port' => '443',
            'url.full' => 'https://example.com',
            'url.scheme' => 'https',
            'http.request.body.size' => 0,
            'network.peer.address' => '127.0.0.1',
            'network.peer.port' => '443',
            # 'network.type' => 'ipv4',
            'network.protocol.name' => 'http',
            'network.transport' => 'tcp',
            'user_agent.original' => 'i-am-a-test',
        ])];
    }
}

/**
* @param int<0, 4> $spanKind
*/
public function resolveSuppression(int $spanKind, array $attributes): SpanSuppression;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could accept AttributesInterface instead.

Note: SemanticConventionSuppressor should still operate on the underlying ::toArray() to avoid unnecessary method calls.


declare(strict_types=1);

namespace OpenTelemetry\API\Instrumentation\SpanSuppression;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to OpenTelemetry\API\Trace\SpanSuppression as it only applies to traces?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think these now belong in API\Trace

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

2 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy