Welcome to mirror list, hosted at ThFree Co, Russian Federation.

web_transaction.rb « metrics « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: fcfa86734e8b2f8cfe5dabf29556930f0ca25db3 (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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# frozen_string_literal: true

module Gitlab
  module Metrics
    # Exclusive transaction-type metrics for web servers (including Web/Api/Git
    # fleet). One instance of this class is created for each request going
    # through the Rack metric middleware. Any metrics dispatched with this
    # instance include metadata such as controller, action, feature category,
    # etc.
    class WebTransaction < Transaction
      THREAD_KEY = :_gitlab_metrics_transaction
      BASE_LABEL_KEYS = %i(controller action feature_category).freeze

      CONTROLLER_KEY = 'action_controller.instance'
      ENDPOINT_KEY = 'api.endpoint'
      ALLOWED_SUFFIXES = Set.new(%w[json js atom rss xml zip])
      SMALL_BUCKETS = [0.1, 0.25, 0.5, 1.0, 2.5, 5.0].freeze

      class << self
        def current
          Thread.current[THREAD_KEY]
        end

        def prometheus_metric(name, type, &block)
          fetch_metric(type, name) do
            # set default metric options
            docstring "#{name.to_s.humanize} #{type}"

            evaluate(&block)
            # always filter sensitive labels and merge with base ones
            label_keys BASE_LABEL_KEYS | (label_keys - ::Gitlab::Metrics::Transaction::FILTERED_LABEL_KEYS)
          end
        end
      end

      def initialize(env)
        super()
        @env = env
      end

      def run
        Thread.current[THREAD_KEY] = self

        started_at = System.monotonic_time

        status, _, _ = retval = yield

        finished_at = System.monotonic_time
        duration = finished_at - started_at
        record_duration_if_needed(status, duration)

        retval
      ensure
        Thread.current[THREAD_KEY] = nil
      end

      def labels
        return @labels if @labels

        # memoize transaction labels only source env variables were present
        @labels = if @env[CONTROLLER_KEY]
                    labels_from_controller || {}
                  elsif @env[ENDPOINT_KEY]
                    labels_from_endpoint || {}
                  end

        @labels || {}
      end

      private

      def record_duration_if_needed(status, duration)
        return unless Gitlab::Metrics.record_duration_for_status?(status)

        observe(:gitlab_transaction_duration_seconds, duration) do
          buckets SMALL_BUCKETS
        end
      end

      def labels_from_controller
        controller = @env[CONTROLLER_KEY]

        action = "#{controller.action_name}"

        # Devise exposes a method called "request_format" that does the below.
        # However, this method is not available to all controllers (e.g. certain
        # Doorkeeper controllers). As such we use the underlying code directly.
        suffix = controller.request.format.try(:ref).to_s

        # Sometimes the request format is set to silly data such as
        # "application/xrds+xml" or actual URLs. To prevent such values from
        # increasing the cardinality of our metrics, we limit the number of
        # possible suffixes.
        if suffix && ALLOWED_SUFFIXES.include?(suffix)
          action = "#{action}.#{suffix}"
        end

        { controller: controller.class.name, action: action, feature_category: feature_category }
      end

      def labels_from_endpoint
        endpoint = @env[ENDPOINT_KEY]

        begin
          route = endpoint.route
        rescue StandardError
          # endpoint.route is calling env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info]
          # but env[Grape::Env::GRAPE_ROUTING_ARGS] is nil in the case of a 405 response
          # so we're rescuing exceptions and bailing out
        end

        if route
          path = endpoint_paths_cache[route.request_method][route.path]

          { controller: 'Grape', action: "#{route.request_method} #{path}", feature_category: feature_category }
        end
      end

      def endpoint_paths_cache
        @endpoint_paths_cache ||= Hash.new do |hash, http_method|
          hash[http_method] = Hash.new do |inner_hash, raw_path|
            inner_hash[raw_path] = endpoint_instrumentable_path(raw_path)
          end
        end
      end

      def endpoint_instrumentable_path(raw_path)
        raw_path.sub('(.:format)', '').sub('/:version', '')
      end

      def feature_category
        ::Gitlab::ApplicationContext.current_context_attribute(:feature_category) || ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT
      end
    end
  end
end