BigW Consortium Gitlab

influx_db.rb 5.79 KB
Newer Older
1 2 3
module Gitlab
  module Metrics
    module InfluxDb
4
      extend ActiveSupport::Concern
5
      include Gitlab::Metrics::Methods
6

7
      EXECUTION_MEASUREMENT_BUCKETS = [0.001, 0.01, 0.1, 1].freeze
8

9 10
      MUTEX = Mutex.new
      private_constant :MUTEX
11

12 13 14 15
      class_methods do
        def influx_metrics_enabled?
          settings[:enabled] || false
        end
16

17 18 19
        # Prometheus histogram buckets used for arbitrary code measurements

        def settings
20 21 22 23 24 25 26 27 28 29 30 31
          @settings ||= begin
            current_settings = Gitlab::CurrentSettings.current_application_settings

            {
              enabled: current_settings[:metrics_enabled],
              pool_size: current_settings[:metrics_pool_size],
              timeout: current_settings[:metrics_timeout],
              method_call_threshold: current_settings[:metrics_method_call_threshold],
              host: current_settings[:metrics_host],
              port: current_settings[:metrics_port],
              sample_interval: current_settings[:metrics_sample_interval] || 15,
              packet_size: current_settings[:metrics_packet_size] || 1
32
          }
33
          end
34 35 36 37 38 39 40 41 42 43 44 45 46
        end

        def mri?
          RUBY_ENGINE == 'ruby'
        end

        def method_call_threshold
          # This is memoized since this method is called for every instrumented
          # method. Loading data from an external cache on every method call slows
          # things down too much.
          # in milliseconds
          @method_call_threshold ||= settings[:method_call_threshold]
        end
47

48 49 50 51 52 53 54 55 56
        def submit_metrics(metrics)
          prepared = prepare_metrics(metrics)

          pool&.with do |connection|
            prepared.each_slice(settings[:packet_size]) do |slice|
              begin
                connection.write_points(slice)
              rescue StandardError
              end
57 58
            end
          end
59 60 61
        rescue Errno::EADDRNOTAVAIL, SocketError => ex
          Gitlab::EnvironmentLogger.error('Cannot resolve InfluxDB address. GitLab Performance Monitoring will not work.')
          Gitlab::EnvironmentLogger.error(ex)
62 63
        end

64 65 66
        def prepare_metrics(metrics)
          metrics.map do |hash|
            new_hash = hash.symbolize_keys
67

68 69 70 71 72 73
            new_hash[:tags].each do |key, value|
              if value.blank?
                new_hash[:tags].delete(key)
              else
                new_hash[:tags][key] = escape_value(value)
              end
74 75
            end

76 77
            new_hash
          end
78 79
        end

80 81
        def escape_value(value)
          value.to_s.gsub('=', '\\=')
82 83
        end

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
        # Measures the execution time of a block.
        #
        # Example:
        #
        #     Gitlab::Metrics.measure(:find_by_username_duration) do
        #       User.find_by_username(some_username)
        #     end
        #
        # name - The name of the field to store the execution time in.
        #
        # Returns the value yielded by the supplied block.
        def measure(name)
          trans = current_transaction

          return yield unless trans

          real_start = Time.now.to_f
          cpu_start = System.cpu_time

          retval = yield

          cpu_stop = System.cpu_time
          real_stop = Time.now.to_f

          real_time = (real_stop - real_start)
          cpu_time = cpu_stop - cpu_start

          real_duration_seconds = fetch_histogram("gitlab_#{name}_real_duration_seconds".to_sym) do
            docstring "Measure #{name}"
            base_labels Transaction::BASE_LABELS
            buckets EXECUTION_MEASUREMENT_BUCKETS
          end
116

117
          real_duration_seconds.observe(trans.labels, real_time)
118

119 120 121 122
          cpu_duration_seconds = fetch_histogram("gitlab_#{name}_cpu_duration_seconds".to_sym) do
            docstring "Measure #{name}"
            base_labels Transaction::BASE_LABELS
            buckets EXECUTION_MEASUREMENT_BUCKETS
123
            with_feature "prometheus_metrics_measure_#{name}_cpu_duration"
124 125
          end
          cpu_duration_seconds.observe(trans.labels, cpu_time)
126

127 128 129 130
          # InfluxDB stores the _real_time and _cpu_time time values as milliseconds
          trans.increment("#{name}_real_time", real_time.in_milliseconds, false)
          trans.increment("#{name}_cpu_time", cpu_time.in_milliseconds, false)
          trans.increment("#{name}_call_count", 1, false)
131

132 133
          retval
        end
134

135 136 137 138 139
        # Sets the action of the current transaction (if any)
        #
        # action - The name of the action.
        def action=(action)
          trans = current_transaction
140

141 142
          trans&.action = action
        end
143

144 145 146 147 148
        # Tracks an event.
        #
        # See `Gitlab::Metrics::Transaction#add_event` for more details.
        def add_event(*args)
          trans = current_transaction
149

150 151
          trans&.add_event(*args)
        end
152

153 154 155 156 157 158 159 160 161
        # Returns the prefix to use for the name of a series.
        def series_prefix
          @series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_'
        end

        # Allow access from other metrics related middlewares
        def current_transaction
          Transaction.current
        end
162

163 164 165 166 167 168 169 170 171 172 173 174 175 176
        # When enabled this should be set before being used as the usual pattern
        # "@foo ||= bar" is _not_ thread-safe.
        # rubocop:disable Gitlab/ModuleWithInstanceVariables
        def pool
          if influx_metrics_enabled?
            if @pool.nil?
              MUTEX.synchronize do
                @pool ||= ConnectionPool.new(size: settings[:pool_size], timeout: settings[:timeout]) do
                  host = settings[:host]
                  port = settings[:port]

                  InfluxDB::Client
                    .new(udp: { host: host, port: port })
                end
177 178
              end
            end
179

180 181
            @pool
          end
182
        end
183
        # rubocop:enable Gitlab/ModuleWithInstanceVariables
184 185 186
      end
    end
  end
187
end