server_metrics 0.0.9.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.1.0
2
+
3
+ * Optimization for collecting processes on Linux
4
+
1
5
  ## 0.0.9.0
2
6
 
3
7
  * Reporting process memory in MB, not in page size.
@@ -1,4 +1,5 @@
1
1
  require 'sys/proctable'
2
+ require 'server_metrics/lib/proctable_lite' # used on linux
2
3
  require 'server_metrics/system_info'
3
4
 
4
5
  # Collects information on processes. Groups processes running under the same command, and sums up their CPU & memory usage.
@@ -20,6 +21,7 @@ class ServerMetrics::Processes
20
21
  @last_run
21
22
  @last_jiffies
22
23
  @last_process_list
24
+ @proc_table_klass = ServerMetrics::SystemInfo.os =~ /linux/ ? SysLite::ProcTable : Sys::ProcTable # this is used in calculate_processes. On Linux, use our optimized version
23
25
  end
24
26
 
25
27
 
@@ -63,7 +65,7 @@ class ServerMetrics::Processes
63
65
  # the collector has to be run twice to get CPU data.
64
66
  def calculate_processes
65
67
  ## 1. get a list of all processes
66
- processes = Sys::ProcTable.ps.map{|p| ServerMetrics::Processes::Process.new(p) } # our Process object adds a method some behavior
68
+ processes = @proc_table_klass.ps.map{|p| ServerMetrics::Processes::Process.new(p) } # our Process object adds a method some behavior
67
69
 
68
70
  ## 2. loop through each process and calculate the CPU time.
69
71
  # The CPU values returned by ProcTable are cumulative for the life of the process, which is not what we want.
