diff --git a/lib/datadog/ci/ext/telemetry.rb b/lib/datadog/ci/ext/telemetry.rb index 6fa5ccce..589efc6b 100644 --- a/lib/datadog/ci/ext/telemetry.rb +++ b/lib/datadog/ci/ext/telemetry.rb @@ -71,9 +71,7 @@ module Telemetry TAG_RESPONSE_COMPRESSED = "rs_compressed" TAG_COMMAND = "command" TAG_COVERAGE_ENABLED = "coverage_enabled" - TAG_ITR_ENABLED = "itr_enabled" - TAG_ITR_SKIP_ENABLED = "itr_skip_enabled" - TAG_REQUIRE_GIT = "require_git" + TAG_ITR_SKIP_ENABLED = "itrskip_enabled" TAG_PROVIDER = "provider" TAG_AUTO_INJECTED = "auto_injected" diff --git a/lib/datadog/ci/git/search_commits.rb b/lib/datadog/ci/git/search_commits.rb index 2dcc78b3..350e9ece 100644 --- a/lib/datadog/ci/git/search_commits.rb +++ b/lib/datadog/ci/git/search_commits.rb @@ -3,8 +3,11 @@ require "json" require "set" +require_relative "../ext/telemetry" require_relative "../ext/transport" +require_relative "../transport/telemetry" require_relative "../utils/git" +require_relative "../utils/telemetry" module Datadog module CI @@ -25,7 +28,23 @@ def call(repository_url, commits) path: Ext::Transport::DD_API_GIT_SEARCH_COMMITS_PATH, payload: request_payload(repository_url, commits) ) - raise ApiError, "Failed to search commits: #{http_response.inspect}" unless http_response.ok? + + Transport::Telemetry.api_requests( + Ext::Telemetry::METRIC_GIT_REQUESTS_SEARCH_COMMITS, + 1, + compressed: http_response.request_compressed + ) + Utils::Telemetry.distribution(Ext::Telemetry::METRIC_GIT_REQUESTS_SEARCH_COMMITS_MS, http_response.duration_ms) + + unless http_response.ok? + Transport::Telemetry.api_requests_errors( + Ext::Telemetry::METRIC_GIT_REQUESTS_SEARCH_COMMITS_ERRORS, + 1, + error_type: http_response.telemetry_error_type, + status_code: http_response.code + ) + raise ApiError, "Failed to search commits: #{http_response.inspect}" + end response_payload = parse_json_response(http_response) extract_commits(response_payload) diff --git a/lib/datadog/ci/git/tree_uploader.rb b/lib/datadog/ci/git/tree_uploader.rb index 43389090..e4ab929f 100644 --- a/lib/datadog/ci/git/tree_uploader.rb +++ b/lib/datadog/ci/git/tree_uploader.rb @@ -8,6 +8,9 @@ require_relative "upload_packfile" require_relative "packfiles" +require_relative "../ext/telemetry" +require_relative "../utils/telemetry" + module Datadog module CI module Git @@ -63,12 +66,16 @@ def call(repository_url) head_commit_sha: head_commit, repository_url: repository_url ) + packfiles_count = 0 Packfiles.generate(included_commits: new_commits, excluded_commits: known_commits) do |filepath| + packfiles_count += 1 uploader.call(filepath: filepath) rescue UploadPackfile::ApiError => e Datadog.logger.debug("Packfile upload failed with #{e}") break end + + Utils::Telemetry.distribution(Ext::Telemetry::METRIC_GIT_REQUESTS_OBJECT_PACK_FILES, packfiles_count.to_f) ensure Datadog.logger.debug("Git tree upload finished") end diff --git a/lib/datadog/ci/git/upload_packfile.rb b/lib/datadog/ci/git/upload_packfile.rb index 9ff23f53..7bacdd7c 100644 --- a/lib/datadog/ci/git/upload_packfile.rb +++ b/lib/datadog/ci/git/upload_packfile.rb @@ -3,6 +3,11 @@ require "json" require "securerandom" +require_relative "../ext/transport" +require_relative "../ext/telemetry" +require_relative "../transport/telemetry" +require_relative "../utils/telemetry" + module Datadog module CI module Git @@ -34,7 +39,23 @@ def call(filepath:) headers: {Ext::Transport::HEADER_CONTENT_TYPE => content_type} ) - raise ApiError, "Failed to upload packfile: #{http_response.inspect}" unless http_response.ok? + Transport::Telemetry.api_requests( + Ext::Telemetry::METRIC_GIT_REQUESTS_OBJECT_PACK, + 1, + compressed: http_response.request_compressed + ) + Utils::Telemetry.distribution(Ext::Telemetry::METRIC_GIT_REQUESTS_OBJECT_PACK_MS, http_response.duration_ms) + Utils::Telemetry.distribution(Ext::Telemetry::METRIC_GIT_REQUESTS_OBJECT_PACK_BYTES, http_response.request_size.to_f) + + unless http_response.ok? + Transport::Telemetry.api_requests_errors( + Ext::Telemetry::METRIC_GIT_REQUESTS_OBJECT_PACK_ERRORS, + 1, + error_type: http_response.telemetry_error_type, + status_code: http_response.code + ) + raise ApiError, "Failed to upload packfile: #{http_response.inspect}" + end end private diff --git a/lib/datadog/ci/test_optimisation/component.rb b/lib/datadog/ci/test_optimisation/component.rb index a34b9924..b455f828 100644 --- a/lib/datadog/ci/test_optimisation/component.rb +++ b/lib/datadog/ci/test_optimisation/component.rb @@ -5,11 +5,13 @@ require "datadog/core/utils/forking" require_relative "../ext/test" +require_relative "../ext/telemetry" require_relative "../ext/transport" require_relative "../git/local_repository" require_relative "../utils/parsing" +require_relative "../utils/telemetry" require_relative "coverage/event" require_relative "skippable" @@ -239,6 +241,8 @@ def fetch_skippable_tests(test_session:, git_tree_upload_worker:) Datadog.logger.debug { "Fetched skippable tests: \n #{@skippable_tests}" } Datadog.logger.debug { "Found #{@skippable_tests.count} skippable tests." } Datadog.logger.debug { "ITR correlation ID: #{@correlation_id}" } + + Utils::Telemetry.inc(Ext::Telemetry::METRIC_ITR_SKIPPABLE_TESTS_RESPONSE_TESTS, @skippable_tests.count) end def code_coverage_mode diff --git a/lib/datadog/ci/test_optimisation/skippable.rb b/lib/datadog/ci/test_optimisation/skippable.rb index 8cd4b660..8a63e71d 100644 --- a/lib/datadog/ci/test_optimisation/skippable.rb +++ b/lib/datadog/ci/test_optimisation/skippable.rb @@ -2,9 +2,12 @@ require "json" +require_relative "../ext/telemetry" require_relative "../ext/transport" require_relative "../ext/test" +require_relative "../transport/telemetry" require_relative "../utils/test_run" +require_relative "../utils/telemetry" module Datadog module CI @@ -75,6 +78,27 @@ def fetch_skippable_tests(test_session) payload: request_payload ) + Transport::Telemetry.api_requests( + Ext::Telemetry::METRIC_ITR_SKIPPABLE_TESTS_REQUEST, + 1, + compressed: http_response.request_compressed + ) + Utils::Telemetry.distribution(Ext::Telemetry::METRIC_ITR_SKIPPABLE_TESTS_REQUEST_MS, http_response.duration_ms) + Utils::Telemetry.distribution( + Ext::Telemetry::METRIC_ITR_SKIPPABLE_TESTS_RESPONSE_BYTES, + http_response.response_size.to_f, + {Ext::Telemetry::TAG_RESPONSE_COMPRESSED => http_response.gzipped_content?.to_s} + ) + + unless http_response.ok? + Transport::Telemetry.api_requests_errors( + Ext::Telemetry::METRIC_ITR_SKIPPABLE_TESTS_REQUEST_ERRORS, + 1, + error_type: http_response.telemetry_error_type, + status_code: http_response.code + ) + end + Response.new(http_response) end diff --git a/lib/datadog/ci/transport/adapters/net.rb b/lib/datadog/ci/transport/adapters/net.rb index 36281787..a92de751 100644 --- a/lib/datadog/ci/transport/adapters/net.rb +++ b/lib/datadog/ci/transport/adapters/net.rb @@ -66,7 +66,7 @@ class Response attr_reader :http_response # Stats for telemetry - attr_accessor :request_compressed, :request_size + attr_accessor :duration_ms, :request_compressed, :request_size def initialize(http_response) @http_response = http_response @@ -81,6 +81,10 @@ def payload @decompressed_payload = Gzip.decompress(http_response.body) end + def response_size + http_response.body.bytesize + end + def header(name) http_response[name] end diff --git a/lib/datadog/ci/transport/event_platform_transport.rb b/lib/datadog/ci/transport/event_platform_transport.rb index 8b585e99..a3ceb482 100644 --- a/lib/datadog/ci/transport/event_platform_transport.rb +++ b/lib/datadog/ci/transport/event_platform_transport.rb @@ -4,7 +4,6 @@ require "datadog/core/encoding" require "datadog/core/chunker" -require "datadog/core/utils/time" require_relative "telemetry" @@ -49,18 +48,13 @@ def send_events(events) end Telemetry.endpoint_payload_events_count(chunk.count, endpoint: telemetry_endpoint_tag) - response = nil - # @type var request_duration_ms: Float - request_duration_ms = Core::Utils::Time.measure(:float_millisecond) do - response = send_payload(encoded_payload) - end - # @type var response: Datadog::CI::Transport::Adapters::Net::Response + response = send_payload(encoded_payload) Telemetry.endpoint_payload_requests( 1, endpoint: telemetry_endpoint_tag, compressed: response.request_compressed ) - Telemetry.endpoint_payload_requests_ms(request_duration_ms, endpoint: telemetry_endpoint_tag) + Telemetry.endpoint_payload_requests_ms(response.duration_ms, endpoint: telemetry_endpoint_tag) Telemetry.endpoint_payload_bytes(response.request_size, endpoint: telemetry_endpoint_tag) # HTTP layer could send events and exhausted retries (if any) diff --git a/lib/datadog/ci/transport/http.rb b/lib/datadog/ci/transport/http.rb index c00412cd..00f0c118 100644 --- a/lib/datadog/ci/transport/http.rb +++ b/lib/datadog/ci/transport/http.rb @@ -3,6 +3,8 @@ require "delegate" require "socket" +require "datadog/core/utils/time" + require_relative "gzip" require_relative "adapters/net" require_relative "../ext/transport" @@ -39,29 +41,37 @@ def request( backoff: INITIAL_BACKOFF, accept_compressed_response: false ) - if compress - headers[Ext::Transport::HEADER_CONTENT_ENCODING] = Ext::Transport::CONTENT_ENCODING_GZIP - payload = Gzip.compress(payload) - end + response = nil - if accept_compressed_response - headers[Ext::Transport::HEADER_ACCEPT_ENCODING] = Ext::Transport::CONTENT_ENCODING_GZIP - end + duration_ms = Core::Utils::Time.measure(:float_millisecond) do + if compress + headers[Ext::Transport::HEADER_CONTENT_ENCODING] = Ext::Transport::CONTENT_ENCODING_GZIP + payload = Gzip.compress(payload) + end - Datadog.logger.debug do - "Sending #{verb} request: host=#{host}; port=#{port}; ssl_enabled=#{ssl}; " \ - "compression_enabled=#{compress}; path=#{path}; payload_size=#{payload.size}" - end + if accept_compressed_response + headers[Ext::Transport::HEADER_ACCEPT_ENCODING] = Ext::Transport::CONTENT_ENCODING_GZIP + end + + Datadog.logger.debug do + "Sending #{verb} request: host=#{host}; port=#{port}; ssl_enabled=#{ssl}; " \ + "compression_enabled=#{compress}; path=#{path}; payload_size=#{payload.size}" + end - response = perform_http_call(path: path, payload: payload, headers: headers, verb: verb, retries: retries, backoff: backoff) + response = perform_http_call(path: path, payload: payload, headers: headers, verb: verb, retries: retries, backoff: backoff) - Datadog.logger.debug do - "Received server response: #{response.inspect}" + Datadog.logger.debug do + "Received server response: #{response.inspect}" + end end + # @type var response: Datadog::CI::Transport::Adapters::Net::Response + # @type var duration_ms: Float # set some stats about the request response.request_compressed = compress response.request_size = payload.bytesize + response.duration_ms = duration_ms + response end diff --git a/lib/datadog/ci/transport/remote_settings_api.rb b/lib/datadog/ci/transport/remote_settings_api.rb index e07e999f..9207c6e0 100644 --- a/lib/datadog/ci/transport/remote_settings_api.rb +++ b/lib/datadog/ci/transport/remote_settings_api.rb @@ -4,8 +4,11 @@ require "datadog/core/environment/identity" +require_relative "../ext/telemetry" require_relative "../ext/transport" +require_relative "../transport/telemetry" require_relative "../utils/parsing" +require_relative "../utils/telemetry" module Datadog module CI @@ -36,6 +39,14 @@ def payload default_payload rescue JSON::ParserError => e Datadog.logger.error("Failed to parse settings response payload: #{e}. Payload was: #{resp.payload}") + + Transport::Telemetry.api_requests_errors( + Ext::Telemetry::METRIC_GIT_REQUESTS_SETTINGS_ERRORS, + 1, + error_type: "invalid_json", + status_code: nil + ) + @json = default_payload end end @@ -69,7 +80,34 @@ def fetch_library_settings(test_session) payload: request_payload ) - Response.new(http_response) + Transport::Telemetry.api_requests( + Ext::Telemetry::METRIC_GIT_REQUESTS_SETTINGS, + 1, + compressed: http_response.request_compressed + ) + Utils::Telemetry.distribution(Ext::Telemetry::METRIC_GIT_REQUESTS_SETTINGS_MS, http_response.duration_ms) + + unless http_response.ok? + Transport::Telemetry.api_requests_errors( + Ext::Telemetry::METRIC_GIT_REQUESTS_SETTINGS_ERRORS, + 1, + error_type: http_response.telemetry_error_type, + status_code: http_response.code + ) + end + + response = Response.new(http_response) + + Utils::Telemetry.inc( + Ext::Telemetry::METRIC_GIT_REQUESTS_SETTINGS_RESPONSE, + 1, + { + Ext::Telemetry::TAG_COVERAGE_ENABLED => response.payload[Ext::Transport::DD_API_SETTINGS_RESPONSE_CODE_COVERAGE_KEY], + Ext::Telemetry::TAG_ITR_SKIP_ENABLED => response.payload[Ext::Transport::DD_API_SETTINGS_RESPONSE_TESTS_SKIPPING_KEY] + } + ) + + response end private diff --git a/lib/datadog/ci/transport/telemetry.rb b/lib/datadog/ci/transport/telemetry.rb index f12befbf..0cfa07a4 100644 --- a/lib/datadog/ci/transport/telemetry.rb +++ b/lib/datadog/ci/transport/telemetry.rb @@ -60,16 +60,33 @@ def self.endpoint_payload_bytes(bytesize, endpoint:) def self.endpoint_payload_requests_errors(count, endpoint:, error_type:, status_code:) tags = tags(endpoint: endpoint) - - tags[Ext::Telemetry::TAG_ERROR_TYPE] = error_type if error_type - tags[Ext::Telemetry::TAG_STATUS_CODE] = status_code.to_s if status_code + set_error_tags(tags, error_type: error_type, status_code: status_code) Utils::Telemetry.inc(Ext::Telemetry::METRIC_ENDPOINT_PAYLOAD_REQUESTS_ERRORS, count, tags) end + def self.api_requests(metric_name, count, compressed:) + tags = {} + tags[Ext::Telemetry::TAG_REQUEST_COMPRESSED] = "true" if compressed + + Utils::Telemetry.inc(metric_name, count, tags) + end + + def self.api_requests_errors(metric_name, count, error_type:, status_code:) + tags = {} + set_error_tags(tags, error_type: error_type, status_code: status_code) + + Utils::Telemetry.inc(metric_name, count, tags) + end + def self.tags(endpoint:) {Ext::Telemetry::TAG_ENDPOINT => endpoint} end + + def self.set_error_tags(tags, error_type:, status_code:) + tags[Ext::Telemetry::TAG_ERROR_TYPE] = error_type if error_type + tags[Ext::Telemetry::TAG_STATUS_CODE] = status_code.to_s if status_code + end end end end diff --git a/sig/datadog/ci/ext/telemetry.rbs b/sig/datadog/ci/ext/telemetry.rbs index 6a89b389..f3c41744 100644 --- a/sig/datadog/ci/ext/telemetry.rbs +++ b/sig/datadog/ci/ext/telemetry.rbs @@ -112,11 +112,7 @@ module Datadog TAG_COVERAGE_ENABLED: "coverage_enabled" - TAG_ITR_ENABLED: "itr_enabled" - - TAG_ITR_SKIP_ENABLED: "itr_skip_enabled" - - TAG_REQUIRE_GIT: "require_git" + TAG_ITR_SKIP_ENABLED: "itrskip_enabled" TAG_PROVIDER: "provider" diff --git a/sig/datadog/ci/git/search_commits.rbs b/sig/datadog/ci/git/search_commits.rbs index e650957f..0b4835c0 100644 --- a/sig/datadog/ci/git/search_commits.rbs +++ b/sig/datadog/ci/git/search_commits.rbs @@ -17,7 +17,7 @@ module Datadog def request_payload: (String repository_url, Array[String] commits) -> String - def parse_json_response: (Datadog::Core::Transport::Response response) -> Hash[String, untyped] + def parse_json_response: (Datadog::CI::Transport::Adapters::Net::Response response) -> Hash[String, untyped] def extract_commits: (Hash[String, untyped] response) -> Set[String] end diff --git a/sig/datadog/ci/test_optimisation/skippable.rbs b/sig/datadog/ci/test_optimisation/skippable.rbs index 4c704ea5..dc1333a6 100644 --- a/sig/datadog/ci/test_optimisation/skippable.rbs +++ b/sig/datadog/ci/test_optimisation/skippable.rbs @@ -7,10 +7,10 @@ module Datadog @config_tags: Hash[String, String] class Response - @http_response: Datadog::Core::Transport::HTTP::Adapters::Net::Response? + @http_response: Datadog::CI::Transport::Adapters::Net::Response? @json: Hash[String, untyped]? - def initialize: (Datadog::Core::Transport::HTTP::Adapters::Net::Response? http_response) -> void + def initialize: (Datadog::CI::Transport::Adapters::Net::Response? http_response) -> void def ok?: () -> bool diff --git a/sig/datadog/ci/transport/adapters/net.rbs b/sig/datadog/ci/transport/adapters/net.rbs index 2f87e561..f25eddc8 100644 --- a/sig/datadog/ci/transport/adapters/net.rbs +++ b/sig/datadog/ci/transport/adapters/net.rbs @@ -40,6 +40,8 @@ module Datadog attr_accessor request_size: Integer + attr_accessor duration_ms: Float + def initialize: (::Net::HTTPResponse http_response) -> void def payload: () -> String @@ -64,6 +66,8 @@ module Datadog def telemetry_error_type: () -> String? + def response_size: () -> Integer + def trace_count: () -> 0 def inspect: () -> ::String diff --git a/sig/datadog/ci/transport/http.rbs b/sig/datadog/ci/transport/http.rbs index b2d3d21a..4548ac33 100644 --- a/sig/datadog/ci/transport/http.rbs +++ b/sig/datadog/ci/transport/http.rbs @@ -1,11 +1,3 @@ -class SimpleDelegator - @decompressed_payload: String - - def __getobj__: () -> Datadog::Core::Transport::Response - def gzipped?: (String payload) -> bool - def payload: () -> String -end - module Datadog module CI module Transport diff --git a/sig/datadog/ci/transport/remote_settings_api.rbs b/sig/datadog/ci/transport/remote_settings_api.rbs index 925066bf..d017100d 100644 --- a/sig/datadog/ci/transport/remote_settings_api.rbs +++ b/sig/datadog/ci/transport/remote_settings_api.rbs @@ -3,10 +3,10 @@ module Datadog module Transport class RemoteSettingsApi class Response - @http_response: Datadog::Core::Transport::HTTP::Adapters::Net::Response? + @http_response: Datadog::CI::Transport::Adapters::Net::Response? @json: Hash[String, untyped]? - def initialize: (Datadog::Core::Transport::HTTP::Adapters::Net::Response? http_response) -> void + def initialize: (Datadog::CI::Transport::Adapters::Net::Response? http_response) -> void def ok?: () -> bool diff --git a/sig/datadog/ci/transport/telemetry.rbs b/sig/datadog/ci/transport/telemetry.rbs index c1c97783..1c5a2131 100644 --- a/sig/datadog/ci/transport/telemetry.rbs +++ b/sig/datadog/ci/transport/telemetry.rbs @@ -18,7 +18,13 @@ module Datadog def self.endpoint_payload_requests_errors: (Integer count, endpoint: String, error_type: String?, status_code: Integer?) -> void + def self.api_requests: (String metric_name, Integer count, compressed: bool) -> void + + def self.api_requests_errors: (String metric_name, Integer count, error_type: String?, status_code: Integer?) -> void + def self.tags: (endpoint: String) -> Hash[String, String] + + def self.set_error_tags: (Hash[String, String] tags, error_type: String?, status_code: Integer?) -> void end end end diff --git a/spec/datadog/ci/contrib/knapsack_rspec/instrumentation_spec.rb b/spec/datadog/ci/contrib/knapsack_rspec/instrumentation_spec.rb index 5b3f4d40..acfaf0c4 100644 --- a/spec/datadog/ci/contrib/knapsack_rspec/instrumentation_spec.rb +++ b/spec/datadog/ci/contrib/knapsack_rspec/instrumentation_spec.rb @@ -19,6 +19,8 @@ ["./spec/datadog/ci/contrib/knapsack_rspec/suite_under_test/some_test_rspec.rb"], [] ) + # raise to prevent Knapsack from running Kernel.exit(0) + allow(KnapsackPro::Report).to receive(:save_node_queue_to_api).and_raise(ArgumentError) end # Yields to a block in a new RSpec global context. All RSpec diff --git a/spec/datadog/ci/contrib/knapsack_rspec_go/instrumentation_spec.rb b/spec/datadog/ci/contrib/knapsack_rspec_go/instrumentation_spec.rb index 5d759db0..61f3dd1b 100644 --- a/spec/datadog/ci/contrib/knapsack_rspec_go/instrumentation_spec.rb +++ b/spec/datadog/ci/contrib/knapsack_rspec_go/instrumentation_spec.rb @@ -39,6 +39,9 @@ def devnull ["./spec/datadog/ci/contrib/knapsack_rspec_go/suite_under_test/some_test_rspec.rb"], [] ) + + # raise to prevent Knapsack from running Kernel.exit(0) + allow(KnapsackPro::Report).to receive(:save_node_queue_to_api).and_raise(ArgumentError) end it "instruments this rspec session" do diff --git a/spec/datadog/ci/git/search_commits_spec.rb b/spec/datadog/ci/git/search_commits_spec.rb index 9bbadd36..ea664353 100644 --- a/spec/datadog/ci/git/search_commits_spec.rb +++ b/spec/datadog/ci/git/search_commits_spec.rb @@ -3,10 +3,14 @@ require_relative "../../../../lib/datadog/ci/git/search_commits" RSpec.describe Datadog::CI::Git::SearchCommits do + include_context "Telemetry spy" + let(:api) { double("api") } subject(:search_commits) { described_class.new(api: api) } describe "#call" do + subject { search_commits.call(repository_url, commits) } + let(:repository_url) { "https://datadoghq.com/git/test.git" } let(:commits) { ["c7f893648f656339f62fb7b4d8a6ecdf7d063835"] } @@ -25,16 +29,32 @@ end context "when the API request fails" do - let(:http_response) { double("http_response", ok?: false, inspect: "error message") } + let(:http_response) do + double( + "http_response", + ok?: false, + inspect: "error message", + request_compressed: true, + duration_ms: 1.2, + telemetry_error_type: "network", + code: nil + ) + end it "raises an error" do - expect { search_commits.call(repository_url, commits) } + expect { subject } .to raise_error(Datadog::CI::Git::SearchCommits::ApiError, "Failed to search commits: error message") + + metric = telemetry_metric(:inc, "git_requests.search_commits_errors") + expect(metric.value).to eq(1) + expect(metric.tags).to eq("error_type" => "network") end end context "when the API request is successful" do - let(:http_response) { double("http_response", ok?: true, payload: response_payload) } + let(:http_response) do + double("http_response", ok?: true, payload: response_payload, request_compressed: true, duration_ms: 1.2) + end let(:response_payload) do { data: [ @@ -52,9 +72,12 @@ payload: "{\"meta\":{\"repository_url\":\"https://datadoghq.com/git/test.git\"},\"data\":[{\"id\":\"c7f893648f656339f62fb7b4d8a6ecdf7d063835\",\"type\":\"commit\"}]}" ).and_return(http_response) - expect(search_commits.call(repository_url, commits)).to eq(Set.new(["c7f893648f656339f62fb7b4d8a6ecdf7d063835"])) + expect(subject).to eq(Set.new(["c7f893648f656339f62fb7b4d8a6ecdf7d063835"])) end + it_behaves_like "emits telemetry metric", :inc, "git_requests.search_commits" + it_behaves_like "emits telemetry metric", :distribution, "git_requests.search_commits_ms" + context "when the request contains an invalid commit SHA" do let(:commits) { ["INVALID_SHA", "c7f893648f656339f62fb7b4d8a6ecdf7d063835"] } @@ -64,7 +87,7 @@ payload: "{\"meta\":{\"repository_url\":\"https://datadoghq.com/git/test.git\"},\"data\":[{\"id\":\"c7f893648f656339f62fb7b4d8a6ecdf7d063835\",\"type\":\"commit\"}]}" ).and_return(http_response) - expect(search_commits.call(repository_url, commits)).to eq(Set.new(["c7f893648f656339f62fb7b4d8a6ecdf7d063835"])) + expect(subject).to eq(Set.new(["c7f893648f656339f62fb7b4d8a6ecdf7d063835"])) end end @@ -81,7 +104,7 @@ end it "raises an error" do - expect { search_commits.call(repository_url, commits) } + expect { subject } .to raise_error( Datadog::CI::Git::SearchCommits::ApiError, "Invalid commit type response {\"id\"=>\"c7f893648f656339f62fb7b4d8a6ecdf7d063835\", \"type\"=>\"invalid\"}" @@ -102,7 +125,7 @@ end it "raises an error" do - expect { search_commits.call(repository_url, commits) } + expect { subject } .to raise_error(Datadog::CI::Git::SearchCommits::ApiError, "Invalid commit SHA response INVALID_SHA") end end @@ -111,7 +134,7 @@ let(:response_payload) { "invalid json" } it "raises an error" do - expect { search_commits.call(repository_url, commits) } + expect { subject } .to raise_error( Datadog::CI::Git::SearchCommits::ApiError, "Failed to parse search commits response: unexpected token at 'invalid json'. Payload was: invalid json" @@ -123,7 +146,7 @@ let(:response_payload) { {}.to_json } it "raises an error" do - expect { search_commits.call(repository_url, commits) } + expect { subject } .to raise_error( Datadog::CI::Git::SearchCommits::ApiError, "Malformed search commits response: key not found: \"data\". Payload was: {}" @@ -143,7 +166,7 @@ end it "raises an error" do - expect { search_commits.call(repository_url, commits) } + expect { subject } .to raise_error( Datadog::CI::Git::SearchCommits::ApiError, "Invalid commit type response {\"id\"=>\"c7f893648f656339f62fb7b4d8a6ecdf7d063835\"}" diff --git a/spec/datadog/ci/git/tree_uploader_spec.rb b/spec/datadog/ci/git/tree_uploader_spec.rb index 19e9c55a..cb2035a5 100644 --- a/spec/datadog/ci/git/tree_uploader_spec.rb +++ b/spec/datadog/ci/git/tree_uploader_spec.rb @@ -3,6 +3,8 @@ require_relative "../../../../lib/datadog/ci/git/tree_uploader" RSpec.describe Datadog::CI::Git::TreeUploader do + include_context "Telemetry spy" + let(:api) { double("api") } subject(:tree_uploader) { described_class.new(api: api) } @@ -11,6 +13,8 @@ end describe "#call" do + subject { tree_uploader.call(repository_url) } + let(:repository_url) { "https://datadoghq.com/git/test.git" } let(:latest_commits) { %w[c7f893648f656339f62fb7b4d8a6ecdf7d063835 13c988d4f15e06bcdd0b0af290086a3079cdadb0] } let(:head_commit) { "c7f893648f656339f62fb7b4d8a6ecdf7d063835" } @@ -28,7 +32,7 @@ it "logs a debug message and aborts the git upload" do expect(Datadog.logger).to receive(:debug).with("API is not configured, aborting git upload") - tree_uploader.call(repository_url) + subject end end @@ -43,7 +47,7 @@ it "logs a debug message and aborts the git upload" do expect(Datadog.logger).to receive(:debug).with("Got empty latest commits list, aborting git upload") - tree_uploader.call(repository_url) + subject end end @@ -55,7 +59,7 @@ it "logs a debug message and aborts the git upload" do expect(Datadog.logger).to receive(:debug).with("SearchCommits failed with test error, aborting git upload") - tree_uploader.call(repository_url) + subject end end @@ -65,7 +69,7 @@ it "logs a debug message and aborts the git upload" do expect(Datadog.logger).to receive(:debug).with("No new commits to upload") - tree_uploader.call(repository_url) + subject end end @@ -88,7 +92,7 @@ excluded_commits: backend_commits ).and_yield("packfile_path") - tree_uploader.call(repository_url) + subject end end @@ -106,7 +110,7 @@ excluded_commits: backend_commits ).and_yield("packfile_path") - tree_uploader.call(repository_url) + subject end end end @@ -135,7 +139,7 @@ it "logs a debug message and aborts the git upload" do expect(Datadog.logger).to receive(:debug).with("Packfile upload failed with test error") - tree_uploader.call(repository_url) + subject end end @@ -143,8 +147,10 @@ it "uploads the new commits" do expect(upload_packfile).to receive(:call).with(filepath: "packfile_path").and_return(nil) - tree_uploader.call(repository_url) + subject end + + it_behaves_like "emits telemetry metric", :distribution, "git_requests.objects_pack_files", 1.0 end end end diff --git a/spec/datadog/ci/git/upload_packfile_spec.rb b/spec/datadog/ci/git/upload_packfile_spec.rb index 33584e81..e22bfcf8 100644 --- a/spec/datadog/ci/git/upload_packfile_spec.rb +++ b/spec/datadog/ci/git/upload_packfile_spec.rb @@ -3,6 +3,7 @@ require_relative "../../../../lib/datadog/ci/git/upload_packfile" RSpec.describe Datadog::CI::Git::UploadPackfile do + include_context "Telemetry spy" let(:api) { double("api") } subject(:upload_packfile) do @@ -10,13 +11,14 @@ end describe "#call" do + subject { upload_packfile.call(filepath: filepath) } let(:filepath) { "nonexistent" } context "when the API is not configured" do let(:api) { nil } it "raises an error" do - expect { upload_packfile.call(filepath: filepath) } + expect { subject } .to raise_error(Datadog::CI::Git::UploadPackfile::ApiError, "test visibility API is not configured") end end @@ -25,7 +27,9 @@ before do allow(api).to receive(:api_request).and_return(http_response) end - let(:http_response) { double("http_response", ok?: true) } + let(:http_response) do + double("http_response", ok?: true, request_compressed: true, duration_ms: 1.2, request_size: 452) + end context "when file does not exist" do let(:expected_error_message) do @@ -37,7 +41,7 @@ end it "raises an error" do - expect { upload_packfile.call(filepath: filepath) } + expect { subject } .to raise_error( Datadog::CI::Git::UploadPackfile::ApiError, expected_error_message @@ -58,11 +62,26 @@ end context "when the API request fails" do - let(:http_response) { double("http_response", ok?: false, inspect: "error message") } + let(:http_response) do + double( + "http_response", + ok?: false, + request_compressed: true, + duration_ms: 1.2, + request_size: 452, + inspect: "error message", + telemetry_error_type: "network", + code: nil + ) + end it "raises an error" do - expect { upload_packfile.call(filepath: filepath) } + expect { subject } .to raise_error(Datadog::CI::Git::UploadPackfile::ApiError, "Failed to upload packfile: error message") + + metric = telemetry_metric(:inc, "git_requests.objects_pack_errors") + expect(metric.value).to eq(1) + expect(metric.tags).to eq("error_type" => "network") end end @@ -90,8 +109,12 @@ headers: {Datadog::CI::Ext::Transport::HEADER_CONTENT_TYPE => "multipart/form-data; boundary=boundary"} ).and_return(http_response) - upload_packfile.call(filepath: filepath) + subject end + + it_behaves_like "emits telemetry metric", :inc, "git_requests.objects_pack" + it_behaves_like "emits telemetry metric", :distribution, "git_requests.objects_pack_ms" + it_behaves_like "emits telemetry metric", :distribution, "git_requests.objects_pack_bytes" end end end diff --git a/spec/datadog/ci/test_optimisation/component_spec.rb b/spec/datadog/ci/test_optimisation/component_spec.rb index dec1b2e5..c05b306f 100644 --- a/spec/datadog/ci/test_optimisation/component_spec.rb +++ b/spec/datadog/ci/test_optimisation/component_spec.rb @@ -64,7 +64,7 @@ fetch_skippable_tests: instance_double( Datadog::CI::TestOptimisation::Skippable::Response, correlation_id: "42", - tests: Set.new(["suite.test."]) + tests: Set.new(["suite.test.", "suite.test2."]) ) ) end @@ -79,10 +79,12 @@ expect(component.skipping_tests?).to be true expect(component.correlation_id).to eq("42") - expect(component.skippable_tests).to eq(Set.new(["suite.test."])) + expect(component.skippable_tests).to eq(Set.new(["suite.test.", "suite.test2."])) expect(git_worker).to have_received(:wait_until_done) end + + it_behaves_like "emits telemetry metric", :inc, "itr_skippable_tests.response_tests", 2 end context "when remote configuration call returned correct response with strings instead of bools" do diff --git a/spec/datadog/ci/test_optimisation/skippable_spec.rb b/spec/datadog/ci/test_optimisation/skippable_spec.rb index d9699f79..e00c9ce4 100644 --- a/spec/datadog/ci/test_optimisation/skippable_spec.rb +++ b/spec/datadog/ci/test_optimisation/skippable_spec.rb @@ -3,6 +3,8 @@ require_relative "../../../../lib/datadog/ci/test_optimisation/skippable" RSpec.describe Datadog::CI::TestOptimisation::Skippable do + include_context "Telemetry spy" + let(:api) { spy("api") } let(:dd_env) { "ci" } let(:config_tags) { {} } @@ -10,6 +12,8 @@ subject(:client) { described_class.new(api: api, dd_env: dd_env, config_tags: config_tags) } describe "#fetch_skippable_tests" do + subject { client.fetch_skippable_tests(test_session) } + let(:service) { "service" } let(:tracer_span) do Datadog::Tracing::SpanOperation.new("session", service: service).tap do |span| @@ -30,7 +34,7 @@ let(:path) { Datadog::CI::Ext::Transport::DD_API_SKIPPABLE_TESTS_PATH } it "requests the skippable tests" do - client.fetch_skippable_tests(test_session) + subject expect(api).to have_received(:api_request) do |args| expect(args[:path]).to eq(path) @@ -90,7 +94,11 @@ } } ] - }.to_json + }.to_json, + request_compressed: false, + duration_ms: 1.2, + gzipped_content?: false, + response_size: 100 ) end @@ -99,6 +107,10 @@ expect(response.correlation_id).to eq("correlation_id_123") expect(response.tests).to eq(Set.new(["test_suite_name.test_name.string"])) end + + it_behaves_like "emits telemetry metric", :inc, "itr_skippable_tests.request", 1 + it_behaves_like "emits telemetry metric", :distribution, "itr_skippable_tests.request_ms" + it_behaves_like "emits telemetry metric", :distribution, "itr_skippable_tests.response_bytes" end context "when response is not OK" do @@ -106,7 +118,13 @@ double( "http_response", ok?: false, - payload: "" + payload: "", + request_compressed: false, + duration_ms: 1.2, + gzipped_content?: false, + response_size: 100, + telemetry_error_type: nil, + code: 422 ) end @@ -115,6 +133,8 @@ expect(response.correlation_id).to be_nil expect(response.tests).to be_empty end + + it_behaves_like "emits telemetry metric", :inc, "itr_skippable_tests.request_errors", 1 end context "when response is OK but JSON is malformed" do @@ -122,7 +142,11 @@ double( "http_response", ok?: true, - payload: "not json" + payload: "not json", + request_compressed: false, + duration_ms: 1.2, + gzipped_content?: false, + response_size: 100 ) end @@ -155,7 +179,11 @@ "runtime.architecture" => "amd64" } } - }.to_json + }.to_json, + request_compressed: false, + duration_ms: 1.2, + gzipped_content?: false, + response_size: 100 ) end @@ -186,7 +214,7 @@ end it "requests the skippable tests with custom configurations" do - client.fetch_skippable_tests(test_session) + subject expect(api).to have_received(:api_request) do |args| data = JSON.parse(args[:payload])["data"] diff --git a/spec/datadog/ci/transport/http_spec.rb b/spec/datadog/ci/transport/http_spec.rb index 5bf0b272..72055858 100644 --- a/spec/datadog/ci/transport/http_spec.rb +++ b/spec/datadog/ci/transport/http_spec.rb @@ -119,6 +119,8 @@ expect(response.payload).to eq("sample payload") expect(response.request_compressed).to eq(false) expect(response.request_size).to eq(payload.bytesize) + expect(response.response_size).to eq(response_payload.bytesize) + expect(response.duration_ms).to be > 0 expect(response.telemetry_error_type).to be_nil end diff --git a/spec/datadog/ci/transport/remote_settings_api_spec.rb b/spec/datadog/ci/transport/remote_settings_api_spec.rb index b89e3ff6..6fc08576 100644 --- a/spec/datadog/ci/transport/remote_settings_api_spec.rb +++ b/spec/datadog/ci/transport/remote_settings_api_spec.rb @@ -3,6 +3,8 @@ require_relative "../../../../lib/datadog/ci/transport/remote_settings_api" RSpec.describe Datadog::CI::Transport::RemoteSettingsApi do + include_context "Telemetry spy" + let(:api) { spy("api") } let(:dd_env) { "ci" } let(:config_tags) { {} } @@ -10,6 +12,8 @@ subject(:client) { described_class.new(api: api, dd_env: dd_env, config_tags: config_tags) } describe "#fetch_library_settings" do + subject { client.fetch_library_settings(test_session) } + let(:service) { "service" } let(:tracer_span) do Datadog::Tracing::SpanOperation.new("session", service: service).tap do |span| @@ -30,7 +34,7 @@ let(:path) { Datadog::CI::Ext::Transport::DD_API_SETTINGS_PATH } it "requests the settings" do - client.fetch_library_settings(test_session) + subject expect(api).to have_received(:api_request) do |args| expect(args[:path]).to eq(path) @@ -81,7 +85,9 @@ "require_git" => require_git } } - }.to_json + }.to_json, + request_compressed: false, + duration_ms: 1.2 ) end let(:require_git) { false } @@ -95,8 +101,15 @@ "require_git" => require_git }) expect(response.require_git?).to be false + + metric = telemetry_metric(:inc, "git_requests.settings_response") + expect(metric.tags).to eq("coverage_enabled" => true, "itrskip_enabled" => false) end + it_behaves_like "emits telemetry metric", :inc, "git_requests.settings", 1 + it_behaves_like "emits telemetry metric", :distribution, "git_requests.settings_ms" + it_behaves_like "emits telemetry metric", :inc, "git_requests.settings_response" + context "when git is required" do let(:require_git) { "True" } @@ -111,7 +124,11 @@ double( "http_response", ok?: false, - payload: "" + payload: "", + request_compressed: false, + duration_ms: 1.2, + telemetry_error_type: "network", + code: nil ) end @@ -120,6 +137,8 @@ expect(response.payload).to eq("itr_enabled" => false) expect(response.require_git?).to be false end + + it_behaves_like "emits telemetry metric", :inc, "git_requests.settings_errors", 1 end context "when response is OK but JSON is malformed" do @@ -127,7 +146,9 @@ double( "http_response", ok?: true, - payload: "not json" + payload: "not json", + request_compressed: false, + duration_ms: 1.2 ) end @@ -135,10 +156,14 @@ expect(Datadog.logger).to receive(:error).with(/Failed to parse settings response payload/) end - it "parses the response" do + it "returns default response" do expect(response.ok?).to be true expect(response.payload).to eq("itr_enabled" => false) expect(response.require_git?).to be false + + metric = telemetry_metric(:inc, "git_requests.settings_errors") + expect(metric.value).to eq(1) + expect(metric.tags).to eq("error_type" => "invalid_json") end end @@ -154,7 +179,9 @@ "itr_enabled" => true, "require_git" => false } - }.to_json + }.to_json, + request_compressed: false, + duration_ms: 1.2 ) end @@ -181,7 +208,7 @@ let(:config_tags) { {"key" => "value"} } it "requests the settings" do - client.fetch_library_settings(test_session) + subject expect(api).to have_received(:api_request) do |args| data = JSON.parse(args[:payload])["data"] diff --git a/spec/datadog/ci/transport/telemetry_spec.rb b/spec/datadog/ci/transport/telemetry_spec.rb index 89029409..bf7c7cd2 100644 --- a/spec/datadog/ci/transport/telemetry_spec.rb +++ b/spec/datadog/ci/transport/telemetry_spec.rb @@ -172,4 +172,32 @@ end end end + + describe ".api_requests" do + subject { described_class.api_requests(metric_name, count, compressed: compressed) } + + let(:metric_name) { "metric_name" } + let(:count) { 1 } + let(:compressed) { true } + + it "increments the api requests metric" do + expect(Datadog::CI::Utils::Telemetry).to receive(:inc).with( + metric_name, + count, + {Datadog::CI::Ext::Telemetry::TAG_REQUEST_COMPRESSED => "true"} + ) + + subject + end + + context "when not compressed" do + let(:compressed) { false } + + it "increments the api requests metric without request compressed tag" do + expect(Datadog::CI::Utils::Telemetry).to receive(:inc).with(metric_name, count, {}) + + subject + end + end + end end