Skip to content

Commit 5062da2

Browse files
jmacdjpkrohlingkentquirk
authored andcommitted
Introduce sampling package as reference implementation for OTEP 235 (open-telemetry#29720)
**Description:** This is the `pkg/sampling` portion of of open-telemetry#24811. **Link to tracking Issue:** open-telemetry#29738 open-telemetry/opentelemetry-specification#1413 **Testing:** Complete. **Documentation:** New README added. --------- Co-authored-by: Juraci Paixão Kröhling <[email protected]> Co-authored-by: Kent Quirk <[email protected]>
1 parent c77ade6 commit 5062da2

24 files changed

+2271
-0
lines changed

.chloggen/add_pkg_sampling.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: new_component
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: pkg_sampling
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Package of code for parsing OpenTelemetry tracestate probability sampling fields.
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [29738]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [api]

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ pkg/ottl/ @open-telemetry/collect
136136
pkg/pdatatest/ @open-telemetry/collector-contrib-approvers @djaglowski @fatsheep9146
137137
pkg/pdatautil/ @open-telemetry/collector-contrib-approvers @dmitryax
138138
pkg/resourcetotelemetry/ @open-telemetry/collector-contrib-approvers @mx-psi
139+
pkg/sampling/ @open-telemetry/collector-contrib-approvers @jmacd @kentquirk
139140
pkg/stanza/ @open-telemetry/collector-contrib-approvers @djaglowski
140141
pkg/translator/azure/ @open-telemetry/collector-contrib-approvers @open-telemetry/collector-approvers @atoulme @cparkins
141142
pkg/translator/jaeger/ @open-telemetry/collector-contrib-approvers @open-telemetry/collector-approvers @frzifus

.github/ISSUE_TEMPLATE/bug_report.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ body:
133133
- pkg/pdatatest
134134
- pkg/pdatautil
135135
- pkg/resourcetotelemetry
136+
- pkg/sampling
136137
- pkg/stanza
137138
- pkg/translator/azure
138139
- pkg/translator/jaeger

.github/ISSUE_TEMPLATE/feature_request.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ body:
127127
- pkg/pdatatest
128128
- pkg/pdatautil
129129
- pkg/resourcetotelemetry
130+
- pkg/sampling
130131
- pkg/stanza
131132
- pkg/translator/azure
132133
- pkg/translator/jaeger

.github/ISSUE_TEMPLATE/other.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ body:
127127
- pkg/pdatatest
128128
- pkg/pdatautil
129129
- pkg/resourcetotelemetry
130+
- pkg/sampling
130131
- pkg/stanza
131132
- pkg/translator/azure
132133
- pkg/translator/jaeger

pkg/sampling/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include ../../Makefile.Common

pkg/sampling/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# pkg/sampling
2+
3+
## Overview
4+
5+
This package contains utilities for parsing and interpreting the W3C
6+
[TraceState](https://www.w3.org/TR/trace-context/#tracestate-header)
7+
and all sampling-relevant fields specified by OpenTelemetry that may
8+
be found in the OpenTelemetry section of the W3C TraceState.
9+
10+
This package implements the draft specification in [OTEP
11+
235](https://github.com/open-telemetry/oteps/pull/235), which
12+
specifies two fields used by the OpenTelemetry consistent probability
13+
sampling scheme.
14+
15+
These are:
16+
17+
- `th`: the Threshold used to determine whether a TraceID is sampled
18+
- `rv`: an explicit randomness value, which overrides randomness in the TraceID
19+
20+
[OTEP 235](https://github.com/open-telemetry/oteps/pull/235) contains
21+
details on how to interpret these fields. The are not meant to be
22+
human readable, with a few exceptions. The tracestate entry `ot=th:0`
23+
indicates 100% sampling.

pkg/sampling/common.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"
5+
6+
import (
7+
"errors"
8+
"io"
9+
"strings"
10+
11+
"go.uber.org/multierr"
12+
)
13+
14+
// KV represents a key-value parsed from a section of the TraceState.
15+
type KV struct {
16+
Key string
17+
Value string
18+
}
19+
20+
var (
21+
// ErrTraceStateSize is returned when a TraceState is over its
22+
// size limit, as specified by W3C.
23+
ErrTraceStateSize = errors.New("invalid tracestate size")
24+
)
25+
26+
// keyValueScanner defines distinct scanner behaviors for lists of
27+
// key-values.
28+
type keyValueScanner struct {
29+
// maxItems is 32 or -1
30+
maxItems int
31+
// trim is set if OWS (optional whitespace) should be removed
32+
trim bool
33+
// separator is , or ;
34+
separator byte
35+
// equality is = or :
36+
equality byte
37+
}
38+
39+
// commonTraceState is embedded in both W3C and OTel trace states.
40+
type commonTraceState struct {
41+
kvs []KV
42+
}
43+
44+
// ExtraValues returns additional values are carried in this
45+
// tracestate object (W3C or OpenTelemetry).
46+
func (cts commonTraceState) ExtraValues() []KV {
47+
return cts.kvs
48+
}
49+
50+
// trimOws removes optional whitespace on both ends of a string.
51+
// this uses the strict definition for optional whitespace tiven
52+
// in https://www.w3.org/TR/trace-context/#tracestate-header-field-values
53+
func trimOws(input string) string {
54+
return strings.Trim(input, " \t")
55+
}
56+
57+
// scanKeyValues is common code to scan either W3C or OTel tracestate
58+
// entries, as parameterized in the keyValueScanner struct.
59+
func (s keyValueScanner) scanKeyValues(input string, f func(key, value string) error) error {
60+
var rval error
61+
items := 0
62+
for input != "" {
63+
items++
64+
if s.maxItems > 0 && items >= s.maxItems {
65+
// W3C specifies max 32 entries, tested here
66+
// instead of via the regexp.
67+
return ErrTraceStateSize
68+
}
69+
70+
sep := strings.IndexByte(input, s.separator)
71+
72+
var member string
73+
if sep < 0 {
74+
member = input
75+
input = ""
76+
} else {
77+
member = input[:sep]
78+
input = input[sep+1:]
79+
}
80+
81+
if s.trim {
82+
// Trim only required for W3C; OTel does not
83+
// specify whitespace for its value encoding.
84+
member = trimOws(member)
85+
}
86+
87+
if member == "" {
88+
// W3C allows empty list members.
89+
continue
90+
}
91+
92+
eq := strings.IndexByte(member, s.equality)
93+
if eq < 0 {
94+
// We expect to find the `s.equality`
95+
// character in this string because we have
96+
// already validated the whole input syntax
97+
// before calling this parser. I.e., this can
98+
// never happen, and if it did, the result
99+
// would be to skip malformed entries.
100+
continue
101+
}
102+
if err := f(member[:eq], member[eq+1:]); err != nil {
103+
rval = multierr.Append(rval, err)
104+
}
105+
}
106+
return rval
107+
}
108+
109+
// serializer assists with checking and combining errors from
110+
// (io.StringWriter).WriteString().
111+
type serializer struct {
112+
writer io.StringWriter
113+
err error
114+
}
115+
116+
// write handles errors from io.StringWriter.
117+
func (ser *serializer) write(str string) {
118+
_, err := ser.writer.WriteString(str)
119+
ser.check(err)
120+
}
121+
122+
// check handles errors (e.g., from another serializer).
123+
func (ser *serializer) check(err error) {
124+
ser.err = multierr.Append(ser.err, err)
125+
}

pkg/sampling/doc.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// # TraceState representation
5+
//
6+
// A [W3CTraceState] object parses and stores the OpenTelemetry
7+
// tracestate field and any other fields that are present in the
8+
// W3C tracestate header, part of the [W3C tracecontext specification].
9+
//
10+
// An [OpenTelemetryTraceState] object parses and stores fields of
11+
// the OpenTelemetry-specific tracestate field, including those recognized
12+
// for probability sampling and any other fields that are present. The
13+
// syntax of the OpenTelemetry field is specified in [Tracestate handling].
14+
//
15+
// The probability sampling-specific fields used here are specified in
16+
// [OTEP 235]. The principal named fields are:
17+
//
18+
// - T-value: The sampling rejection threshold, expresses a 56-bit
19+
// hexadecimal number of traces that will be rejected by sampling.
20+
// - R-value: The sampling randomness value can be implicit in a TraceID,
21+
// otherwise it is explicitly encoded as an R-value.
22+
//
23+
// # Low-level types
24+
//
25+
// The three key data types implemented in this package represent sampling
26+
// decisions.
27+
//
28+
// - [Threshold]: Represents an exact sampling probability.
29+
// - [Randomness]: Randomness used for sampling decisions.
30+
// - [Threshold.Probability]: a float64 in the range [MinSamplingProbability, 1.0].
31+
//
32+
// # Example use-case
33+
//
34+
// To configure a consistent tail sampler in an OpenTelemetry
35+
// Collector using a fixed probability for all traces in an
36+
// "equalizing" arrangement, where the effect of sampling is
37+
// conditioned on how much sampling has already taken place, use the
38+
// following pseudocode.
39+
//
40+
// func Setup() {
41+
// // Get a fixed probability value from the configuration, in
42+
// // the range (0, 1].
43+
// probability := *FLAG_probability
44+
//
45+
// // Calculate the sampling threshold from probability using 3
46+
// // hex digits of precision.
47+
// fixedThreshold, err = ProbabilityToThresholdWithPrecision(probability, 3)
48+
// if err != nil {
49+
// // error case: Probability is not valid.
50+
// }
51+
// }
52+
//
53+
// func MakeDecision(tracestate string, tid TraceID) bool {
54+
// // Parse the incoming tracestate
55+
// ts, err := NewW3CTraceState(tracestate)
56+
// if err != nil {
57+
// // error case: Tracestate is ill-formed.
58+
// }
59+
// // For an absolute probability sample, we check the incoming
60+
// // tracestate to see whether it was already sampled enough.
61+
// if len(ts.OTelValue().TValue()) != 0 {
62+
// // If the incoming tracestate was already sampled at
63+
// // least as much as our threshold implies, then its
64+
// // (rejection) threshold is higher. If so, then no
65+
// // further sampling is called for.
66+
// if ThresholdGreater(ts.OTelValue().TValueThreshold(), fixedThreshold) {
67+
// return true
68+
// }
69+
// }
70+
// var rnd Randomness
71+
// // If the R-value is present, use it. If not, rely on TraceID
72+
// // randomness. Note that OTLP v1.1.0 introduces a new Span flag
73+
// // to convey trace randomness correctly, and if the context has
74+
// // neither the randomness bit set or the R-value set, we need a
75+
// // fallback, which can be to synthesize an R-value or to assume
76+
// // the TraceID has sufficient randomness. This detail is left
77+
// // out of scope.
78+
// if rval, hasRval := ts.OTelValue().RValueRandomness(); hasRv {
79+
// rnd = rval
80+
// } else {
81+
// rnd = TraceIDToRandomness(tid)
82+
// }
83+
//
84+
// return fixedThreshold.ShouldSample(rnd)
85+
// }
86+
//
87+
// [W3C tracecontext specification]: https://www.w3.org/TR/trace-context/#tracestate-header
88+
// [Tracestate handling]: https://opentelemetry.io/docs/specs/otel/trace/tracestate-handling/
89+
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"

0 commit comments

Comments
 (0)