@@ -0,0 +1,284 @@
1
+ # This is a special-purpose version Sys::Proctable, optimized for collecting fewer metrics (but running faster) on Linux only
2
+ # In process.rb, we conditionally use this class when the host OS is Linux.
3
+
4
+ # The Sys module serves as a namespace only.
5
+ module SysLite
6
+
7
+ # The ProcTable class encapsulates process table information.
8
+ class ProcTable
9
+
10
+ # Error typically raised if the ProcTable.ps method fails.
11
+ class Error < StandardError; end
12
+
13
+ # There is no constructor
14
+ private_class_method :new
15
+
16
+ # The version of the sys-proctable library
17
+ VERSION = '0.9.3'
18
+
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
+
24
+ @fields = [
25
+ 'cmdline', # Complete command line
26
+ 'cwd', # Current working directory
27
+ 'environ', # Environment
28
+ 'exe', # Actual pathname of the executed command
29
+ 'fd', # File descriptors open by process
30
+ 'root', # Root directory of process
31
+ 'pid', # Process ID
32
+ 'comm', # Filename of executable
33
+ 'state', # Single character state abbreviation
34
+ 'ppid', # Parent process ID
35
+ 'pgrp', # Process group
36
+ 'session', # Session ID
37
+ 'tty_nr', # TTY (terminal) associated with the process
38
+ 'tpgid', # Group ID of the TTY
39
+ 'flags', # Kernel flags
40
+ 'minflt', # Number of minor faults
41
+ 'cminflt', # Number of minor faults of waited-for children
42
+ 'majflt', # Number of major faults
43
+ 'cmajflt', # Number of major faults of waited-for children
44
+ 'utime', # Number of user mode jiffies
45
+ 'stime', # Number of kernel mode jiffies
46
+ 'cutime', # Number of children's user mode jiffies
47
+ 'cstime', # Number of children's kernel mode jiffies
48
+ 'priority', # Nice value plus 15
49
+ 'nice', # Nice value
50
+ 'itrealvalue', # Time in jiffies before next SIGALRM
51
+ 'starttime', # Time in jiffies since system boot
52
+ 'vsize', # Virtual memory size in bytes
53
+ 'rss', # Resident set size
54
+ 'rlim', # Current limit on the rss in bytes
55
+ 'startcode', # Address above which program text can run
56
+ 'endcode', # Address below which program text can run
57
+ 'startstack', # Address of the startstack
58
+ 'kstkesp', # Kernel stack page address
59
+ 'kstkeip', # Kernel instruction pointer
60
+ 'signal', # Bitmap of pending signals
61
+ 'blocked', # Bitmap of blocked signals
62
+ 'sigignore', # Bitmap of ignored signals
63
+ 'sigcatch', # Bitmap of caught signals
64
+ 'wchan', # Channel in which the process is waiting
65
+ 'nswap', # Number of pages swapped
66
+ 'cnswap', # Cumulative nswap for child processes
67
+ 'exit_signal', # Signal to be sent to parent when process dies
68
+ 'processor', # CPU number last executed on
69
+ 'rt_priority', # Real time scheduling priority
70
+ 'policy', # Scheduling policy
71
+ 'name', # Process name
72
+ 'uid', # Real user ID
73
+ 'euid', # Effective user ID
74
+ 'gid', # Real group ID
75
+ 'egid', # Effective group ID
76
+ 'pctcpu', # Percent of CPU usage (custom field)
77
+ 'pctmem' # Percent of Memory usage (custom field)
78
+ ]
79
+
80
+ public
81
+
82
+ ProcTableStruct = Struct.new('ProcTableStructLite', *@fields)
83
+
84
+ # In block form, yields a ProcTableStruct for each process entry that you
85
+ # have rights to. This method returns an array of ProcTableStruct's in
86
+ # non-block form.
87
+ #
88
+ # If a +pid+ is provided, then only a single ProcTableStruct is yielded or
89
+ # returned, or nil if no process information is found for that +pid+.
90
+ #
91
+ # Example:
92
+ #
93
+ # # Iterate over all processes
94
+ # ProcTable.ps do |proc_info|
95
+ # p proc_info
96
+ # end
97
+ #
98
+ # # Print process table information for only pid 1001
99
+ # p ProcTable.ps(1001)
100
+ #
101
+ #--
102
+ # It's possible that a process could terminate while gathering
103
+ # information for that process. When that happens, this library
104
+ # will simply skip to the next record. In short, this library will
105
+ # either return all information for a process, or none at all.
106
+ #
107
+ def self.ps(pid=nil)
108
+ array = block_given? ? nil : []
109
+ struct = nil
110
+
111
+ raise TypeError unless pid.is_a?(Fixnum) if pid
112
+
113
+ Dir.foreach("/proc"){ |file|
114
+ next if file =~ /\D/ # Skip non-numeric directories
115
+ next unless file.to_i == pid if pid
116
+
117
+ struct = ProcTableStruct.new
118
+
119
+ # Get /proc/<pid>/cmdline information. Strip out embedded nulls.
120
+ # begin
121
+ # data = IO.read("/proc/#{file}/cmdline").tr("\000", ' ').strip
122
+ # struct.cmdline = data
123
+ # rescue
124
+ # next # Process terminated, on to the next process
125
+ # end
126
+
127
+ # Get /proc/<pid>/cwd information
128
+ # struct.cwd = File.readlink("/proc/#{file}/cwd") rescue nil
129
+
130
+ # Get /proc/<pid>/environ information. Environment information
131
+ # is represented as a Hash, with the environment variable as the
132
+ # key and its value as the hash value.
133
+ struct.environ = {}
134
+
135
+ # begin
136
+ # IO.read("/proc/#{file}/environ").split("\0").each{ |str|
137
+ # key, value = str.split('=')
138
+ # struct.environ[key] = value
139
+ # }
140
+ # rescue Errno::EACCES, Errno::ESRCH, Errno::ENOENT
141
+ # # Ignore and move on.
142
+ # end
143
+
144
+ # Get /proc/<pid>/exe information
145
+ # struct.exe = File.readlink("/proc/#{file}/exe") rescue nil
146
+
147
+ # Get /proc/<pid>/fd information. File descriptor information
148
+ # is represented as a Hash, with the fd as the key, and its
149
+ # symlink as the value.
150
+ struct.fd = {}
151
+
152
+ # begin
153
+ # Dir["/proc/#{file}/fd/*"].each do |fd|
154
+ # struct.fd[File.basename(fd)] = File.readlink(fd) rescue nil
155
+ # end
156
+ # rescue
157
+ # # Ignore and move on
158
+ # end
159
+
160
+ # Get /proc/<pid>/root information
161
+ # struct.root = File.readlink("/proc/#{file}/root") rescue nil
162
+
163
+ # Get /proc/<pid>/stat information
164
+ stat = IO.read("/proc/#{file}/stat") rescue next
165
+
166
+ # Deal with spaces in comm name. Courtesy of Ara Howard.
167
+ re = %r/\([^\)]+\)/
168
+ comm = stat[re]
169
+ comm.tr!(' ', '-')
170
+ stat[re] = comm
171
+
172
+ stat = stat.split
173
+
174
+ struct.pid = stat[0].to_i
175
+ struct.comm = stat[1].tr('()','') # Remove parens
176
+ # struct.state = stat[2]
177
+ # struct.ppid = stat[3].to_i
178
+ # struct.pgrp = stat[4].to_i
179
+ # struct.session = stat[5].to_i
180
+ # struct.tty_nr = stat[6].to_i
181
+ # struct.tpgid = stat[7].to_i
182
+ # struct.flags = stat[8].to_i
183
+ # struct.minflt = stat[9].to_i
184
+ # struct.cminflt = stat[10].to_i
185
+ # struct.majflt = stat[11].to_i
186
+ # struct.cmajflt = stat[12].to_i
187
+ struct.utime = stat[13].to_i
188
+ struct.stime = stat[14].to_i
189
+ # struct.cutime = stat[15].to_i
190
+ # struct.cstime = stat[16].to_i
191
+ # struct.priority = stat[17].to_i
192
+ # struct.nice = stat[18].to_i
193
+ # Skip 19
194
+ # struct.itrealvalue = stat[20].to_i
195
+ # struct.starttime = stat[21].to_i
196
+ # struct.vsize = stat[22].to_i
197
+ struct.rss = stat[23].to_i
198
+ # struct.rlim = stat[24].to_i
199
+ # struct.startcode = stat[25].to_i
200
+ # struct.endcode = stat[26].to_i
201
+ # struct.startstack = stat[27].to_i
202
+ # struct.kstkesp = stat[28].to_i
203
+ # struct.kstkeip = stat[29].to_i
204
+ # struct.signal = stat[30].to_i
205
+ # struct.blocked = stat[31].to_i
206
+ # struct.sigignore = stat[32].to_i
207
+ # struct.sigcatch = stat[33].to_i
208
+ # struct.wchan = stat[34].to_i
209
+ # struct.nswap = stat[35].to_i
210
+ # struct.cnswap = stat[36].to_i
211
+ # struct.exit_signal = stat[37].to_i
212
+ # struct.processor = stat[38].to_i
213
+ # struct.rt_priority = stat[39].to_i
214
+ # struct.policy = stat[40].to_i
215
+
216
+ # Get /proc/<pid>/status information (name, uid, euid, gid, egid)
217
+ # IO.foreach("/proc/#{file}/status") do |line|
218
+ # case line
219
+ # when /Name:\s*?(\w+)/
220
+ # struct.name = $1
221
+ # when /Uid:\s*?(\d+)\s*?(\d+)/
222
+ # struct.uid = $1.to_i
223
+ # struct.euid = $2.to_i
224
+ # when /Gid:\s*?(\d+)\s*?(\d+)/
225
+ # struct.gid = $1.to_i
226
+ # struct.egid = $2.to_i
227
+ # end
228
+ # end
229
+
230
+ # If cmdline is empty use comm instead
231
+ # struct.cmdline = struct.comm if struct.cmdline.empty?
232
+
233
+ # Manually calculate CPU and memory usage
234
+ # struct.pctcpu = get_pctcpu(struct.utime, struct.starttime)
235
+ # struct.pctmem = get_pctmem(struct.rss)
236
+
237
+ struct.freeze # This is read-only data
238
+
239
+ if block_given?
240
+ yield struct
241
+ else
242
+ array << struct
243
+ end
244
+ }
245
+
246
+ pid ? struct : array
247
+ end
248
+
249
+ # Returns an array of fields that each ProcTableStruct will contain. This
250
+ # may be useful if you want to know in advance what fields are available
251
+ # without having to perform at least one read of the /proc table.
252
+ #
253
+ # Example:
254
+ #
255
+ # Sys::ProcTable.fields.each{ |field|
256
+ # puts "Field: #{field}"
257
+ # }
258
+ #
259
+ def self.fields
260
+ @fields
261
+ end
262
+
263
+ private
264
+
265
+ # Calculate the percentage of memory usage for the given process.
266
+ #
267
+ def self.get_pctmem(rss)
268
+ return nil unless @mem_total
269
+ page_size = 4096
270
+ rss_total = rss * page_size
271
+ sprintf("%3.2f", (rss_total.to_f / @mem_total) * 100).to_f
272
+ end
273
+
274
+ # Calculate the percentage of CPU usage for the given process.
275
+ #
276
+ def self.get_pctcpu(utime, start_time)
277
+ return nil unless @boot_time
278
+ hertz = 100.0
279
+ utime = (utime * 10000).to_f
280
+ stime = (start_time.to_f / hertz) + @boot_time
281
+ sprintf("%3.2f", (utime / 10000.0) / (Time.now.to_i - stime)).to_f
282
+ end
283
+ end
284
+ end
@@ -1,3 +1,3 @@
1
1
  module ServerMetrics
