datadog 2.1.0 → 2.2.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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -2
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +19 -1
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +41 -0
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +1 -1
  6. data/ext/datadog_profiling_native_extension/crashtracker.c +1 -1
  7. data/ext/datadog_profiling_native_extension/extconf.rb +6 -4
  8. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +47 -1
  9. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +1 -1
  10. data/ext/datadog_profiling_native_extension/stack_recorder.c +13 -6
  11. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
  12. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +1 -1
  13. data/lib/datadog/appsec/extensions.rb +1 -0
  14. data/lib/datadog/core/configuration/components.rb +6 -3
  15. data/lib/datadog/core/configuration/settings.rb +39 -0
  16. data/lib/datadog/core/configuration.rb +3 -17
  17. data/lib/datadog/core/deprecations.rb +58 -0
  18. data/lib/datadog/core/environment/yjit.rb +5 -0
  19. data/lib/datadog/core/runtime/ext.rb +1 -0
  20. data/lib/datadog/core/runtime/metrics.rb +6 -0
  21. data/lib/datadog/core/telemetry/component.rb +107 -0
  22. data/lib/datadog/core/telemetry/event.rb +100 -25
  23. data/lib/datadog/core/telemetry/ext.rb +2 -0
  24. data/lib/datadog/core/telemetry/http/adapters/net.rb +1 -1
  25. data/lib/datadog/core/telemetry/metric.rb +167 -0
  26. data/lib/datadog/core/telemetry/metrics_collection.rb +81 -0
  27. data/lib/datadog/core/telemetry/metrics_manager.rb +81 -0
  28. data/lib/datadog/core/telemetry/request.rb +1 -1
  29. data/lib/datadog/core/telemetry/worker.rb +173 -0
  30. data/lib/datadog/core/utils/only_once_successful.rb +76 -0
  31. data/lib/datadog/core.rb +2 -19
  32. data/lib/datadog/opentelemetry/sdk/propagator.rb +5 -10
  33. data/lib/datadog/opentelemetry/sdk/span_processor.rb +5 -2
  34. data/lib/datadog/profiling/collectors/code_provenance.rb +18 -5
  35. data/lib/datadog/profiling/component.rb +18 -1
  36. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +410 -0
  37. data/lib/datadog/profiling.rb +1 -0
  38. data/lib/datadog/tracing/contrib/action_cable/event.rb +1 -1
  39. data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +1 -1
  40. data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +1 -1
  41. data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +1 -1
  42. data/lib/datadog/tracing/contrib/action_mailer/event.rb +4 -6
  43. data/lib/datadog/tracing/contrib/action_mailer/events/deliver.rb +9 -4
  44. data/lib/datadog/tracing/contrib/action_mailer/events/process.rb +3 -2
  45. data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +1 -5
  46. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  47. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  48. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  49. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  50. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  51. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  52. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  53. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  54. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
  55. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  56. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
  57. data/lib/datadog/tracing/contrib/active_support/cache/event.rb +32 -0
  58. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +156 -0
  59. data/lib/datadog/tracing/contrib/active_support/cache/events.rb +34 -0
  60. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +45 -41
  61. data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +17 -40
  62. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +4 -1
  63. data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +29 -6
  64. data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +16 -4
  65. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +33 -29
  66. data/lib/datadog/tracing/contrib/analytics.rb +5 -0
  67. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +5 -0
  68. data/lib/datadog/tracing/contrib/graphql/patcher.rb +8 -2
  69. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +166 -0
  70. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +25 -0
  71. data/lib/datadog/tracing/contrib/kafka/consumer_event.rb +1 -1
  72. data/lib/datadog/tracing/contrib/kafka/consumer_group_event.rb +1 -1
  73. data/lib/datadog/tracing/contrib/kafka/event.rb +1 -1
  74. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +3 -3
  75. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +3 -3
  76. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +3 -3
  77. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +3 -3
  78. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +3 -3
  79. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +3 -3
  80. data/lib/datadog/tracing/contrib/racecar/event.rb +2 -2
  81. data/lib/datadog/tracing/contrib/rails/ext.rb +9 -0
  82. data/lib/datadog/tracing/contrib/rails/patcher.rb +7 -0
  83. data/lib/datadog/tracing/contrib/rails/runner.rb +95 -0
  84. data/lib/datadog/tracing/distributed/b3_multi.rb +1 -1
  85. data/lib/datadog/tracing/distributed/b3_single.rb +3 -1
  86. data/lib/datadog/tracing/distributed/datadog.rb +2 -2
  87. data/lib/datadog/tracing/distributed/propagation.rb +9 -2
  88. data/lib/datadog/tracing/distributed/trace_context.rb +3 -2
  89. data/lib/datadog/tracing/span_operation.rb +3 -2
  90. data/lib/datadog/tracing/trace_operation.rb +7 -3
  91. data/lib/datadog/tracing/trace_segment.rb +4 -1
  92. data/lib/datadog/tracing/tracer.rb +9 -2
  93. data/lib/datadog/tracing.rb +5 -1
  94. data/lib/datadog/version.rb +2 -2
  95. metadata +22 -9
  96. data/lib/datadog/core/telemetry/client.rb +0 -95
  97. data/lib/datadog/core/telemetry/heartbeat.rb +0 -33
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'emitter'
4
+ require_relative 'event'
5
+ require_relative 'metrics_manager'
6
+ require_relative 'worker'
7
+ require_relative '../utils/forking'
8
+
9
+ module Datadog
10
+ module Core
11
+ module Telemetry
12
+ # Telemetry entrypoint, coordinates sending telemetry events at various points in app lifecycle.
13
+ class Component
14
+ attr_reader :enabled
15
+
16
+ include Core::Utils::Forking
17
+
18
+ # @param enabled [Boolean] Determines whether telemetry events should be sent to the API
19
+ # @param metrics_enabled [Boolean] Determines whether telemetry metrics should be sent to the API
20
+ # @param heartbeat_interval_seconds [Float] How frequently heartbeats will be reported, in seconds.
21
+ # @param metrics_aggregation_interval_seconds [Float] How frequently metrics will be aggregated, in seconds.
22
+ # @param [Boolean] dependency_collection Whether to send the `app-dependencies-loaded` event
23
+ def initialize(
24
+ heartbeat_interval_seconds:,
25
+ metrics_aggregation_interval_seconds:,
26
+ dependency_collection:,
27
+ enabled: true,
28
+ metrics_enabled: true
29
+ )
30
+ @enabled = enabled
31
+ @stopped = false
32
+
33
+ @metrics_manager = MetricsManager.new(
34
+ enabled: enabled && metrics_enabled,
35
+ aggregation_interval: metrics_aggregation_interval_seconds
36
+ )
37
+
38
+ @worker = Telemetry::Worker.new(
39
+ enabled: @enabled,
40
+ heartbeat_interval_seconds: heartbeat_interval_seconds,
41
+ metrics_aggregation_interval_seconds: metrics_aggregation_interval_seconds,
42
+ emitter: Emitter.new,
43
+ metrics_manager: @metrics_manager,
44
+ dependency_collection: dependency_collection
45
+ )
46
+ @worker.start
47
+ end
48
+
49
+ def disable!
50
+ @enabled = false
51
+ @worker.enabled = false
52
+ end
53
+
54
+ def stop!
55
+ return if @stopped
56
+
57
+ @worker.stop(true)
58
+ @stopped = true
59
+ end
60
+
61
+ def emit_closing!
62
+ return if !@enabled || forked?
63
+
64
+ @worker.enqueue(Event::AppClosing.new)
65
+ end
66
+
67
+ def integrations_change!
68
+ return if !@enabled || forked?
69
+
70
+ @worker.enqueue(Event::AppIntegrationsChange.new)
71
+ end
72
+
73
+ # Report configuration changes caused by Remote Configuration.
74
+ def client_configuration_change!(changes)
75
+ return if !@enabled || forked?
76
+
77
+ @worker.enqueue(Event::AppClientConfigurationChange.new(changes, 'remote_config'))
78
+ end
79
+
80
+ # Increments a count metric.
81
+ def inc(namespace, metric_name, value, tags: {}, common: true)
82
+ @metrics_manager.inc(namespace, metric_name, value, tags: tags, common: common)
83
+ end
84
+
85
+ # Decremenets a count metric.
86
+ def dec(namespace, metric_name, value, tags: {}, common: true)
87
+ @metrics_manager.dec(namespace, metric_name, value, tags: tags, common: common)
88
+ end
89
+
90
+ # Tracks gauge metric.
91
+ def gauge(namespace, metric_name, value, tags: {}, common: true)
92
+ @metrics_manager.gauge(namespace, metric_name, value, tags: tags, common: common)
93
+ end
94
+
95
+ # Tracks rate metric.
96
+ def rate(namespace, metric_name, value, tags: {}, common: true)
97
+ @metrics_manager.rate(namespace, metric_name, value, tags: tags, common: common)
98
+ end
99
+
100
+ # Tracks distribution metric.
101
+ def distribution(namespace, metric_name, value, tags: {}, common: true)
102
+ @metrics_manager.distribution(namespace, metric_name, value, tags: tags, common: common)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -3,7 +3,16 @@
3
3
  module Datadog
