sampling

package module
v0.99.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 22, 2024 License: Apache-2.0 Imports: 9 Imported by: 0

README

pkg/sampling

Overview

This package contains utilities for parsing and interpreting the W3C TraceState and all sampling-relevant fields specified by OpenTelemetry that may be found in the OpenTelemetry section of the W3C TraceState.

This package implements the draft specification in OTEP 235, which specifies two fields used by the OpenTelemetry consistent probability sampling scheme.

These are:

  • th: the Threshold used to determine whether a TraceID is sampled
  • rv: an explicit randomness value, which overrides randomness in the TraceID

OTEP 235 contains details on how to interpret these fields. The are not meant to be human readable, with a few exceptions. The tracestate entry ot=th:0 indicates 100% sampling.

Documentation

Index

Examples

Constants

View Source
const (
	// MaxAdjustedCount is 2^56 i.e. 0x100000000000000 i.e., 1<<56.
	MaxAdjustedCount uint64 = 1 << 56

	// NumHexDigits is the number of hex digits equalling 56 bits.
	// This is the limit of sampling precision.
	NumHexDigits = 56 / hexBits
)
View Source
const MinSamplingProbability = 1.0 / float64(MaxAdjustedCount)

MinSamplingProbability is the smallest representable probability and is the inverse of MaxAdjustedCount.

Variables

View Source
var (
	// ErrTValueSize is returned for t-values longer than NumHexDigits hex digits.
	ErrTValueSize = errors.New("t-value exceeds 14 hex digits")

	// ErrEmptyTValue indicates no t-value was found, i.e., no threshold available.
	ErrTValueEmpty = errors.New("t-value is empty")

	// AlwaysSampleThreshold represents 100% sampling.
	AlwaysSampleThreshold = Threshold{/* contains filtered or unexported fields */}

	// NeverSampledThreshold is a threshold value that will always not sample.
	// The TValue() corresponding with this threshold is an empty string.
	NeverSampleThreshold = Threshold{/* contains filtered or unexported fields */}
)
View Source
var AllProbabilitiesRandomness = Randomness{/* contains filtered or unexported fields */}

AllProbabilitiesRandomness is sampled at all probabilities.

View Source
var (

	// ErrInconsistentSampling is returned when a sampler update
	// is illogical, indicating that the tracestate was not
	// modified.  Preferably, Samplers will avoid seeing this
	// error by using a ThresholdGreater() test, which allows them
	// to report a more clear error to the user.  For example, if
	// data arrives sampled at 1/100 and an equalizing sampler is
	// configured for 1/2 sampling, the Sampler may detect the
	// illogical condition itself using ThresholdGreater and skip
	// the call to UpdateTValueWithSampling, which will have no
	// effect and return this error.  How a sampler decides to
	// handle this condition is up to the sampler: for example the
	// equalizing sampler can decide to pass through a span
	// indicating 1/100 sampling or it can reject the span.
	ErrInconsistentSampling = errors.New("cannot raise existing sampling probability")
)
View Source
var ErrProbabilityRange = errors.New("sampling probability out of the range [1/MaxAdjustedCount, 1]")

ErrProbabilityRange is returned when a value should be in the range [1/MaxAdjustedCount, 1].

View Source
var ErrRValueSize = errors.New("r-value must have 14 hex digits")

ErrRValueSize is returned by RValueToRandomess in case of unexpected size.

View Source
var (
	// ErrTraceStateSize is returned when a TraceState is over its
	// size limit, as specified by W3C.
	ErrTraceStateSize = errors.New("invalid tracestate size")
)

Functions

func ThresholdGreater

func ThresholdGreater(a, b Threshold) bool

ThresholdGreater allows direct comparison of Threshold values. Greater thresholds equate with smaller sampling probabilities.

func ThresholdLessThan

func ThresholdLessThan(a, b Threshold) bool

ThresholdLessThan allows direct comparison of Threshold values. Smaller thresholds equate with greater sampling probabilities.

Types

type KV

type KV struct {
	Key   string
	Value string
}

KV represents a key-value parsed from a section of the TraceState.

type OpenTelemetryTraceState

type OpenTelemetryTraceState struct {
	// contains filtered or unexported fields
}

OpenTelemetryTraceState represents the `ot` section of the W3C tracestate which is specified generically in https://opentelemetry.io/docs/specs/otel/trace/tracestate-handling/.

OpenTelemetry defines two specific values that convey sampling probability, known as T-Value (with "th", for threshold), R-Value (with key "rv", for random value), and extra values.

func NewOpenTelemetryTraceState

func NewOpenTelemetryTraceState(input string) (OpenTelemetryTraceState, error)

NewOpenTelemetryTraceState returns a parsed representation of the OpenTelemetry tracestate section. Errors indicate an invalid tracestate was received.

func (*OpenTelemetryTraceState) AdjustedCount

func (otts *OpenTelemetryTraceState) AdjustedCount() float64

AdjustedCount returns the adjusted count for this item. If the TValue string is empty, this returns 0, otherwise returns Threshold.AdjustedCount().

Example

ExampleOpenTelemetryTraceState_AdjustedCount shows how to access the adjusted count for a sampled context when it has non-zero probability.

w3c, err := NewW3CTraceState("ot=th:c")
if err != nil {
	panic(err)
}
ot := w3c.OTelValue()

fmt.Printf("Adjusted count for T-value %q: %f", ot.TValue(), ot.AdjustedCount())
Output:

Adjusted count for T-value "c": 4.000000

func (*OpenTelemetryTraceState) ClearRValue

func (otts *OpenTelemetryTraceState) ClearRValue()

ClearRValue unsets explicit randomness.

func (*OpenTelemetryTraceState) ClearTValue

func (otts *OpenTelemetryTraceState) ClearTValue()

ClearTValue is used to unset TValue, for use in cases where it is inconsistent on arrival.

func (OpenTelemetryTraceState) ExtraValues

func (cts OpenTelemetryTraceState) ExtraValues() []KV

ExtraValues returns additional values are carried in this tracestate object (W3C or OpenTelemetry).

func (*OpenTelemetryTraceState) HasAnyValue

func (otts *OpenTelemetryTraceState) HasAnyValue() bool

HasAnyValue returns true if there are any fields in this tracestate, including any extra values.

func (*OpenTelemetryTraceState) RValue

func (otts *OpenTelemetryTraceState) RValue() string

RValue returns the R-value (key: "rv") as a string or empty if there is no R-value set.

func (*OpenTelemetryTraceState) RValueRandomness

func (otts *OpenTelemetryTraceState) RValueRandomness() (Randomness, bool)

RValueRandomness returns the randomness object corresponding with RValue() and a boolean indicating whether the R-value is set.

func (*OpenTelemetryTraceState) Serialize

func (otts *OpenTelemetryTraceState) Serialize(w io.StringWriter) error

Serialize encodes this TraceState object.

func (*OpenTelemetryTraceState) SetRValue

func (otts *OpenTelemetryTraceState) SetRValue(randomness Randomness)

SetRValue establishes explicit randomness for this TraceState.

func (*OpenTelemetryTraceState) TValue

func (otts *OpenTelemetryTraceState) TValue() string

TValue returns the T-value (key: "th") as a string or empty if there is no T-value set.

func (*OpenTelemetryTraceState) TValueThreshold

func (otts *OpenTelemetryTraceState) TValueThreshold() (Threshold, bool)

TValueThreshold returns the threshold object corresponding with TValue() and a boolean (equal to len(TValue()) != 0 indicating whether the T-value is valid.

func (*OpenTelemetryTraceState) UpdateTValueWithSampling

func (otts *OpenTelemetryTraceState) UpdateTValueWithSampling(sampledThreshold Threshold) error

UpdateTValueWithSampling modifies the TValue of this object, which changes its adjusted count. It is not logical to modify a sampling probability in the direction of larger probability. This prevents accidental loss of adjusted count.

If the change of TValue leads to inconsistency, an error is returned.

type Randomness

type Randomness struct {
	// contains filtered or unexported fields
}

Randomness may be derived from R-value or TraceID.

Randomness contains 56 bits of randomness, derived in one of two ways, see: https://www.w3.org/TR/trace-context-2/#randomness-of-trace-id

func RValueToRandomness

func RValueToRandomness(s string) (Randomness, error)

RValueToRandomness parses NumHexDigits hex bytes into a Randomness.

Example
// Any 14 hex digits is a valid R-value.
const exampleRvalue = "d29d6a7215ced0"

// This converts to the internal unsigned integer representation.
rnd, _ := RValueToRandomness(exampleRvalue)

// The result prints the same as the input.
fmt.Printf("RValueToRandomness(%q).RValue() = %s", exampleRvalue, rnd.RValue())
Output:

RValueToRandomness("d29d6a7215ced0").RValue() = d29d6a7215ced0

func TraceIDToRandomness

func TraceIDToRandomness(id pcommon.TraceID) Randomness

TraceIDToRandomness returns randomness from a TraceID (assumes the traceparent random flag was set).

Example
// TraceID represented in hex as "abababababababababd29d6a7215ced0"
var exampleTid = pcommon.TraceID{
	// 9 meaningless bytes
	0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
	// 7 bytes randomness
	0xd2, 0x9d, 0x6a, 0x72, 0x15, 0xce, 0xd0,
}
rnd := TraceIDToRandomness(exampleTid)

fmt.Printf("TraceIDToRandomness(%q).RValue() = %s", exampleTid, rnd.RValue())
Output:

TraceIDToRandomness("abababababababababd29d6a7215ced0").RValue() = d29d6a7215ced0

func UnsignedToRandomness added in v0.98.0

func UnsignedToRandomness(x uint64) (Randomness, error)

UnsignedToRandomness constructs a randomness using 56 random bits of unsigned number. If the input is out of range, an invalid value will be returned with an error.

func (Randomness) RValue

func (rnd Randomness) RValue() string

RValue formats the r-value encoding.

func (Randomness) Unsigned added in v0.98.0

func (rnd Randomness) Unsigned() uint64

Unsigned returns the unsigned representation of the random value. Items of data SHOULD be sampled when:

Threshold.Unsigned() <= // Randomness.Unsigned().

type Threshold

type Threshold struct {
	// contains filtered or unexported fields
}

Threshold represents an exact sampling probability using 56 bits of precision. A Threshold expresses the number of spans, out of 2**56, that are rejected.

These 56 bits are compared against 56 bits of randomness, either extracted from an R-value or a TraceID having the W3C-specified randomness bit set.

Because Thresholds store 56 bits of information and floating point values store 52 bits of significand, some conversions between Threshold and probability values are lossy. The kinds of loss that occur depend on where in the probability scale it happens, as the step between adjacent floating point values adjusts with the exponent.

func ProbabilityToThreshold

func ProbabilityToThreshold(prob float64) (Threshold, error)

ProbabilityToThreshold converts a probability to a Threshold. It returns an error when the probability is out-of-range.

Example (Limitedprecision)

ExampleProbabilityToThreshold_limitedprecision demonstrates the gap between Threshold values and probability values is not equal, clarifying which conversions are lossy.

next := func(x float64, n int) float64 {
	for ; n < 0; n++ {
		x = math.Nextafter(x, 0)
	}
	return x
}

// At probability 50% or above, only 52 bits of precision are
// available for floating point representation.
//
// In the range 1/2 to 1: 52 bits of precision are available; 4 trailing zero bits;
// In the range 1/4 to 1/2: 52 bits of precision are available; 3 trailing zero bits;
// In the range 1/8 to 1/4: 52 bits of precision are available; 2 trailing zero bits;
// In the range 1/16 to 1/8: 52 bits of precision are available; 1 trailing zero bits;
// Probabilties less than 1/16: 51 bits of precision are available
// Probabilties less than 1/32: 50 bits of precision are available.
// ...
// Probabilties less than 0x1p-N: 55-N bits of precision are available.
// ...
// Probabilities less than 0x1p-55: 0 bits of precision.
const large = 15.0 / 16
const half = 8.0 / 16
const quarter = 4.0 / 16
const eighth = 2.0 / 16
const small = 1.0 / 16
for _, prob := range []float64{
	// Values from 1/2 to 15/16: last T-value digit always "8".
	next(large, 0),
	next(large, -1),
	next(large, -2),
	next(large, -3),
	0,
	// Values from 1/4 to 1/2: last T-value digit always
	// "4", "8", or "c".
	next(half, 0),
	next(half, -1),
	next(half, -2),
	next(half, -3),
	0,
	// Values from 1/8 to 1/4, last T-value digit can be any
	// even hex digit.
	next(quarter, 0),
	next(quarter, -1),
	next(quarter, -2),
	next(quarter, -3),
	0,
	// Values from 1/16 to 1/8: Every adjacent probability
	// value maps to an exact Threshold.
	next(eighth, 0),
	next(eighth, -1),
	next(eighth, -2),
	next(eighth, -3),
	0,
	// Values less than 1/16 demonstrate lossy behavior.
	// Here probability values can express more values
	// than Thresholds can, so multiple probability values
	// map to the same Threshold.  Here, 1/16 and the next
	// descending floating point value both map to T-value
	// "f".
	next(small, 0),
	next(small, -1),
	next(small, -2),
	next(small, -3),
} {
	if prob == 0 {
		fmt.Println("--")
		continue
	}
	tval, _ := ProbabilityToThreshold(prob)
	fmt.Println(tval.TValue())
}
Output:

1
10000000000008
1000000000001
10000000000018
--
8
80000000000004
80000000000008
8000000000000c
--
c
c0000000000002
c0000000000004
c0000000000006
--
e
e0000000000001
e0000000000002
e0000000000003
--
f
f
f0000000000001
f0000000000001
Example (Rounding)

ExampleProbabilityToThreshold_rounding demonstrates that with full precision, the resulting t-value appears to round in an unexpected way.

// 1/3 sampling corresponds with a rejection threshold of (1 - 1/3).
const exampleProb = 1.0 / 3.0

// 1/3 in decimal is the repeating fraction of 6 (0.333333), while in
// hexadecimal it is the repeating fraction of a (0x0.555555).
tval, _ := ProbabilityToThreshold(exampleProb)

// Note the trailing hex "c" below, which does not match
// intuition for a repeating pattern of hex "a" digits.  Why
// is the final digit not hex "b"?  The reason it is hex "c"
// is that ProbabilityToThreshold computes the number of spans
// selected as a 56-bit integer using a 52-bit significand.
// Because the fraction uses fewer bits than the threshold,
// the last digit rounds down, with 0x55555555555554 spans
// rejected out of 0x100000000000000.  The subtraction of 0x4
// from 0x10 leads to a trailing hex "c".
fmt.Println(tval.TValue())
Output:

aaaaaaaaaaaaac
Example (Verysmall)

ExampleProbabilityToThreshold_verysmall shows the smallest expressible sampling probability values.

for _, prob := range []float64{
	MinSamplingProbability, // Skip 1 out of 2**56
	0x2p-56,                // Skip 2 out of 2**56
	0x3p-56,                // Skip 3 out of 2**56
	0x4p-56,                // Skip 4 out of 2**56
	0x8p-56,                // Skip 8 out of 2**56
	0x10p-56,               // Skip 0x10 out of 2**56
} {
	// Note that precision is automatically raised for
	// such small probabilities, because leading 'f' and
	// '0' digits are discounted.
	tval, _ := ProbabilityToThresholdWithPrecision(prob, 3)
	fmt.Println(tval.TValue())
}
Output:

ffffffffffffff
fffffffffffffe
fffffffffffffd
fffffffffffffc
fffffffffffff8
fffffffffffff

func ProbabilityToThresholdWithPrecision

func ProbabilityToThresholdWithPrecision(fraction float64, precision int) (Threshold, error)

ProbabilityToThresholdWithPrecision is like ProbabilityToThreshold with support for reduced precision. The `precision` argument determines how many significant hex digits will be used to encode the exact probability.

Example

ExampleProbabilityToThresholdWithPrecision demonstrates how 1/3, 2/3, and 3/3 are encoded with precision 3. When working with arbitrary floating point values, it is recommended to use an explicit precision parameter so that T-values are both reasonably compact and accurate.

const divisor = 3.0
const precision = 3

for dividend := 1.0; dividend <= divisor; dividend++ {
	tval, _ := ProbabilityToThresholdWithPrecision(dividend/divisor, precision)
	fmt.Println(tval.TValue())
}
Output:

aab
555
0

func TValueToThreshold

func TValueToThreshold(s string) (Threshold, error)

TValueToThreshold returns a Threshold. Because TValue strings have trailing zeros omitted, this function performs the reverse.

Example

ExampleTValueToThreshold demonstrates how to convert a T-value string to a Threshold value.

// "c" corresponds with rejecting 3/4 traces (or 0xc out of
// 0x10), which is 25% sampling.
const exampleTvalue = "c"

tval, _ := TValueToThreshold(exampleTvalue)

fmt.Printf("Probability(%q) = %f", exampleTvalue, tval.Probability())
Output:

Probability("c") = 0.250000

func UnsignedToThreshold added in v0.98.0

func UnsignedToThreshold(unsigned uint64) (Threshold, error)

UnsignedToThreshold constructs a threshold expressed in terms defined by number of rejections out of MaxAdjustedCount, which equals the number of randomness values.

func (Threshold) AdjustedCount added in v0.98.0

func (th Threshold) AdjustedCount() float64

AdjustedCount returns the adjusted count for this item, which is the representativity of the item due to sampling, equal to the inverse of sampling probability. If the threshold equals NeverSampleThreshold, the item should not have been sampled, in which case the Adjusted count is zero.

This term is defined here: https://opentelemetry.io/docs/specs/otel/trace/tracestate-probability-sampling/

func (Threshold) Probability

func (t Threshold) Probability() float64

Probability is the sampling ratio in the range [MinSamplingProb, 1].

func (Threshold) ShouldSample

func (th Threshold) ShouldSample(rnd Randomness) bool

ShouldSample returns true when the span passes this sampler's consistent sampling decision. The sampling decision can be expressed as a T <= R.

Example

ExampleTValueToThreshold demonstrates how to calculate whether a Threshold, calculated from a T-value, should be sampled at a given probability.

const exampleTvalue = "c"
const exampleRvalue = "d29d6a7215ced0"

tval, _ := TValueToThreshold(exampleTvalue)
rval, _ := RValueToRandomness(exampleRvalue)

fmt.Printf("TValueToThreshold(%q).ShouldSample(RValueToRandomness(%q) = %v",
	tval.TValue(), rval.RValue(), tval.ShouldSample(rval))
Output:

TValueToThreshold("c").ShouldSample(RValueToRandomness("d29d6a7215ced0") = true
Example (Traceid)

ExampleTValueToThreshold_traceid demonstrates how to calculate whether a Threshold, calculated from a T-value, should be sampled at a given probability.

const exampleTvalue = "c"

// The leading 9 bytes (18 hex digits) of the TraceID string
// are not used, only the trailing 7 bytes (14 hex digits,
// i.e., 56 bits) are used.  Here, the dont-care digits are
// set to 0xab and the R-value is "bd29d6a7215ced0".
const exampleHexTraceID = "abababababababababd29d6a7215ced0"
var tid pcommon.TraceID
idbytes, _ := hex.DecodeString(exampleHexTraceID)
copy(tid[:], idbytes)

tval, _ := TValueToThreshold(exampleTvalue)
rval := TraceIDToRandomness(tid)

fmt.Printf("TValueToThreshold(%q).ShouldSample(TraceIDToRandomness(%q) = %v",
	tval.TValue(), exampleHexTraceID, tval.ShouldSample(rval))
Output:

TValueToThreshold("c").ShouldSample(TraceIDToRandomness("abababababababababd29d6a7215ced0") = true

func (Threshold) TValue

func (th Threshold) TValue() string

TValue encodes a threshold, which is a variable-length hex string up to 14 characters. The empty string is returned for 100% sampling.

func (Threshold) Unsigned added in v0.98.0

func (th Threshold) Unsigned() uint64

Unsigned expresses the number of Randomness values (out of MaxAdjustedCount) that are rejected or not sampled. 0 means 100% sampling.

type W3CTraceState

type W3CTraceState struct {
	// contains filtered or unexported fields
}

W3CTraceState represents the a parsed W3C `tracestate` header.

This type receives and passes through `tracestate` fields defined by all vendors, while it parses and validates the OpenTelemetryTraceState field. After parsing the W3CTraceState, access the OpenTelemetry-defined fields using W3CTraceState.OTelValue.

Example

ExampleW3CTraceState_Serialize shows how to parse and print a W3C tracestate.

// This tracestate value encodes two sections, "ot" from
// OpenTelemetry and "zz" from a vendor.
w3c, err := NewW3CTraceState("ot=th:c;rv:d29d6a7215ced0;pn:abc,zz=vendorcontent")
if err != nil {
	panic(err)
}
ot := w3c.OTelValue()

fmt.Println("T-Value:", ot.TValue())
fmt.Println("R-Value:", ot.RValue())
fmt.Println("OTel Extra:", ot.ExtraValues())
fmt.Println("Other Extra:", w3c.ExtraValues())
Output:

T-Value: c
R-Value: d29d6a7215ced0
OTel Extra: [{pn abc}]
Other Extra: [{zz vendorcontent}]

func NewW3CTraceState

func NewW3CTraceState(input string) (w3c W3CTraceState, _ error)

NewW3CTraceState parses a W3C trace state, with special attention to the embedded OpenTelemetry trace state field.

func (W3CTraceState) ExtraValues

func (cts W3CTraceState) ExtraValues() []KV

ExtraValues returns additional values are carried in this tracestate object (W3C or OpenTelemetry).

func (*W3CTraceState) HasAnyValue

func (w3c *W3CTraceState) HasAnyValue() bool

HasAnyValue indicates whether there are any values in this tracestate, including extra values.

func (*W3CTraceState) OTelValue

func (w3c *W3CTraceState) OTelValue() *OpenTelemetryTraceState

OTelValue returns the OpenTelemetry tracestate value.

func (*W3CTraceState) Serialize

func (w3c *W3CTraceState) Serialize(w io.StringWriter) error

Serialize encodes this tracestate object for use as a W3C tracestate header value.

Example

ExampleW3CTraceState_Serialize shows how to modify and serialize a new W3C tracestate.

w3c, err := NewW3CTraceState("")
if err != nil {
	panic(err)
}
// Suppose a parent context was unsampled, the child span has
// been sampled at 25%.  The child's context should carry the
// T-value of "c", serialize as "ot=th:c".
th, err := ProbabilityToThreshold(0.25)
if err != nil {
	panic(err)
}

// The update uses both the Threshold and its encoded string
// value, since in some code paths the Threshold will have
// just been parsed from a T-value, and in other code paths
// the T-value will be precalculated.
err = w3c.OTelValue().UpdateTValueWithSampling(th)
if err != nil {
	panic(err)
}

var buf strings.Builder
err = w3c.Serialize(&buf)
if err != nil {
	panic(err)
}

fmt.Println(buf.String())
Output:

ot=th:c

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL