server_metrics 0.0.9.0 → 0.1.0

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