4
4
  module Core
5
5
  module Telemetry
6
+ # Collection of telemetry events
6
7
  class Event
8
+ extend Core::Utils::Forking
9
+
10
+ # returns sequence that increments every time the configuration changes
11
+ def self.configuration_sequence
12
+ after_fork! { @sequence = Datadog::Core::Utils::Sequence.new(1) }
13
+ @sequence ||= Datadog::Core::Utils::Sequence.new(1)
14
+ end
15
+
7
16
  # Base class for all Telemetry V2 events.
8
17
  class Base
9
18
  # The type of the event.
@@ -12,8 +21,7 @@ module Datadog
12
21
  def type; end
13
22
 
14
23
  # The JSON payload for the event.
15
- # @param seq_id [Integer] The sequence ID for the event.
16
- def payload(seq_id)
24
+ def payload
17
25
  {}
18
26
  end
19
27
  end
@@ -24,8 +32,7 @@ module Datadog
24
32
  'app-started'
25
33
  end
26
34
 
27
- def payload(seq_id)
28
- @seq_id = seq_id
35
+ def payload
29
36
  {
30
37
  products: products,
31
38
  configuration: configuration,
@@ -38,6 +45,7 @@ module Datadog
38
45
  private
39
46
 
40
47
  def products
48
+ # @type var products: Hash[Symbol, Hash[Symbol, Object]]
41
49
  products = {
42
50
  appsec: {
43
51
  enabled: Datadog::AppSec.enabled?,
@@ -79,16 +87,19 @@ module Datadog
79
87
  ].freeze
80
88
 
81
89
  # rubocop:disable Metrics/AbcSize
90
+ # rubocop:disable Metrics/MethodLength
82
91
  def configuration
83
92
  config = Datadog.configuration
93
+ seq_id = Event.configuration_sequence.next
84
94
 
85
95
  list = [
86
- conf_value('DD_AGENT_HOST', config.agent.host),
87
- conf_value('DD_AGENT_TRANSPORT', agent_transport(config)),
88
- conf_value('DD_TRACE_SAMPLE_RATE', to_value(config.tracing.sampling.default_rate)),
96
+ conf_value('DD_AGENT_HOST', config.agent.host, seq_id),
97
+ conf_value('DD_AGENT_TRANSPORT', agent_transport(config), seq_id),
98
+ conf_value('DD_TRACE_SAMPLE_RATE', to_value(config.tracing.sampling.default_rate), seq_id),
89
99
  conf_value(
90
100
  'DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED',
91
- config.tracing.contrib.global_default_service_name.enabled
101
+ config.tracing.contrib.global_default_service_name.enabled,
102
+ seq_id
92
103
  ),
93
104
  ]
94
105
 
@@ -97,32 +108,45 @@ module Datadog
97
108
  peer_service_mapping = config.tracing.contrib.peer_service_mapping
98
109
  peer_service_mapping_str = peer_service_mapping.map { |key, value| "#{key}:#{value}" }.join(',')
99
110
  end
100
- list << conf_value('DD_TRACE_PEER_SERVICE_MAPPING', peer_service_mapping_str)
111
+ list << conf_value('DD_TRACE_PEER_SERVICE_MAPPING', peer_service_mapping_str, seq_id)
101
112
 
102
113
  # Whitelist of configuration options to send in additional payload object
103
114
  TARGET_OPTIONS.each do |option|
104
115
  split_option = option.split('.')
105
- list << conf_value(option, to_value(config.dig(*split_option)))
116
+ list << conf_value(option, to_value(config.dig(*split_option)), seq_id)
106
117
  end
107
118
 
108
119
  # Add some more custom additional payload values here
109
120
  list.push(
110
- conf_value('tracing.auto_instrument.enabled', !defined?(Datadog::AutoInstrument::LOADED).nil?),
111
- conf_value('tracing.writer_options.buffer_size', to_value(config.tracing.writer_options[:buffer_size])),
112
- conf_value('tracing.writer_options.flush_interval', to_value(config.tracing.writer_options[:flush_interval])),
113
- conf_value('tracing.opentelemetry.enabled', !defined?(Datadog::OpenTelemetry::LOADED).nil?),
121
+ conf_value('tracing.auto_instrument.enabled', !defined?(Datadog::AutoInstrument::LOADED).nil?, seq_id),
122
+ conf_value(
123
+ 'tracing.writer_options.buffer_size',
124
+ to_value(config.tracing.writer_options[:buffer_size]),
125
+ seq_id
126
+ ),
127
+ conf_value(
128
+ 'tracing.writer_options.flush_interval',
129
+ to_value(config.tracing.writer_options[:flush_interval]),
130
+ seq_id
131
+ ),
132
+ conf_value(
133
+ 'tracing.opentelemetry.enabled',
134
+ !defined?(Datadog::OpenTelemetry::LOADED).nil?,
135
+ seq_id
136
+ ),
114
137
  )
115
- list << conf_value('logger.instance', config.logger.instance.class.to_s) if config.logger.instance
138
+ list << conf_value('logger.instance', config.logger.instance.class.to_s, seq_id) if config.logger.instance
116
139
  if config.respond_to?('appsec')
117
- list << conf_value('appsec.enabled', config.dig('appsec', 'enabled'))
118
- list << conf_value('appsec.sca_enabled', config.dig('appsec', 'sca_enabled'))
140
+ list << conf_value('appsec.enabled', config.dig('appsec', 'enabled'), seq_id)
141
+ list << conf_value('appsec.sca_enabled', config.dig('appsec', 'sca_enabled'), seq_id)
119
142
  end
120
- list << conf_value('ci.enabled', config.dig('ci', 'enabled')) if config.respond_to?('ci')
143
+ list << conf_value('ci.enabled', config.dig('ci', 'enabled'), seq_id) if config.respond_to?('ci')
121
144
 
122
145
  list.reject! { |entry| entry[:value].nil? }
123
146
  list
124
147
  end
125
148
  # rubocop:enable Metrics/AbcSize
149
+ # rubocop:enable Metrics/MethodLength
126
150
 
127
151
  def agent_transport(config)
128
152
  adapter = Core::Configuration::AgentSettingsResolver.call(config).adapter
@@ -133,12 +157,12 @@ module Datadog
133
157
  end
134
158
  end
135
159
 
136
- def conf_value(name, value, origin = 'code')
160
+ def conf_value(name, value, seq_id, origin = 'code')
137
161
  {
138
162
  name: name,
139
163
  value: value,
140
164
  origin: origin,
141
- seq_id: @seq_id,
165
+ seq_id: seq_id,
142
166
  }
143
167
  end
144
168
 
@@ -168,7 +192,7 @@ module Datadog
168
192
  'app-dependencies-loaded'
169
193
  end
170
194
 
171
- def payload(seq_id)
195
+ def payload
172
196
  { dependencies: dependencies }
173
197
  end
174
198
 
@@ -191,7 +215,7 @@ module Datadog
191
215
  'app-integrations-change'
192
216
  end
193
217
 
194
- def payload(seq_id)
218
+ def payload
195
219
  { integrations: integrations }
196
220
  end
197
221
 
@@ -244,18 +268,20 @@ module Datadog
244
268
  @origin = origin
245
269
  end
246
270
 
247
- def payload(seq_id)
248
- { configuration: configuration(seq_id) }
271
+ def payload
272
+ { configuration: configuration }
249
273
  end
250
274
 
251
- def configuration(seq_id)
275
+ def configuration
252
276
  config = Datadog.configuration
277
+ seq_id = Event.configuration_sequence.next
253
278
 
254
279
  res = @changes.map do |name, value|
255
280
  {
256
281
  name: name,
257
282
  value: value,
258
283
  origin: @origin,
284
+ seq_id: seq_id,
259
285
  }
260
286
  end
261
287
 
@@ -285,6 +311,55 @@ module Datadog
285
311
  'app-closing'
286
312
  end
287
313
  end
314
+
315
+ # Telemetry class for the 'generate-metrics' event
316
+ class GenerateMetrics < Base
317
+ def type
318
+ 'generate-metrics'
319
+ end
320
+
321
+ def initialize(namespace, metric_series)
322
+ super()
323
+ @namespace = namespace
324
+ @metric_series = metric_series
325
+ end
326
+
327
+ def payload
328
+ {
329
+ namespace: @namespace,
330
+ series: @metric_series.map(&:to_h)
331
+ }
332
+ end
333
+ end
334
+
335
+ # Telemetry class for the 'distributions' event
336
+ class Distributions < GenerateMetrics
337
+ def type
338
+ 'distributions'
339
+ end
340
+ end
341
+
342
+ # Telemetry class for the 'message-batch' event
343
+ class MessageBatch
344
+ attr_reader :events
345
+
346
+ def type
347
+ 'message-batch'
348
+ end
349
+
350
+ def initialize(events)
351
+ @events = events
352
+ end
353
+
354
+ def payload
355
+ @events.map do |event|
356
+ {
357
+ request_type: event.type,
358
+ payload: event.payload,
359
+ }
360
+ end
361
+ end
362
+ end
288
363
  end
289
364
  end
290
365
  end
@@ -5,7 +5,9 @@ module Datadog
5
5
  module Telemetry
6
6
  module Ext
7
7
  ENV_ENABLED = 'DD_INSTRUMENTATION_TELEMETRY_ENABLED'
8
+ ENV_METRICS_ENABLED = 'DD_TELEMETRY_METRICS_ENABLED'
8
9
  ENV_HEARTBEAT_INTERVAL = 'DD_TELEMETRY_HEARTBEAT_INTERVAL'
10
+ ENV_METRICS_AGGREGATION_INTERVAL = 'DD_TELEMETRY_METRICS_AGGREGATION_INTERVAL'
9
11
  ENV_DEPENDENCY_COLLECTION = 'DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED'
10
12
  ENV_INSTALL_ID = 'DD_INSTRUMENTATION_INSTALL_ID'
11
13
  ENV_INSTALL_TYPE = 'DD_INSTRUMENTATION_INSTALL_TYPE'
@@ -15,7 +15,7 @@ module Datadog
15
15
  :timeout,
16
16
  :ssl
17
17
 
18
- DEFAULT_TIMEOUT = 30
18
+ DEFAULT_TIMEOUT = 2
19
19
 
20
20
  def initialize(hostname:, port: nil, timeout: DEFAULT_TIMEOUT, ssl: true)
21
21
  @hostname = hostname
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Core
5
+ module Telemetry
6
+ # Telemetry metrics data model (internal Datadog metrics for client libraries)
7
+ module Metric
8
+ # Base class for all metric types
9
+ class Base
10
+ attr_reader :name, :tags, :values, :common
11
+
12
+ # @param name [String] metric name
13
+ # @param tags [Array<String>|Hash{String=>String}] metric tags as hash or array of "tag:val" strings
14
+ # @param common [Boolean] true if the metric is common for all languages, false for Ruby-specific metric
15
+ def initialize(name, tags: {}, common: true)
16
+ @name = name
17
+ @values = []
18
+ @tags = tags_to_array(tags)
19
+ @common = common
20
+ end
21
+
22
+ def id
23
+ @id ||= "#{type}::#{name}::#{tags.join(',')}"
24
+ end
25
+
26
+ def track(value)
27
+ raise NotImplementedError, 'method must be implemented in subclasses'
28
+ end
29
+
30
+ def type
31
+ raise NotImplementedError, 'method must be implemented in subclasses'
32
+ end
33
+
34
+ def to_h
35
+ {
36
+ metric: name,
37
+ points: values,
38
+ type: type,
39
+ tags: tags,
40
+ common: common
41
+ }
42
+ end
43
+
44
+ private
45
+
46
+ def tags_to_array(tags)
47
+ return tags if tags.is_a?(Array)
48
+
49
+ tags.map { |k, v| "#{k}:#{v}" }
50
+ end
51
+ end
52
+
53
+ # Base class for metrics that require aggregation interval
54
+ class IntervalMetric < Base
55
+ attr_reader :interval
56
+
57
+ # @param name [String] metric name
58
+ # @param tags [Array<String>|Hash{String=>String}] metric tags as hash of array of "tag:val" strings
59
+ # @param common [Boolean] true if the metric is common for all languages, false for Ruby-specific metric
60
+ # @param interval [Integer] metrics aggregation interval in seconds
61
+ def initialize(name, interval:, tags: {}, common: true)
62
+ raise ArgumentError, 'interval must be a positive number' if interval.nil? || interval <= 0
63
+
64
+ super(name, tags: tags, common: common)
65
+
66
+ @interval = interval
67
+ end
68
+
69
+ def to_h
70
+ res = super
71
+ res[:interval] = interval
72
+ res
73
+ end
74
+ end
75
+
76
+ # Count metric adds up all the submitted values in a time interval. This would be suitable for a
77
+ # metric tracking the number of website hits, for instance.
78
+ class Count < Base
79
+ TYPE = 'count'
80
+
81
+ def type
82
+ TYPE
83
+ end
84
+
85
+ def track(value)
86
+ value = value.to_i
87
+
88
+ if values.empty?
89
+ values << [Time.now.to_i, value]
90
+ else
91
+ values[0][0] = Time.now.to_i
92
+ values[0][1] += value
93
+ end
94
+ nil
95
+ end
96
+ end
97
+
98
+ # A gauge type takes the last value reported during the interval. This type would make sense for tracking RAM or
99
+ # CPU usage, where taking the last value provides a representative picture of the host’s behavior during the time
100
+ # interval.
101
+ class Gauge < IntervalMetric
102
+ TYPE = 'gauge'
103
+
104
+ def type
105
+ TYPE
106
+ end
107
+
108
+ def track(value)
109
+ if values.empty?
110
+ values << [Time.now.to_i, value]
111
+ else
112
+ values[0][0] = Time.now.to_i
113
+ values[0][1] = value
114
+ end
115
+ nil
116
+ end
117
+ end
118
+
119
+ # The rate type takes the count and divides it by the length of the time interval. This is useful if you’re
120
+ # interested in the number of hits per second.
121
+ class Rate < IntervalMetric
122
+ TYPE = 'rate'
123
+
124
+ def initialize(name, interval:, tags: {}, common: true)
125
+ super
126
+
127
+ @value = 0.0
128
+ end
129
+
130
+ def type
131
+ TYPE
132
+ end
133
+
134
+ def track(value = 1.0)
135
+ @value += value
136
+ @values = [[Time.now.to_i, @value / interval]]
137
+ nil
138
+ end
139
+ end
140
+
141
+ # Distribution metric represents the global statistical distribution of a set of values.
142
+ class Distribution < Base
143
+ TYPE = 'distributions'
144
+
145
+ def type
146
+ TYPE
147
+ end
148
+
149
+ def track(value)
150
+ values << value
151
+ nil
152
+ end
153
+
154
+ # distribution metric data does not have type field
155
+ def to_h
156
+ {
157
+ metric: name,
158
+ points: values,
159
+ tags: tags,
160
+ common: common
161
+ }
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'event'
4
+ require_relative 'metric'
5
+
6
+ module Datadog
7
+ module Core
8
+ module Telemetry
9
+ # MetricsCollection is a thread-safe collection of metrics per namespace
10
+ class MetricsCollection
11
+ attr_reader :namespace, :interval
12
+
13
+ def initialize(namespace, aggregation_interval:)
14
+ @namespace = namespace
15
+ @interval = aggregation_interval
16
+
17
+ @mutex = Mutex.new
18
+
19
+ @metrics = {}
20
+ @distributions = {}
21
+ end
22
+
23
+ def inc(metric_name, value, tags: {}, common: true)
24
+ metric = Metric::Count.new(metric_name, tags: tags, common: common)
25
+ fetch_or_add_metric(metric, value)
26
+ end
27
+
28
+ def dec(metric_name, value, tags: {}, common: true)
29
+ metric = Metric::Count.new(metric_name, tags: tags, common: common)
30
+ fetch_or_add_metric(metric, -value)
31
+ end
32
+
33
+ def gauge(metric_name, value, tags: {}, common: true)
34
+ metric = Metric::Gauge.new(metric_name, tags: tags, common: common, interval: @interval)
35
+ fetch_or_add_metric(metric, value)
36
+ end
37
+
38
+ def rate(metric_name, value, tags: {}, common: true)
39
+ metric = Metric::Rate.new(metric_name, tags: tags, common: common, interval: @interval)
40
+ fetch_or_add_metric(metric, value)
41
+ end
42
+
43
+ def distribution(metric_name, value, tags: {}, common: true)
44
+ metric = Metric::Distribution.new(metric_name, tags: tags, common: common)
45
+ fetch_or_add_distribution(metric, value)
46
+ end
47
+
48
+ def flush!
49
+ @mutex.synchronize do
50
+ events = []
51
+ events << Event::GenerateMetrics.new(@namespace, @metrics.values) if @metrics.any?
52
+ events << Event::Distributions.new(@namespace, @distributions.values) if @distributions.any?
53
+
54
+ @metrics = {}
55
+ @distributions = {}
56
+
57
+ events
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def fetch_or_add_metric(metric, value)
64
+ @mutex.synchronize do
65
+ m = (@metrics[metric.id] ||= metric)
66
+ m.track(value)
67
+ end
68
+ nil
69
+ end
70
+
71
+ def fetch_or_add_distribution(metric, value)
72
+ @mutex.synchronize do
73
+ m = (@distributions[metric.id] ||= metric)
74
+ m.track(value)
75
+ end
76
+ nil
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'metrics_collection'
4
+
5
+ module Datadog
6
+ module Core
7
+ module Telemetry
8
+ # MetricsManager aggregates and flushes metrics and distributions
9
+ class MetricsManager
10
+ attr_reader :enabled
11
+
12
+ def initialize(aggregation_interval:, enabled:)
13
+ @interval = aggregation_interval
14
+ @enabled = enabled
15
+ @mutex = Mutex.new
16
+
17
+ @collections = {}
18
+ end
19
+
20
+ def inc(namespace, metric_name, value, tags: {}, common: true)
21
+ return unless @enabled
22
+
23
+ # collection is thread-safe internally
24
+ collection = fetch_or_create_collection(namespace)
25
+ collection.inc(metric_name, value, tags: tags, common: common)
26
+ end
27
+
28
+ def dec(namespace, metric_name, value, tags: {}, common: true)
29
+ return unless @enabled
30
+
31
+ # collection is thread-safe internally
32
+ collection = fetch_or_create_collection(namespace)
33
+ collection.dec(metric_name, value, tags: tags, common: common)
34
+ end
35
+
36
+ def gauge(namespace, metric_name, value, tags: {}, common: true)
37
+ return unless @enabled
38
+
39
+ # collection is thread-safe internally
40
+ collection = fetch_or_create_collection(namespace)
41
+ collection.gauge(metric_name, value, tags: tags, common: common)
42
+ end
43
+
44
+ def rate(namespace, metric_name, value, tags: {}, common: true)
45
+ return unless @enabled
46
+
47
+ # collection is thread-safe internally
48
+ collection = fetch_or_create_collection(namespace)
49
+ collection.rate(metric_name, value, tags: tags, common: common)
50
+ end
51
+
52
+ def distribution(namespace, metric_name, value, tags: {}, common: true)
53
+ return unless @enabled
54
+
55
+ # collection is thread-safe internally
56
+ collection = fetch_or_create_collection(namespace)
57
+ collection.distribution(metric_name, value, tags: tags, common: common)
58
+ end
59
+
60
+ def flush!
61
+ return [] unless @enabled
62
+
63
+ collections = @mutex.synchronize { @collections.values }
64
+ collections.reduce([]) { |events, collection| events + collection.flush! }
65
+ end
66
+
67
+ def disable!
68
+ @enabled = false
69
+ end
70
+
71
+ private
72
+
73
+ def fetch_or_create_collection(namespace)
74
+ @mutex.synchronize do
75
+ @collections[namespace] ||= MetricsCollection.new(namespace, aggregation_interval: @interval)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -17,7 +17,7 @@ module Datadog
17
17
  application: application,
18
18
  debug: false,
19
19
  host: host,
20
- payload: event.payload(seq_id),
20
+ payload: event.payload,
21
21
  request_type: event.type,
22
22
  runtime_id: Core::Environment::Identity.id,
23
23
  seq_id: seq_id,