datadog-ci 1.2.0 → 1.3.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -2
  3. data/lib/datadog/ci/configuration/components.rb +30 -3
  4. data/lib/datadog/ci/contrib/cucumber/formatter.rb +13 -9
  5. data/lib/datadog/ci/contrib/minitest/runnable.rb +5 -1
  6. data/lib/datadog/ci/contrib/minitest/runner.rb +6 -2
  7. data/lib/datadog/ci/contrib/minitest/test.rb +7 -3
  8. data/lib/datadog/ci/contrib/rspec/example.rb +6 -2
  9. data/lib/datadog/ci/contrib/rspec/example_group.rb +5 -1
  10. data/lib/datadog/ci/contrib/rspec/knapsack_pro/runner.rb +6 -2
  11. data/lib/datadog/ci/contrib/rspec/runner.rb +6 -2
  12. data/lib/datadog/ci/ext/environment/providers/appveyor.rb +1 -1
  13. data/lib/datadog/ci/ext/environment/providers/aws_code_pipeline.rb +1 -1
  14. data/lib/datadog/ci/ext/environment/providers/azure.rb +1 -1
  15. data/lib/datadog/ci/ext/environment/providers/bitbucket.rb +1 -1
  16. data/lib/datadog/ci/ext/environment/providers/bitrise.rb +1 -1
  17. data/lib/datadog/ci/ext/environment/providers/buddy.rb +1 -1
  18. data/lib/datadog/ci/ext/environment/providers/buildkite.rb +1 -1
  19. data/lib/datadog/ci/ext/environment/providers/circleci.rb +1 -1
  20. data/lib/datadog/ci/ext/environment/providers/codefresh.rb +1 -1
  21. data/lib/datadog/ci/ext/environment/providers/github_actions.rb +1 -1
  22. data/lib/datadog/ci/ext/environment/providers/gitlab.rb +1 -1
  23. data/lib/datadog/ci/ext/environment/providers/jenkins.rb +1 -1
  24. data/lib/datadog/ci/ext/environment/providers/teamcity.rb +1 -1
  25. data/lib/datadog/ci/ext/environment/providers/travis.rb +1 -1
  26. data/lib/datadog/ci/ext/environment.rb +17 -0
  27. data/lib/datadog/ci/ext/telemetry.rb +120 -0
  28. data/lib/datadog/ci/git/local_repository.rb +116 -25
  29. data/lib/datadog/ci/git/search_commits.rb +20 -1
  30. data/lib/datadog/ci/git/telemetry.rb +37 -0
  31. data/lib/datadog/ci/git/tree_uploader.rb +7 -0
  32. data/lib/datadog/ci/git/upload_packfile.rb +22 -1
  33. data/lib/datadog/ci/test.rb +5 -0
  34. data/lib/datadog/ci/test_optimisation/component.rb +16 -2
  35. data/lib/datadog/ci/test_optimisation/coverage/transport.rb +10 -1
  36. data/lib/datadog/ci/test_optimisation/skippable.rb +24 -0
  37. data/lib/datadog/ci/test_optimisation/telemetry.rb +56 -0
  38. data/lib/datadog/ci/test_visibility/component.rb +95 -235
  39. data/lib/datadog/ci/test_visibility/context.rb +274 -0
  40. data/lib/datadog/ci/test_visibility/{context → store}/global.rb +1 -1
  41. data/lib/datadog/ci/test_visibility/{context → store}/local.rb +1 -1
  42. data/lib/datadog/ci/test_visibility/telemetry.rb +69 -0
  43. data/lib/datadog/ci/test_visibility/transport.rb +8 -9
  44. data/lib/datadog/ci/transport/adapters/net.rb +42 -11
  45. data/lib/datadog/ci/transport/adapters/net_http_client.rb +17 -0
  46. data/lib/datadog/ci/transport/adapters/telemetry_webmock_safe_adapter.rb +28 -0
  47. data/lib/datadog/ci/transport/event_platform_transport.rb +42 -5
  48. data/lib/datadog/ci/transport/http.rb +49 -21
  49. data/lib/datadog/ci/transport/remote_settings_api.rb +39 -1
  50. data/lib/datadog/ci/transport/telemetry.rb +93 -0
  51. data/lib/datadog/ci/utils/identity.rb +20 -0
  52. data/lib/datadog/ci/utils/parsing.rb +2 -1
  53. data/lib/datadog/ci/utils/telemetry.rb +23 -0
  54. data/lib/datadog/ci/version.rb +1 -1
  55. data/lib/datadog/ci.rb +30 -0
  56. metadata +16 -6
