Skip to content
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

implement rule-based sampler #279

Merged
merged 15 commits into from
Sep 12, 2024
Merged
5 changes: 5 additions & 0 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
'Propagation/TraceResponse',
'ResourceDetectors/Azure',
'ResourceDetectors/Container',
'Sampler/RuleBased',
'Shims/OpenTracing',
'Symfony'
]
Expand Down Expand Up @@ -104,6 +105,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 @@ -66,6 +66,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>
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
Loading