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 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: