Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monotonic clock #1173

Merged
merged 17 commits into from
Sep 17, 2020
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ def start_span(name, id, payload, start = nil)

# Start a trace
tracer.trace(@span_name, @options.dup).tap do |span|
# Assign start time if provided
span.start_time = start unless start.nil?
# Start span if time is provided
span.start(start) unless start.nil?
payload[:datadog_span] = span
end
end
Expand Down
7 changes: 4 additions & 3 deletions lib/ddtrace/contrib/grape/endpoint.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

require 'ddtrace/ext/http'
require 'ddtrace/ext/errors'
require 'ddtrace/contrib/analytics'
Expand Down Expand Up @@ -95,7 +96,7 @@ def endpoint_run(name, start, finish, id, payload)
span.set_tag(Ext::TAG_ROUTE_ENDPOINT, api_view) unless api_view.nil?
span.set_tag(Ext::TAG_ROUTE_PATH, path)
ensure
span.start_time = start
span.start(start)
span.finish(finish)
end
rescue StandardError => e
Expand Down Expand Up @@ -134,7 +135,7 @@ def endpoint_render(name, start, finish, id, payload)

span.set_error(payload[:exception_object]) unless payload[:exception_object].nil?
ensure
span.start_time = start
span.start(start)
span.finish(finish)
end
rescue StandardError => e
Expand Down Expand Up @@ -169,7 +170,7 @@ def endpoint_run_filters(name, start, finish, id, payload)
span.set_error(payload[:exception_object]) unless payload[:exception_object].nil?
span.set_tag(Ext::TAG_FILTER_TYPE, type.to_s)
ensure
span.start_time = start
span.start(start)
span.finish(finish)
end
rescue StandardError => e
Expand Down
117 changes: 90 additions & 27 deletions lib/ddtrace/span.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
require 'ddtrace/analytics'
require 'ddtrace/forced_tracing'
require 'ddtrace/diagnostics/health'
require 'ddtrace/span/duration'
require 'ddtrace/utils/time'

module Datadog
# Represents a logical unit of work in the system. Each trace consists of one or more spans.
Expand Down Expand Up @@ -36,12 +38,12 @@ class Span
NUMERIC_TAG_SIZE_RANGE = (-2**53..2**53)

attr_accessor :name, :service, :resource, :span_type,
:start_time, :end_time,
:span_id, :trace_id, :parent_id,
:status, :sampled,
:tracer, :context
:tracer, :context, :duration, :start_time, :end_time

attr_reader :parent

# Create a new span linked to the given tracer. Call the \Tracer method <tt>start_span()</tt>
# and then <tt>finish()</tt> once the tracer operation is over.
#
Expand Down Expand Up @@ -72,11 +74,14 @@ def initialize(tracer, name, options = {})
@parent = nil
@sampled = true

@start_time = nil # set by Tracer.start_span
@end_time = nil # set by Span.finish

@allocation_count_start = now_allocations
@allocation_count_finish = @allocation_count_start

@start_time = nil
brettlangdon marked this conversation as resolved.
Show resolved Hide resolved
@duration_start = nil
@end_time = nil
@duration_end = nil
@wall_clock_duration = false
end

# Set the given key / value tag pair on the span. Keys and values
Expand Down Expand Up @@ -160,6 +165,36 @@ def set_error(e)
set_tag(Ext::Errors::STACK, e.backtrace) unless e.backtrace.empty?
end

# Mark the span started at the current time.
def start(start_time = nil)
brettlangdon marked this conversation as resolved.
Show resolved Hide resolved
# A span should not be started twice. Note that this is not thread-safe,
# start is called from multiple threads, a given span might be started
# several times. Again, one should not do this, so this test is more a
# fallback to avoid very bad things and protect you in most common cases.
return if started?

if start_time
@start_time = start_time
@duration_start = start_time
@wall_clock_duration = true
else
@start_time = Time.now.utc
@duration_start = duration_marker
end

self
end

# for backwards compatibility
def start_time=(time)
time.tap { start(time) }
end

# for backwards compatibility
def end_time=(time)
time.tap { finish(time) }
end

# Mark the span finished at the current time and submit it.
def finish(finish_time = nil)
# A span should not be finished twice. Note that this is not thread-safe,
Expand All @@ -170,12 +205,22 @@ def finish(finish_time = nil)

@allocation_count_finish = now_allocations

# Provide a default start_time if unset, but this should have been set by start_span.
# Using now here causes 0-duration spans, still, this is expected, as we never
# explicitely say when it started.
@start_time ||= Time.now.utc
now = Time.now.utc

@end_time = finish_time.nil? ? Time.now.utc : finish_time # finish this
# Provide a default start_time if unset.
# Using `now` here causes duration to be 0; this is expected
# behavior when start_time is unknown.
start(finish_time || now) unless started?

