datadog-ci 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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