Skip to content

Commit

Permalink
[OpenTracing] Scope and ScopeManager implementation (#473)
Browse files Browse the repository at this point in the history
* Changed: Scope to expect Span and ScopeManager.

* Added: Datadog::OpenTracer::ThreadLocalScope and Manager.
  • Loading branch information
delner committed Jul 16, 2018
1 parent 1d68de4 commit 277294b
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 4 deletions.
2 changes: 2 additions & 0 deletions lib/ddtrace/opentracer.rb
Expand Up @@ -18,6 +18,8 @@ def load_opentracer
require 'ddtrace/opentracer/span_context_factory'
require 'ddtrace/opentracer/scope'
require 'ddtrace/opentracer/scope_manager'
require 'ddtrace/opentracer/thread_local_scope'
require 'ddtrace/opentracer/thread_local_scope_manager'
require 'ddtrace/opentracer/global_tracer'

# Modify the OpenTracing module functions
Expand Down
9 changes: 9 additions & 0 deletions lib/ddtrace/opentracer/scope.rb
@@ -1,6 +1,15 @@
module Datadog
module OpenTracer
# OpenTracing adapter for scope
class Scope < ::OpenTracing::Scope
attr_reader \
:manager,
:span

def initialize(manager:, span:)
@manager = manager
@span = span
end
end
end
end
30 changes: 30 additions & 0 deletions lib/ddtrace/opentracer/thread_local_scope.rb
@@ -0,0 +1,30 @@
module Datadog
module OpenTracer
# OpenTracing adapter for thread local scopes
class ThreadLocalScope < Scope
attr_reader \
:finish_on_close

def initialize(
manager:,
span:,
finish_on_close: true
)
super(manager: manager, span: span)
@finish_on_close = finish_on_close
@previous_scope = manager.active
end

# Mark the end of the active period for the current thread and Scope,
# updating the ScopeManager#active in the process.
#
# NOTE: Calling close more than once on a single Scope instance leads to
# undefined behavior.
def close
return unless equal?(manager.active)
span.finish if finish_on_close
manager.send(:set_scope, @previous_scope)
end
end
end
end
36 changes: 36 additions & 0 deletions lib/ddtrace/opentracer/thread_local_scope_manager.rb
@@ -0,0 +1,36 @@
module Datadog
module OpenTracer
# OpenTracing adapter for thread local scope management
class ThreadLocalScopeManager < ScopeManager
# Make a span instance active.
#
# @param span [Span] the Span that should become active
# @param finish_on_close [Boolean] whether the Span should automatically be
# finished when Scope#close is called
# @return [Scope] instance to control the end of the active period for the
# Span. It is a programming error to neglect to call Scope#close on the
# returned instance.
def activate(span, finish_on_close: true)
ThreadLocalScope.new(manager: self, span: span).tap do |scope|
set_scope(scope)
end
end

# @return [Scope] the currently active Scope which can be used to access the
# currently active Span.
#
# If there is a non-null Scope, its wrapped Span becomes an implicit parent
# (as Reference#CHILD_OF) of any newly-created Span at Tracer#start_active_span
# or Tracer#start_span time.
def active
Thread.current[object_id.to_s]
end

private

def set_scope(scope)
Thread.current[object_id.to_s] = scope
end
end
end
end
12 changes: 8 additions & 4 deletions spec/ddtrace/opentracer/scope_spec.rb
Expand Up @@ -7,11 +7,15 @@
RSpec.describe Datadog::OpenTracer::Scope do
include_context 'OpenTracing helpers'

subject(:scope) { described_class.new }
subject(:scope) { described_class.new(manager: manager, span: span) }
let(:manager) { instance_double(Datadog::OpenTracer::ScopeManager) }
let(:span) { instance_double(Datadog::OpenTracer::Span) }

describe '#span' do
subject(:span) { scope.span }
it { is_expected.to be(OpenTracing::Span::NOOP_INSTANCE) }
it do
is_expected.to have_attributes(
manager: manager,
span: span
)
end

describe '#close' do
Expand Down
40 changes: 40 additions & 0 deletions spec/ddtrace/opentracer/thread_local_scope_manager_spec.rb
@@ -0,0 +1,40 @@
require 'spec_helper'

require 'ddtrace/opentracer'
require 'ddtrace/opentracer/helper'

if Datadog::OpenTracer.supported?
RSpec.describe Datadog::OpenTracer::ThreadLocalScopeManager do
include_context 'OpenTracing helpers'

subject(:scope_manager) { described_class.new }

describe '#activate' do
subject(:activate) { scope_manager.activate(span, finish_on_close: finish_on_close) }
let(:scope) { activate }
let(:span) { instance_double(Datadog::OpenTracer::Span) }
let(:finish_on_close) { true }
it { is_expected.to be_a_kind_of(Datadog::OpenTracer::ThreadLocalScope) }
it { expect(scope.manager).to be(scope_manager) }
it { expect(scope.span).to be(span) }
end

describe '#activate' do
subject(:active) { scope_manager.active }

context 'when no scope has been activated' do
it { is_expected.to be nil }
end

context 'when a scope has been activated' do
let(:scope) { scope_manager.activate(span, finish_on_close: finish_on_close) }
let(:span) { instance_double(Datadog::OpenTracer::Span) }
let(:finish_on_close) { true }

before(:each) { scope } # Activate a scope

it { is_expected.to be(scope) }
end
end
end
end
80 changes: 80 additions & 0 deletions spec/ddtrace/opentracer/thread_local_scope_spec.rb
@@ -0,0 +1,80 @@
require 'spec_helper'

require 'ddtrace/opentracer'
require 'ddtrace/opentracer/helper'

if Datadog::OpenTracer.supported?
RSpec.describe Datadog::OpenTracer::ThreadLocalScope do
include_context 'OpenTracing helpers'

subject(:scope) do
described_class.new(
manager: manager,
span: span,
finish_on_close: finish_on_close
)
end
let(:manager) { Datadog::OpenTracer::ThreadLocalScopeManager.new }
let(:span) { instance_double(Datadog::OpenTracer::Span) }
let(:finish_on_close) { true }
let(:previous_scope) { nil }

before(:each) do
allow(manager).to receive(:active) do
# Unstub after first call
allow(manager).to receive(:active).and_call_original
previous_scope
end
end

it { is_expected.to be_a_kind_of(Datadog::OpenTracer::Scope) }
it { is_expected.to have_attributes(finish_on_close: finish_on_close) }

describe '#close' do
subject(:close) { scope.close }

context 'when the scope is' do
before(:each) do
scope # Initialize the scope, to prevent overstubbing the previous stub
allow(manager).to receive(:active).and_return(active_scope)
end

context 'active' do
let(:active_scope) { scope }

context 'and #finish_on_close' do
context 'is true' do
let(:finish_on_close) { true }

it 'finishes the span and restores the previous scope' do
expect(span).to receive(:finish)
expect(manager).to receive(:set_scope).with(previous_scope)
scope.close
end
end

context 'is false' do
let(:finish_on_close) { false }

it 'does not finish the span but restores the previous scope' do
expect(span).to_not receive(:finish)
expect(manager).to receive(:set_scope).with(previous_scope)
scope.close
end
end
end
end

context 'not active' do
let(:active_scope) { instance_double(described_class) }

it 'should do nothing' do
expect(span).to_not receive(:finish)
expect(manager).to_not receive(:set_scope)
scope.close
end
end
end
end
end
end

0 comments on commit 277294b

Please sign in to comment.