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

[SDTEST-482] Add get_time_provider setting to avoid timecop mocking the clock_gettime #3948

Merged
merged 6 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
anmarchenko marked this conversation as resolved.
Show resolved Hide resolved
# It must respect the interface of [Datadog::Core::Utils::Time#get_time] method.
#
# When testing, it can be helpful to use a different monotonic clock time provider.
#
# 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
Loading