server_metrics 1.1.1 → 1.2.0.pre
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/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:
|