sys-proctable 0.7.6 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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