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,363 @@
1
+ require 'sys/proctable/version'
2
+ require 'ffi'
3
+
4
+ module Sys
5
+ class ProcTable
6
+ extend FFI::Library
7
+
8
+ # Error typically raised if the ProcTable.ps method fails.
9
+ class Error < StandardError; end
10
+
11
+ # There is no constructor
12
+ private_class_method :new
13
+
14
+ private
15
+
16
+ PROC_PIDTASKALLINFO = 2
17
+ PROC_PIDTHREADINFO = 5
18
+ PROC_PIDLISTTHREADS = 6
19
+
20
+ CTL_KERN = 1
21
+ KERN_PROCARGS = 38
22
+ KERN_PROCARGS2 = 49
23
+ MAXCOMLEN = 16
24
+ MAXPATHLEN = 256
25
+
26
+ MAXTHREADNAMESIZE = 64
27
+ PROC_PIDPATHINFO_MAXSIZE = MAXPATHLEN * 4
28
+
29
+ class ProcBsdInfo < FFI::Struct
30
+ layout(
31
+ :pbi_flags, :uint32_t,
32
+ :pbi_status, :uint32_t,
33
+ :pbi_xstatus, :uint32_t,
34
+ :pbi_pid, :uint32_t,
35
+ :pbi_ppid, :uint32_t,
36
+ :pbi_uid, :uid_t,
37
+ :pbi_gid, :uid_t,
38
+ :pbi_ruid, :uid_t,
39
+ :pbi_rgid, :gid_t,
40
+ :pbi_svuid, :uid_t,
41
+ :pbi_svgid, :gid_t,
42
+ :rfu1, :uint32_t,
43
+ :pbi_comm, [:char, MAXCOMLEN],
44
+ :pbi_name, [:char, MAXCOMLEN * 2],
45
+ :pbi_nfiles, :uint32_t,
46
+ :pbi_pgid, :uint32_t,
47
+ :pbi_pjobc, :uint32_t,
48
+ :e_tdev, :uint32_t,
49
+ :e_tpgid, :uint32_t,
50
+ :pbi_nice, :int32_t,
51
+ :pbi_start_tvsec, :uint64_t,
52
+ :pbi_start_tvusec, :uint64_t
53
+ )
54
+ end
55
+
56
+ class ProcTaskInfo < FFI::Struct
57
+ layout(
58
+ :pti_virtual_size, :uint64_t,
59
+ :pti_resident_size, :uint64_t,
60
+ :pti_total_user, :uint64_t,
61
+ :pti_total_system, :uint64_t,
62
+ :pti_threads_user, :uint64_t,
63
+ :pti_threads_system, :uint64_t,
64
+ :pti_policy, :int32_t,
65
+ :pti_faults, :int32_t,
66
+ :pti_pageins, :int32_t,
67
+ :pti_cow_faults, :int32_t,
68
+ :pti_messages_sent, :int32_t,
69
+ :pti_messages_received, :int32_t,
70
+ :pti_syscalls_mach, :int32_t,
71
+ :pti_syscalls_unix, :int32_t,
72
+ :pti_csw, :int32_t,
73
+ :pti_threadnum, :int32_t,
74
+ :pti_numrunning, :int32_t,
75
+ :pti_priority, :int32_t
76
+ )
77
+ end
78
+
79
+ class ProcThreadInfo < FFI::Struct
80
+ layout(
81
+ :pth_user_time, :uint64_t,
82
+ :pth_system_time, :uint64_t,
83
+ :pth_cpu_usage, :int32_t,
84
+ :pth_policy, :int32_t,
85
+ :pth_run_state, :int32_t,
86
+ :pth_flags, :int32_t,
87
+ :pth_sleep_time, :int32_t,
88
+ :pth_curpri, :int32_t,
89
+ :pth_priority, :int32_t,
90
+ :pth_maxpriority, :int32_t,
91
+ :pth_name, [:char, MAXTHREADNAMESIZE]
92
+ )
93
+ end
94
+
95
+ # Map the fields from the FFI::Structs to the Sys::ProcTable struct on
96
+ # class load to reduce the amount of objects needing to be generated for
97
+ # each invocation of Sys::ProcTable.ps
98
+ all_members = ProcBsdInfo.members + ProcTaskInfo.members + ProcThreadInfo.members
99
+ PROC_STRUCT_FIELD_MAP = all_members.map { |member|
100
+ temp = member.to_s.split('_')
101
+ sproperty = temp.size > 1 ? temp[1..-1].join('_') : temp.first
102
+ [member, sproperty.to_sym]
103
+ }.to_h
104
+
105
+ class ProcTaskAllInfo < FFI::Struct
106
+ layout(:pbsd, ProcBsdInfo, :ptinfo, ProcTaskInfo)
107
+ end
108
+
109
+ ffi_lib 'proc'
110
+
111
+ attach_function :proc_listallpids, [:pointer, :int], :int
112
+ attach_function :proc_pidinfo, [:int, :int, :uint64_t, :pointer, :int], :int
113
+
114
+ ffi_lib FFI::Library::LIBC
115
+
116
+ attach_function :sysctl, [:pointer, :uint, :pointer, :pointer, :pointer, :size_t], :int
117
+
118
+ # These mostly mimic the struct members, but we've added a few custom ones as well.
119
+ @fields = %w[
120
+ flags status xstatus pid ppid uid gid ruid rgid svuid svgid rfu1 comm
121
+ name nfiles pgid pjobc tdev tpgid nice start_tvsec start_tvusec
122
+ virtual_size resident_size total_user total_system threads_user
123
+ threads_system policy faults pageins cow_faults messages_sent
124
+ messages_received syscalls_mach syscalls_unix csw threadnum numrunning
125
+ priority cmdline exe environ threadinfo
126
+ ]
127
+
128
+ # Add a couple aliases to make it similar to Linux
129
+ ProcTableStruct = Struct.new("ProcTableStruct", *@fields) do
130
+ alias vsize virtual_size
131
+ alias rss resident_size
132
+ end
133
+
134
+ ThreadInfoStruct = Struct.new("ThreadInfo", :user_time, :system_time,
135
+ :cpu_usage, :policy, :run_state, :flags, :sleep_time, :curpri,
136
+ :priority, :maxpriority, :name
137
+ )
138
+
139
+ public
140
+
141
+ # Returns an array of fields that each ProcTableStruct will contain. This
142
+ # may be useful if you want to know in advance what fields are available
143
+ # without having to perform at least one read of the process table.
144
+ #
145
+ # Example:
146
+ #
147
+ # Sys::ProcTable.fields.each{ |field|
148
+ # puts "Field: #{field}"
149
+ # }
150
+ #
151
+ def self.fields
152
+ @fields
153
+ end
154
+
155
+ # In block form, yields a ProcTableStruct for each process entry that you
156
+ # have rights to. This method returns an array of ProcTableStruct's in
157
+ # non-block form.
158
+ #
159
+ # If a +pid+ is provided, then only a single ProcTableStruct is yielded or
160
+ # returned, or nil if no process information is found for that +pid+.
161
+ #
162
+ # Example:
163
+ #
164
+ # # Iterate over all processes
165
+ # ProcTable.ps do |proc_info|
166
+ # p proc_info
167
+ # end
168
+ #
169
+ # # Print process table information for only pid 1001
170
+ # p ProcTable.ps(pid: 1001)
171
+ #
172
+ # # Same as above, but do not include thread information
173
+ # p ProcTable.ps(pid: 1001, thread_info: false)
174
+ #
175
+ def self.ps(**kwargs)
176
+ pid = kwargs[:pid]
177
+ raise TypeError unless pid.is_a?(Numeric) if pid
178
+
179
+ num = proc_listallpids(nil, 0)
180
+ ptr = FFI::MemoryPointer.new(:pid_t, num)
181
+ num = proc_listallpids(ptr, ptr.size)
182
+
183
+ raise SystemCallError.new('proc_listallpids', FFI.errno) if num == 0
184
+
185
+ pids = ptr.get_array_of_int32(0, num).sort
186
+ array = block_given? ? nil : []
187
+
188
+ pids.each do |lpid|
189
+ next unless pid == lpid if pid
190
+ info = ProcTaskAllInfo.new
191
+
192
+ nb = proc_pidinfo(lpid, PROC_PIDTASKALLINFO, 0, info, info.size)
193
+
194
+ if nb <= 0
195
+ if [Errno::EPERM::Errno, Errno::ESRCH::Errno].include?(FFI.errno)
196
+ next # Either we don't have permission, or the pid no longer exists
197
+ else
198
+ raise SystemCallError.new('proc_pidinfo', FFI.errno)
199
+ end
200
+ end
201
+
202
+ # Avoid potentially invalid data
203
+ next if nb != info.size
204
+
205
+ struct = ProcTableStruct.new
206
+
207
+ # Pass by reference
208
+ get_cmd_args_and_env(lpid, struct)
209
+ get_thread_info(lpid, struct, info[:ptinfo]) unless kwargs[:thread_info] == false
210
+
211
+ # Chop the leading xx_ from the FFI struct members for our ruby struct.
212
+ info.members.each do |nested|
213
+ info[nested].members.each do |member|
214
+ if info[nested][member].is_a?(FFI::StructLayout::CharArray)
215
+ struct[PROC_STRUCT_FIELD_MAP[member]] = info[nested][member].to_s
216
+ else
217
+ struct[PROC_STRUCT_FIELD_MAP[member]] = info[nested][member]
218
+ end
219
+ end
220
+ end
221
+
222
+ struct.freeze
223
+
224
+ if block_given?
225
+ yield struct
226
+ else
227
+ array << struct
228
+ end
229
+ end
230
+
231
+ return nil if array.nil?
232
+ pid ? array.first : array
233
+ end
234
+
235
+ private
236
+
237
+ # Returns an array of ThreadInfo objects for the given pid.
238
+ #
239
+ def self.get_thread_info(pid, struct, ptinfo)
240
+ buf = FFI::MemoryPointer.new(:uint64_t, ptinfo[:pti_threadnum])
241
+ num = proc_pidinfo(pid, PROC_PIDLISTTHREADS, 0, buf, buf.size)
242
+
243
+ if num <= 0
244
+ if [Errno::EPERM::Errno, Errno::ESRCH::Errno].include?(FFI.errno)
245
+ return # Either we don't have permission, or the pid no longer exists
246
+ else
247
+ raise SystemCallError.new('proc_pidinfo', FFI.errno)
248
+ end
249
+ end
250
+
251
+ max = ptinfo[:pti_threadnum]
252
+ struct[:threadinfo] = []
253
+
254
+ 0.upto(max-1) do |index|
255
+ tinfo = ProcThreadInfo.new
256
+ nb = proc_pidinfo(pid, PROC_PIDTHREADINFO, buf[index].read_uint64, tinfo, tinfo.size)
257
+
258
+ if nb <= 0
259
+ if [Errno::EPERM::Errno, Errno::ESRCH::Errno].include?(FFI.errno)
260
+ return # Either we don't have permission, or the pid no longer exists
261
+ else
262
+ raise SystemCallError.new('proc_pidinfo', FFI.errno)
263
+ end
264
+ end
265
+
266
+ tinfo_struct = ThreadInfoStruct.new(
267
+ tinfo[:pth_user_time],
268
+ tinfo[:pth_system_time],
269
+ tinfo[:pth_cpu_usage],
270
+ tinfo[:pth_policy],
271
+ tinfo[:pth_run_state],
272
+ tinfo[:pth_flags],
273
+ tinfo[:pth_sleep_time],
274
+ tinfo[:pth_curpri],
275
+ tinfo[:pth_priority],
276
+ tinfo[:pth_maxpriority],
277
+ tinfo[:pth_name].to_s,
278
+ )
279
+
280
+ struct[:threadinfo] << tinfo_struct
281
+ end
282
+ end
283
+
284
+ # Get the command line arguments, as well as the environment settings,
285
+ # for the given PID.
286
+ #
287
+ def self.get_cmd_args_and_env(pid, struct)
288
+ len = FFI::MemoryPointer.new(:size_t)
289
+ mib = FFI::MemoryPointer.new(:int, 3)
290
+
291
+ # Since we may not have access to the process information due
292
+ # to improper privileges, just bail if we see a failure here.
293
+
294
+ # First use KERN_PROCARGS2 to discover the argc value of the running process.
295
+ mib.write_array_of_int([CTL_KERN, KERN_PROCARGS2, pid])
296
+ return if sysctl(mib, 3, nil, len, nil, 0) < 0
297
+
298
+ buf = FFI::MemoryPointer.new(:char, len.read_ulong)
299
+ return if sysctl(mib, 3, buf, len, nil, 0) < 0
300
+
301
+ # The argc value is located in the first byte of buf
302
+ argc = buf.read_bytes(1).ord
303
+ buf.free
304
+
305
+ # Now use KERN_PROCARGS to fetch the rest of the process information
306
+ mib.write_array_of_int([CTL_KERN, KERN_PROCARGS, pid])
307
+ return if sysctl(mib, 3, nil, len, nil, 0) < 0
308
+
309
+ buf = FFI::MemoryPointer.new(:char, len.read_ulong)
310
+ return if sysctl(mib, 3, buf, len, nil, 0) < 0
311
+
312
+ exe = buf.read_string # Read up to first null, does not include args
313
+ struct[:exe] = exe
314
+
315
+ # Parse the rest of the information out of a big, ugly string
316
+ array = buf.read_bytes(len.read_ulong).split(0.chr)
317
+ array.delete('') # Delete empty strings
318
+
319
+ # The format that sysctl outputs is as follows:
320
+ #
321
+ # [full executable path]
322
+ # [executable name]
323
+ # [arguments]
324
+ # [environment variables]
325
+ # ...
326
+ # \FF\BF
327
+ # [full executable path]
328
+ #
329
+ # Strip the first executable path and the last two entries from the array.
330
+ # What is left is the name, arguments, and environment variables
331
+ array = array[1..-3]
332
+
333
+ # It seems that argc sometimes returns a bogus value. In that case, delete
334
+ # any environment variable strings, and reset the argc value.
335
+ #
336
+ if argc > array.size
337
+ array.delete_if{ |e| e.include?('=') }
338
+ argc = array.size
339
+ end
340
+
341
+ cmdline = ''
342
+
343
+ # Extract the full command line and its arguments from the array
344
+ argc.times do
345
+ cmdline << ' ' + array.shift
346
+ end
347
+
348
+ struct[:cmdline] = cmdline.strip
349
+
350
+ # Anything remaining at this point is a collection of key=value
351
+ # pairs which we convert into a hash.
352
+ environ = array.inject({}) do |hash, string|
353
+ if string && string.include?('=')
354
+ key, value = string.split('=')
355
+ hash[key] = value
356
+ end
357
+ hash
358
+ end
359
+
360
+ struct[:environ] = environ
361
+ end
362
+ end
363
+ end
@@ -0,0 +1,363 @@
1
+ require 'ffi'
2
+ require 'sys/proctable/version'
3
+
4
+ module Sys
5
+ class ProcTable
6
+ extend FFI::Library
7
+
8
+ # Error typically raised if the ProcTable.ps method fails.
9
+ class Error < StandardError; end
10
+
11
+ # There is no constructor
12
+ private_class_method :new
13
+
14
+ private
15
+
16
+ ffi_lib :kvm
17
+
18
+ attach_function :devname, [:dev_t, :mode_t], :string
19
+ attach_function :kvm_open, [:string, :string, :string, :int, :string], :pointer
20
+ attach_function :kvm_close, [:pointer], :int
21
+ attach_function :kvm_getprocs, [:pointer, :int, :int, :pointer], :pointer
22
+ attach_function :kvm_getargv, [:pointer, :pointer, :int], :pointer
23
+
24
+ POSIX_ARG_MAX = 4096
25
+
26
+ KERN_PROC_PID = 1
27
+ KERN_PROC_PROC = 8
28
+
29
+ S_IFCHR = 0020000
30
+
31
+ WMESGLEN = 8
32
+ LOCKNAMELEN = 8
33
+ OCOMMLEN = 16
34
+ COMMLEN = 19
35
+ KI_EMULNAMELEN = 16
36
+ KI_NGROUPS = 16
37
+ LOGNAMELEN = 17
38
+ KI_NSPARE_INT = 9
39
+ KI_NSPARE_LONG = 12
40
+ KI_NSPARE_PTR = 6
41
+
42
+ class Timeval < FFI::Struct
43
+ layout(:tv_sec, :time_t, :tv_usec, :suseconds_t)
44
+ end
45
+
46
+ class Priority < FFI::Struct
47
+ layout(
48
+ :pri_class, :uchar,
49
+ :pri_level, :uchar,
50
+ :pri_native, :uchar,
51
+ :pri_user, :uchar
52
+ )
53
+ end
54
+
55
+ class Rusage < FFI::Struct
56
+ layout(
57
+ :ru_utime, Timeval,
58
+ :ru_stime, Timeval,
59
+ :ru_maxrss, :long,
60
+ :ru_ixrss, :long,
61
+ :ru_idrss, :long,
62
+ :ru_isrss, :long,
63
+ :ru_minflt, :long,
64
+ :ru_majflt, :long,
65
+ :ru_nswap, :long,
66
+ :ru_inblock, :long,
67
+ :ru_oublock, :long,
68
+ :ru_msgsnd, :long,
69
+ :ru_msgrcv, :long,
70
+ :ru_nsignals, :long,
71
+ :ru_nvcsw, :long,
72
+ :ru_nivcsw, :long
73
+ )
74
+ end
75
+
76
+ class Pargs < FFI::Struct
77
+ layout(
78
+ :ar_ref, :uint,
79
+ :ar_length, :uint,
80
+ :ar_args, [:uchar,1]
81
+ )
82
+ end
83
+
84
+ class KInfoProc < FFI::Struct
85
+ layout(
86
+ :ki_structsize, :int,
87
+ :ki_layout, :int,
88
+ :ki_args, :pointer,
89
+ :ki_paddr, :pointer,
90
+ :ki_addr, :pointer,
91
+ :ki_tracep, :pointer,
92
+ :ki_textvp, :pointer,
93
+ :ki_fd, :pointer,
94
+ :ki_vmspace, :pointer,
95
+ :ki_wchan, :pointer,
96
+ :ki_pid, :pid_t,
97
+ :ki_ppid, :pid_t,
98
+ :ki_pgid, :pid_t,
99
+ :ki_tpgid, :pid_t,
100
+ :ki_sid, :pid_t,
101
+ :ki_tsid, :pid_t,
102
+ :ki_jobc, :short,
103
+ :ki_spare_short1, :short,
104
+ :ki_tdev, :dev_t,
105
+ :ki_siglist, [:uint32_t, 4],
106
+ :ki_sigmask, [:uint32_t, 4],
107
+ :ki_sigignore, [:uint32_t, 4],
108
+ :ki_sigcatch, [:uint32_t, 4],
109
+ :ki_uid, :uid_t,
110
+ :ki_ruid, :uid_t,
111
+ :ki_svuid, :uid_t,
112
+ :ki_rgid, :gid_t,
113
+ :ki_svgid, :gid_t,
114
+ :ki_ngroups, :short,
115
+ :ki_spare_short2, :short,
116
+ :ki_groups, [:gid_t, KI_NGROUPS],
117
+ :ki_size, :uint32_t,
118
+ :ki_rssize, :segsz_t,
119
+ :ki_swrss, :segsz_t,
120
+ :ki_tsize, :segsz_t,
121
+ :ki_dsize, :segsz_t,
122
+ :ki_ssize, :segsz_t,
123
+ :ki_xstat, :u_short,
124
+ :ki_acflag, :u_short,
125
+ :ki_pctcpu, :fixpt_t,
126
+ :ki_estcpu, :uint,
127
+ :ki_slptime, :uint,
128
+ :ki_swtime, :uint,
129
+ :ki_swtime, :int,
130
+ :ki_runtime, :uint64_t,
131
+ :ki_start, Timeval,
132
+ :ki_childtime, Timeval,
133
+ :ki_flag, :long,
134
+ :ki_kiflag, :long,
135
+ :ki_traceflag, :int,
136
+ :ki_stat, :char,
137
+ :ki_nice, :char,
138
+ :ki_lock, :char,
139
+ :ki_rqindex, :char,
140
+ :ki_oncpu, :uchar,
141
+ :ki_lastcpu, :uchar,
142
+ :ki_ocomm, [:char, OCOMMLEN+1],
143
+ :ki_wmesg, [:char, WMESGLEN+1],
144
+ :ki_login, [:char, LOGNAMELEN+1],
145
+ :ki_lockname, [:char, LOCKNAMELEN+1],
146
+ :ki_comm, [:char, COMMLEN+1],
147
+ :ki_emul, [:char, KI_EMULNAMELEN+1],
148
+ :ki_sparestrings, [:char, 68],
149
+ :ki_spareints, [:int, KI_NSPARE_INT],
150
+ :ki_cr_flags, :uint,
151
+ :ki_jid, :int,
152
+ :ki_numthreads, :int,
153
+ :ki_tid, :pid_t,
154
+ :ki_pri, Priority,
155
+ :ki_rusage, Rusage,
156
+ :ki_rusage_ch, Rusage,
157
+ :ki_pcb, :pointer,
158
+ :ki_kstack, :pointer,
159
+ :ki_udata, :pointer,
160
+ :ki_tdaddr, :pointer,
161
+ :ki_spareptrs, [:pointer, KI_NSPARE_PTR],
162
+ :ki_sparelongs, [:long, KI_NSPARE_LONG],
163
+ :ki_sflags, :long,
164
+ :ki_tdflags, :long
165
+ )
166
+ end
167
+
168
+ @fields = %w[
169
+ pid ppid pgid tpgid sid tsid jobc uid ruid rgid
170
+ ngroups groups size rssize swrss tsize dsize ssize
171
+ xstat acflag pctcpu estcpu slptime swtime runtime start
172
+ flag state nice lock rqindex oncpu lastcpu wmesg login
173
+ lockname comm ttynum ttydev jid priority usrpri cmdline
174
+ utime stime maxrss ixrss idrss isrss minflt majflt nswap
175
+ inblock oublock msgsnd msgrcv nsignals nvcsw nivcsw
176
+ ]
177
+
178
+ ProcTableStruct = Struct.new('ProcTableStruct', *@fields)
179
+
180
+ public
181
+
182
+ # In block form, yields a ProcTableStruct for each process entry that you
183
+ # have rights to. This method returns an array of ProcTableStruct's in
184
+ # non-block form.
185
+ #
186
+ # If a +pid+ is provided, then only a single ProcTableStruct is yielded or
187
+ # returned, or nil if no process information is found for that +pid+.
188
+ #
189
+ # Example:
190
+ #
191
+ # # Iterate over all processes
192
+ # ProcTable.ps do |proc_info|
193
+ # p proc_info
194
+ # end
195
+ #
196
+ # # Print process table information for only pid 1001
197
+ # p ProcTable.ps(1001)
198
+ #
199
+ def self.ps(**kwargs)
200
+ pid = kwargs[:pid]
201
+
202
+ begin
203
+ kd = kvm_open(nil, nil, nil, 0, nil)
204
+
205
+ if kd.null?
206
+ raise SystemCallError.new('kvm_open', FFI.errno)
207
+ end
208
+
209
+ ptr = FFI::MemoryPointer.new(:int) # count
210
+
211
+ if pid
212
+ procs = kvm_getprocs(kd, KERN_PROC_PID, pid, ptr)
213
+ else
214
+ procs = kvm_getprocs(kd, KERN_PROC_PROC, 0, ptr)
215
+ end
216
+
217
+ if procs.null?
218
+ if pid && FFI.errno == Errno::ESRCH::Errno
219
+ return nil
220
+ else
221
+ raise SystemCallError.new('kvm_getprocs', FFI.errno)
222
+ end
223
+ end
224
+
225
+ count = ptr.read_int
226
+ array = []
227
+
228
+ 0.upto(count-1){ |i|
229
+ cmd = nil
230
+ kinfo = KInfoProc.new(procs[i * KInfoProc.size])
231
+
232
+ args = kvm_getargv(kd, kinfo, 0)
233
+
234
+ unless args.null?
235
+ cmd = []
236
+
237
+ until ((ptr = args.read_pointer).null?)
238
+ cmd << ptr.read_string
239
+ args += FFI::Type::POINTER.size
240
+ end
241
+
242
+ cmd = cmd.join(' ')
243
+ end
244
+
245
+ struct = ProcTableStruct.new(
246
+ kinfo[:ki_pid],
247
+ kinfo[:ki_ppid],
248
+ kinfo[:ki_pgid],
249
+ kinfo[:ki_tpgid],
250
+ kinfo[:ki_sid],
251
+ kinfo[:ki_tsid],
252
+ kinfo[:ki_jobc],
253
+ kinfo[:ki_uid],
254
+ kinfo[:ki_ruid],
255
+ kinfo[:ki_rgid],
256
+ kinfo[:ki_ngroups],
257
+ kinfo[:ki_groups].to_a[0...kinfo[:ki_ngroups]],
258
+ kinfo[:ki_size],
259
+ kinfo[:ki_rssize],
260
+ kinfo[:ki_swrss],
261
+ kinfo[:ki_tsize],
262
+ kinfo[:ki_dsize],
263
+ kinfo[:ki_ssize],
264
+ kinfo[:ki_xstat],
265
+ kinfo[:ki_acflag],
266
+ kinfo[:ki_pctcpu].to_f,
267
+ kinfo[:ki_estcpu],
268
+ kinfo[:ki_slptime],
269
+ kinfo[:ki_swtime],
270
+ kinfo[:ki_runtime],
271
+ Time.at(kinfo[:ki_start][:tv_sec]),
272
+ kinfo[:ki_flag],
273
+ get_state(kinfo[:ki_stat]),
274
+ kinfo[:ki_nice],
275
+ kinfo[:ki_lock],
276
+ kinfo[:ki_rqindex],
277
+ kinfo[:ki_oncpu],
278
+ kinfo[:ki_lastcpu],
279
+ kinfo[:ki_wmesg].to_s,
280
+ kinfo[:ki_login].to_s,
281
+ kinfo[:ki_lockname].to_s,
282
+ kinfo[:ki_comm].to_s,
283
+ kinfo[:ki_tdev],
284
+ devname(kinfo[:ki_tdev], S_IFCHR),
285
+ kinfo[:ki_jid],
286
+ kinfo[:ki_pri][:pri_level],
287
+ kinfo[:ki_pri][:pri_user],
288
+ cmd,
289
+ kinfo[:ki_rusage][:ru_utime][:tv_sec],
290
+ kinfo[:ki_rusage][:ru_stime][:tv_sec],
291
+ kinfo[:ki_rusage][:ru_maxrss],
292
+ kinfo[:ki_rusage][:ru_ixrss],
293
+ kinfo[:ki_rusage][:ru_idrss],
294
+ kinfo[:ki_rusage][:ru_isrss],
295
+ kinfo[:ki_rusage][:ru_minflt],
296
+ kinfo[:ki_rusage][:ru_majflt],
297
+ kinfo[:ki_rusage][:ru_nswap],
298
+ kinfo[:ki_rusage][:ru_inblock],
299
+ kinfo[:ki_rusage][:ru_oublock],
300
+ kinfo[:ki_rusage][:ru_msgsnd],
301
+ kinfo[:ki_rusage][:ru_msgrcv],
302
+ kinfo[:ki_rusage][:ru_nsignals],
303
+ kinfo[:ki_rusage][:ru_nvcsw],
304
+ kinfo[:ki_rusage][:ru_nivcsw]
305
+ )
306
+
307
+ struct.freeze # This is readonly data
308
+
309
+ if block_given?
310
+ yield struct
311
+ else
312
+ array << struct
313
+ end
314
+ }
315
+ ensure
316
+ kvm_close(kd) unless kd.null?
317
+ end
318
+
319
+ if block_given?
320
+ nil
321
+ else
322
+ pid ? array.first : array
323
+ end
324
+ end
325
+
326
+ # Returns an array of fields that each ProcTableStruct will contain. This
327
+ # may be useful if you want to know in advance what fields are available
328
+ # without having to perform at least one read of the /proc table.
329
+ #
330
+ # Example:
331
+ #
332
+ # Sys::ProcTable.fields.each{ |field|
333
+ # puts "Field: #{field}"
334
+ # }
335
+ #
336
+ def self.fields
337
+ @fields
338
+ end
339
+
340
+ private
341
+
342
+ SIDL = 1
343
+ SRUN = 2
344
+ SSLEEP = 3
345
+ SSTOP = 4
346
+ SZOMB = 5
347
+ SWAIT = 6
348
+ SLOCK = 7
349
+
350
+ def self.get_state(int)
351
+ case int
352
+ when SIDL; "idle"
353
+ when SRUN; "run"
354
+ when SSLEEP; "sleep"
355
+ when SSTOP; "stop"
356
+ when SZOMB; "zombie"
357
+ when SWAIT; "waiting"
358
+ when SLOCK; "locked"
359
+ else; "unknown"
360
+ end
361
+ end
362
+ end
363
+ end