@@ -0,0 +1,274 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "datadog/tracing"
4
+ require "datadog/tracing/contrib/component"
5
+ require "datadog/tracing/trace_digest"
6
+
7
+ require_relative "store/global"
8
+ require_relative "store/local"
9
+
10
+ require_relative "../ext/app_types"
11
+ require_relative "../ext/environment"
12
+ require_relative "../ext/test"
13
+
14
+ require_relative "../span"
15
+ require_relative "../test"
16
+ require_relative "../test_session"
17
+ require_relative "../test_module"
18
+ require_relative "../test_suite"
19
+
20
+ module Datadog
21
+ module CI
22
+ module TestVisibility
23
+ # Manages current in-memory context for test visibility (such as active test session, suite, test, etc.).
24
+ # Its responsibility includes building domain models for test visibility as well.
25
+ # Internally it uses Datadog::Tracing module to create spans.
26
+ class Context
27
+ def initialize
28
+ @local_context = Store::Local.new
29
+ @global_context = Store::Global.new
30
+ end
31
+
32
+ def start_test_session(service: nil, tags: {})
33
+ @global_context.fetch_or_activate_test_session do
34
+ tracer_span = start_datadog_tracer_span(
35
+ "test.session", build_tracing_span_options(service, Ext::AppTypes::TYPE_TEST_SESSION)
36
+ )
37
+ set_session_context(tags, tracer_span)
38
+
39
+ build_test_session(tracer_span, tags)
40
+ end
41
+ end
42
+
43
+ def start_test_module(test_module_name, service: nil, tags: {})
44
+ @global_context.fetch_or_activate_test_module do
45
+ set_inherited_globals(tags)
46
+ set_session_context(tags)
47
+
48
+ tracer_span = start_datadog_tracer_span(
49
+ test_module_name, build_tracing_span_options(service, Ext::AppTypes::TYPE_TEST_MODULE)
50
+ )
51
+ set_module_context(tags, tracer_span)
52
+
53
+ build_test_module(tracer_span, tags)
54
+ end
55
+ end
56
+
57
+ def start_test_suite(test_suite_name, service: nil, tags: {})
58
+ @global_context.fetch_or_activate_test_suite(test_suite_name) do
59
+ set_inherited_globals(tags)
60
+ set_session_context(tags)
61
+ set_module_context(tags)
62
+
63
+ tracer_span = start_datadog_tracer_span(
64
+ test_suite_name, build_tracing_span_options(service, Ext::AppTypes::TYPE_TEST_SUITE)
65
+ )
66
+ set_suite_context(tags, span: tracer_span)
67
+
68
+ build_test_suite(tracer_span, tags)
69
+ end
70
+ end
71
+
72
+ def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
73
+ set_inherited_globals(tags)
74
+ set_session_context(tags)
75
+ set_module_context(tags)
76
+ set_suite_context(tags, name: test_suite_name)
77
+
78
+ tags[Ext::Test::TAG_NAME] = test_name
79
+ tags[Ext::Test::TAG_TYPE] ||= Ext::Test::Type::TEST
80
+
81
+ span_options = build_tracing_span_options(
82
+ service,
83
+ Ext::AppTypes::TYPE_TEST,
84
+ # :resource is needed for the agent APM protocol to work correctly (for older agent versions)
85
+ # :continue_from is required to start a new trace for each test
86
+ {resource: test_name, continue_from: Datadog::Tracing::TraceDigest.new}
87
+ )
88
+
89
+ if block
90
+ start_datadog_tracer_span(test_name, span_options) do |tracer_span|
91
+ test = build_test(tracer_span, tags)
92
+
93
+ @local_context.activate_test(test) do
94
+ block.call(test)
95
+ end
96
+ end
97
+ else
98
+ tracer_span = start_datadog_tracer_span(test_name, span_options)
99
+ test = build_test(tracer_span, tags)
100
+ @local_context.activate_test(test)
101
+ test
102
+ end
103
+ end
104
+
105
+ def trace(span_name, type: "span", tags: {}, &block)
106
+ span_options = build_tracing_span_options(
107
+ nil, # service name is completely optional for custom spans
108
+ type,
109
+ {resource: span_name}
110
+ )
111
+
112
+ if block
113
+ start_datadog_tracer_span(span_name, span_options) do |tracer_span|
114
+ block.call(build_span(tracer_span, tags))
115
+ end
116
+ else
117
+ tracer_span = start_datadog_tracer_span(span_name, span_options)
118
+
119
+ build_span(tracer_span, tags)
120
+ end
121
+ end
122
+
123
+ def active_span
124
+ tracer_span = Datadog::Tracing.active_span
125
+ Span.new(tracer_span) if tracer_span
126
+ end
127
+
128
+ def active_test
129
+ @local_context.active_test
130
+ end
131
+
132
+ def active_test_session
133
+ @global_context.active_test_session
134
+ end
135
+
136
+ def active_test_module
137
+ @global_context.active_test_module
138
+ end
139
+
140
+ def active_test_suite(test_suite_name)
141
+ @global_context.active_test_suite(test_suite_name)
142
+ end
143
+
144
+ def single_active_test_suite
145
+ @global_context.fetch_single_test_suite
146
+ end
147
+
148
+ def deactivate_test
149
+ @local_context.deactivate_test
150
+ end
151
+
152
+ def deactivate_test_session
153
+ @global_context.deactivate_test_session!
154
+ end
155
+
156
+ def deactivate_test_module
157
+ @global_context.deactivate_test_module!
158
+ end
159
+
160
+ def deactivate_test_suite(test_suite_name)
161
+ @global_context.deactivate_test_suite!(test_suite_name)
162
+ end
163
+
164
+ private
165
+
166
+ # BUILDING DOMAIN MODELS
167
+ def build_test_session(tracer_span, tags)
168
+ test_session = TestSession.new(tracer_span)
169
+ set_initial_tags(test_session, tags)
170
+ test_session
171
+ end
172
+
173
+ def build_test_module(tracer_span, tags)
174
+ test_module = TestModule.new(tracer_span)
175
+ set_initial_tags(test_module, tags)
176
+ test_module
177
+ end
178
+
179
+ def build_test_suite(tracer_span, tags)
180
+ test_suite = TestSuite.new(tracer_span)
181
+ set_initial_tags(test_suite, tags)
182
+ test_suite
183
+ end
184
+
185
+ def build_test(tracer_span, tags)
186
+ test = Test.new(tracer_span)
187
+ set_initial_tags(test, tags)
188
+ test
189
+ end
190
+
191
+ def build_span(tracer_span, tags)
192
+ span = Span.new(tracer_span)
193
+ set_initial_tags(span, tags)
194
+ span
195
+ end
196
+
197
+ # TAGGING
198
+ def set_initial_tags(ci_span, tags)
199
+ @environment_tags ||= Ext::Environment.tags(ENV).freeze
200
+
201
+ ci_span.set_default_tags
202
+ ci_span.set_environment_runtime_tags
203
+
204
+ ci_span.set_tags(tags)
205
+ ci_span.set_tags(@environment_tags)
206
+ end
207
+
208
+ # PROPAGATING CONTEXT FROM TOP-LEVEL TO THE LOWER LEVELS
209
+ def set_inherited_globals(tags)
210
+ # this code achieves the same as @global_context.inheritable_session_tags.merge(tags)
211
+ # but without allocating a new hash
212
+ @global_context.inheritable_session_tags.each do |key, value|
213
+ tags[key] = value unless tags.key?(key)
214
+ end
215
+ end
216
+
217
+ def set_session_context(tags, test_session = nil)
218
+ test_session ||= active_test_session
219
+ tags[Ext::Test::TAG_TEST_SESSION_ID] = test_session.id.to_s if test_session
220
+ end
221
+
222
+ def set_module_context(tags, test_module = nil)
223
+ test_module ||= active_test_module
224
+ if test_module
225
+ tags[Ext::Test::TAG_TEST_MODULE_ID] = test_module.id.to_s
226
+ tags[Ext::Test::TAG_MODULE] = test_module.name
227
+ end
228
+ end
229
+
230
+ def set_suite_context(tags, span: nil, name: nil)
231
+ return if span.nil? && name.nil?
232
+
233
+ test_suite = span || active_test_suite(name)
234
+
235
+ if test_suite
236
+ tags[Ext::Test::TAG_TEST_SUITE_ID] = test_suite.id.to_s
237
+ tags[Ext::Test::TAG_SUITE] = test_suite.name
238
+ else
239
+ tags[Ext::Test::TAG_SUITE] = name
240
+ end
241
+ end
242
+
243
+ # INTERACTIONS WITH TRACING
244
+ def start_datadog_tracer_span(span_name, span_options, &block)
245
+ if block
246
+ Datadog::Tracing.trace(span_name, **span_options) do |tracer_span, trace|
247
+ set_trace_origin(trace)
248
+
249
+ yield tracer_span
250
+ end
251
+ else
252
+ tracer_span = Datadog::Tracing.trace(span_name, **span_options)
253
+ trace = Datadog::Tracing.active_trace
254
+ set_trace_origin(trace)
255
+
256
+ tracer_span
257
+ end
258
+ end
259
+
260
+ # Sets trace's origin to ciapp-test because tracing requires so
261
+ def set_trace_origin(trace)
262
+ trace&.origin = Ext::Test::CONTEXT_ORIGIN
263
+ end
264
+
265
+ def build_tracing_span_options(service, type, other_options = {})
266
+ other_options[:service] = service || @global_context.service
267
+ other_options[:type] = type
268
+
269
+ other_options
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
@@ -3,7 +3,7 @@
3
3
  module Datadog
