server_metrics 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|