Skip to content

Commit

Permalink
implement rule-based sampler (#279)
Browse files Browse the repository at this point in the history
* initial port from Nevay/otel-sdk-contrib-sampler
* rename key to contrib_rule_based
to avoid future collision with an official rule-based sampler, change the name to something less likely to conflict.
  • Loading branch information
brettmc committed Sep 12, 2024
1 parent 9b97522 commit e5a13ce
Show file tree
Hide file tree
Showing 35 changed files with 1,810 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
'Propagation/TraceResponse',
'ResourceDetectors/Azure',
'ResourceDetectors/Container',
'Sampler/RuleBased',
'Shims/OpenTracing',
'Symfony'
]
Expand Down Expand Up @@ -111,6 +112,10 @@ jobs:
php-version: 7.4
- project: 'ResourceDetectors/Container'
php-version: 7.4
- project: 'Sampler/RuleBased'
php-version: 7.4
- project: 'Sampler/RuleBased'
php-version: 8.0
steps:
- uses: actions/checkout@v4

Expand Down
2 changes: 2 additions & 0 deletions .gitsplit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ splits:
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-detector-azure.git"
- prefix: "src/ResourceDetectors/Container"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-detector-container.git"
- prefix: "src/Sampler/RuleBased"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-sampler-rulebased.git"
- prefix: "src/Shims/OpenTracing"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-shim-opentracing.git"
# List of references to split (defined as regexp)
Expand Down
13 changes: 13 additions & 0 deletions src/Sampler/RuleBased/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
* text=auto

*.md diff=markdown
*.php diff=php

/.gitattributes export-ignore
/.gitignore export-ignore
/.phan export-ignore
/.php-cs-fixer.php export-ignore
/phpstan.neon.dist export-ignore
/phpunit.xml.dist export-ignore
/psalm.xml.dist export-ignore
/tests export-ignore
1 change: 1 addition & 0 deletions src/Sampler/RuleBased/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.phpunit.cache
370 changes: 370 additions & 0 deletions src/Sampler/RuleBased/.phan/config.php

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions src/Sampler/RuleBased/.php-cs-fixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
$finder = PhpCsFixer\Finder::create()
->exclude('vendor')
->exclude('var/cache')
->in(__DIR__);

$config = new PhpCsFixer\Config();
return $config->setRules([
'concat_space' => ['spacing' => 'one'],
'declare_equal_normalize' => ['space' => 'none'],
'is_null' => true,
'modernize_types_casting' => true,
'ordered_imports' => true,
'php_unit_construct' => true,
'single_line_comment_style' => true,
'yoda_style' => false,
'@PSR2' => true,
'array_syntax' => ['syntax' => 'short'],
'blank_line_after_opening_tag' => true,
'blank_line_before_statement' => true,
'cast_spaces' => true,
'declare_strict_types' => true,
'type_declaration_spaces' => true,
'include' => true,
'lowercase_cast' => true,
'new_with_parentheses' => true,
'no_extra_blank_lines' => true,
'no_leading_import_slash' => true,
'echo_tag_syntax' => true,
'no_unused_imports' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'phpdoc_order' => true,
'phpdoc_scalar' => true,
'phpdoc_types' => true,
'short_scalar_cast' => true,
'blank_lines_before_namespace' => true,
'single_quote' => true,
'trailing_comma_in_multiline' => true,
])
->setRiskyAllowed(true)
->setFinder($finder);

87 changes: 87 additions & 0 deletions src/Sampler/RuleBased/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Contrib Sampler

Provides additional samplers that are not part of the official specification.

## Installation

```shell
composer require open-telemetry/sampler-rule-based
```

## RuleBasedSampler

Allows sampling based on a list of rule sets. The first matching rule set will decide the sampling result.

```php
$sampler = new RuleBasedSampler(
[
new RuleSet(
[
new SpanKindRule(Kind::Server),
new AttributeRule('url.path', '~^/health$~'),
],
new AlwaysOffSampler(),
),
],
new AlwaysOnSampler(),
);
```