if finish_time
@end_time = finish_time
@duration_start = @start_time
@duration_end = finish_time
@wall_clock_duration = true
else
@end_time = now
@duration_end = @wall_clock_duration ? now : duration_marker
end

# Finish does not really do anything if the span is not bound to a tracer and a context.
return self if @tracer.nil? || @context.nil?
Expand All @@ -195,11 +240,6 @@ def finish(finish_time = nil)
self
end

# Return whether the span is finished or not.
def finished?
!@end_time.nil?
end

# Return a string representation of the span.
def to_s
"Span(name:#{@name},sid:#{@span_id},tid:#{@trace_id},pid:#{@parent_id})"
Expand Down Expand Up @@ -232,7 +272,7 @@ def allocations

# Return the hash representation of the current span.
def to_hash
h = {
{
span_id: @span_id,
parent_id: @parent_id,
trace_id: @trace_id,
Expand All @@ -244,21 +284,18 @@ def to_hash
metrics: @metrics,
allocations: allocations,
error: @status
}

if !@start_time.nil? && !@end_time.nil?
h[:start] = (@start_time.to_f * 1e9).to_i
h[:duration] = ((@end_time - @start_time) * 1e9).to_i
}.tap do |h|
if complete?
h[:start] = (start_time.to_f * 1e9).to_i
h[:duration] = (duration.to_f * 1e9).to_i
end
end

h
end

# Return a human readable version of the span
def pretty_print(q)
start_time = (@start_time.to_f * 1e9).to_i rescue '-'
end_time = (@end_time.to_f * 1e9).to_i rescue '-'
duration = ((@end_time - @start_time) * 1e9).to_i rescue 0
start_time = (start_time.to_f * 1e9).to_i rescue '-'
end_time = (end_time.to_f * 1e9).to_i rescue '-'
q.group 0 do
q.breakable
q.text "Name: #{@name}\n"
Expand All @@ -271,7 +308,7 @@ def pretty_print(q)
q.text "Error: #{@status}\n"
q.text "Start: #{start_time}\n"
q.text "End: #{end_time}\n"
q.text "Duration: #{duration}\n"
q.text "Duration: #{duration.to_f}\n"
q.text "Allocations: #{allocations}\n"
q.group(2, 'Tags: [', "]\n") do
q.breakable
Expand All @@ -288,8 +325,34 @@ def pretty_print(q)
end
end

# Return whether the duration is started or not
def started?
!@start_time.nil?
end

# Return whether the duration is finished or not.
def finished?
!@end_time.nil?
end

def complete?
brettlangdon marked this conversation as resolved.
Show resolved Hide resolved
started? && finished?
end

def duration
if @wall_clock_duration
(@start_time - @end_time).to_f rescue 0.0
else
(@duration_end - @duration_start).to_f rescue 0.0
end
end

private

def duration_marker
Utils::Time.get_time
end

if defined?(JRUBY_VERSION) || Gem::Version.new(RUBY_VERSION) < Gem::Version.new(VERSION::MINIMUM_RUBY_VERSION)
def now_allocations
0
Expand Down
6 changes: 3 additions & 3 deletions lib/ddtrace/tracer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,7 @@ def guess_context_and_parent(child_of)
# * +start_time+: when the span actually starts (defaults to \now)
# * +tags+: extra tags which should be added to the span.
def start_span(name, options = {})
start_time = options.fetch(:start_time, Time.now.utc)

start_time = options[:start_time]
tags = options.fetch(:tags, {})

span_options = options.select do |k, _v|
Expand All @@ -212,9 +211,10 @@ def start_span(name, options = {})
# child span
span.parent = parent # sets service, trace_id, parent_id, sampled
end

span.set_tags(@tags) unless @tags.empty?
span.set_tags(tags) unless tags.empty?
span.start_time = start_time
span.start(start_time)

# this could at some point be optional (start_active_span vs start_manual_span)
ctx.add_span(span) unless ctx.nil?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

it do
expect(tracer).to receive(:trace).with(span_name, options).and_return(span).ordered
expect(span).to receive(:start_time=).with(start).and_return(span).ordered
expect(span).to receive(:start).with(start).and_return(span).ordered
expect(spy).to receive(:call).with(span, name, id, payload).ordered
expect(span).to receive(:finish).with(finish).and_return(span).ordered
is_expected.to be(span)
Expand All @@ -47,7 +47,7 @@

it 'finishes tracing anyways' do
expect(tracer).to receive(:trace).with(span_name, options).and_return(span).ordered
expect(span).to receive(:start_time=).with(start).and_return(span).ordered
expect(span).to receive(:start).with(start).and_return(span).ordered
expect(span).to receive(:finish).with(finish).and_return(span).ordered
is_expected.to be(span)
end
Expand Down