process.rb 3.08 KB
Newer Older
1 2
# frozen_string_literal: true

3 4
require_relative "memstats"

Ahmad Sherif's avatar
Ahmad Sherif committed
5
module GitLab
6
  module Exporter
Ahmad Sherif's avatar
Ahmad Sherif committed
7 8 9 10 11
    # A helper class to extract memory info from /proc/<pid>/status
    #

    # A helper class to stats from /proc/<pid>/stat
    #
Ben Kochie's avatar
Ben Kochie committed
12 13
    # See: man 5 proc
    #
Ahmad Sherif's avatar
Ahmad Sherif committed
14 15 16
    # It takes a pid
    class ProcessStats
      def initialize(pid)
17 18
        @pid = pid
        @user_hertz = retrieve_user_hertz
19
        @stats = populate_info
Ahmad Sherif's avatar
Ahmad Sherif committed
20 21 22 23 24 25
      end

      def valid?
        !@stats.nil?
      end

Ben Kochie's avatar
Ben Kochie committed
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
      def cpu_time
        (@stats[14].to_i + @stats[15].to_i) / @user_hertz
      end

      def start_time
        @stats[22].to_i / @user_hertz
      end

      def vsize
        # Virtual memory size in bytes.
        @stats[23].to_i
      end

      def rss
        # Resident Set Size: number of pages the process has in real memory.
        @stats[24].to_i * 4096
Ahmad Sherif's avatar
Ahmad Sherif committed
42 43 44 45 46
      end

      private

      def populate_info
Ben Kochie's avatar
Ben Kochie committed
47
        # Pad the array by one element to make field numbers match the man page.
48
        [""].concat(File.read("/proc/#{@pid}/stat").split(" "))
Ahmad Sherif's avatar
Ahmad Sherif committed
49 50 51
      rescue Errno::ENOENT
        nil
      end
52 53 54 55 56 57

      def retrieve_user_hertz
        Process.clock_getres(:TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID, :hertz)
      rescue Errno::EINVAL
        100.0
      end
Ahmad Sherif's avatar
Ahmad Sherif committed
58 59 60 61 62 63 64 65 66 67 68 69
    end

    # Probes a process for info then writes metrics to a target
    class ProcessProber
      def initialize(options, metrics: PrometheusMetrics.new)
        @metrics = metrics
        @name    = options[:name]
        @pids    = if options[:pid_or_pattern] =~ /^\d+$/
                     [options[:pid_or_pattern]]
                   else
                     Utils.pgrep(options[:pid_or_pattern])
                   end
70
        @use_quantiles = options.fetch(:quantiles, false)
Ahmad Sherif's avatar
Ahmad Sherif committed
71 72
      end

Ben Kochie's avatar
Ben Kochie committed
73
      def probe_stat
Ahmad Sherif's avatar
Ahmad Sherif committed
74 75 76 77
        @pids.each do |pid|
          stats = ProcessStats.new(pid)
          next unless stats.valid?

78 79 80
          labels = { name: @name.downcase }
          labels[:pid] = pid unless @use_quantiles

Ben Kochie's avatar
Ben Kochie committed
81 82 83 84
          @metrics.add("process_cpu_seconds_total", stats.cpu_time, @use_quantiles, **labels)
          @metrics.add("process_resident_memory_bytes", stats.rss, @use_quantiles, **labels)
          @metrics.add("process_virtual_memory_bytes", stats.vsize, @use_quantiles, **labels)
          @metrics.add("process_start_time_seconds", stats.start_time, @use_quantiles, **labels)
Ahmad Sherif's avatar
Ahmad Sherif committed
85 86 87 88 89
        end

        self
      end

90 91 92 93 94 95
      def probe_count
        @metrics.add("process_count", @pids.count, name: @name.downcase)

        self
      end

96 97
      def probe_smaps
        @pids.each do |pid|
98
          stats = ::GitLab::Exporter::MemStats::Aggregator.new(pid)
99 100 101 102 103 104

          next unless stats.valid?

          labels = { name: @name.downcase }
          labels[:pid] = pid unless @use_quantiles

105
          ::GitLab::Exporter::MemStats::Mapping::FIELDS.each do |field|
106 107 108 109 110 111 112 113 114 115 116
            value = stats.totals[field]

            if value >= 0
              @metrics.add("process_smaps_#{field}_bytes", value * 1024, @use_quantiles, **labels)
            end
          end
        end

        self
      end

Ahmad Sherif's avatar
Ahmad Sherif committed
117 118 119 120 121 122
      def write_to(target)
        target.write(@metrics.to_s)
      end
    end
  end
end