4
4
  module CI
5
5
  module TestVisibility
6
- module Context
6
+ module Store
7
7
  # This context is shared between threads and represents the current test session and test module.
8
8
  class Global
9
9
  def initialize
@@ -3,7 +3,7 @@
3
3
  module Datadog
4
4
  module CI
5
5
  module TestVisibility
6
- module Context
6
+ module Store
7
7
  class Local
8
8
  def initialize
9
9
  @key = :datadog_ci_active_test
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../ext/app_types"
4
+ require_relative "../ext/environment"
5
+ require_relative "../ext/telemetry"
6
+ require_relative "../ext/test"
7
+ require_relative "../utils/telemetry"
8
+
9
+ module Datadog
10
+ module CI
11
+ module TestVisibility
12
+ # Telemetry for test visibility
13
+ module Telemetry
14
+ SPAN_TYPE_TO_TELEMETRY_EVENT_TYPE = {
15
+ Ext::AppTypes::TYPE_TEST => Ext::Telemetry::EventType::TEST,
16
+ Ext::AppTypes::TYPE_TEST_SUITE => Ext::Telemetry::EventType::SUITE,
17
+ Ext::AppTypes::TYPE_TEST_MODULE => Ext::Telemetry::EventType::MODULE,
18
+ Ext::AppTypes::TYPE_TEST_SESSION => Ext::Telemetry::EventType::SESSION
19
+ }.freeze
20
+
21
+ def self.event_created(span)
22
+ Utils::Telemetry.inc(Ext::Telemetry::METRIC_EVENT_CREATED, 1, event_tags_from_span(span))
23
+ end
24
+
25
+ def self.event_finished(span)
26
+ tags = event_tags_from_span(span)
27
+ add_browser_tags!(span, tags)
28
+ Utils::Telemetry.inc(Ext::Telemetry::METRIC_EVENT_FINISHED, 1, tags)
29
+ end
30
+
31
+ def self.test_session_started(test_session)
32
+ Utils::Telemetry.inc(
33
+ Ext::Telemetry::METRIC_TEST_SESSION,
34
+ 1,
35
+ {
36
+ Ext::Telemetry::TAG_AUTO_INJECTED => "false", # ruby doesn't support auto injection yet
37
+ Ext::Telemetry::TAG_PROVIDER =>
38
+ test_session.get_tag(Ext::Environment::TAG_PROVIDER_NAME) ||
39
+ Ext::Telemetry::Provider::UNSUPPORTED
40
+ }
41
+ )
42
+ end
43
+
44
+ def self.event_tags_from_span(span)
45
+ # base tags for span
46
+ # @type var tags: Hash[String, String]
47
+ tags = {
48
+ Ext::Telemetry::TAG_EVENT_TYPE => SPAN_TYPE_TO_TELEMETRY_EVENT_TYPE.fetch(span.type, "unknown"),
49
+ Ext::Telemetry::TAG_TEST_FRAMEWORK => span.get_tag(Ext::Test::TAG_FRAMEWORK)
50
+ }
51
+
52
+ # ci provider tag
53
+ tags[Ext::Telemetry::TAG_IS_UNSUPPORTED_CI] = "true" if span.get_tag(Ext::Environment::TAG_PROVIDER_NAME).nil?
54
+
55
+ # codeowner tag
56
+ tags[Ext::Telemetry::TAG_HAS_CODEOWNER] = "true" if span.get_tag(Ext::Test::TAG_CODEOWNERS)
57
+
58
+ tags
59
+ end
60
+
61
+ def self.add_browser_tags!(span, tags)
62
+ tags[Ext::Telemetry::TAG_IS_RUM] = "true" if span.get_tag(Ext::Test::TAG_IS_RUM_ACTIVE)
63
+ browser_driver = span.get_tag(Ext::Test::TAG_BROWSER_DRIVER)
64
+ tags[Ext::Telemetry::TAG_BROWSER_DRIVER] = browser_driver if browser_driver
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -3,8 +3,10 @@
3
3
  require "datadog/core/environment/identity"