### Configuration

###### Example: drop spans for the /health endpoint

```yaml
contrib_rule_based:
rule_sets:
- rules:
- span_kind: { kind: SERVER }
- attribute: { key: url.path, pattern: ~^/health$~ }
delegate:
always_off: {}
fallback: # ...
```
###### Example: sample spans with at least one sampled link
```yaml
contrib_rule_based:
rule_sets:
- rules: [ link: { sampled: true } ]
delegate:
always_on: {}
fallback: # ...
```
###### Example: modeling parent based sampler as rule based sampler
```yaml
rule_based:
rule_sets:
- rules: [ parent: { sampled: true, remote: true } ]
delegate: # remote_parent_sampled
- rules: [ parent: { sampled: false, remote: true } ]
delegate: # remote_parent_not_sampled
- rules: [ parent: { sampled: true, remote: false } ]
delegate: # local_parent_sampled
- rules: [ parent: { sampled: false, remote: false } ]
delegate: # local_parent_not_sampled
fallback: # root
```
## AlwaysRecordingSampler
Records all spans to allow the usage of span processors that generate metrics from spans.
```php
$sampler = new AlwaysRecordingSampler(
new ParentBasedSampler(new AlwaysOnSampler()),
);
```

### Configuration

```yaml
always_recording:
sampler: # ...
```
50 changes: 50 additions & 0 deletions src/Sampler/RuleBased/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "open-telemetry/sampler-rule-based",
"description": "OpenTelemetry SDK rule-based sampler",
"keywords": ["opentelemetry", "otel", "sdk", "tracing", "sampler"],
"license": "Apache-2.0",
"require": {
"php": "^8.1",
"open-telemetry/api": "dev-main as 1.1.0",
"open-telemetry/sdk": "dev-main as 1.1.0",
"open-telemetry/sdk-configuration": "dev-main as 0.99"
},
"require-dev": {
"symfony/config": "^5.4 || ^6.4 || ^7.0",
"symfony/yaml": "^6 || ^7",
"friendsofphp/php-cs-fixer": "^3",
"phan/phan": "^5.0",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"psalm/plugin-phpunit": "^0.18.4",
"phpunit/phpunit": "^10 || ^11",
"vimeo/psalm": "^4|^5"
},
"autoload": {
"psr-4": {
"OpenTelemetry\\Contrib\\Sampler\\RuleBased\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-main": "0.1.x-dev"
},
"spi": {
"OpenTelemetry\\Config\\SDK\\Configuration\\ComponentProvider": [
"OpenTelemetry\\Contrib\\Sampler\\RuleBased\\ComponentProvider\\SamplerRuleBased",

"OpenTelemetry\\Contrib\\Sampler\\RuleBased\\ComponentProvider\\SamplingRuleAttribute",
"OpenTelemetry\\Contrib\\Sampler\\RuleBased\\ComponentProvider\\SamplingRuleLink",
"OpenTelemetry\\Contrib\\Sampler\\RuleBased\\ComponentProvider\\SamplingRuleParent",
"OpenTelemetry\\Contrib\\Sampler\\RuleBased\\ComponentProvider\\SamplingRuleSpanKind",
"OpenTelemetry\\Contrib\\Sampler\\RuleBased\\ComponentProvider\\SamplingRuleSpanName"
]
}
},
"config": {
"allow-plugins": {
"php-http/discovery": false,
"tbachert/spi": true
}
}
}
14 changes: 14 additions & 0 deletions src/Sampler/RuleBased/phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon

