metrics.rb 3.0 KB
Newer Older
1 2
module Gitlab
  module Metrics
3 4 5 6
    RAILS_ROOT   = Rails.root.to_s
    METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s
    PATH_REGEX   = /^#{RAILS_ROOT}\/?/

7 8 9 10 11
    # Returns the current settings, ensuring we _always_ have a default set of
    # metrics settings (even during tests, when the migrations are lacking,
    # etc). This ensures the application is able to boot up even when the
    # migrations have not been executed.
    def self.settings
12 13 14 15 16 17 18 19 20 21
      if ApplicationSetting.table_exists? and curr = ApplicationSetting.current
        curr
      else
        {
          metrics_pool_size:             16,
          metrics_timeout:               10,
          metrics_enabled:               false,
          metrics_method_call_threshold: 10
        }
      end
22 23
    end

24
    def self.pool_size
25
      settings[:metrics_pool_size]
26 27 28
    end

    def self.timeout
29
      settings[:metrics_timeout]
30 31 32
    end

    def self.enabled?
33
      settings[:metrics_enabled]
34 35
    end

36 37 38 39
    def self.mri?
      RUBY_ENGINE == 'ruby'
    end

40
    def self.method_call_threshold
41 42 43 44
      # 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[:metrics_method_call_threshold]
45 46
    end

47 48 49 50 51 52 53 54
    def self.pool
      @pool
    end

    def self.hostname
      @hostname
    end

55 56
    # Returns a relative path and line number based on the last application call
    # frame.
57 58
    def self.last_relative_application_frame
      frame = caller_locations.find do |l|
59
        l.path.start_with?(RAILS_ROOT) && !l.path.start_with?(METRICS_ROOT)
60 61 62
      end

      if frame
63
        return frame.path.sub(PATH_REGEX, ''), frame.lineno
64 65 66 67 68
      else
        return nil, nil
      end
    end

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
    def self.submit_metrics(metrics)
      prepared = prepare_metrics(metrics)

      pool.with do |connection|
        prepared.each do |metric|
          begin
            connection.write_points([metric])
          rescue StandardError
          end
        end
      end
    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

102 103 104 105 106 107
    @hostname = Socket.gethostname

    # 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: pool_size, timeout: timeout) do
108 109 110
        host = settings[:metrics_host]
        user = settings[:metrics_username]
        pw   = settings[:metrics_password]
111
        port = settings[:metrics_port]
112

113 114
        InfluxDB::Client.
          new(udp: { host: host, port: port }, username: user, password: pw)
115 116 117 118
      end
    end
  end
end