4
4
 
5
5
  require_relative "serializers/factories/test_level"
6
+ require_relative "../ext/telemetry"
6
7
  require_relative "../ext/transport"
7
8
  require_relative "../transport/event_platform_transport"
9
+ require_relative "../transport/telemetry"
8
10
 
9
11
  module Datadog
10
12
  module CI
@@ -31,6 +33,10 @@ module Datadog
31
33
 
32
34
  private
33
35
 
36
+ def telemetry_endpoint_tag
37
+ Ext::Telemetry::Endpoint::TEST_CYCLE
38
+ end
39
+
34
40
  def send_payload(encoded_payload)
35
41
  api.citestcycle_request(
36
42
  path: Datadog::CI::Ext::Transport::TEST_VISIBILITY_INTAKE_PATH,
@@ -46,21 +52,14 @@ module Datadog
46
52
 
47
53
  def encode_span(trace, span)
48
54
  serializer = serializers_factory.serializer(trace, span, options: {itr_correlation_id: itr&.correlation_id})
49
-
50
55
  if serializer.valid?
51
56
  encoded = encoder.encode(serializer)
52
-
53
- if encoded.size > max_payload_size
54
- # This single event is too large, we can't flush it
55
- Datadog.logger.warn("Dropping test event. Payload too large: '#{span.inspect}'")
56
- Datadog.logger.warn(encoded)
57
-
58
- return nil
59
- end
57
+ return nil if event_too_large?(span, encoded)
60
58
 
61
59
  encoded
62
60
  else
63
61
  Datadog.logger.warn("Invalid event skipped: #{serializer} Errors: #{serializer.validation_errors}")
62
+ CI::Transport::Telemetry.endpoint_payload_dropped(1, endpoint: telemetry_endpoint_tag)
64
63
  nil
65
64
  end
66
65
  end
@@ -3,7 +3,9 @@
3
3
  require "datadog/core/transport/response"
4
4
  require "datadog/core/transport/ext"
5
5
 
6
+ require_relative "net_http_client"
6
7
  require_relative "../gzip"
8
+ require_relative "../../ext/telemetry"
7
9
  require_relative "../../ext/transport"
8
10
 
9
11
  module Datadog
@@ -26,7 +28,7 @@ module Datadog
26
28
  end
27
29
 
28
30
  def open(&block)
29
- req = net_http_client.new(hostname, port)
31
+ req = NetHttpClient.original_net_http.new(hostname, port)
30
32
 
31
33
  req.use_ssl = ssl
32
34
  req.open_timeout = req.read_timeout = timeout
@@ -63,6 +65,8 @@ module Datadog
63
65
  include Datadog::Core::Transport::Response
64
66
 
65
67
  attr_reader :http_response
68
+ # Stats for telemetry
69
+ attr_accessor :duration_ms, :request_compressed, :request_size
66
70
 
67
71
  def initialize(http_response)
68
72
  @http_response = http_response
@@ -77,6 +81,10 @@ module Datadog
77
81
  @decompressed_payload = Gzip.decompress(http_response.body)
78
82
  end
79
83
 
84
+ def response_size
85
+ http_response.body.bytesize
86
+ end
87
+
80
88
  def header(name)
81
89
  http_response[name]
82
90
  end
@@ -86,7 +94,10 @@ module Datadog
86
94
  end
87
95
 
88
96
  def ok?
89
- code.between?(200, 299)
97
+ http_code = code
98
+ return false if http_code.nil?
99
+
100
+ http_code.between?(200, 299)
90
101
  end
91
102
 
92
103
  def unsupported?
@@ -98,11 +109,17 @@ module Datadog
98
109
  end
99
110
 
100
111
  def client_error?
101
- code.between?(400, 499)
112
+ http_code = code
113
+ return false if http_code.nil?
114
+
115
+ http_code.between?(400, 499)
102
116
  end
103
117
 
104
118
  def server_error?
105
- code.between?(500, 599)
119
+ http_code = code
120
+ return false if http_code.nil?
121
+
122
+ http_code.between?(500, 599)
106
123
  end
107
124
 
108
125
  def gzipped_content?
@@ -119,17 +136,31 @@ module Datadog
119
136
  first_bytes.b == Ext::Transport::GZIP_MAGIC_NUMBER
120
137
  end
121
138
 
122
- def inspect
123
- "#{super}, http_response:#{http_response}"
139
+ def error
140
+ nil
124
141
  end
125
- end
126
142
 
127
- private
143
+ def telemetry_error_type
144
+ return nil if ok?
145
+
146
+ case error
147
+ when nil
148
+ Ext::Telemetry::ErrorType::STATUS_CODE
149
+ when Timeout::Error
150
+ Ext::Telemetry::ErrorType::TIMEOUT
151
+ else
152
+ Ext::Telemetry::ErrorType::NETWORK
153
+ end
154
+ end
128
155
 
129
- def net_http_client
130
- return ::Net::HTTP unless defined?(WebMock::HttpLibAdapters::NetHttpAdapter::OriginalNetHTTP)
156
+ # compatibility with Datadog::Tracing transport layer
157
+ def trace_count
158
+ 0
159
+ end
131
160
 
132
- WebMock::HttpLibAdapters::NetHttpAdapter::OriginalNetHTTP
161
+ def inspect
162
+ "#{super}, http_response:#{http_response}"
163
+ end
133
164
  end
134
165
  end
135
166
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module Transport
6
+ module Adapters
7
+ module NetHttpClient
8
+ def self.original_net_http
9
+ return ::Net::HTTP unless defined?(WebMock::HttpLibAdapters::NetHttpAdapter::OriginalNetHTTP)
10
+
11
+ WebMock::HttpLibAdapters::NetHttpAdapter::OriginalNetHTTP
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "net_http_client"
4
+
5
+ module Datadog
6
+ module CI
7
+ module Transport
8
+ module Adapters
9
+ module TelemetryWebmockSafeAdapter
10
+ def self.included(base)
11
+ base.prepend(InstanceMethods)
12
+ end
13
+
14
+ module InstanceMethods
15
+ def open(&block)
16
+ req = NetHttpClient.original_net_http.new(@hostname, @port)
17
+
18
+ req.use_ssl = @ssl
19
+ req.open_timeout = req.read_timeout = @timeout
20
+
21
+ req.start(&block)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -5,6 +5,8 @@ require "msgpack"
5
5
  require "datadog/core/encoding"
6
6
  require "datadog/core/chunker"
7
7
 
8
+ require_relative "telemetry"
9
+
8
10
  module Datadog
9
11
  module CI
10
12
  module Transport
@@ -24,12 +26,19 @@ module Datadog
24
26
 
25
27
  Datadog.logger.debug { "[#{self.class.name}] Sending #{events.count} events..." }
26
28
 
27
- encoded_events = encode_events(events)
28
- if encoded_events.empty?
29
- Datadog.logger.debug { "[#{self.class.name}] Empty encoded events list, skipping send" }
30
- return []
29
+ encoded_events = []
30
+ # @type var serialization_duration_ms: Float
31
+ serialization_duration_ms = Core::Utils::Time.measure(:float_millisecond) do
32
+ encoded_events = encode_events(events)
33
+ if encoded_events.empty?
34
+ Datadog.logger.debug { "[#{self.class.name}] Empty encoded events list, skipping send" }
35
+ return []
36
+ end
31
37
  end
32
38
 
39
+ Telemetry.events_enqueued_for_serialization(encoded_events.count)
40
+ Telemetry.endpoint_payload_serialization_ms(serialization_duration_ms, endpoint: telemetry_endpoint_tag)
41
+
33
42
  responses = []
34
43
 
35
44
  Datadog::Core::Chunker.chunk_by_size(encoded_events, max_payload_size).map do |chunk|
@@ -37,9 +46,28 @@ module Datadog
37
46
  Datadog.logger.debug do
38
47
  "[#{self.class.name}] Send chunk of #{chunk.count} events; payload size #{encoded_payload.size}"
39
48
  end
49
+ Telemetry.endpoint_payload_events_count(chunk.count, endpoint: telemetry_endpoint_tag)
40
50
 
41
51
  response = send_payload(encoded_payload)
42
52
 
53
+ Telemetry.endpoint_payload_requests(
54
+ 1,
55
+ endpoint: telemetry_endpoint_tag, compressed: response.request_compressed
56
+ )
57
+ Telemetry.endpoint_payload_requests_ms(response.duration_ms, endpoint: telemetry_endpoint_tag)
58
+ Telemetry.endpoint_payload_bytes(response.request_size, endpoint: telemetry_endpoint_tag)
59
+
60
+ # HTTP layer could send events and exhausted retries (if any)
61
+ unless response.ok?
62
+ Telemetry.endpoint_payload_dropped(chunk.count, endpoint: telemetry_endpoint_tag)
63
+ Telemetry.endpoint_payload_requests_errors(
64
+ 1,
65
+ endpoint: telemetry_endpoint_tag,
66
+ error_type: response.telemetry_error_type,
67
+ status_code: response.code
68
+ )
69
+ end
70
+
43
71
  responses << response
44
72
  end
45
73
 
@@ -48,6 +76,10 @@ module Datadog
48
76
 
49
77
  private
50
78
 
79
+ def telemetry_endpoint_tag
80
+ raise NotImplementedError, "must be implemented by the subclass"
81
+ end
82
+
51
83
  def encoder
52
84
  Datadog::Core::Encoding::MsgpackEncoder
53
85
  end
@@ -65,9 +97,14 @@ module Datadog
65
97
  return false unless encoded_event.size > max_payload_size
66
98
 
67
99
  # This single event is too large, we can't flush it
68
- Datadog.logger.warn("[#{self.class.name}] Dropping coverage event. Payload too large: '#{event.inspect}'")
100
+ Datadog.logger.warn(
101
+ "[#{self.class.name}] Dropping test visibility event for endpoint [#{telemetry_endpoint_tag}]. " \
102
+ "Payload too large: '#{event.inspect}'"
103
+ )
69
104
  Datadog.logger.warn(encoded_event)
70
105
 
106
+ Telemetry.endpoint_payload_dropped(1, endpoint: telemetry_endpoint_tag)
107
+
71
108
  true
72
109
  end
73
110