parameters:
tmpDir: var/cache/phpstan
level: 5
paths:
- src
- tests
ignoreErrors:
-
message: "#Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface::.*#"
paths:
- src/
22 changes: 22 additions & 0 deletions src/Sampler/RuleBased/phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" backupGlobals="false" cacheResult="false" colors="false" processIsolation="false" stopOnError="false" stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false" stopOnRisky="false" timeoutForSmallTests="1" timeoutForMediumTests="10" timeoutForLargeTests="60" cacheDirectory=".phpunit.cache" backupStaticProperties="false" requireCoverageMetadata="false">
<php>
<ini name="date.timezone" value="UTC"/>
<ini name="display_errors" value="On"/>
<ini name="display_startup_errors" value="On"/>
<ini name="error_reporting" value="E_ALL"/>
</php>
<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>src</directory>
</include>
</source>
</phpunit>
34 changes: 34 additions & 0 deletions src/Sampler/RuleBased/psalm.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0"?>
<psalm
errorLevel="3"
cacheDirectory="var/cache/psalm"
findUnusedBaselineEntry="false"
findUnusedCode="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd">
<projectFiles>
<directory name="src"/>
<directory name="tests"/>
</projectFiles>
<plugins>
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
</plugins>
<issueHandlers>
<UndefinedInterfaceMethod>
<errorLevel type="suppress">
<directory name="src/ComponentProvider"/>
</errorLevel>
</UndefinedInterfaceMethod>
<PossiblyNullReference>
<errorLevel type="suppress">
<directory name="src/ComponentProvider"/>
</errorLevel>
</PossiblyNullReference>
<MoreSpecificImplementedParamType>
<errorLevel type="suppress">
<directory name="src/ComponentProvider"/>
</errorLevel>
</MoreSpecificImplementedParamType>
</issueHandlers>
</psalm>
71 changes: 71 additions & 0 deletions src/Sampler/RuleBased/src/ComponentProvider/SamplerRuleBased.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Sampler\RuleBased\ComponentProvider;

use OpenTelemetry\Config\SDK\Configuration\ComponentPlugin;
use OpenTelemetry\Config\SDK\Configuration\ComponentProvider;
use OpenTelemetry\Config\SDK\Configuration\ComponentProviderRegistry;
use OpenTelemetry\Config\SDK\Configuration\Context;
use OpenTelemetry\Contrib\Sampler\RuleBased\RuleBasedSampler;
use OpenTelemetry\Contrib\Sampler\RuleBased\RuleSet;
use OpenTelemetry\Contrib\Sampler\RuleBased\SamplingRule;
use OpenTelemetry\SDK\Trace\SamplerInterface;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;

/**
* @implements ComponentProvider<SamplerInterface>
*/
final class SamplerRuleBased implements ComponentProvider
{
/**
* @param array{
* rule_sets: list<array{
* rules: list<ComponentPlugin<SamplingRule>>,
* delegate: ComponentPlugin<SamplerInterface>,
* }>,
* fallback: ComponentPlugin<SamplerInterface>,
* } $properties
*/
public function createPlugin(array $properties, Context $context): SamplerInterface
{
$ruleSets = [];
foreach ($properties['rule_sets'] as $ruleSet) {
$samplingRules = [];
foreach ($ruleSet['rules'] as $rule) {
$samplingRules[] = $rule->create($context);
}

$ruleSets[] = new RuleSet(
$samplingRules,
$ruleSet['delegate']->create($context),
);
}

return new RuleBasedSampler(
$ruleSets,
$properties['fallback']->create($context),
);
}

public function getConfig(ComponentProviderRegistry $registry): ArrayNodeDefinition
{
$node = new ArrayNodeDefinition('contrib_rule_based');
$node
->children()
->arrayNode('rule_sets')
->arrayPrototype()
->children()
->append($registry->componentArrayList('rules', SamplingRule::class)->isRequired()->cannotBeEmpty())
->append($registry->component('delegate', SamplerInterface::class)->isRequired())
->end()
->end()
->end()
->append($registry->component('fallback', SamplerInterface::class)->isRequired())
->end()
;

return $node;
}
}
Loading

0 comments on commit e5a13ce

Please sign in to comment.