Skip to content

Commit

Permalink
Merge pull request #3948 from DataDog/anmarchenko/get_monotonic_time_…
Browse files Browse the repository at this point in the history
…provider

[SDTEST-482] Add get_time_provider setting to avoid timecop mocking the clock_gettime
  • Loading branch information
anmarchenko authored Sep 26, 2024
2 parents a6b9175 + b03599c commit db60bdc
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 0 deletions.
27 changes: 27 additions & 0 deletions lib/datadog/core/configuration/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,33 @@ def initialize(*_)
end
end

# The monotonic clock time provider used by Datadog. This option is internal and is used by `datadog-ci`
# gem to avoid traces' durations being skewed by timecop.
#
# It must respect the interface of [Datadog::Core::Utils::Time#get_time] method.
#
# For [Timecop](https://rubygems.org/gems/timecop), for example,
# `->(unit = :float_second) { ::Process.clock_gettime_without_mock(::Process::CLOCK_MONOTONIC, unit) }`
# allows Datadog features to use the real monotonic time when time is frozen with
# `Timecop.mock_process_clock = true`.
#
# @default `->(unit = :float_second) { ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, unit)}`
# @return [Proc<Numeric>]
option :get_time_provider do |o|
o.default_proc { |unit = :float_second| ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, unit) }
o.type :proc

o.after_set do |get_time_provider|
Core::Utils::Time.get_time_provider = get_time_provider
end

o.resetter do |_value|
->(unit = :float_second) { ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, unit) }.tap do |default|
Core::Utils::Time.get_time_provider = default
end
end
end

# The `version` tag in Datadog. Use it to enable [Deployment Tracking](https://docs.datadoghq.com/tracing/deployment_tracking/).
# @see https://docs.datadoghq.com/getting_started/tagging/unified_service_tagging
# @default `DD_VERSION` environment variable, otherwise `nils`
Expand Down
12 changes: 12 additions & 0 deletions lib/datadog/core/utils/time.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ def now_provider=(block)
define_singleton_method(:now, &block)
end

# Overrides the implementation of `#get_time
# with the provided callable.
#
# Overriding the method `#get_time` instead of
# indirectly calling `block` removes
# one level of method call overhead.
#
# @param block [Proc] block that accepts unit and returns timestamp in the requested unit
def get_time_provider=(block)
define_singleton_method(:get_time, &block)
end

def measure(unit = :float_second)
before = get_time(unit)
yield
Expand Down
1 change: 1 addition & 0 deletions sig/datadog/core/utils/time.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Datadog
def self?.get_time: (?::Symbol unit) -> ::Numeric
def self?.now: () -> ::Time
def self?.now_provider=: (^() -> ::Time block) -> void
def self?.get_time_provider=: (^(?::Symbol unit) -> ::Numeric block) -> void
def self?.measure: (?::Symbol unit) { () -> void } -> ::Numeric
def self?.as_utc_epoch_ns: (::Time time) -> ::Integer
end
Expand Down
67 changes: 67 additions & 0 deletions spec/datadog/core/configuration/settings_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,73 @@
end
end

describe '#get_time_provider=' do
subject(:set_get_time_provider) { settings.get_time_provider = get_time_provider }

after { settings.reset! }

let(:get_time) { 1 }

let(:get_time_new_milliseconds) { 42 }
let(:get_time_new_seconds) { 0.042 }

let(:unit) { :float_second }
let(:get_time_provider) do
new_milliseconds = get_time_new_milliseconds # Capture for closure
new_seconds = get_time_new_seconds # Capture for closure

->(unit) { unit == :float_millisecond ? new_milliseconds : new_seconds }
end

context 'when default' do
before { allow(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC, unit).and_return(1) }

it 'delegates to Process.clock_gettime' do
expect(settings.get_time_provider.call(unit)).to eq(get_time)
expect(Datadog::Core::Utils::Time.get_time(unit)).to eq(get_time)
end
end

context 'when given a value' do
before { set_get_time_provider }

context 'when unit is :float_second' do
it 'returns the provided time in float seconds' do
expect(settings.get_time_provider.call(unit)).to eq(get_time_new_seconds)
expect(Datadog::Core::Utils::Time.get_time(unit)).to eq(get_time_new_seconds)
end
end

context 'when unit is :float_millisecond' do
let(:unit) { :float_millisecond }

it 'returns the provided time in float milliseconds' do
expect(settings.get_time_provider.call(unit)).to eq(get_time_new_milliseconds)
expect(Datadog::Core::Utils::Time.get_time(unit)).to eq(get_time_new_milliseconds)
end
end
end

context 'then reset' do
let(:original_get_time) { 1 }

before do
set_get_time_provider
allow(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC, unit).and_return(original_get_time)
end

it 'returns the provided time' do
expect(settings.get_time_provider.call(unit)).to eq(get_time_new_seconds)
expect(Datadog::Core::Utils::Time.get_time(unit)).to eq(get_time_new_seconds)

settings.reset!

expect(settings.get_time_provider.call(unit)).to eq(original_get_time)
expect(Datadog::Core::Utils::Time.get_time(unit)).to eq(original_get_time)
end
end
end

# Important note: These settings are used as inputs of the AgentSettingsResolver and are used by all components
# that consume its result (e.g. tracing, profiling, and telemetry, as of January 2023).
describe '#agent' do
Expand Down
21 changes: 21 additions & 0 deletions spec/datadog/tracing/span_operation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,27 @@
expect(span_op.end_time - span_op.start_time).to eq 0
end
end

context 'with get_time_provider set' do
let(:clock_increment) { 0.42 }
before do
incr = clock_increment
clock_time = clock_increment
Datadog.configure do |c|
# Use a custom clock provider that increments by `clock_increment`
c.get_time_provider = ->(_unit = :float_second) { clock_time += incr }
end
end

after { without_warnings { Datadog.configuration.reset! } }

it 'sets the duration to the provider increment' do
span_op.start
span_op.stop

expect(span_op.duration).to be_within(0.01).of(clock_increment)
end
end
end

context 'with start_time provided' do
Expand Down

0 comments on commit db60bdc

Please sign in to comment.