From c1a609e2924713392a28228b23c1f4fee0365efc Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Thu, 5 Sep 2019 16:40:40 -0400 Subject: [PATCH] Add TraceFlags class (#57) --- .../propagation/binary_format.rb | 2 +- api/lib/opentelemetry/trace.rb | 40 +++++++++++- api/lib/opentelemetry/trace/span_context.rb | 20 +++--- api/lib/opentelemetry/trace/trace_flags.rb | 50 +++++++++++++++ api/lib/opentelemetry/trace/tracer.rb | 6 +- .../opentelemetry/trace/span_context_test.rb | 62 +++++++++++++++++++ .../opentelemetry/trace/trace_flags_test.rb | 33 ++++++++++ 7 files changed, 199 insertions(+), 14 deletions(-) create mode 100644 api/lib/opentelemetry/trace/trace_flags.rb create mode 100644 api/test/opentelemetry/trace/span_context_test.rb create mode 100644 api/test/opentelemetry/trace/trace_flags_test.rb diff --git a/api/lib/opentelemetry/distributed_context/propagation/binary_format.rb b/api/lib/opentelemetry/distributed_context/propagation/binary_format.rb index 0d9ae10c40..23474cb6c2 100644 --- a/api/lib/opentelemetry/distributed_context/propagation/binary_format.rb +++ b/api/lib/opentelemetry/distributed_context/propagation/binary_format.rb @@ -22,7 +22,7 @@ def to_bytes(span_context) def from_bytes(bytes) raise ArgumentError if bytes.nil? - SpanContext.INVALID + Trace::SpanContext::INVALID end end end diff --git a/api/lib/opentelemetry/trace.rb b/api/lib/opentelemetry/trace.rb index 92c9132d31..25af2d0ea3 100644 --- a/api/lib/opentelemetry/trace.rb +++ b/api/lib/opentelemetry/trace.rb @@ -4,9 +4,47 @@ # # SPDX-License-Identifier: Apache-2.0 +module OpenTelemetry + # The Trace API allows recording a set of events, triggered as a result of a + # single logical operation, consolidated across various components of an + # application. + module Trace + # An invalid trace identifier, a 16-byte array with all zero bytes, encoded + # as a hexadecimal string. + INVALID_TRACE_ID = ('0' * 32).freeze + + # An invalid span identifier, an 8-byte array with all zero bytes, encoded + # as a hexadecimal string. + INVALID_SPAN_ID = ('0' * 16).freeze + + # Generates a valid trace identifier, a 16-byte array with at least one + # non-zero byte, encoded as a hexadecimal string. + # + # @return [String] a hexadecimal string encoding of a valid trace ID. + def self.generate_trace_id + loop do + id = Random::DEFAULT.bytes(16).unpack1('H*') + return id unless id == INVALID_TRACE_ID + end + end + + # Generates a valid span identifier, an 8-byte array with at least one + # non-zero byte, encoded as a hexadecimal string. + # + # @return [String] a hexadecimal string encoding of a valid span ID. + def self.generate_span_id + loop do + id = Random::DEFAULT.bytes(8).unpack1('H*') + return id unless id == INVALID_SPAN_ID + end + end + end +end + +require 'opentelemetry/trace/trace_flags' +require 'opentelemetry/trace/samplers' require 'opentelemetry/trace/span_context' require 'opentelemetry/trace/span_kind' require 'opentelemetry/trace/span' require 'opentelemetry/trace/status' require 'opentelemetry/trace/tracer' -require 'opentelemetry/trace/samplers' diff --git a/api/lib/opentelemetry/trace/span_context.rb b/api/lib/opentelemetry/trace/span_context.rb index b2babaab93..2495065ecf 100644 --- a/api/lib/opentelemetry/trace/span_context.rb +++ b/api/lib/opentelemetry/trace/span_context.rb @@ -6,25 +6,27 @@ module OpenTelemetry module Trace - # A SpanContext contains the state that must propagate to child @see Spans and across process boundaries. - # It contains the identifiers (a @see TraceId and @see SpanId) associated with the @see Span and a set of - # @see TraceOptions. + # A SpanContext contains the state that must propagate to child {Span}s and across process boundaries. + # It contains the identifiers (a trace ID and span ID) associated with the {Span} and a set of + # {TraceFlags}. class SpanContext - attr_reader :trace_id, :span_id, :trace_options + attr_reader :trace_id, :span_id, :trace_flags def initialize( - trace_id: generate_trace_id, - span_id: generate_span_id, - trace_options: TraceOptions::DEFAULT + trace_id: Trace.generate_trace_id, + span_id: Trace.generate_span_id, + trace_flags: TraceFlags::DEFAULT ) @trace_id = trace_id @span_id = span_id - @trace_options = trace_options + @trace_flags = trace_flags end def valid? - !(@trace_id.zero? || @span_id.zero?) + @trace_id != INVALID_TRACE_ID && @span_id != INVALID_SPAN_ID end + + INVALID = new(trace_id: INVALID_TRACE_ID, span_id: INVALID_SPAN_ID) end end end diff --git a/api/lib/opentelemetry/trace/trace_flags.rb b/api/lib/opentelemetry/trace/trace_flags.rb new file mode 100644 index 0000000000..78a5db1747 --- /dev/null +++ b/api/lib/opentelemetry/trace/trace_flags.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# Copyright 2019 OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Trace + # TraceFlags contain details about the trace. Unlike Tracestate values, + # TraceFlags are present in all traces. Currently, the only TraceFlag is a + # boolean {sampled?} {https://www.w3.org/TR/trace-context/#trace-flags flag}. + class TraceFlags + class << self + private :new # rubocop:disable Style/AccessModifierDeclarations + + # Returns a newly created {TraceFlags} with the specified flags. + # + # @param [Integer] flags 8-bit byte of bit flags + # @raise [ArgumentError] If flags is not an 8-bit byte + # @return [TraceFlags] + def from_byte(flags) + raise ArgumentError, 'flags must be an 8-bit byte' unless flags & ~0xFF == 0 # rubocop:disable Style/NumericPredicate + + new(flags) + end + end + + # @api private + # The constructor is private and only for use internally by the class. + # Users should use the {from_byte} factory method to obtain a {TraceFlags} + # instance. + # + # @param [Integer] flags 8-bit byte of bit flags + # @return [TraceFlags] + def initialize(flags) + @flags = flags + end + + # Returns whether the caller may have recorded trace data. When false, + # the caller did not record trace data out-of-band. + # + # @return [Boolean] + def sampled? + (@flags & 1) != 0 + end + + DEFAULT = from_byte(0) + end + end +end diff --git a/api/lib/opentelemetry/trace/tracer.rb b/api/lib/opentelemetry/trace/tracer.rb index 4d18bf0895..0edee0086b 100644 --- a/api/lib/opentelemetry/trace/tracer.rb +++ b/api/lib/opentelemetry/trace/tracer.rb @@ -50,10 +50,10 @@ def start_span(name, with_parent: nil, with_parent_context: nil, attributes: nil raise ArgumentError if name.nil? span_context = with_parent&.context || with_parent_context || current_span.context - if SpanContext.INVALID == span_context - Span.create_random - else + if span_context.valid? Span.new(span_context) + else + Span.create_random end end diff --git a/api/test/opentelemetry/trace/span_context_test.rb b/api/test/opentelemetry/trace/span_context_test.rb new file mode 100644 index 0000000000..95eceec59b --- /dev/null +++ b/api/test/opentelemetry/trace/span_context_test.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'test_helper' + +describe OpenTelemetry::Trace::SpanContext do + let(:span_context) { OpenTelemetry::Trace::SpanContext.new } + let(:invalid_context) { OpenTelemetry::Trace::SpanContext::INVALID } + + describe '#initialize' do + it 'must generate valid span_id and trace_id by default' do + span_context.trace_id.wont_equal(OpenTelemetry::Trace::INVALID_TRACE_ID) + span_context.span_id.wont_equal(OpenTelemetry::Trace::INVALID_SPAN_ID) + end + end + + describe '#valid?' do + it 'is true by default' do + span_context.must_be(:valid?) + end + + it 'is false for invalid context' do + invalid_context.wont_be(:valid?) + end + end + + describe '#trace_id' do + it 'reflects the value passed in' do + trace_id = OpenTelemetry::Trace.generate_trace_id + context = OpenTelemetry::Trace::SpanContext.new(trace_id: trace_id) + context.trace_id.must_equal(trace_id) + end + + it 'is invalid for invalid context' do + invalid_context.trace_id + .must_equal(OpenTelemetry::Trace::INVALID_TRACE_ID) + end + end + + describe '#span_id' do + it 'reflects the value passed in' do + span_id = OpenTelemetry::Trace.generate_span_id + context = OpenTelemetry::Trace::SpanContext.new(span_id: span_id) + context.span_id.must_equal(span_id) + end + + it 'is invalid for invalid context' do + invalid_context.span_id.must_equal(OpenTelemetry::Trace::INVALID_SPAN_ID) + end + end + + describe '#trace_flags' do + it 'is unsampled by default' do + span_context.trace_flags.wont_be(:sampled?) + end + + it 'reflects the value passed in' do + flags = OpenTelemetry::Trace::TraceFlags.from_byte(1) + context = OpenTelemetry::Trace::SpanContext.new(trace_flags: flags) + context.trace_flags.must_equal(flags) + end + end +end diff --git a/api/test/opentelemetry/trace/trace_flags_test.rb b/api/test/opentelemetry/trace/trace_flags_test.rb new file mode 100644 index 0000000000..072e29b643 --- /dev/null +++ b/api/test/opentelemetry/trace/trace_flags_test.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'test_helper' + +describe OpenTelemetry::Trace::TraceFlags do + describe '.new' do + it 'is private' do + -> { OpenTelemetry::Trace::TraceFlags.new(0) }\ + .must_raise(NoMethodError) + end + end + describe '.from_byte' do + it 'can be initialized with a byte' do + flags = OpenTelemetry::Trace::TraceFlags.from_byte(0) + flags.sampled?.must_equal(false) + end + + it 'enforces flags is an 8-bit byte' do + -> { OpenTelemetry::Trace::TraceFlags.from_byte(256) }\ + .must_raise(ArgumentError) + end + end + + describe '#sampled?' do + it 'reflects the least-significant bit in the flags' do + sampled = OpenTelemetry::Trace::TraceFlags.from_byte(1) + not_sampled = OpenTelemetry::Trace::TraceFlags.from_byte(0) + + sampled.sampled?.must_equal(true) + not_sampled.sampled?.must_equal(false) + end + end +end