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 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
- ENV['LANG'] = 'C' # forcing english for parsing
26
- uptime_output = `uptime`
27
- matches = uptime_output.match(/load averages?: ([\d.]+),? ([\d.]+),? ([\d.]+)\Z/)
28
-
29
- number_of_processors = ServerMetrics::SystemInfo.num_processors
30
-
31
- report(:last_minute => matches[1].to_f / number_of_processors,
32
- :last_five_minutes => matches[2].to_f / number_of_processors,
33
- :last_fifteen_minutes => matches[3].to_f / number_of_processors)
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 = `cat /proc/stat 2>&1`
42
-
43
- if $? and !$?.success?
44
- raise ProcStatError, output
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.split(/\n/).collect { |line| line.split }
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
- @df_output = `df -Pkh`.split("\n")
11
- @devices = `mount`.split("\n").grep(/^\/dev/).map{|l|l.split.first} # any device that starts with /dev
12
- @disk_stats = `cat /proc/diskstats`.split("\n")
10
+
11
+ @disk_stats = File.read("/proc/diskstats").lines.to_a
13
12
 
14
- @devices.each do |device|
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=@df_output.first
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
- @df_output[1..@df_output.size-1].each do |line|
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
- `cat /proc/meminfo`.each_line do |line|
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 = %x(cat /proc/net/dev).split("\n")[2..-1]
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
- # delegate everything else to ProcTable::Struct
199
- def method_missing(sym, *args, &block)
200
- @pts.send sym, *args, &block
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.foreach("/proc"){ |file|
128
- next if file =~ /\D/ # Skip non-numeric directories
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
@@ -20,7 +20,7 @@ module ServerMetrics
20
20
  if os =~ /(darwin|freebsd)/
21
21
  `sysctl -n hw.ncpu`.to_i
22
22
  elsif os =~ /linux/
23
- lines = `cat /proc/cpuinfo`.split("\n")
23
+ lines = File.read("/proc/cpuinfo").lines.to_a
24
24
  lines.grep(/^processor\s*:/i).size
25
25
  end
26
26
  rescue
@@ -1,3 +1,3 @@
1
1
  module ServerMetrics
2
- VERSION = "1.1.1"
2
+ VERSION = "1.2.0.pre"
3
3
  end
@@ -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.1.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-01-21 00:00:00.000000000 Z
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: '0'
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: