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