Class: ServerMetrics::Processes

Inherits:
Object
  • Object
show all
Defined in:
lib/server_metrics/collectors/processes.rb

Overview

Collects information on processes. Groups processes running under the same command, and sums up their CPU & memory usage. CPU is calculated **since the last run**, and is a pecentage of overall CPU usage during the time span since the instance was last run.

FAQ:

1) top and htop show PIDs. Why doesn’t this class? This class aggregates processes. So if you have 10 apache processes running, it will report the total memory and CPU for all instances, and also report that there are 10 processes.

2) why are the process CPU numbers lower than [top|htop]? We normalize the CPU usage according to the number of CPUs your server has. Top and htop don’t do that. So on a 8 CPU system, you’d expect these numbers to be almost an order of magnitude lower.

www.linuxquestions.org/questions/linux-general-1/per-process-cpu-utilization-557577/

SYSTEM COMPATIBILITY NOTES

  • on Linux systems with the /proc filesystem: will return all process metrics

  • on other systems with sys/proctable installed: will generally return a process list and counts

  • everywhere else: no process info returned

Defined Under Namespace

Classes: Process

Constant Summary collapse

DEFAULT_PAGE_SIZE =

most common - used if page size can’t be retrieved. units are bytes.

4096

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Processes

Returns a new instance of Processes.



32
33
34
35
36
37
38
39
40
41
42
# File 'lib/server_metrics/collectors/processes.rb', line 32

def initialize(options={})
  @last_run
  @last_jiffies
  @last_process_list
  if ServerMetrics::SystemInfo.os =~ /linux/
    @proc_table_klass = SysLite::ProcTable
  elsif Object.const_defined?('Sys') && Sys.const_defined?('ProcTable')
    @proc_table_klass = Sys::ProcTable
  end

end

Class Method Details

.from_hash(hash) ⇒ Object

for reinstantiating from a hash why not just use marshall? this is a lot more manageable written to the Scout agent’s history file.



152
153
154
155
156
157
158
# File 'lib/server_metrics/collectors/processes.rb', line 152

def self.from_hash(hash)
  p=new(hash[:options])
  p.instance_variable_set('@last_run', hash[:last_run])
  p.instance_variable_set('@last_jiffies', hash[:last_jiffies])
  p.instance_variable_set('@last_process_list', hash[:last_process_list])
  p
end

Instance Method Details

#calculate_processesObject

called from run(). This method lists all the processes running on the server, groups them by command, and calculates CPU time for each process. Since CPU time has to be calculated relative to the last sample, the collector has to be run twice to get CPU data.



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
# File 'lib/server_metrics/collectors/processes.rb', line 73

def calculate_processes
  ## 1. get a list of all processes
  processes = @proc_table_klass.ps.map{|p| ServerMetrics::Processes::Process.new(p) } # our Process object adds a method some behavior

  ## 2. loop through each process and calculate the CPU time.
  # The CPU values returned by ProcTable are cumulative for the life of the process, which is not what we want.
  # So, we rely on @last_process_list to make this calculation. If a process wasn't around last time, we use it's cumulative CPU time so far, which will be accurate enough.
  now = Time.now
  current_jiffies = get_jiffies
  if @last_run && @last_jiffies && @last_process_list
    elapsed_time = now - @last_run # in seconds
    elapsed_jiffies = current_jiffies - @last_jiffies
    if elapsed_time >= 1
      processes.each do |p|
        p.set_recent_cpu(@last_process_list[p.pid],elapsed_jiffies,num_processors)
      end
    end
  end

  ## 3. group by command and aggregate the CPU
  grouped = {}
  processes.each do |proc|
    grouped[proc.comm] ||= {
        :cpu => 0,
        :memory => 0,
        :count => 0,
        :cmdlines => []
    }
    grouped[proc.comm][:count]    += 1
    grouped[proc.comm][:cpu]      += proc.recent_cpu_percentage
    if proc.has?(:rss) # mac doesn't return rss. Mac returns 0 for memory usage
      # converted to MB from bytes
      grouped[proc.comm][:memory]   += (proc.rss.to_f*page_size) / 1024 / 1024
    end
    grouped[proc.comm][:cmdlines] << proc.cmdline if !grouped[proc.comm][:cmdlines].include?(proc.cmdline)
  end # processes.each

  # {pid => cpu_snapshot, pid2 => cpu_snapshot ...}
  processes_to_store = processes.inject(Hash.new) do |hash, proc|
    hash[proc.pid] = proc.combined_cpu
    hash
  end

  @last_process_list = processes_to_store
  @last_jiffies = current_jiffies
  @last_run = now

  grouped
end

#get_jiffiesObject

We need this because the process CPU utilization is measured in jiffies. In order to calculate the process’ % usage of total CPU resources, we need to know how many jiffies have passed.

While jiffies isn’t a fixed value (it can vary between 100 and 250 per second), we assume it is 100 jiffies/second (10 milliseconds/jiffy) because that is most common.



128
129
130
# File 'lib/server_metrics/collectors/processes.rb', line 128

def get_jiffies
  (Time.now.to_f*100).to_i
end

#num_processorsObject



140
141
142
# File 'lib/server_metrics/collectors/processes.rb', line 140

def num_processors
  @num_processors ||= ServerMetrics::SystemInfo.num_processors  
end

#page_sizeObject

Sys::ProcTable.ps returns rss in pages, not in bytes. Returns the page size in bytes.



134
135
136
137
138
# File 'lib/server_metrics/collectors/processes.rb', line 134

def page_size
  @page_size ||= %x(getconf PAGESIZE).to_i
rescue
  @page_size = DEFAULT_PAGE_SIZE
end

#runObject

This is the main method to call. It returns a hash of processes, keyed by the executable name.

{‘mysqld’ =>

   {
    :cmd => "mysqld",    # the command (without the path of arguments being run)
    :count    => 1,      # the number of these processes (grouped by the above command)
    :cpu      => 34,     # the percentage of the total computational resources available (across all cores/CPU) that these processes are using.
    :memory   => 2,      # the percentage of total memory that these processes are using.
    :cmd_lines => ["cmd args1", "cmd args2"]
   },
'apache' =>
   {
    ....
   }

}



61
62
63
64
65
66
67
68
# File 'lib/server_metrics/collectors/processes.rb', line 61

def run
  if @proc_table_klass
    @processes = calculate_processes # returns a hash
    @processes.keys.inject(@processes) { |processes, key| processes[key][:cmd] = key; processes }
  else
    @processes = {}
  end
end

#to_hashObject

for persisting to a file – conforms to same basic API as the Collectors do. why not just use marshall? This is a lot more manageable written to the Scout agent’s history file.



146
147
148
# File 'lib/server_metrics/collectors/processes.rb', line 146

def to_hash
  {:last_run=>@last_run, :last_jiffies=>@last_jiffies, :last_process_list=>@last_process_list}
end