server_metrics 1.1.1 → 1.2.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +11 -0
- data/lib/server_metrics/collectors/cpu.rb +24 -15
- data/lib/server_metrics/collectors/disk.rb +25 -6
- data/lib/server_metrics/collectors/memory.rb +1 -1
- data/lib/server_metrics/collectors/network.rb +1 -1
- data/lib/server_metrics/collectors/processes.rb +27 -3
- data/lib/server_metrics/lib/proctable_lite.rb +4 -147
- data/lib/server_metrics/system_info.rb +1 -1
- data/lib/server_metrics/version.rb +1 -1
- data/test/performance/prof.rb +44 -0
- metadata +8 -11
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
# 1.2.0
|
2
|
+
|
3
|
+
Performance related:
|
4
|
+
|
5
|
+
* Using `Dir#glob` vs. `Dir#foreach`
|
6
|
+
* Using `File#read` vs. `cat FILE`.
|
7
|
+
* Removing commented out and unused elements from `SysLite::ProcTable`
|
8
|
+
* Removed method_missing logic in `Processes::Process` to access the `ProcTableStruct`
|
9
|
+
* `Disk` added caching logic via a `@option[:ttl]` to execute slow system calls at a lower interval (ex: ev 60 seconds)
|
10
|
+
* `String#lines#to_a` vs. `String#split('\n')` (faster)
|
11
|
+
|
1
12
|
## 1.1.1
|
2
13
|
|
3
14
|
* Handling Infinite and NaN Process CPU Usage.
|
@@ -21,16 +21,22 @@ class ServerMetrics::Cpu < ServerMetrics::Collector
|
|
21
21
|
rescue ProcStatError
|
22
22
|
@error = "could not retrieve CPU stats from /proc/stat"
|
23
23
|
end
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
24
|
+
|
25
|
+
# This requires a system call, which is slow. `scout_realtime` doesn't display server load, so this
|
26
|
+
# option allows `scout_realtime` to not collect load averages.
|
27
|
+
if !@options[:skip_load]
|
28
|
+
ENV['LANG'] = 'C' # forcing english for parsing
|
29
|
+
uptime_output = `uptime`
|
30
|
+
matches = uptime_output.match(/load averages?: ([\d.]+),? ([\d.]+),? ([\d.]+)\Z/)
|
31
|
+
|
32
|
+
report(:last_minute => matches[1].to_f / num_processors,
|
33
|
+
:last_five_minutes => matches[2].to_f / num_processors,
|
34
|
+
:last_fifteen_minutes => matches[3].to_f / num_processors)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def num_processors
|
39
|
+
@num_processors ||= ServerMetrics::SystemInfo.num_processors
|
34
40
|
end
|
35
41
|
|
36
42
|
# Helper class
|
@@ -38,13 +44,16 @@ class ServerMetrics::Cpu < ServerMetrics::Collector
|
|
38
44
|
attr_accessor :user, :system, :idle, :iowait, :interrupts, :procs_running, :procs_blocked, :time, :steal
|
39
45
|
|
40
46
|
def self.fetch
|
41
|
-
output =
|
42
|
-
|
43
|
-
|
44
|
-
|
47
|
+
output = nil
|
48
|
+
begin
|
49
|
+
output = File.read("/proc/stat")
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
# No such file or directory - /proc/stat
|
52
|
+
# /proc/stat doesn't exist on this system.
|
53
|
+
raise ProcStatError
|
45
54
|
end
|
46
55
|
|
47
|
-
data = output.
|
56
|
+
data = output.lines.collect { |line| line.split }
|
48
57
|
|
49
58
|
cpu_stats = CpuStats.new
|
50
59
|
|
@@ -7,23 +7,42 @@ class ServerMetrics::Disk < ServerMetrics::MultiCollector
|
|
7
7
|
|
8
8
|
def build_report
|
9
9
|
ENV['LANG'] = 'C' # forcing English for parsing
|
10
|
-
|
11
|
-
@
|
12
|
-
@disk_stats = `cat /proc/diskstats`.split("\n")
|
10
|
+
|
11
|
+
@disk_stats = File.read("/proc/diskstats").lines.to_a
|
13
12
|
|
14
|
-
|
13
|
+
devices.each do |device|
|
15
14
|
get_sizes(device) # does its own reporting
|
16
15
|
get_stats(device) if linux? # does its own reporting
|
17
16
|
end
|
18
17
|
end
|
18
|
+
|
19
|
+
# System calls are slow. Read once every minute and not on every innvocation.
|
20
|
+
def df_output
|
21
|
+
if @last_df_output.nil? or @last_df_output < (Time.now-@options[:ttl].to_i*60)
|
22
|
+
@last_df_output = Time.now
|
23
|
+
@df_output = `df -Pkh`.lines.to_a
|
24
|
+
else
|
25
|
+
@df_output
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# System calls are slow. Read once every minute and not on every innvocation.
|
30
|
+
def devices
|
31
|
+
if @devices.nil? or @last_devices_output < (Time.now-@options[:ttl].to_i*60)
|
32
|
+
@last_devices_output = Time.now
|
33
|
+
@devices = `mount`.split("\n").grep(/^\/dev/).map{|l|l.split.first} # any device that starts with /dev
|
34
|
+
else
|
35
|
+
@devices
|
36
|
+
end
|
37
|
+
end
|
19
38
|
|
20
39
|
# called from build_report for each device
|
21
40
|
def get_sizes(device)
|
22
|
-
header_line
|
41
|
+
header_line=df_output.first
|
23
42
|
headers = header_line.split(/\s+/,6) # limit to 6 columns - last column is "mounted on"
|
24
43
|
parsed_lines=[] # Each line will look like {"%iused" => "38%","Avail" => "289Gi", "Capacity=> "38%", "Filesystem"=> "/dev/disk0s2","Mounted => "/", "Size" => "465Gi", "Used" => "176Gi", "ifree" => "75812051", "iused" => "46116178"}
|
25
44
|
|
26
|
-
|
45
|
+
df_output[1..df_output.size-1].each do |line|
|
27
46
|
values=line.split(/\s+/,6)
|
28
47
|
parsed_lines<<Hash[headers.zip(values)]
|
29
48
|
end
|
@@ -17,7 +17,7 @@ class ServerMetrics::Memory < ServerMetrics::Collector
|
|
17
17
|
|
18
18
|
def linux_memory
|
19
19
|
mem_info = {}
|
20
|
-
|
20
|
+
File.read("/proc/meminfo").each_line do |line|
|
21
21
|
_, key, value = *line.match(/^(\w+):\s+(\d+)\s/)
|
22
22
|
mem_info[key] = value.to_i
|
23
23
|
end
|
@@ -5,7 +5,7 @@ class ServerMetrics::Network < ServerMetrics::MultiCollector
|
|
5
5
|
def build_report
|
6
6
|
|
7
7
|
if linux?
|
8
|
-
lines =
|
8
|
+
lines = File.read("/proc/net/dev").lines.to_a[2..-1]
|
9
9
|
interfaces = []
|
10
10
|
lines.each do |line|
|
11
11
|
iface, rest = line.split(':', 2).collect { |e| e.strip }
|
@@ -195,9 +195,33 @@ class ServerMetrics::Processes
|
|
195
195
|
(utime || 0) + (stime || 0) # utime and stime aren't available on mac. Result is %cpu is 0 on mac.
|
196
196
|
end
|
197
197
|
|
198
|
-
#
|
199
|
-
def
|
200
|
-
@pts.
|
198
|
+
# For better performance, not using #method_missing to just pass these off to ProcTable::Struct.
|
199
|
+
def pid
|
200
|
+
@pts.pid
|
201
|
+
end
|
202
|
+
|
203
|
+
def comm
|
204
|
+
@comm ||= @pts.comm
|
205
|
+
end
|
206
|
+
|
207
|
+
def cmdline
|
208
|
+
@pts.cmdline
|
209
|
+
end
|
210
|
+
|
211
|
+
def ppid
|
212
|
+
@pts.ppid
|
213
|
+
end
|
214
|
+
|
215
|
+
def utime
|
216
|
+
@pts.utime
|
217
|
+
end
|
218
|
+
|
219
|
+
def stime
|
220
|
+
@pts.stime
|
221
|
+
end
|
222
|
+
|
223
|
+
def rss
|
224
|
+
@pts.rss
|
201
225
|
end
|
202
226
|
end
|
203
227
|
end
|
@@ -17,9 +17,6 @@ module SysLite
|
|
17
17
|
VERSION = '0.9.3'
|
18
18
|
|
19
19
|
private
|
20
|
-
|
21
|
-
# @mem_total = IO.read("/proc/meminfo")[/MemTotal.*/].split[1].to_i * 1024 rescue nil
|
22
|
-
# @boot_time = IO.read("/proc/stat")[/btime.*/].split.last.to_i rescue nil
|
23
20
|
|
24
21
|
# Handles a special case on Ubuntu - kthreadd generates many children (200+).
|
25
22
|
# These are aggregated together and reported as a single process, kthreadd.
|
@@ -39,57 +36,17 @@ module SysLite
|
|
39
36
|
@fields = [
|
40
37
|
'cmdline', # Complete command line
|
41
38
|
'cwd', # Current working directory
|
42
|
-
'environ', # Environment
|
43
39
|
'exe', # Actual pathname of the executed command
|
44
|
-
'fd', # File descriptors open by process
|
45
|
-
'root', # Root directory of process
|
46
40
|
'pid', # Process ID
|
47
41
|
'comm', # Filename of executable
|
48
|
-
'state', # Single character state abbreviation
|
49
42
|
'ppid', # Parent process ID
|
50
|
-
'pgrp', # Process group
|
51
|
-
'session', # Session ID
|
52
|
-
'tty_nr', # TTY (terminal) associated with the process
|
53
|
-
'tpgid', # Group ID of the TTY
|
54
|
-
'flags', # Kernel flags
|
55
|
-
'minflt', # Number of minor faults
|
56
|
-
'cminflt', # Number of minor faults of waited-for children
|
57
|
-
'majflt', # Number of major faults
|
58
|
-
'cmajflt', # Number of major faults of waited-for children
|
59
43
|
'utime', # Number of user mode jiffies
|
60
44
|
'stime', # Number of kernel mode jiffies
|
61
45
|
'cutime', # Number of children's user mode jiffies
|
62
46
|
'cstime', # Number of children's kernel mode jiffies
|
63
|
-
'priority', # Nice value plus 15
|
64
|
-
'nice', # Nice value
|
65
|
-
'itrealvalue', # Time in jiffies before next SIGALRM
|
66
|
-
'starttime', # Time in jiffies since system boot
|
67
47
|
'vsize', # Virtual memory size in bytes
|
68
48
|
'rss', # Resident set size
|
69
|
-
'rlim', # Current limit on the rss in bytes
|
70
|
-
'startcode', # Address above which program text can run
|
71
|
-
'endcode', # Address below which program text can run
|
72
|
-
'startstack', # Address of the startstack
|
73
|
-
'kstkesp', # Kernel stack page address
|
74
|
-
'kstkeip', # Kernel instruction pointer
|
75
|
-
'signal', # Bitmap of pending signals
|
76
|
-
'blocked', # Bitmap of blocked signals
|
77
|
-
'sigignore', # Bitmap of ignored signals
|
78
|
-
'sigcatch', # Bitmap of caught signals
|
79
|
-
'wchan', # Channel in which the process is waiting
|
80
|
-
'nswap', # Number of pages swapped
|
81
|
-
'cnswap', # Cumulative nswap for child processes
|
82
|
-
'exit_signal', # Signal to be sent to parent when process dies
|
83
|
-
'processor', # CPU number last executed on
|
84
|
-
'rt_priority', # Real time scheduling priority
|
85
|
-
'policy', # Scheduling policy
|
86
49
|
'name', # Process name
|
87
|
-
'uid', # Real user ID
|
88
|
-
'euid', # Effective user ID
|
89
|
-
'gid', # Real group ID
|
90
|
-
'egid', # Effective group ID
|
91
|
-
'pctcpu', # Percent of CPU usage (custom field)
|
92
|
-
'pctmem' # Percent of Memory usage (custom field)
|
93
50
|
]
|
94
51
|
|
95
52
|
public
|
@@ -123,57 +80,13 @@ module SysLite
|
|
123
80
|
array = block_given? ? nil : []
|
124
81
|
struct = nil
|
125
82
|
raise TypeError unless pid.is_a?(Fixnum) if pid
|
126
|
-
|
127
|
-
Dir.
|
128
|
-
|
83
|
+
|
84
|
+
Dir.chdir("/proc")
|
85
|
+
Dir.glob("[0-9]*").each do |file|
|
129
86
|
next unless file.to_i == pid if pid
|
130
87
|
|
131
88
|
struct = ProcTableStruct.new
|
132
89
|
|
133
|
-
# Get /proc/<pid>/cmdline information. Strip out embedded nulls.
|
134
|
-
# begin
|
135
|
-
# data = IO.read("/proc/#{file}/cmdline").tr("\000", ' ').strip
|
136
|
-
# struct.cmdline = data
|
137
|
-
# rescue
|
138
|
-
# next # Process terminated, on to the next process
|
139
|
-
# end
|
140
|
-
|
141
|
-
# Get /proc/<pid>/cwd information
|
142
|
-
# struct.cwd = File.readlink("/proc/#{file}/cwd") rescue nil
|
143
|
-
|
144
|
-
# Get /proc/<pid>/environ information. Environment information
|
145
|
-
# is represented as a Hash, with the environment variable as the
|
146
|
-
# key and its value as the hash value.
|
147
|
-
struct.environ = {}
|
148
|
-
|
149
|
-
# begin
|
150
|
-
# IO.read("/proc/#{file}/environ").split("\0").each{ |str|
|
151
|
-
# key, value = str.split('=')
|
152
|
-
# struct.environ[key] = value
|
153
|
-
# }
|
154
|
-
# rescue Errno::EACCES, Errno::ESRCH, Errno::ENOENT
|
155
|
-
# # Ignore and move on.
|
156
|
-
# end
|
157
|
-
|
158
|
-
# Get /proc/<pid>/exe information
|
159
|
-
# struct.exe = File.readlink("/proc/#{file}/exe") rescue nil
|
160
|
-
|
161
|
-
# Get /proc/<pid>/fd information. File descriptor information
|
162
|
-
# is represented as a Hash, with the fd as the key, and its
|
163
|
-
# symlink as the value.
|
164
|
-
struct.fd = {}
|
165
|
-
|
166
|
-
# begin
|
167
|
-
# Dir["/proc/#{file}/fd/*"].each do |fd|
|
168
|
-
# struct.fd[File.basename(fd)] = File.readlink(fd) rescue nil
|
169
|
-
# end
|
170
|
-
# rescue
|
171
|
-
# # Ignore and move on
|
172
|
-
# end
|
173
|
-
|
174
|
-
# Get /proc/<pid>/root information
|
175
|
-
# struct.root = File.readlink("/proc/#{file}/root") rescue nil
|
176
|
-
|
177
90
|
# Get /proc/<pid>/stat information
|
178
91
|
stat = IO.read("/proc/#{file}/stat") rescue next
|
179
92
|
|
@@ -188,66 +101,10 @@ module SysLite
|
|
188
101
|
struct.pid = stat[0].to_i
|
189
102
|
# Remove parens. Note this could be overwritten in #get_comm_group_name.
|
190
103
|
struct.comm = stat[1].tr('()','')
|
191
|
-
# struct.state = stat[2]
|
192
104
|
struct.ppid = stat[3].to_i
|
193
|
-
# struct.pgrp = stat[4].to_i
|
194
|
-
# struct.session = stat[5].to_i
|
195
|
-
# struct.tty_nr = stat[6].to_i
|
196
|
-
# struct.tpgid = stat[7].to_i
|
197
|
-
# struct.flags = stat[8].to_i
|
198
|
-
# struct.minflt = stat[9].to_i
|
199
|
-
# struct.cminflt = stat[10].to_i
|
200
|
-
# struct.majflt = stat[11].to_i
|
201
|
-
# struct.cmajflt = stat[12].to_i
|
202
105
|
struct.utime = stat[13].to_i
|
203
106
|
struct.stime = stat[14].to_i
|
204
|
-
# struct.cutime = stat[15].to_i
|
205
|
-
# struct.cstime = stat[16].to_i
|
206
|
-
# struct.priority = stat[17].to_i
|
207
|
-
# struct.nice = stat[18].to_i
|
208
|
-
# Skip 19
|
209
|
-
# struct.itrealvalue = stat[20].to_i
|
210
|
-
# struct.starttime = stat[21].to_i
|
211
|
-
# struct.vsize = stat[22].to_i
|
212
107
|
struct.rss = stat[23].to_i
|
213
|
-
# struct.rlim = stat[24].to_i
|
214
|
-
# struct.startcode = stat[25].to_i
|
215
|
-
# struct.endcode = stat[26].to_i
|
216
|
-
# struct.startstack = stat[27].to_i
|
217
|
-
# struct.kstkesp = stat[28].to_i
|
218
|
-
# struct.kstkeip = stat[29].to_i
|
219
|
-
# struct.signal = stat[30].to_i
|
220
|
-
# struct.blocked = stat[31].to_i
|
221
|
-
# struct.sigignore = stat[32].to_i
|
222
|
-
# struct.sigcatch = stat[33].to_i
|
223
|
-
# struct.wchan = stat[34].to_i
|
224
|
-
# struct.nswap = stat[35].to_i
|
225
|
-
# struct.cnswap = stat[36].to_i
|
226
|
-
# struct.exit_signal = stat[37].to_i
|
227
|
-
# struct.processor = stat[38].to_i
|
228
|
-
# struct.rt_priority = stat[39].to_i
|
229
|
-
# struct.policy = stat[40].to_i
|
230
|
-
|
231
|
-
# Get /proc/<pid>/status information (name, uid, euid, gid, egid)
|
232
|
-
# IO.foreach("/proc/#{file}/status") do |line|
|
233
|
-
# case line
|
234
|
-
# when /Name:\s*?(\w+)/
|
235
|
-
# struct.name = $1
|
236
|
-
# when /Uid:\s*?(\d+)\s*?(\d+)/
|
237
|
-
# struct.uid = $1.to_i
|
238
|
-
# struct.euid = $2.to_i
|
239
|
-
# when /Gid:\s*?(\d+)\s*?(\d+)/
|
240
|
-
# struct.gid = $1.to_i
|
241
|
-
# struct.egid = $2.to_i
|
242
|
-
# end
|
243
|
-
# end
|
244
|
-
|
245
|
-
# If cmdline is empty use comm instead
|
246
|
-
# struct.cmdline = struct.comm if struct.cmdline.empty?
|
247
|
-
|
248
|
-
# Manually calculate CPU and memory usage
|
249
|
-
# struct.pctcpu = get_pctcpu(struct.utime, struct.starttime)
|
250
|
-
# struct.pctmem = get_pctmem(struct.rss)
|
251
108
|
|
252
109
|
# don't report kthreadd chidren individually - aggregate into the parent.
|
253
110
|
if kthreadd_child?(struct.ppid)
|
@@ -267,7 +124,7 @@ module SysLite
|
|
267
124
|
else
|
268
125
|
array << struct
|
269
126
|
end
|
270
|
-
|
127
|
+
end # Dir.glob
|
271
128
|
|
272
129
|
if pid
|
273
130
|
struct
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Generates ruby-prof output based on the config options used by `scout_realtime` optimized for performance.
|
2
|
+
# ruby prof.rb
|
3
|
+
require "rubygems"
|
4
|
+
require "server_metrics"
|
5
|
+
require "ruby-prof"
|
6
|
+
|
7
|
+
class Harness
|
8
|
+
attr_accessor :num_runs, :latest_run
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@num_runs=0
|
12
|
+
|
13
|
+
@collectors={:disks => ServerMetrics::Disk.new(:ttl => 60), :cpu => ServerMetrics::Cpu.new(:skip_load => true), :memory => ServerMetrics::Memory.new(), :network => ServerMetrics::Network.new(), :processes=>ServerMetrics::Processes.new()}
|
14
|
+
@system_info = ServerMetrics::SystemInfo.to_h
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
collector_res={}
|
19
|
+
@collectors.each_pair do |name, collector|
|
20
|
+
collector_res[name] = collector.run
|
21
|
+
end
|
22
|
+
|
23
|
+
@latest_run = collector_res.merge(:system_info => @system_info)
|
24
|
+
|
25
|
+
@num_runs +=1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
harness = Harness.new
|
30
|
+
|
31
|
+
harness.run
|
32
|
+
sleep 1
|
33
|
+
RUNS = 300
|
34
|
+
result = RubyProf.profile do
|
35
|
+
i=0
|
36
|
+
while i < RUNS do
|
37
|
+
i +=1
|
38
|
+
harness.run
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Print a graph profile to text
|
43
|
+
printer = RubyProf::GraphPrinter.new(result)
|
44
|
+
printer.print(STDOUT, {})
|
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: server_metrics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.2.0.pre
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Andre Lewis
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2014-
|
14
|
+
date: 2014-03-03 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -140,6 +140,7 @@ files:
|
|
140
140
|
- test/fixtures/disk.txt
|
141
141
|
- test/fixtures/memory.txt
|
142
142
|
- test/fixtures/network.txt
|
143
|
+
- test/performance/prof.rb
|
143
144
|
- test/test.rb
|
144
145
|
- test/test_basics.rb
|
145
146
|
- test/test_helper.rb
|
@@ -157,18 +158,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
157
158
|
- - ! '>='
|
158
159
|
- !ruby/object:Gem::Version
|
159
160
|
version: '0'
|
160
|
-
segments:
|
161
|
-
- 0
|
162
|
-
hash: 4512934295163218647
|
163
161
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
162
|
none: false
|
165
163
|
requirements:
|
166
|
-
- - ! '
|
164
|
+
- - ! '>'
|
167
165
|
- !ruby/object:Gem::Version
|
168
|
-
version:
|
169
|
-
segments:
|
170
|
-
- 0
|
171
|
-
hash: 4512934295163218647
|
166
|
+
version: 1.3.1
|
172
167
|
requirements: []
|
173
168
|
rubyforge_project:
|
174
169
|
rubygems_version: 1.8.23
|
@@ -180,7 +175,9 @@ test_files:
|
|
180
175
|
- test/fixtures/disk.txt
|
181
176
|
- test/fixtures/memory.txt
|
182
177
|
- test/fixtures/network.txt
|
178
|
+
- test/performance/prof.rb
|
183
179
|
- test/test.rb
|
184
180
|
- test/test_basics.rb
|
185
181
|
- test/test_helper.rb
|
186
182
|
- test/test_with_fixtures.rb
|
183
|
+
has_rdoc:
|