summaryrefslogtreecommitdiff
path: root/lib/gitlab/metrics/subscribers/external_http.rb
blob: 94c5d965200050bae18713b1bf22a05492b95ec1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# frozen_string_literal: true

module Gitlab
  module Metrics
    module Subscribers
      # Class for tracking the total time spent in external HTTP
      # See more at https://gitlab.com/gitlab-org/labkit-ruby/-/blob/v0.14.0/lib/gitlab-labkit.rb#L18
      class ExternalHttp < ActiveSupport::Subscriber
        attach_to :external_http

        DEFAULT_STATUS_CODE = 'undefined'

        DETAIL_STORE = :external_http_detail_store
        COUNTER = :external_http_count
        DURATION = :external_http_duration_s

        KNOWN_PAYLOAD_KEYS = [COUNTER, DURATION].freeze

        def self.detail_store
          ::Gitlab::SafeRequestStore[DETAIL_STORE] ||= []
        end

        def self.duration
          Gitlab::SafeRequestStore[DURATION].to_f
        end

        def self.request_count
          Gitlab::SafeRequestStore[COUNTER].to_i
        end

        def self.payload
          {
            COUNTER => request_count,
            DURATION => duration
          }
        end

        def request(event)
          payload = event.payload
          add_to_detail_store(payload)
          add_to_request_store(payload)
          expose_metrics(payload)
        end

        private

        def current_transaction
          ::Gitlab::Metrics::Transaction.current
        end

        def add_to_detail_store(payload)
          return unless Gitlab::PerformanceBar.enabled_for_request?

          self.class.detail_store << {
            duration: payload[:duration],
            scheme: payload[:scheme],
            method: payload[:method],
            host: payload[:host],
            port: payload[:port],
            path: payload[:path],
            query: payload[:query],
            code: payload[:code],
            exception_object: payload[:exception_object],
            backtrace: Gitlab::BacktraceCleaner.clean_backtrace(caller)
          }
        end

        def add_to_request_store(payload)
          return unless Gitlab::SafeRequestStore.active?

          Gitlab::SafeRequestStore[COUNTER] = Gitlab::SafeRequestStore[COUNTER].to_i + 1
          Gitlab::SafeRequestStore[DURATION] = Gitlab::SafeRequestStore[DURATION].to_f + payload[:duration].to_f
        end

        def expose_metrics(payload)
          return unless current_transaction

          labels = { method: payload[:method], code: payload[:code] || DEFAULT_STATUS_CODE }

          current_transaction.increment(:gitlab_external_http_total, 1, labels) do
            docstring 'External HTTP calls'
            label_keys labels.keys
          end

          current_transaction.observe(:gitlab_external_http_duration_seconds, payload[:duration]) do
            docstring 'External HTTP time'
            buckets [0.001, 0.01, 0.1, 1.0, 2.0, 5.0]
          end

          if payload[:exception_object].present?
            current_transaction.increment(:gitlab_external_http_exception_total, 1) do
              docstring 'External HTTP exceptions'
            end
          end
        end
      end
    end
  end
end