Skip to content

Commit 5883a27

Browse files
jmacdMrAliasAneurysm9
authored
OTel-Go Consistent Probability Sampler and conformance tests (#1379)
* Second prototype (based on OTel-Go draft PR #2177 from main repo) * comments * add a test for sampled vs r/p-value consistency * adding tests * test various D values * test spec * have the test print the specification table * output the table used in the spec * remove a package * comment * add tracestate handling tests * remove a file * add parent test * 100% coverage * lint * propose short tests * clarify test settings * raise test timeout * raise more * disable statistical test w/ race detector * missing file * test fix * mod tidy * shorten the test runtime * even shorter * shorten the test; lint * run 3 / 15 non-deterministically * Update samplers/probability/consistent/sampler.go * Apply suggestions from code review Co-authored-by: Tyler Yahn <[email protected]> * test one more thing * lint * changelog & readme * revert accidental golint fix * godoc Co-authored-by: Tyler Yahn <[email protected]> Co-authored-by: Anthony Mirabella <[email protected]>
1 parent 672ba73 commit 5883a27

12 files changed

+1680
-0
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- Consistent probability sampler implementation. (#1379)
14+
1115
### Fixed
1216

1317
- Fix the `otelmux` middleware by using `SpanKindServer` when deciding the `SpanStatus`.
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package consistent // import "go.opentelemetry.io/contrib/samplers/probability/consistent"
16+
17+
import "math"
18+
19+
// These are IEEE 754 double-width floating point constants used with
20+
// math.Float64bits.
21+
const (
22+
offsetExponentMask = 0x7ff0000000000000
23+
offsetExponentBias = 1023
24+
significandBits = 52
25+
)
26+
27+
// expFromFloat64 returns floor(log2(x)).
28+
func expFromFloat64(x float64) int {
29+
return int((math.Float64bits(x)&offsetExponentMask)>>significandBits) - offsetExponentBias
30+
}
31+
32+
// expToFloat64 returns 2^x.
33+
func expToFloat64(x int) float64 {
34+
return math.Float64frombits(uint64(offsetExponentBias+x) << significandBits)
35+
}
36+
37+
// splitProb returns the two values of log-adjusted-count nearest to p
38+
// Example:
39+
//
40+
// splitProb(0.375) => (2, 1, 0.5)
41+
//
42+
// indicates to sample with probability (2^-2) 50% of the time
43+
// and (2^-1) 50% of the time.
44+
func splitProb(p float64) (uint8, uint8, float64) {
45+
if p < 2e-62 {
46+
// Note: spec.
47+
return pZeroValue, pZeroValue, 1
48+
}
49+
// Take the exponent and drop the significand to locate the
50+
// smaller of two powers of two.
51+
exp := expFromFloat64(p)
52+
53+
// Low is the smaller of two log-adjusted counts, the negative
54+
// of the exponent computed above.
55+
low := -exp
56+
// High is the greater of two log-adjusted counts (i.e., one
57+
// less than low, a smaller adjusted count means a larger
58+
// probability).
59+
high := low - 1
60+
61+
// Return these to probability values and use linear
62+
// interpolation to compute the required probability of
63+
// choosing the low-probability Sampler.
64+
lowP := expToFloat64(-low)
65+
highP := expToFloat64(-high)
66+
lowProb := (highP - p) / (highP - lowP)
67+
68+
return uint8(low), uint8(high), lowProb
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package consistent
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/require"
21+
)
22+
23+
func TestSplitProb(t *testing.T) {
24+
require.Equal(t, -1, expFromFloat64(0.6))
25+
require.Equal(t, -2, expFromFloat64(0.4))
26+
require.Equal(t, 0.5, expToFloat64(-1))
27+
require.Equal(t, 0.25, expToFloat64(-2))
28+
29+
for _, tc := range []struct {
30+
in float64
31+
low uint8
32+
lowProb float64
33+
}{
34+
// Probability 0.75 corresponds with choosing S=1 (the
35+
// "low" probability) 50% of the time and S=0 (the
36+
// "high" probability) 50% of the time.
37+
{0.75, 1, 0.5},
38+
{0.6, 1, 0.8},
39+
{0.9, 1, 0.2},
40+
41+
// Powers of 2 exactly
42+
{1, 0, 1},
43+
{0.5, 1, 1},
44+
{0.25, 2, 1},
45+
46+
// Smaller numbers
47+
{0.05, 5, 0.4},
48+
{0.1, 4, 0.4}, // 0.1 == 0.4 * 1/16 + 0.6 * 1/8
49+
{0.003, 9, 0.464},
50+
51+
// Special cases:
52+
{0, 63, 1},
53+
} {
54+
low, high, lowProb := splitProb(tc.in)
55+
require.Equal(t, tc.low, low, "got %v want %v", low, tc.low)
56+
if lowProb != 1 {
57+
require.Equal(t, tc.low-1, high, "got %v want %v", high, tc.low-1)
58+
}
59+
require.InEpsilon(t, tc.lowProb, lowProb, 1e-6, "got %v want %v", lowProb, tc.lowProb)
60+
}
61+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module go.opentelemetry.io/contrib/samplers/probability/consistent
2+
3+
go 1.16
4+
5+
require (
6+
github.com/stretchr/testify v1.7.1
7+
go.opentelemetry.io/otel v1.6.1
8+
go.opentelemetry.io/otel/sdk v1.6.1
9+
go.opentelemetry.io/otel/trace v1.6.1
10+
)
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
4+
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
5+
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
6+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
7+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
8+
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
9+
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
10+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
11+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
12+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
13+
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
14+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
15+
go.opentelemetry.io/otel v1.6.1 h1:6r1YrcTenBvYa1x491d0GGpTVBsNECmrc/K6b+zDeis=
16+
go.opentelemetry.io/otel v1.6.1/go.mod h1:blzUabWHkX6LJewxvadmzafgh/wnvBSDBdOuwkAtrWQ=
17+
go.opentelemetry.io/otel/sdk v1.6.1 h1:ZmcNyMhcuAYIb/Nr6QhBPTMopMTbov/47wHt1gibkoY=
18+
go.opentelemetry.io/otel/sdk v1.6.1/go.mod h1:IVYrddmFZ+eJqu2k38qD3WezFR2pymCzm8tdxyh3R4E=
19+
go.opentelemetry.io/otel/trace v1.6.1 h1:f8c93l5tboBYZna1nWk0W9DYyMzJXDWdZcJZ0Kb400U=
20+
go.opentelemetry.io/otel/trace v1.6.1/go.mod h1:RkFRM1m0puWIq10oxImnGEduNBzxiN7TXluRBtE+5j0=
21+
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw=
22+
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
23+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
24+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
25+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
26+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
27+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package consistent // import "go.opentelemetry.io/contrib/samplers/probability/consistent"
16+
17+
import (
18+
"strings"
19+
20+
"go.opentelemetry.io/otel"
21+
sdktrace "go.opentelemetry.io/otel/sdk/trace"
22+
"go.opentelemetry.io/otel/trace"
23+
)
24+
25+
type (
26+
parentProbabilitySampler struct {
27+
delegate sdktrace.Sampler
28+
}
29+
)
30+
31+
// ParentProbabilityBased is an implementation of the OpenTelemetry
32+
// Trace Sampler interface that provides additional checks for tracestate
33+
// Probability Sampling fields.
34+
func ParentProbabilityBased(root sdktrace.Sampler, samplers ...sdktrace.ParentBasedSamplerOption) sdktrace.Sampler {
35+
return &parentProbabilitySampler{
36+
delegate: sdktrace.ParentBased(root, samplers...),
37+
}
38+
}
39+
40+
// ShouldSample implements "go.opentelemetry.io/otel/sdk/trace".Sampler.
41+
func (p *parentProbabilitySampler) ShouldSample(params sdktrace.SamplingParameters) sdktrace.SamplingResult {
42+
psc := trace.SpanContextFromContext(params.ParentContext)
43+
44+
// Note: We do not check psc.IsValid(), i.e., we repair the tracestate
45+
// with or without a parent TraceId and SpanId.
46+
state := psc.TraceState()
47+
48+
otts, err := parseOTelTraceState(state.Get(traceStateKey), psc.IsSampled())
49+
50+
if err != nil {
51+
otel.Handle(err)
52+
value := otts.serialize()
53+
if len(value) > 0 {
54+
// Note: see the note in
55+
// "go.opentelemetry.io/otel/trace".TraceState.Insert(). The
56+
// error below is not a condition we're supposed to handle.
57+
state, _ = state.Insert(traceStateKey, value)
58+
} else {
59+
state = state.Delete(traceStateKey)
60+
}
61+
62+
// Fix the broken tracestate before calling the delegate.
63+
params.ParentContext = trace.ContextWithSpanContext(params.ParentContext, psc.WithTraceState(state))
64+
}
65+
66+
return p.delegate.ShouldSample(params)
67+
}
68+
69+
// Description returns the same description as the built-in
70+
// ParentBased sampler, with "ParentBased" replaced by
71+
// "ParentProbabilityBased".
72+
func (p *parentProbabilitySampler) Description() string {
73+
return "ParentProbabilityBased" + strings.TrimPrefix(p.delegate.Description(), "ParentBased")
74+
}

0 commit comments

Comments
 (0)