datadog 2.23.0 → 2.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.c +17 -5
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +4 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +12 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +4 -0
- data/ext/datadog_profiling_native_extension/profiling.c +2 -0
- data/lib/datadog/appsec/context.rb +2 -1
- data/lib/datadog/appsec/remote.rb +1 -9
- data/lib/datadog/appsec/security_engine/result.rb +2 -1
- data/lib/datadog/core/configuration/config_helper.rb +1 -1
- data/lib/datadog/core/configuration/deprecations.rb +2 -2
- data/lib/datadog/core/configuration/option_definition.rb +4 -2
- data/lib/datadog/core/configuration/options.rb +8 -5
- data/lib/datadog/core/configuration/settings.rb +14 -3
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -1
- data/lib/datadog/core/environment/cgroup.rb +52 -25
- data/lib/datadog/core/environment/container.rb +140 -46
- data/lib/datadog/core/environment/ext.rb +1 -0
- data/lib/datadog/core/environment/process.rb +9 -1
- data/lib/datadog/core/rate_limiter.rb +9 -1
- data/lib/datadog/core/remote/client.rb +14 -6
- data/lib/datadog/core/remote/component.rb +6 -4
- data/lib/datadog/core/remote/configuration/content.rb +15 -2
- data/lib/datadog/core/remote/configuration/digest.rb +14 -7
- data/lib/datadog/core/remote/configuration/repository.rb +1 -1
- data/lib/datadog/core/remote/configuration/target.rb +13 -6
- data/lib/datadog/core/remote/transport/config.rb +3 -16
- data/lib/datadog/core/remote/transport/http/config.rb +4 -44
- data/lib/datadog/core/remote/transport/http/negotiation.rb +0 -39
- data/lib/datadog/core/remote/transport/http.rb +13 -24
- data/lib/datadog/core/remote/transport/negotiation.rb +7 -16
- data/lib/datadog/core/telemetry/component.rb +52 -13
- data/lib/datadog/core/telemetry/event/app_started.rb +34 -0
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
- data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
- data/lib/datadog/core/telemetry/request.rb +17 -3
- data/lib/datadog/core/telemetry/transport/http/telemetry.rb +2 -32
- data/lib/datadog/core/telemetry/transport/http.rb +21 -16
- data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -10
- data/lib/datadog/core/telemetry/worker.rb +88 -32
- data/lib/datadog/core/transport/ext.rb +2 -0
- data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
- data/lib/datadog/core/transport/http/api/instance.rb +4 -21
- data/lib/datadog/core/transport/http/builder.rb +9 -5
- data/lib/datadog/core/transport/http/client.rb +19 -8
- data/lib/datadog/core/transport/http.rb +22 -19
- data/lib/datadog/core/transport/response.rb +9 -0
- data/lib/datadog/core/transport/transport.rb +90 -0
- data/lib/datadog/core/utils/only_once_successful.rb +2 -0
- data/lib/datadog/core/utils/time.rb +1 -1
- data/lib/datadog/core/workers/async.rb +10 -1
- data/lib/datadog/core/workers/interval_loop.rb +44 -3
- data/lib/datadog/core/workers/polling.rb +2 -0
- data/lib/datadog/core/workers/queue.rb +100 -1
- data/lib/datadog/data_streams/processor.rb +1 -1
- data/lib/datadog/data_streams/transport/http/stats.rb +1 -36
- data/lib/datadog/data_streams/transport/http.rb +5 -6
- data/lib/datadog/data_streams/transport/stats.rb +3 -17
- data/lib/datadog/di/contrib/active_record.rb +31 -5
- data/lib/datadog/di/el/compiler.rb +8 -4
- data/lib/datadog/di/error.rb +5 -0
- data/lib/datadog/di/instrumenter.rb +17 -4
- data/lib/datadog/di/probe_builder.rb +2 -1
- data/lib/datadog/di/probe_manager.rb +37 -31
- data/lib/datadog/di/probe_notification_builder.rb +15 -2
- data/lib/datadog/di/remote.rb +89 -84
- data/lib/datadog/di/transport/diagnostics.rb +7 -35
- data/lib/datadog/di/transport/http/diagnostics.rb +1 -31
- data/lib/datadog/di/transport/http/input.rb +1 -31
- data/lib/datadog/di/transport/http.rb +28 -17
- data/lib/datadog/di/transport/input.rb +7 -34
- data/lib/datadog/di.rb +61 -5
- data/lib/datadog/open_feature/evaluation_engine.rb +2 -1
- data/lib/datadog/open_feature/remote.rb +3 -10
- data/lib/datadog/open_feature/transport.rb +9 -11
- data/lib/datadog/opentelemetry/api/baggage.rb +1 -1
- data/lib/datadog/opentelemetry/configuration/settings.rb +2 -2
- data/lib/datadog/opentelemetry/metrics.rb +21 -14
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +5 -8
- data/lib/datadog/profiling/collectors/code_provenance.rb +27 -2
- data/lib/datadog/profiling/collectors/info.rb +2 -1
- data/lib/datadog/profiling/component.rb +12 -11
- data/lib/datadog/profiling/http_transport.rb +4 -1
- data/lib/datadog/tracing/contrib/extensions.rb +10 -2
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +31 -32
- data/lib/datadog/tracing/contrib/status_range_matcher.rb +2 -1
- data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +3 -1
- data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +6 -3
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
- data/lib/datadog/tracing/remote.rb +1 -9
- data/lib/datadog/tracing/span_event.rb +2 -2
- data/lib/datadog/tracing/span_operation.rb +9 -4
- data/lib/datadog/tracing/trace_operation.rb +44 -6
- data/lib/datadog/tracing/tracer.rb +42 -16
- data/lib/datadog/tracing/transport/http/traces.rb +2 -50
- data/lib/datadog/tracing/transport/http.rb +15 -9
- data/lib/datadog/tracing/transport/io/client.rb +1 -1
- data/lib/datadog/tracing/transport/traces.rb +6 -66
- data/lib/datadog/tracing/workers/trace_writer.rb +5 -0
- data/lib/datadog/tracing/writer.rb +1 -0
- data/lib/datadog/version.rb +2 -2
- metadata +7 -13
- data/lib/datadog/core/remote/transport/http/api.rb +0 -53
- data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
- data/lib/datadog/core/transport/http/api/spec.rb +0 -36
- data/lib/datadog/data_streams/transport/http/api.rb +0 -33
- data/lib/datadog/data_streams/transport/http/client.rb +0 -21
- data/lib/datadog/di/transport/http/api.rb +0 -42
- data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
- data/lib/datadog/tracing/transport/http/api.rb +0 -44
|
@@ -10,7 +10,7 @@ module Datadog
|
|
|
10
10
|
# Current monotonic time
|
|
11
11
|
#
|
|
12
12
|
# @param unit [Symbol] unit for the resulting value, same as ::Process#clock_gettime, defaults to :float_second
|
|
13
|
-
# @return [
|
|
13
|
+
# @return [Float|Integer] timestamp in the requested unit, since some unspecified starting point
|
|
14
14
|
def get_time(unit = :float_second)
|
|
15
15
|
Process.clock_gettime(Process::CLOCK_MONOTONIC, unit)
|
|
16
16
|
end
|
|
@@ -8,6 +8,11 @@ module Datadog
|
|
|
8
8
|
module Async
|
|
9
9
|
# Adds threading behavior to workers
|
|
10
10
|
# to run tasks asynchronously.
|
|
11
|
+
#
|
|
12
|
+
# This module is included in Polling module, and has no other
|
|
13
|
+
# direct users.
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
11
16
|
module Thread
|
|
12
17
|
FORK_POLICY_STOP = :stop
|
|
13
18
|
FORK_POLICY_RESTART = :restart
|
|
@@ -24,7 +29,11 @@ module Datadog
|
|
|
24
29
|
# Methods that must be prepended
|
|
25
30
|
module PrependedMethods
|
|
26
31
|
def perform(*args)
|
|
27
|
-
|
|
32
|
+
unless started?
|
|
33
|
+
start_async do
|
|
34
|
+
self.result = super(*args)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
28
37
|
end
|
|
29
38
|
end
|
|
30
39
|
|
|
@@ -5,6 +5,11 @@ module Datadog
|
|
|
5
5
|
module Workers
|
|
6
6
|
# Adds looping behavior to workers, with a sleep
|
|
7
7
|
# interval between each loop.
|
|
8
|
+
#
|
|
9
|
+
# This module is included in Polling module, and has no other
|
|
10
|
+
# direct users.
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
8
13
|
module IntervalLoop
|
|
9
14
|
BACK_OFF_RATIO = 1.2
|
|
10
15
|
BACK_OFF_MAX = 5
|
|
@@ -38,15 +43,39 @@ module Datadog
|
|
|
38
43
|
|
|
39
44
|
def stop_loop
|
|
40
45
|
mutex.synchronize do
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
# Do not call run_loop? from this method to see if the loop
|
|
47
|
+
# is running, because @run_loop is normally initialized by
|
|
48
|
+
# the background thread and if the stop is requested right
|
|
49
|
+
# after the worker starts, the background thread may be created
|
|
50
|
+
# (and scheduled) but hasn't run yet, thus skipping the
|
|
51
|
+
# write to @run_loop here would leave the thread running forever.
|
|
43
52
|
@run_loop = false
|
|
53
|
+
|
|
54
|
+
# It is possible that we don't need to signal shutdown if
|
|
55
|
+
# @run_loop was not initialized (i.e. we changed it from not
|
|
56
|
+
# defined to false above). But let's be safe and signal the
|
|
57
|
+
# shutdown anyway, I don't see what harm it can cause.
|
|
44
58
|
shutdown.signal
|
|
45
59
|
end
|
|
46
60
|
|
|
61
|
+
# Previously, this method would return false (and do nothing)
|
|
62
|
+
# if the worker was not running the loop. However, this was racy -
|
|
63
|
+
# see https://github.com/DataDog/ruby-guild/issues/279.
|
|
64
|
+
# stop_loop now always sets the state to "stop requested" and,
|
|
65
|
+
# correspondingly, always returns true.
|
|
66
|
+
#
|
|
67
|
+
# There is some test code that returns false when mocking this
|
|
68
|
+
# method - most likely this method should be treated as a void one
|
|
69
|
+
# and the caller should assume that the stop was always requested.
|
|
47
70
|
true
|
|
48
71
|
end
|
|
49
72
|
|
|
73
|
+
# TODO This overwrites Queue's +work_pending?+ method with an
|
|
74
|
+
# implementation that, to me, is at leat questionable semantically:
|
|
75
|
+
# the Queue's idea of pending work is if the buffer is not empty,
|
|
76
|
+
# but this module says that work is pending if the work processing
|
|
77
|
+
# loop is scheduled to run (in other words, as long as the background
|
|
78
|
+
# thread is running, there is always pending work).
|
|
50
79
|
def work_pending?
|
|
51
80
|
run_loop?
|
|
52
81
|
end
|
|
@@ -104,7 +133,19 @@ module Datadog
|
|
|
104
133
|
|
|
105
134
|
def perform_loop
|
|
106
135
|
mutex.synchronize do
|
|
107
|
-
@run_loop
|
|
136
|
+
unless defined?(@run_loop)
|
|
137
|
+
# This write must only happen if @run_loop is not defined
|
|
138
|
+
# (i.e., not initialized). In the case when the worker is
|
|
139
|
+
# asked to stop right after it is created, the thread may not
|
|
140
|
+
# have run yet by the time +stop_loop+ is invoked and
|
|
141
|
+
# we need to preserve the stop-requested state from
|
|
142
|
+
# +stop_loop+ to +perform_loop+.
|
|
143
|
+
#
|
|
144
|
+
# If the workers are refactored to use classes and inheritance
|
|
145
|
+
# and their state, such as @run_loop, is initialized in
|
|
146
|
+
# constructors, the write can be made unconditional.
|
|
147
|
+
@run_loop = true
|
|
148
|
+
end
|
|
108
149
|
|
|
109
150
|
shutdown.wait(mutex, loop_wait_time) if loop_wait_before_first_iteration?
|
|
110
151
|
end
|
|
@@ -5,6 +5,17 @@ module Datadog
|
|
|
5
5
|
module Workers
|
|
6
6
|
# Adds queue behavior to workers, with a buffer
|
|
7
7
|
# to which items can be queued then dequeued.
|
|
8
|
+
#
|
|
9
|
+
# This module is included in some but not all workers.
|
|
10
|
+
# Notably, Data Streams Processor uses a queue but implements it
|
|
11
|
+
# inline rather than using this module.
|
|
12
|
+
#
|
|
13
|
+
# The workers that do include Queue also include Polling, which
|
|
14
|
+
# in turn includes Async::Thread and IntervalLoop. This means
|
|
15
|
+
# we have e.g. +in_iteration?+ always available in any worker
|
|
16
|
+
# that includes Queue.
|
|
17
|
+
#
|
|
18
|
+
# @api private
|
|
8
19
|
module Queue
|
|
9
20
|
def self.included(base)
|
|
10
21
|
base.prepend(PrependedMethods)
|
|
@@ -13,11 +24,16 @@ module Datadog
|
|
|
13
24
|
# Methods that must be prepended
|
|
14
25
|
module PrependedMethods
|
|
15
26
|
def perform(*args)
|
|
16
|
-
|
|
27
|
+
if work_pending?
|
|
28
|
+
work = dequeue
|
|
29
|
+
super(*work)
|
|
30
|
+
end
|
|
17
31
|
end
|
|
18
32
|
end
|
|
19
33
|
|
|
20
34
|
def buffer
|
|
35
|
+
# Why is this an unsynchronized Array and not a Core::Buffer
|
|
36
|
+
# instance?
|
|
21
37
|
@buffer ||= []
|
|
22
38
|
end
|
|
23
39
|
|
|
@@ -34,10 +50,93 @@ module Datadog
|
|
|
34
50
|
!buffer.empty?
|
|
35
51
|
end
|
|
36
52
|
|
|
53
|
+
# Wait for the worker to finish handling all work that has already
|
|
54
|
+
# been submitted to it.
|
|
55
|
+
#
|
|
56
|
+
# If the worker is not enabled, returns nil.
|
|
57
|
+
# If the worker is enabled, returns whether, at the point of return,
|
|
58
|
+
# there was no pending or in progress work.
|
|
59
|
+
#
|
|
60
|
+
# Flushing can time out because there is a constant stream of work
|
|
61
|
+
# submitted at the same or higher rate than it is processed.
|
|
62
|
+
# Flushing can also fail if the worker thread is not running -
|
|
63
|
+
# this method will not flush from the calling thread.
|
|
64
|
+
def flush(timeout: nil)
|
|
65
|
+
# Default timeout is 5 seconds.
|
|
66
|
+
# Specific workers can override it to be more or less
|
|
67
|
+
timeout ||= 5
|
|
68
|
+
|
|
69
|
+
# Nothing needs to be done if the worker is not enabled.
|
|
70
|
+
return nil unless enabled?
|
|
71
|
+
|
|
72
|
+
unless running?
|
|
73
|
+
unless buffer.empty?
|
|
74
|
+
# If we are asked to flush but the worker is not running,
|
|
75
|
+
# do not flush from the caller thread. If the buffer is not
|
|
76
|
+
# empty, it will not be flushed. Log a warning to this effect.
|
|
77
|
+
#
|
|
78
|
+
# We are not guaranteed to have a logger as an instance method,
|
|
79
|
+
# reference the global for now - all other worker methods
|
|
80
|
+
# also reference the logger globally.
|
|
81
|
+
# TODO inject it into worker instances.
|
|
82
|
+
Datadog.logger.debug { "Asked to flush #{self} when the worker is not running" }
|
|
83
|
+
return false
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
started = Utils::Time.get_time
|
|
88
|
+
loop do
|
|
89
|
+
# The AppStarted event is triggered by the worker itself,
|
|
90
|
+
# from the worker thread. As such the main thread has no way
|
|
91
|
+
# to delay itself until that event is queued and we need some
|
|
92
|
+
# way to wait until that event is sent out to assert on it in
|
|
93
|
+
# the test suite. Check the run once flag which *should*
|
|
94
|
+
# indicate the event has been queued (at which point our queue
|
|
95
|
+
# depth check should wait until it's sent).
|
|
96
|
+
# This is still a hack because the flag can be overridden
|
|
97
|
+
# either way with or without the event being sent out.
|
|
98
|
+
# Note that if the AppStarted sending fails, this check
|
|
99
|
+
# will return false and flushing will be blocked until the
|
|
100
|
+
# 15 second timeout.
|
|
101
|
+
# Note that the first wait interval between telemetry event
|
|
102
|
+
# sending is 10 seconds, the timeout needs to be strictly
|
|
103
|
+
# greater than that.
|
|
104
|
+
return true if idle?
|
|
105
|
+
|
|
106
|
+
return false if Utils::Time.get_time - started > timeout
|
|
107
|
+
|
|
108
|
+
sleep 0.5
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
37
112
|
protected
|
|
38
113
|
|
|
39
114
|
attr_writer \
|
|
40
115
|
:buffer
|
|
116
|
+
|
|
117
|
+
# Returns whether this worker has no pending work and is not actively
|
|
118
|
+
# working.
|
|
119
|
+
#
|
|
120
|
+
# The reason why "actively working" is considered is that we use
|
|
121
|
+
# flushing to ensure all work is completed before asserting on the
|
|
122
|
+
# outcome in the tests - if work is happening in a background thread,
|
|
123
|
+
# it's too early to assert on its results.
|
|
124
|
+
def idle?
|
|
125
|
+
# We have a +work_pending?+ method in this class that semantically
|
|
126
|
+
# would be appropriate here instead of calling +buffer.empty?+.
|
|
127
|
+
# Unfortunately IntervalLoop replaces our implementation of
|
|
128
|
+
# +work_pending?+ with one that doesn't make sense at least for the
|
|
129
|
+
# Queue. And we can't change the order of module includes because
|
|
130
|
+
# they all override +perform+ and the correct behavior depends on
|
|
131
|
+
# placing IntervalLoop after Queue.
|
|
132
|
+
#
|
|
133
|
+
# The TraceWriter worker then defines +work_pending?+ to be the
|
|
134
|
+
# same as Queue implementation here... Essentially, it demands
|
|
135
|
+
# the behavior that perhaps should be applied to all workers.
|
|
136
|
+
#
|
|
137
|
+
# Until this mess is untangled, call +buffer.empty?+ here.
|
|
138
|
+
buffer.empty? && !in_iteration?
|
|
139
|
+
end
|
|
41
140
|
end
|
|
42
141
|
end
|
|
43
142
|
end
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../stats'
|
|
4
|
-
require_relative 'client'
|
|
5
|
-
require_relative '../../../core/transport/http/response'
|
|
6
4
|
require_relative '../../../core/transport/http/api/endpoint'
|
|
7
|
-
require_relative '../../../core/transport/http/
|
|
8
|
-
require_relative '../../../core/transport/http/api/instance'
|
|
5
|
+
require_relative '../../../core/transport/http/response'
|
|
9
6
|
|
|
10
7
|
module Datadog
|
|
11
8
|
module DataStreams
|
|
@@ -23,38 +20,6 @@ module Datadog
|
|
|
23
20
|
end
|
|
24
21
|
|
|
25
22
|
module API
|
|
26
|
-
# HTTP API Spec for DSM
|
|
27
|
-
class Spec < Core::Transport::HTTP::API::Spec
|
|
28
|
-
attr_accessor :stats
|
|
29
|
-
|
|
30
|
-
def send_stats(env, &block)
|
|
31
|
-
raise Core::Transport::HTTP::API::Spec::EndpointNotDefinedError.new('stats', self) if stats.nil?
|
|
32
|
-
|
|
33
|
-
stats.call(env, &block)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def encoder
|
|
37
|
-
# DSM handles encoding in the transport layer (MessagePack + gzip)
|
|
38
|
-
# so we don't need an encoder at the API level
|
|
39
|
-
nil
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# HTTP API Instance for DSM
|
|
44
|
-
class Instance < Core::Transport::HTTP::API::Instance
|
|
45
|
-
def send_stats(env)
|
|
46
|
-
unless spec.is_a?(Stats::API::Spec)
|
|
47
|
-
raise Core::Transport::HTTP::API::Instance::EndpointNotSupportedError.new(
|
|
48
|
-
'stats', self
|
|
49
|
-
)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
spec.send_stats(env) do |request_env|
|
|
53
|
-
call(request_env)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
23
|
# Endpoint for submitting DSM stats data
|
|
59
24
|
class Endpoint < Core::Transport::HTTP::API::Endpoint
|
|
60
25
|
def initialize(path)
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../../core/transport/http'
|
|
4
|
-
require_relative 'http/api'
|
|
5
|
-
require_relative 'http/client'
|
|
6
4
|
require_relative 'http/stats'
|
|
7
5
|
require_relative 'stats'
|
|
8
6
|
|
|
@@ -11,6 +9,10 @@ module Datadog
|
|
|
11
9
|
module Transport
|
|
12
10
|
# HTTP transport for Data Streams Monitoring
|
|
13
11
|
module HTTP
|
|
12
|
+
V01 = Stats::API::Endpoint.new(
|
|
13
|
+
'/v0.1/pipeline_stats'
|
|
14
|
+
)
|
|
15
|
+
|
|
14
16
|
module_function
|
|
15
17
|
|
|
16
18
|
# Builds a new Transport::HTTP::Client with default settings
|
|
@@ -19,7 +21,6 @@ module Datadog
|
|
|
19
21
|
logger:
|
|
20
22
|
)
|
|
21
23
|
Core::Transport::HTTP.build(
|
|
22
|
-
api_instance_class: Stats::API::Instance,
|
|
23
24
|
agent_settings: agent_settings,
|
|
24
25
|
logger: logger,
|
|
25
26
|
headers: {
|
|
@@ -27,9 +28,7 @@ module Datadog
|
|
|
27
28
|
'Content-Encoding' => 'gzip'
|
|
28
29
|
}
|
|
29
30
|
) do |transport|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
transport.api API::V01, apis[API::V01], default: true
|
|
31
|
+
transport.api 'v0.1', V01, default: true
|
|
33
32
|
|
|
34
33
|
# Call block to apply any customization, if provided
|
|
35
34
|
yield(transport) if block_given?
|
|
@@ -4,6 +4,7 @@ require 'msgpack'
|
|
|
4
4
|
require 'zlib'
|
|
5
5
|
require_relative '../../core/transport/parcel'
|
|
6
6
|
require_relative '../../core/transport/request'
|
|
7
|
+
require_relative '../../core/transport/transport'
|
|
7
8
|
|
|
8
9
|
module Datadog
|
|
9
10
|
module DataStreams
|
|
@@ -25,18 +26,7 @@ module Datadog
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
# Transport for Data Streams Monitoring stats
|
|
28
|
-
class Transport
|
|
29
|
-
attr_reader :client, :apis, :current_api_id, :logger
|
|
30
|
-
|
|
31
|
-
def initialize(apis, default_api, logger:)
|
|
32
|
-
@apis = apis
|
|
33
|
-
@logger = logger
|
|
34
|
-
@default_api = default_api
|
|
35
|
-
@current_api_id = default_api
|
|
36
|
-
|
|
37
|
-
@client = DataStreams::Transport::HTTP::Client.new(current_api, logger: @logger)
|
|
38
|
-
end
|
|
39
|
-
|
|
29
|
+
class Transport < Core::Transport::Transport
|
|
40
30
|
def send_stats(payload)
|
|
41
31
|
# MessagePack encode and gzip compress the payload
|
|
42
32
|
msgpack_data = MessagePack.pack(payload)
|
|
@@ -47,11 +37,7 @@ module Datadog
|
|
|
47
37
|
request = Request.new(parcel)
|
|
48
38
|
|
|
49
39
|
# Send to agent
|
|
50
|
-
client.
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def current_api
|
|
54
|
-
apis[@current_api_id]
|
|
40
|
+
client.send_request(:stats, request)
|
|
55
41
|
end
|
|
56
42
|
end
|
|
57
43
|
end
|
|
@@ -1,12 +1,38 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
# steep thinks all of the arguments are nil here and does not know what ActiveRecord is.
|
|
4
|
+
# steep:ignore:start
|
|
5
|
+
|
|
6
|
+
Datadog::DI::Serializer.register(
|
|
7
|
+
# This serializer uses a dynamic condition to determine its applicability
|
|
8
|
+
# to a particular value. A simpler case could have been a serializer for
|
|
9
|
+
# a particular class, but in this case any ActiveRecord model is covered
|
|
10
|
+
# and they all have different classes.
|
|
11
|
+
#
|
|
12
|
+
# An alternative could have been to make DI specifically provide lookup
|
|
13
|
+
# logic for "instances of classes derived from X", but a condition Proc
|
|
14
|
+
# is more universal.
|
|
15
|
+
condition: lambda { |value| ActiveRecord::Base === value }
|
|
16
|
+
) do |serializer, value, name:, depth:|
|
|
17
|
+
# +serializer+ is an instance of DI::Serializer.
|
|
18
|
+
# Use it to perform the serialization to primitive values.
|
|
19
|
+
#
|
|
20
|
+
# +value+ is the value to serialize. It should match the condition
|
|
21
|
+
# provided above, meaning it would be an ActiveRecord::Base instance.
|
|
22
|
+
#
|
|
23
|
+
# +name+ is the name of the (local/instance) variable being serialized.
|
|
24
|
+
# The name is used by DI for redaction (upstream of serialization logic),
|
|
25
|
+
# and could potentially be used for redaction here also.
|
|
26
|
+
#
|
|
27
|
+
# +depth+ is the remaining depth for serializing collections and objects.
|
|
28
|
+
# It should always be an integer.
|
|
29
|
+
# Reduce it by 1 when invoking +serialize_value+ on the contents of +value+.
|
|
30
|
+
# This serializer could also potentially do its own depth limiting.
|
|
6
31
|
value_to_serialize = {
|
|
7
32
|
attributes: value.attributes,
|
|
8
33
|
new_record: value.new_record?,
|
|
9
34
|
}
|
|
10
|
-
serializer.serialize_value(value_to_serialize, depth: depth
|
|
11
|
-
# steep:ignore:end
|
|
35
|
+
serializer.serialize_value(value_to_serialize, depth: depth - 1, type: value.class)
|
|
12
36
|
end
|
|
37
|
+
|
|
38
|
+
# steep:ignore:end
|
|
@@ -22,7 +22,8 @@ module Datadog
|
|
|
22
22
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
# Steep: https://github.com/soutaro/steep/issues/363
|
|
26
|
+
OPERATORS = { # steep:ignore IncompatibleAssignment
|
|
26
27
|
'eq' => '==',
|
|
27
28
|
'ne' => '!=',
|
|
28
29
|
'ge' => '>=',
|
|
@@ -31,16 +32,19 @@ module Datadog
|
|
|
31
32
|
'lt' => '<',
|
|
32
33
|
}.freeze
|
|
33
34
|
|
|
35
|
+
# Steep: https://github.com/soutaro/steep/issues/363
|
|
34
36
|
SINGLE_ARG_METHODS = %w[
|
|
35
37
|
len isEmpty isUndefined
|
|
36
|
-
].freeze
|
|
38
|
+
].freeze # steep:ignore IncompatibleAssignment
|
|
37
39
|
|
|
40
|
+
# Steep: https://github.com/soutaro/steep/issues/363
|
|
38
41
|
TWO_ARG_METHODS = %w[
|
|
39
42
|
startsWith endsWith contains matches
|
|
40
43
|
getmember index instanceof
|
|
41
|
-
].freeze
|
|
44
|
+
].freeze # steep:ignore IncompatibleAssignment
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
# Steep: https://github.com/soutaro/steep/issues/363
|
|
47
|
+
MULTI_ARG_METHODS = { # steep:ignore IncompatibleAssignment
|
|
44
48
|
'and' => '&&',
|
|
45
49
|
'or' => '||',
|
|
46
50
|
}.freeze
|
data/lib/datadog/di/error.rb
CHANGED
|
@@ -42,6 +42,11 @@ module Datadog
|
|
|
42
42
|
class ProbePreviouslyFailed < Error
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# Raised when trying to instrument a probe when there is existing
|
|
46
|
+
# instrumentation for the same probe id.
|
|
47
|
+
class AlreadyInstrumented < Error
|
|
48
|
+
end
|
|
49
|
+
|
|
45
50
|
# Raised when installing a line probe and multiple files match the
|
|
46
51
|
# specified path suffix.
|
|
47
52
|
# A probe must be installed into one file only, since UI only
|
|
@@ -166,7 +166,10 @@ module Datadog
|
|
|
166
166
|
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
|
167
167
|
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
|
|
168
168
|
end
|
|
169
|
-
|
|
169
|
+
# We intentionally do not use Core::Utils::Time.get_time
|
|
170
|
+
# here because the time provider may be overridden by the
|
|
171
|
+
# customer, and DI is not allowed to invoke customer code.
|
|
172
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
170
173
|
|
|
171
174
|
rv = nil
|
|
172
175
|
begin
|
|
@@ -190,7 +193,7 @@ module Datadog
|
|
|
190
193
|
# the instrumentation callback runs.
|
|
191
194
|
end
|
|
192
195
|
|
|
193
|
-
duration =
|
|
196
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
|
194
197
|
# The method itself is not part of the stack trace because
|
|
195
198
|
# we are getting the stack trace from outside of the method.
|
|
196
199
|
# Add the method in manually as the top frame.
|
|
@@ -256,6 +259,8 @@ module Datadog
|
|
|
256
259
|
|
|
257
260
|
probe.instrumentation_module = mod
|
|
258
261
|
cls.send(:prepend, mod)
|
|
262
|
+
|
|
263
|
+
DI.instrumented_count_inc(:method)
|
|
259
264
|
end
|
|
260
265
|
end
|
|
261
266
|
|
|
@@ -268,6 +273,8 @@ module Datadog
|
|
|
268
273
|
if mod = probe.instrumentation_module
|
|
269
274
|
mod.send(:remove_method, probe.method_name)
|
|
270
275
|
probe.instrumentation_module = nil
|
|
276
|
+
|
|
277
|
+
DI.instrumented_count_dec(:method)
|
|
271
278
|
end
|
|
272
279
|
end
|
|
273
280
|
end
|
|
@@ -470,12 +477,16 @@ module Datadog
|
|
|
470
477
|
# actual_path could be nil if we don't use targeted trace points.
|
|
471
478
|
probe.instrumented_path = actual_path
|
|
472
479
|
|
|
473
|
-
|
|
480
|
+
# TracePoint#enable returns false when it succeeds.
|
|
481
|
+
rv = if iseq
|
|
474
482
|
tp.enable(target: iseq, target_line: line_no)
|
|
475
483
|
else
|
|
476
484
|
tp.enable
|
|
477
485
|
end
|
|
478
|
-
|
|
486
|
+
|
|
487
|
+
DI.instrumented_count_inc(:line)
|
|
488
|
+
|
|
489
|
+
rv
|
|
479
490
|
end
|
|
480
491
|
true
|
|
481
492
|
end
|
|
@@ -485,6 +496,8 @@ module Datadog
|
|
|
485
496
|
if tp = probe.instrumentation_trace_point
|
|
486
497
|
tp.disable
|
|
487
498
|
probe.instrumentation_trace_point = nil
|
|
499
|
+
|
|
500
|
+
DI.instrumented_count_dec(:line)
|
|
488
501
|
end
|
|
489
502
|
end
|
|
490
503
|
end
|
|
@@ -94,6 +94,19 @@ module Datadog
|
|
|
94
94
|
# matches.
|
|
95
95
|
def add_probe(probe)
|
|
96
96
|
@lock.synchronize do
|
|
97
|
+
if @installed_probes[probe.id]
|
|
98
|
+
# Either this probe was already installed, or another probe was
|
|
99
|
+
# installed with the same id (previous version perhaps?).
|
|
100
|
+
# Since our state tracking is keyed by probe id, we cannot
|
|
101
|
+
# install this probe since we won't have a way of removing the
|
|
102
|
+
# instrumentation for the probe with the same id which is already
|
|
103
|
+
# installed.
|
|
104
|
+
#
|
|
105
|
+
# The exception raised here will be caught below and logged and
|
|
106
|
+
# reported to telemetry.
|
|
107
|
+
raise Error::AlreadyInstrumented, "Probe with id #{probe.id} is already in installed probes"
|
|
108
|
+
end
|
|
109
|
+
|
|
97
110
|
# Probe failed to install previously, do not try to install it again.
|
|
98
111
|
if msg = @failed_probes[probe.id]
|
|
99
112
|
# TODO test this path
|
|
@@ -134,38 +147,30 @@ module Datadog
|
|
|
134
147
|
end
|
|
135
148
|
end
|
|
136
149
|
|
|
137
|
-
# Removes
|
|
138
|
-
#
|
|
139
|
-
|
|
140
|
-
# Remote config contains the list of currently defined probes; any
|
|
141
|
-
# probes not in that list have been removed by user and should be
|
|
142
|
-
# de-instrumented from the application.
|
|
143
|
-
def remove_other_probes(probe_ids)
|
|
150
|
+
# Removes probe with specified id. The probe could be pending or
|
|
151
|
+
# installed. Does nothing if there is no probe with the specified id.
|
|
152
|
+
def remove_probe(probe_id)
|
|
144
153
|
@lock.synchronize do
|
|
145
|
-
@pending_probes.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
logger.debug { "di: error removing #{probe.type} probe at #{probe.location} (#{probe.id}): #{exc.class}: #{exc}" }
|
|
166
|
-
telemetry&.report(exc, description: "Error removing probe")
|
|
167
|
-
end
|
|
168
|
-
end
|
|
154
|
+
@pending_probes.delete(probe_id)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Do not delete the probe from the registry here in case
|
|
158
|
+
# deinstrumentation fails - though I don't know why deinstrumentation
|
|
159
|
+
# would fail and how we could recover if it does.
|
|
160
|
+
# I plan on tracking the number of outstanding (instrumented) probes
|
|
161
|
+
# in the future, and if deinstrumentation fails I would want to
|
|
162
|
+
# keep that probe as "installed" for the count, so that we can
|
|
163
|
+
# investigate the situation.
|
|
164
|
+
if probe = @installed_probes[probe_id]
|
|
165
|
+
begin
|
|
166
|
+
instrumenter.unhook(probe)
|
|
167
|
+
@installed_probes.delete(probe_id)
|
|
168
|
+
rescue => exc
|
|
169
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
170
|
+
# Silence all exceptions?
|
|
171
|
+
# TODO should we propagate here and rescue upstream?
|
|
172
|
+
logger.debug { "di: error removing #{probe.type} probe at #{probe.location} (#{probe.id}): #{exc.class}: #{exc}" }
|
|
173
|
+
telemetry&.report(exc, description: "Error removing probe")
|
|
169
174
|
end
|
|
170
175
|
end
|
|
171
176
|
end
|
|
@@ -185,6 +190,7 @@ module Datadog
|
|
|
185
190
|
# TODO is it OK to hook from trace point handler?
|
|
186
191
|
# TODO the class is now defined, but can hooking still fail?
|
|
187
192
|
instrumenter.hook(probe, self)
|
|
193
|
+
@installed_probes[probe.id] = probe
|
|
188
194
|
@pending_probes.delete(probe.id)
|
|
189
195
|
break
|
|
190
196
|
rescue Error::DITargetNotDefined
|