summaryrefslogtreecommitdiff
path: root/lib/gitlab/metrics.rb
blob: cb8db2f1e9fa6aabfa5dba62102c91e398c49dab (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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
module Gitlab
  module Metrics
    extend Gitlab::CurrentSettings

    RAILS_ROOT   = Rails.root.to_s
    METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s
    PATH_REGEX   = /^#{RAILS_ROOT}\/?/

    def self.settings
      @settings ||= {
        enabled:               current_application_settings[:metrics_enabled],
        pool_size:             current_application_settings[:metrics_pool_size],
        timeout:               current_application_settings[:metrics_timeout],
        method_call_threshold: current_application_settings[:metrics_method_call_threshold],
        host:                  current_application_settings[:metrics_host],
        port:                  current_application_settings[:metrics_port],
        sample_interval:       current_application_settings[:metrics_sample_interval] || 15,
        packet_size:           current_application_settings[:metrics_packet_size] || 1
      }
    end

    def self.enabled?
      settings[:enabled] || false
    end

    def self.mri?
      RUBY_ENGINE == 'ruby'
    end

    def self.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.
      @method_call_threshold ||= settings[:method_call_threshold]
    end

    def self.pool
      @pool
    end

    def self.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
        end
      end
    rescue Errno::EADDRNOTAVAIL, SocketError => ex
      Gitlab::EnvironmentLogger.error('Cannot resolve InfluxDB address. GitLab Performance Monitoring will not work.')
      Gitlab::EnvironmentLogger.error(ex)
    end

    def self.prepare_metrics(metrics)
      metrics.map do |hash|
        new_hash = hash.symbolize_keys

        new_hash[:tags].each do |key, value|
          if value.blank?
            new_hash[:tags].delete(key)
          else
            new_hash[:tags][key] = escape_value(value)
          end
        end

        new_hash
      end
    end

    def self.escape_value(value)
      value.to_s.gsub('=', '\\=')
    end

    # 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 self.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) * 1000.0
      cpu_time = cpu_stop - cpu_start

      trans.increment("#{name}_real_time", real_time)
      trans.increment("#{name}_cpu_time", cpu_time)
      trans.increment("#{name}_call_count", 1)

      retval
    end

    # Adds a tag to the current transaction (if any)
    #
    # name - The name of the tag to add.
    # value - The value of the tag.
    def self.tag_transaction(name, value)
      trans = current_transaction

      trans&.add_tag(name, value)
    end

    # Sets the action of the current transaction (if any)
    #
    # action - The name of the action.
    def self.action=(action)
      trans = current_transaction

      trans&.action = action
    end

    # Tracks an event.
    #
    # See `Gitlab::Metrics::Transaction#add_event` for more details.
    def self.add_event(*args)
      trans = current_transaction

      trans&.add_event(*args)
    end

    # Returns the prefix to use for the name of a series.
    def self.series_prefix
      @series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_'
    end

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

    # When enabled this should be set before being used as the usual pattern
    # "@foo ||= bar" is _not_ thread-safe.
    if enabled?
      @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
    end
  end
end