sys-proctable 0.7.6 → 1.2.4

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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/{CHANGES → CHANGES.rdoc} +197 -0
  5. data/LICENSE +177 -0
  6. data/MANIFEST.rdoc +26 -0
  7. data/README.md +158 -0
  8. data/Rakefile +94 -0
  9. data/benchmarks/bench_ips_ps.rb +63 -0
  10. data/benchmarks/bench_ps.rb +21 -0
  11. data/examples/example_ps.rb +20 -0
  12. data/lib/aix/sys/proctable.rb +458 -0
  13. data/lib/darwin/sys/proctable.rb +406 -0
  14. data/lib/freebsd/sys/proctable.rb +363 -0
  15. data/lib/linux/sys/proctable.rb +315 -0
  16. data/lib/linux/sys/proctable/cgroup_entry.rb +50 -0
  17. data/lib/linux/sys/proctable/smaps.rb +118 -0
  18. data/lib/sunos/sys/proctable.rb +456 -0
  19. data/lib/sys-proctable.rb +1 -0
  20. data/lib/sys-top.rb +1 -0
  21. data/lib/sys/proctable.rb +18 -0
  22. data/lib/sys/proctable/version.rb +6 -0
  23. data/lib/sys/top.rb +28 -19
  24. data/lib/windows/sys/proctable.rb +208 -0
  25. data/spec/spec_helper.rb +22 -0
  26. data/spec/sys_proctable_aix_spec.rb +328 -0
  27. data/spec/sys_proctable_all_spec.rb +90 -0
  28. data/spec/sys_proctable_darwin_spec.rb +120 -0
  29. data/spec/sys_proctable_freebsd_spec.rb +210 -0
  30. data/spec/sys_proctable_linux_spec.rb +323 -0
  31. data/spec/sys_proctable_sunos_spec.rb +316 -0
  32. data/spec/sys_proctable_windows_spec.rb +317 -0
  33. data/spec/sys_top_spec.rb +51 -0
  34. data/sys-proctable.gemspec +48 -0
  35. metadata +150 -67
  36. metadata.gz.sig +7 -0
  37. data/MANIFEST +0 -41
  38. data/README +0 -140
  39. data/doc/freebsd.txt +0 -90
  40. data/doc/hpux.txt +0 -77
  41. data/doc/linux.txt +0 -85
  42. data/doc/solaris.txt +0 -99
  43. data/doc/top.txt +0 -53
  44. data/doc/windows.txt +0 -122
  45. data/ext/extconf.rb +0 -98
  46. data/ext/sunos/sunos.c +0 -374
  47. data/ext/sunos/sunos.h +0 -177
  48. data/ext/version.h +0 -2
  49. data/test/tc_all.rb +0 -59
  50. data/test/tc_freebsd.rb +0 -45
  51. data/test/tc_hpux.rb +0 -49
  52. data/test/tc_kvm_bsd.rb +0 -31
  53. data/test/tc_linux.rb +0 -45
  54. data/test/tc_sunos.rb +0 -52
  55. data/test/tc_top.rb +0 -26
  56. data/test/tc_windows.rb +0 -40
  57. data/test/test_memleak.rb +0 -54
