server_metrics 0.0.6 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/example_processes.rb +4 -2
- data/lib/server_metrics.rb +0 -2
- data/lib/server_metrics/collectors/processes.rb +72 -29
- data/lib/server_metrics/version.rb +1 -1
- data/test/test_helper.rb +4 -4
- metadata +4 -4
data/example_processes.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require "rubygems"
|
2
|
-
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path(__FILE__), "lib") # set the loadpath for convenience during development
|
4
|
+
require "server_metrics"
|
5
|
+
|
3
6
|
require "pry"
|
4
7
|
require "awesome_print"
|
5
8
|
|
6
|
-
|
7
9
|
p = ServerMetrics::Processes.new(1)
|
8
10
|
puts "stating ..."
|
9
11
|
p.run
|
data/lib/server_metrics.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
require 'sys/proctable'
|
2
|
+
require 'server_metrics/system_info'
|
2
3
|
|
3
4
|
# Collects information on processes. Groups processes running under the same command, and sums up their CPU & memory usage.
|
4
|
-
# CPU is calculated **since the last run
|
5
|
-
#
|
6
|
-
|
5
|
+
# CPU is calculated **since the last run**, and is a pecentage of overall CPU usage during the timeframe
|
6
|
+
# http://www.linuxquestions.org/questions/linux-general-1/per-process-cpu-utilization-557577/
|
7
7
|
class ServerMetrics::Processes
|
8
8
|
|
9
9
|
def initialize(options={})
|
10
10
|
@last_run
|
11
|
+
@last_jiffies
|
11
12
|
@last_process_list
|
12
13
|
end
|
13
14
|
|
@@ -51,42 +52,58 @@ class ServerMetrics::Processes
|
|
51
52
|
# and calculates CPU time for each process. Since CPU time has to be calculated relative to the last sample,
|
52
53
|
# the collector has to be run twice to get CPU data.
|
53
54
|
def calculate_processes
|
54
|
-
|
55
|
-
|
56
|
-
|
55
|
+
|
56
|
+
## 1. get a list of all processes
|
57
|
+
processes = Sys::ProcTable.ps.map{|p| ServerMetrics::Processes::Process.new(p) } # our Process object adds a method and adds some behavior
|
58
|
+
|
59
|
+
## 2. loop through each process and calculate the CPU time.
|
60
|
+
# The CPU values returned by ProcTable are cumulative for the life of the process, which is not what we want.
|
61
|
+
# 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.
|
62
|
+
now = Time.now
|
63
|
+
current_jiffies = get_jiffies
|
64
|
+
if @last_run && @last_jiffies && @last_process_list
|
65
|
+
elapsed_time = now - @last_run # in seconds
|
66
|
+
elapsed_jiffies = current_jiffies - @last_jiffies
|
67
|
+
if elapsed_time >= 1
|
68
|
+
processes.each do |p|
|
69
|
+
if last_cpu = @last_process_list[p.pid]
|
70
|
+
p.recent_cpu = p.combined_cpu - last_cpu
|
71
|
+
else
|
72
|
+
p.recent_cpu = p.combined_cpu # this process wasn't around last time, so just use the cumulative CPU time for its existence so far
|
73
|
+
end
|
74
|
+
# a) p.recent_cpu / elapsed_jiffies = the amount of CPU time this process has taken divided by the total "time slots" the CPU has available
|
75
|
+
# b) * 100 ... this turns it into a percentage
|
76
|
+
# b) / num_processors ... this normalizes for the the number of processors in the system, so it reflects the amount of CPU power avaiable as a whole
|
77
|
+
p.recent_cpu_percentage = ((p.recent_cpu.to_f / elapsed_jiffies.to_f ) * 100.0) / ServerMetrics::SystemInfo.num_processors.to_f
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
## 3. group by command and aggregate the CPU
|
83
|
+
grouped = {}
|
57
84
|
processes.each do |proc|
|
58
85
|
grouped[proc.comm] ||= {
|
59
|
-
:count => 0,
|
60
|
-
:raw_cpu => 0,
|
61
86
|
:cpu => 0,
|
62
87
|
:memory => 0,
|
63
|
-
:
|
88
|
+
:count => 0,
|
64
89
|
:cmdlines => []
|
65
90
|
}
|
66
|
-
grouped[proc.comm][:count]
|
67
|
-
grouped[proc.comm][:
|
68
|
-
grouped[proc.comm][:memory]
|
69
|
-
grouped[proc.comm][:uid] = proc.uid
|
91
|
+
grouped[proc.comm][:count] += 1
|
92
|
+
grouped[proc.comm][:cpu] += proc.recent_cpu_percentage || 0
|
93
|
+
grouped[proc.comm][:memory] += proc.rss.to_f / 1024.0
|
70
94
|
grouped[proc.comm][:cmdlines] << proc.cmdline if !grouped[proc.comm][:cmdlines].include?(proc.cmdline)
|
71
95
|
end # processes.each
|
72
96
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
if elapsed_time >= 1
|
78
|
-
grouped.each do |name, values|
|
79
|
-
if last_values = @last_process_list[name]
|
80
|
-
cpu_since_last_sample = values[:raw_cpu] - last_values[:raw_cpu]
|
81
|
-
grouped[name][:cpu] = (cpu_since_last_sample/(elapsed_time * ServerMetrics::SystemInfo.num_processors))*100
|
82
|
-
else
|
83
|
-
grouped.reject!(name) # no data from last run. don't report anything.
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
97
|
+
# {pid => cpu_snapshot, pid2 => cpu_snapshot ...}
|
98
|
+
processes_to_store = processes.inject(Hash.new) do |hash, proc|
|
99
|
+
hash[proc.pid] = proc.combined_cpu
|
100
|
+
hash
|
87
101
|
end
|
88
|
-
|
102
|
+
|
103
|
+
@last_process_list = processes_to_store
|
104
|
+
@last_jiffies = current_jiffies
|
89
105
|
@last_run = now
|
106
|
+
|
90
107
|
grouped
|
91
108
|
end
|
92
109
|
|
@@ -97,10 +114,17 @@ class ServerMetrics::Processes
|
|
97
114
|
@processes.map { |key, hash| {:cmd => key}.merge(hash) }.sort { |a, b| a[order_by] <=> b[order_by] }.reverse[0...num]
|
98
115
|
end
|
99
116
|
|
117
|
+
# Relies on the /proc directory (/proc/timer_list). We need this because the process CPU utilization is measured in jiffies.
|
118
|
+
# In order to calculate the process' % usage of total CPU resources, we need to know how many jiffies have passed.
|
119
|
+
# Unfortunately, jiffies isn't a fixed value (it can vary between 100 and 250 per second), so we need to calculate it ourselves.
|
120
|
+
def get_jiffies
|
121
|
+
`cat /proc/timer_list`.match(/^jiffies: (\d+)$/)[1].to_i
|
122
|
+
end
|
123
|
+
|
100
124
|
# for persisting to a file -- conforms to same basic API as the Collectors do.
|
101
125
|
# why not just use marshall? This is a lot more manageable written to the Scout agent's history file.
|
102
126
|
def to_hash
|
103
|
-
{:last_run=>@last_run, :last_process_list=>@last_process_list}
|
127
|
+
{:last_run=>@last_run, :last_jiffies=>@last_jiffies, :last_process_list=>@last_process_list}
|
104
128
|
end
|
105
129
|
|
106
130
|
# for reinstantiating from a hash
|
@@ -108,8 +132,27 @@ class ServerMetrics::Processes
|
|
108
132
|
def self.from_hash(hash)
|
109
133
|
p=new(hash[:options])
|
110
134
|
p.instance_variable_set('@last_run', hash[:last_run])
|
135
|
+
p.instance_variable_set('@last_jiffies', hash[:last_jiffies])
|
111
136
|
p.instance_variable_set('@last_process_list', hash[:last_process_list])
|
112
137
|
p
|
113
138
|
end
|
114
139
|
|
140
|
+
# a thin wrapper around Sys:ProcTable's ProcTableStruct. We're using it to add some fields and behavior.
|
141
|
+
# Beyond what we're adding, it just passes through to its instance of ProcTableStruct
|
142
|
+
class Process
|
143
|
+
attr_accessor :recent_cpu, :recent_cpu_percentage # used to store the calculation of CPU since last sample
|
144
|
+
def initialize(proctable_struct)
|
145
|
+
@pts=proctable_struct
|
146
|
+
@recent_cpu = 0
|
147
|
+
end
|
148
|
+
def combined_cpu
|
149
|
+
# best thread I've seen on cutime vs utime & cstime vs stime: https://www.ruby-forum.com/topic/93176
|
150
|
+
# trying the metric that doesn't include the consumption of child processes
|
151
|
+
utime + stime
|
152
|
+
end
|
153
|
+
# delegate everything else to ProcTable::Struct
|
154
|
+
def method_missing(sym, *args, &block)
|
155
|
+
@pts.send sym, *args, &block
|
156
|
+
end
|
157
|
+
end
|
115
158
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
$LOAD_PATH << File.expand_path( File.dirname(__FILE__) + '
|
2
|
-
require '
|
3
|
-
require 'rubygems'
|
1
|
+
$LOAD_PATH << File.expand_path( File.dirname(__FILE__) + '/../lib' ) # needed because when running tests, Rubygems manage $LOAD_PATH like it does in runtime
|
2
|
+
require 'rubygems' # so the development-only dependencies below can be loaded
|
4
3
|
require 'pry'
|
5
4
|
require 'awesome_print'
|
6
5
|
require 'timecop'
|
6
|
+
require 'test/unit'
|
7
7
|
require 'mocha/setup'
|
8
|
+
require 'server_metrics'
|
8
9
|
|
9
|
-
require 'lib/server_metrics'
|
10
10
|
AwesomePrint.defaults = {
|
11
11
|
:indent => -2,
|
12
12
|
:sort_keys =>true
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: server_metrics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-11-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sys-proctable
|
@@ -171,7 +171,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
171
171
|
version: '0'
|
172
172
|
segments:
|
173
173
|
- 0
|
174
|
-
hash:
|
174
|
+
hash: -865855317494333286
|
175
175
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
176
|
none: false
|
177
177
|
requirements:
|
@@ -180,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
180
|
version: '0'
|
181
181
|
segments:
|
182
182
|
- 0
|
183
|
-
hash:
|
183
|
+
hash: -865855317494333286
|
184
184
|
requirements: []
|
185
185
|
rubyforge_project:
|
186
186
|
rubygems_version: 1.8.25
|