2
- VERSION = "0.0.9.0"
2
+ VERSION = "0.1.0"
3
3
  end
data/test/test_basics.rb CHANGED
@@ -84,7 +84,7 @@ class TestBasics < Test::Unit::TestCase
84
84
  p.instance_variable_set '@last_run', last_run
85
85
  p.instance_variable_set '@last_process_list', "bogus value"
86
86
 
87
- assert_equal({:last_run=>last_run,:last_process_list=>"bogus value"}, p.to_hash)
87
+ assert_equal({:last_run=>last_run,:last_jiffies=>nil,:last_process_list=>"bogus value"}, p.to_hash)
88
88
  end
89
89
 
90
90
  def test_processes_from_hash
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.9.0
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2014-01-09 00:00:00.000000000 Z
14
+ date: 2014-01-11 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: sys-proctable
@@ -147,6 +147,7 @@ files:
147
147
  - lib/server_metrics/collectors/memory.rb
148
148
  - lib/server_metrics/collectors/network.rb
149
149
  - lib/server_metrics/collectors/processes.rb
150
+ - lib/server_metrics/lib/proctable_lite.rb
150
151
  - lib/server_metrics/multi_collector.rb
151
152
  - lib/server_metrics/system_info.rb
152
153
  - lib/server_metrics/version.rb
@@ -174,7 +175,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
174
175
  version: '0'
175
176
  segments:
176
177
  - 0
177
- hash: -2321548191254875842
178
+ hash: 1552107528440235904
178
179
  required_rubygems_version: !ruby/object:Gem::Requirement
179
180
  none: false
180
181
  requirements:
@@ -183,7 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
183
184
  version: '0'
184
185
  segments:
185
186
  - 0
186
- hash: -2321548191254875842
187
+ hash: 1552107528440235904
187
188
  requirements: []
188
189
  rubyforge_project:
189
190
  rubygems_version: 1.8.23