@@ -0,0 +1,315 @@
1
+ require 'sys/proctable/version'
2
+ require_relative 'proctable/cgroup_entry'
3
+ require_relative 'proctable/smaps'
4
+
5
+ # The Sys module serves as a namespace only.
6
+ module Sys
7
+
8
+ # The ProcTable class encapsulates process table information.
9
+ class ProcTable
10
+
11
+ # Error typically raised if the ProcTable.ps method fails.
12
+ class Error < StandardError; end
13
+
14
+ # There is no constructor
15
+ private_class_method :new
16
+
17
+ private
18
+
19
+ @mem_total = IO.read("/proc/meminfo")[/MemTotal.*/].split[1].to_i * 1024 rescue nil
20
+ @boot_time = IO.read("/proc/stat")[/btime.*/].split.last.to_i rescue nil
21
+
22
+ @fields = [
23
+ 'cmdline', # Complete command line
24
+ 'cwd', # Current working directory
25
+ 'environ', # Environment
26
+ 'exe', # Actual pathname of the executed command
27
+ 'fd', # File descriptors open by process
28
+ 'root', # Root directory of process
29
+ 'pid', # Process ID
30
+ 'comm', # Filename of executable
31
+ 'state', # Single character state abbreviation
32
+ 'ppid', # Parent process ID
33
+ 'pgrp', # Process group
34
+ 'session', # Session ID
35
+ 'tty_nr', # TTY (terminal) associated with the process
36
+ 'tpgid', # Group ID of the TTY
37
+ 'flags', # Kernel flags
38
+ 'minflt', # Number of minor faults
39
+ 'cminflt', # Number of minor faults of waited-for children
40
+ 'majflt', # Number of major faults
41
+ 'cmajflt', # Number of major faults of waited-for children
42
+ 'utime', # Number of user mode jiffies
43
+ 'stime', # Number of kernel mode jiffies
44
+ 'cutime', # Number of children's user mode jiffies
45
+ 'cstime', # Number of children's kernel mode jiffies
46
+ 'priority', # Nice value plus 15
47
+ 'nice', # Nice value
48
+ 'num_threads', # Number of threads in this process
49
+ 'itrealvalue', # Time in jiffies before next SIGALRM
50
+ 'starttime', # Time in jiffies since system boot
51
+ 'vsize', # Virtual memory size in bytes
52
+ 'rss', # Resident set size
53
+ 'rlim', # Current limit on the rss in bytes
54
+ 'startcode', # Address above which program text can run
55
+ 'endcode', # Address below which program text can run
56
+ 'startstack', # Address of the startstack
57
+ 'kstkesp', # Kernel stack page address
58
+ 'kstkeip', # Kernel instruction pointer
59
+ 'signal', # Bitmap of pending signals
60
+ 'blocked', # Bitmap of blocked signals
61
+ 'sigignore', # Bitmap of ignored signals
62
+ 'sigcatch', # Bitmap of caught signals
63
+ 'wchan', # Channel in which the process is waiting
64
+ 'nswap', # Number of pages swapped
65
+ 'cnswap', # Cumulative nswap for child processes
66
+ 'exit_signal', # Signal to be sent to parent when process dies
67
+ 'processor', # CPU number last executed on
68
+ 'rt_priority', # Real time scheduling priority
69
+ 'policy', # Scheduling policy
70
+ 'name', # Process name
71
+ 'uid', # Real user ID
72
+ 'euid', # Effective user ID
73
+ 'gid', # Real group ID
74
+ 'egid', # Effective group ID
75
+ 'pctcpu', # Percent of CPU usage (custom field)
76
+ 'pctmem', # Percent of Memory usage (custom field)
77
+ 'nlwp', # Number of Light-Weight Processes associated with the process (threads)
78
+ 'cgroup', # Control groups to which the process belongs
79
+ 'smaps' # Process memory size for all mapped files
80
+ ]
81
+
82
+ public
83
+
84
+ ProcTableStruct = Struct.new('ProcTableStruct', *@fields)
85
+
86
+ # In block form, yields a ProcTableStruct for each process entry that you
87
+ # have rights to. This method returns an array of ProcTableStruct's in
88
+ # non-block form.
89
+ #
90
+ # If a +pid+ is provided, then only a single ProcTableStruct is yielded or
91
+ # returned, or nil if no process information is found for that +pid+.
92
+ #
93
+ # Example:
94
+ #
95
+ # # Iterate over all processes
96
+ # ProcTable.ps do |proc_info|
97
+ # p proc_info
98
+ # end
99
+ #
100
+ # # Print process table information for only pid 1001
101
+ # p ProcTable.ps(pid: 1001)
102
+ #
103
+ # # Skip smaps collection and/or cgroup collection
104
+ # p ProcTable.ps(smaps: false, cgroup: false)
105
+ #
106
+ #--
107
+ # It's possible that a process could terminate while gathering
108
+ # information for that process. When that happens, this library
109
+ # will simply skip to the next record. In short, this library will
110
+ # either return all information for a process, or none at all.
111
+ #
112
+ def self.ps(**kwargs)
113
+ pid = kwargs[:pid]
114
+ smaps = kwargs[:smaps]
115
+ cgroup = kwargs[:cgroup]
116
+
117
+ array = block_given? ? nil : []
118
+ struct = nil
119
+
120
+ raise TypeError unless pid.is_a?(Numeric) if pid
121
+
122
+ Dir.foreach("/proc"){ |file|
123
+ next if file =~ /\D/ # Skip non-numeric directories
124
+ next unless file.to_i == pid if pid
125
+
126
+ struct = ProcTableStruct.new
127
+
128
+ # Get /proc/<pid>/cmdline information. Strip out embedded nulls.
129
+ begin
130
+ data = IO.read("/proc/#{file}/cmdline").tr("\000", ' ').strip
131
+ struct.cmdline = data
132
+ rescue
133
+ next # Process terminated, on to the next process
134
+ end
135
+
136
+ # Get /proc/<pid>/cwd information
137
+ struct.cwd = File.readlink("/proc/#{file}/cwd") rescue nil
138
+
139
+ # Get /proc/<pid>/environ information. Environment information
140
+ # is represented as a Hash, with the environment variable as the
141
+ # key and its value as the hash value.
142
+ struct.environ = {}
143
+
144
+ begin
145
+ IO.read("/proc/#{file}/environ").split("\0").each{ |str|
146
+ key, value = str.split('=')
147
+ struct.environ[key] = value
148
+ }
149
+ rescue Errno::EACCES, Errno::ESRCH, Errno::ENOENT
150
+ # Ignore and move on.
151
+ end
152
+
153
+ # Get /proc/<pid>/exe information
154
+ struct.exe = File.readlink("/proc/#{file}/exe") rescue nil
155
+
156
+ # Get /proc/<pid>/fd information. File descriptor information
157
+ # is represented as a Hash, with the fd as the key, and its
158
+ # symlink as the value.
159
+ struct.fd = {}
160
+
161
+ begin
162
+ Dir["/proc/#{file}/fd/*"].each do |fd|
163
+ struct.fd[File.basename(fd)] = File.readlink(fd) rescue nil
164
+ end
165
+ rescue
166
+ # Ignore and move on
167
+ end
168
+
169
+ # Get /proc/<pid>/root information
170
+ struct.root = File.readlink("/proc/#{file}/root") rescue nil
171
+
172
+ # Get /proc/<pid>/stat information
173
+ stat = IO.read("/proc/#{file}/stat") rescue next
174
+
175
+ # Get number of LWP, one directory for each in /proc/<pid>/task/
176
+ # Every process has at least one thread, so if we fail to read the task directory, set nlwp to 1.
177
+ struct.nlwp = Dir.glob("/proc/#{file}/task/*").length rescue struct.nlwp = 1
178
+
179
+ # Get control groups to which the process belongs
180
+ unless cgroup == false
181
+ struct.cgroup = IO.readlines("/proc/#{file}/cgroup").map { |l| CgroupEntry.new(l) } rescue []
182
+ end
183
+
184
+ # Read smaps, returning a parsable string if we don't have permissions.
185
+ # Note: We're blindly rescuing because File.readable?/readable_real?
186
+ # are true for a file in the /proc fileystem but raises a Errno:EACCESS
187
+ # when your try to read it without permissions.
188
+ unless smaps == false
189
+ smaps_contents = IO.read("/proc/#{file}/smaps") rescue ""
190
+ struct.smaps = Smaps.new(file, smaps_contents)
191
+ end
192
+
193
+ # Deal with spaces in comm name. Courtesy of Ara Howard.
194
+ re = %r/\([^\)]+\)/
195
+ comm = stat[re]
196
+ comm.tr!(' ', '-')
197
+ stat[re] = comm
198
+
199
+ stat = stat.split
200
+
201
+ struct.pid = stat[0].to_i
202
+ struct.comm = stat[1].tr('()','') # Remove parens
203
+ struct.state = stat[2]
204
+ struct.ppid = stat[3].to_i
205
+ struct.pgrp = stat[4].to_i
206
+ struct.session = stat[5].to_i
207
+ struct.tty_nr = stat[6].to_i
208
+ struct.tpgid = stat[7].to_i
209
+ struct.flags = stat[8].to_i
210
+ struct.minflt = stat[9].to_i
211
+ struct.cminflt = stat[10].to_i
212
+ struct.majflt = stat[11].to_i
213
+ struct.cmajflt = stat[12].to_i
214
+ struct.utime = stat[13].to_i
215
+ struct.stime = stat[14].to_i
216
+ struct.cutime = stat[15].to_i
217
+ struct.cstime = stat[16].to_i
218
+ struct.priority = stat[17].to_i
219
+ struct.nice = stat[18].to_i
220
+ struct.num_threads = stat[19].to_i
221
+ struct.itrealvalue = stat[20].to_i
222
+ struct.starttime = stat[21].to_i
223
+ struct.vsize = stat[22].to_i
224
+ struct.rss = stat[23].to_i
225
+ struct.rlim = stat[24].to_i
226
+ struct.startcode = stat[25].to_i
227
+ struct.endcode = stat[26].to_i
228
+ struct.startstack = stat[27].to_i
229
+ struct.kstkesp = stat[28].to_i
230
+ struct.kstkeip = stat[29].to_i
231
+ struct.signal = stat[30].to_i
232
+ struct.blocked = stat[31].to_i
233
+ struct.sigignore = stat[32].to_i
234
+ struct.sigcatch = stat[33].to_i
235
+ struct.wchan = stat[34].to_i
236
+ struct.nswap = stat[35].to_i
237
+ struct.cnswap = stat[36].to_i
238
+ struct.exit_signal = stat[37].to_i
239
+ struct.processor = stat[38].to_i
240
+ struct.rt_priority = stat[39].to_i
241
+ struct.policy = stat[40].to_i
242
+
243
+ # Get /proc/<pid>/status information (name, uid, euid, gid, egid)
244
+ begin
245
+ IO.foreach("/proc/#{file}/status") do |line|
246
+ case line
247
+ when /Name:\s*?(\w+)/
248
+ struct.name = $1
249
+ when /Uid:\s*?(\d+)\s*?(\d+)/
250
+ struct.uid = $1.to_i
251
+ struct.euid = $2.to_i
252
+ when /Gid:\s*?(\d+)\s*?(\d+)/
253
+ struct.gid = $1.to_i
254
+ struct.egid = $2.to_i
255
+ end
256
+ end
257
+ rescue Errno::ESRCH, Errno::ENOENT
258
+ next
259
+ end
260
+
261
+ # If cmdline is empty use comm instead
262
+ struct.cmdline = struct.comm if struct.cmdline.empty?
263
+
264
+ # Manually calculate CPU and memory usage
265
+ struct.pctcpu = get_pctcpu(struct.utime, struct.starttime)
266
+ struct.pctmem = get_pctmem(struct.rss)
267
+
268
+ struct.freeze # This is read-only data
269
+
270
+ if block_given?
271
+ yield struct
272
+ else
273
+ array << struct
274
+ end
275
+ }
276
+
277
+ pid ? struct : array
278
+ end
279
+
280
+ # Returns an array of fields that each ProcTableStruct will contain. This
281
+ # may be useful if you want to know in advance what fields are available
282
+ # without having to perform at least one read of the /proc table.
283
+ #
284
+ # Example:
285
+ #
286
+ # Sys::ProcTable.fields.each{ |field|
287
+ # puts "Field: #{field}"
288
+ # }
289
+ #
290
+ def self.fields
291
+ @fields
292
+ end
293
+
294
+ private
295
+
296
+ # Calculate the percentage of memory usage for the given process.
297
+ #
298
+ def self.get_pctmem(rss)
299
+ return nil unless @mem_total
300
+ page_size = 4096
301
+ rss_total = rss * page_size
302
+ sprintf("%3.2f", (rss_total.to_f / @mem_total) * 100).to_f
303
+ end
304
+
305
+ # Calculate the percentage of CPU usage for the given process.
306
+ #
307
+ def self.get_pctcpu(utime, start_time)
308
+ return nil unless @boot_time
309
+ hertz = 100.0
310
+ utime = (utime * 10000).to_f
311
+ stime = (start_time.to_f / hertz) + @boot_time
312
+ sprintf("%3.2f", (utime / 10000.0) / (Time.now.to_i - stime)).to_f
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,50 @@
1
+ module Sys
2
+ class ProcTable
3
+ # This represents a cgroup entry
4
+ #
5
+ # Have a look at `man 5 proc` on a linux distribution, to get some more
6
+ # information about the lines and their fields in `/proc/[pid]/cgroup`.
7
+ #
8
+ # Example:
9
+ #
10
+ # entry = CgroupEntry.new '7:devices:/init.scope'
11
+ # entry.hierarchy_id # => 7
12
+ # entry.subsystems # => ['devices']
13
+ # entry.control_group # => '/init.scope'
14
+ #
15
+ class CgroupEntry
16
+ # Create a new cgroup entry object
17
+ #
18
+ # This expects a string of '7:devices:/init.scope' - see `man 5 proc` for a
19
+ # reference.
20
+ def initialize(string)
21
+ @string = string.chomp
22
+ @fields = @string.split(/:/)
23
+ rescue
24
+ @fields = []
25
+ end
26
+
27
+ # This returns the hierarchy id of the cgroup entry
28
+ def hierarchy_id
29
+ @fields[0].to_i
30
+ end
31
+
32
+ # Return sets of subsystems bound to the hierarchy
33
+ def subsystems
34
+ @fields[1].split(/,/)
35
+ rescue
36
+ []
37
+ end
38
+
39
+ # control group in the hierarchy to which the process belongs
40
+ def control_group
41
+ @fields[2]
42
+ end
43
+
44
+ # Return the line itself
45
+ def to_s
46
+ @string
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,118 @@
1
+ module Sys
2
+ class ProcTable
3
+ # Smaps represents a process' memory size for all mapped files
4
+ #
5
+ # A single mapped file memory entry looks like this:
6
+ #
7
+ # 00400000-004d4000 r-xp 00000000 fd:00 785 /bin/bash
8
+ # Size: 848 kB
9
+ # Rss: 572 kB
10
+ # Pss: 572 kB
11
+ # Shared_Clean: 0 kB
12
+ # Shared_Dirty: 0 kB
13
+ # Private_Clean: 572 kB
14
+ # Private_Dirty: 0 kB
15
+ # Referenced: 572 kB
16
+ # Anonymous: 0 kB
17
+ # AnonHugePages: 0 kB
18
+ # Swap: 0 kB
19
+ # KernelPageSize: 4 kB
20
+ # MMUPageSize: 4 kB
21
+ #
22
+ # Have a look at `man 5 proc` on a linux distribution, to get some more
23
+ # information about the lines and fields in `/proc/[pid]/smaps`.
24
+ #
25
+ # Example:
26
+ #
27
+ # smaps = Smaps.new(123, IO.read("/proc/1234/smaps")
28
+ # => #<Sys::ProcTable::Smaps:0x007f8ac5930768 @pid=123, @pss=107000, @rss=368000, @uss=96000, @swap=192000, @vss=136752000>
29
+ # smaps.pss # => 109568
30
+ # smaps.rss # => 376832
31
+ # smaps.uss # => 98304
32
+ # smaps.swap # => 196608
33
+ # smaps.vss # => 140034048
34
+ #
35
+ class Smaps
36
+
37
+ # Process ID for this smaps
38
+ attr_reader :pid
39
+
40
+ # Proportional set size
41
+ #
42
+ # PSS is the size of private pages added to each shared mapping's size
43
+ # divided by the number of processes that share it. It is meant to
44
+ # provide a better representation of the amount of memory actually used
45
+ # by a process.
46
+ #
47
+ # If a process has 4k of private pages, 4k of shared pages shared with one
48
+ # other process, and 3k of pages shared with two other processes, the PSS
49
+ # is:
50
+ #
51
+ # 4k + (4k / 2) + (3k / 3) = 7k
52
+ #
53
+ attr_reader :pss
54
+ alias_method :proportional_set_size, :pss
55
+
56
+ # Resident set size
57
+ #
58
+ # RSS is the total size of all pages, shared or not, mapped to a process.
59
+ attr_reader :rss
60
+ alias_method :resident_set_size, :rss
61
+
62
+ # Unique set size
63
+ #
64
+ # USS is the total size of all private pages mapped to a process.
65
+ attr_reader :uss
66
+ alias_method :unique_set_size, :uss
67
+
68
+ # Swap
69
+ #
70
+ # Swap is the total size of all swapped pages mapped to a process.
71
+ attr_reader :swap
72
+
73
+ # Virtual set size
74
+ #
75
+ # VSS is the total accessible address space in a process. Since files are
76
+ # lazily loaded, this value represents the total size of all mapped files
77
+ # if they were all loaded.
78
+ attr_reader :vss
79
+ alias_method :virtual_set_size, :vss
80
+
81
+ # Create a new smaps object
82
+ #
83
+ #
84
+ # This expects a process id and a string containing the contents of
85
+ # /proc/PID/smaps - see `man 5 proc` for a reference.
86
+ #
87
+ # The smaps contents are parsed and memory sizes are calculated in bytes.
88
+ def initialize(pid, smaps_contents)
89
+ @pid = pid
90
+ @pss = 0
91
+ @rss = 0
92
+ @uss = 0
93
+ @swap = 0
94
+ @vss = 0
95
+ smaps_contents.each_line { |line| parse_smaps_line(line) }
96
+ end
97
+
98
+ alias_method :to_s, :inspect
99
+
100
+ private
101
+
102
+ def parse_smaps_line(line)
103
+ case line
104
+ when /^Pss:\s+?(\d+)/
105
+ @pss += Regexp.last_match[1].to_i * 1000
106
+ when /^Rss:\s+?(\d+)/
107
+ @rss += Regexp.last_match[1].to_i * 1000
108
+ when /^Size:\s+?(\d+)/
109
+ @vss += Regexp.last_match[1].to_i * 1000
110
+ when /^Swap:\s+?(\d+)/
111
+ @swap += Regexp.last_match[1].to_i * 1000
112
+ when /^Private_(Clean|Dirty):\s+?(\d+)/
113
+ @uss += Regexp.last_match[2].to_i * 1000
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end