-
Notifications
You must be signed in to change notification settings - Fork 284
Expand file tree
/
Copy pathconsistent_probability_tracestate.rb
More file actions
118 lines (107 loc) · 4.35 KB
/
Copy pathconsistent_probability_tracestate.rb
File metadata and controls
118 lines (107 loc) · 4.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# frozen_string_literal: true
# Copyright The OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0
module OpenTelemetry
module SDK
module Trace
module Samplers
# @api private
#
# The ConsistentProbabilityTraceState module implements Tracestate parsing,
# validation and manipulation for the consistent probability-based samplers.
module ConsistentProbabilityTraceState
DECIMAL = /\A\d+\z/
MAX_LIST_LENGTH = 256 # Defined by https://www.w3.org/TR/trace-context/
private_constant(:DECIMAL, :MAX_LIST_LENGTH)
private
# sanitized_tracestate returns an OpenTelemetry Tracestate object with the
# tracestate sanitized according to the Context invariants defined in the
# tracestate probability sampling spec.
#
# If r is nil after the sanitization, it is generated from the trace_id.
#
# This method assumes the parent span context is valid.
#
# @param trace_id [OpenTelemetry::Trace::TraceId] the trace id
# @param span_context [OpenTelemetry::Trace::SpanContext] the parent span context
# @return [OpenTelemetry::Trace::Tracestate] the sanitized tracestate
def sanitized_tracestate(trace_id, span_context)
sampled = span_context.trace_flags.sampled?
tracestate = span_context.tracestate
# rubocop:disable Lint/DuplicateBranch
parse_ot_vendor_tag(tracestate) do |p, r, rest|
if !r.nil? && r > 62
p = r = nil
elsif !p.nil? && p > 63
p = nil
elsif !p.nil? && !r.nil? && !invariant(p, r, sampled)
p = nil
elsif !r.nil?
return tracestate
end
if r.nil?
OpenTelemetry.logger.debug("ConsistentProbabilitySampler: potentially inconsistent trace detected - r: #{r.inspect}")
r = generate_r(trace_id)
end
update_tracestate(tracestate, p, r, rest)
end
# rubocop:enable Lint/DuplicateBranch
end
# parse_ot_vendor_tag parses the 'ot' vendor tag of the tracestate.
# It yields the parsed probability fields and the remaining tracestate.
# It returns the result of the block.
def parse_ot_vendor_tag(tracestate)
return yield(nil, nil, nil) if tracestate.empty?
ot = tracestate.value('ot')
return yield(nil, nil, nil) if ot.nil? || ot.length > MAX_LIST_LENGTH # TODO: warn that we're rejecting the tracestate
p = r = nil
rest = +''
ot.split(';').each do |field|
k, v = field.split(':', 2)
# TODO: "the used keys MUST be unique." - do we need to validate this?
case k
when 'p' then p = decimal(v)
when 'r' then r = decimal(v)
else
rest << ';' unless rest.empty?
rest << field
end
end
rest = nil if rest.empty?
yield(p, r, rest)
end
def update_tracestate(tracestate, p, r, rest) # rubocop:disable Naming/MethodParameterName
if p.nil? && r.nil? && rest.nil?
tracestate.delete('ot')
elsif p.nil? && r.nil?
tracestate.set_value('ot', rest)
elsif p.nil? && rest.nil?
tracestate.set_value('ot', "r:#{r}")
elsif r.nil? && rest.nil?
tracestate.set_value('ot', "p:#{p}")
elsif p.nil?
tracestate.set_value('ot', "r:#{r};#{rest}")
elsif r.nil?
tracestate.set_value('ot', "p:#{p};#{rest}")
elsif rest.nil?
tracestate.set_value('ot', "p:#{p};r:#{r}")
else
tracestate.set_value('ot', "p:#{p};r:#{r};#{rest}")
end
end
def invariant(p, r, sampled) # rubocop:disable Naming/MethodParameterName
((p <= r) == sampled) || (sampled && (p == 63))
end
def decimal(str)
str.to_i if !str.nil? && DECIMAL.match?(str)
end
def generate_r(trace_id)
x = trace_id.unpack1('@8Q>') | 0x3
64 - x.bit_length
end
end
end
end
end
end