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,406 @@
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
+ # JRuby on Mac
30
+ unless defined? FFI::StructLayout::CharArray
31
+ FFI::StructLayout::CharArray = FFI::StructLayout::CharArrayProxy
32
+ end
33
+
34
+ class ProcBsdInfo < FFI::Struct
35
+ layout(
36
+ :pbi_flags, :uint32_t,
37
+ :pbi_status, :uint32_t,
38
+ :pbi_xstatus, :uint32_t,
39
+ :pbi_pid, :uint32_t,
40
+ :pbi_ppid, :uint32_t,
41
+ :pbi_uid, :uid_t,
42
+ :pbi_gid, :uid_t,
43
+ :pbi_ruid, :uid_t,
44
+ :pbi_rgid, :gid_t,
45
+ :pbi_svuid, :uid_t,
46
+ :pbi_svgid, :gid_t,
47
+ :rfu1, :uint32_t,
48
+ :pbi_comm, [:char, MAXCOMLEN],
49
+ :pbi_name, [:char, MAXCOMLEN * 2],
50
+ :pbi_nfiles, :uint32_t,
51
+ :pbi_pgid, :uint32_t,
52
+ :pbi_pjobc, :uint32_t,
53
+ :e_tdev, :uint32_t,
54
+ :e_tpgid, :uint32_t,
55
+ :pbi_nice, :int32_t,
56
+ :pbi_start_tvsec, :uint64_t,
57
+ :pbi_start_tvusec, :uint64_t
58
+ )
59
+ end
60
+
61
+ class ProcTaskInfo < FFI::Struct
62
+ layout(
63
+ :pti_virtual_size, :uint64_t,
64
+ :pti_resident_size, :uint64_t,
65
+ :pti_total_user, :uint64_t,
66
+ :pti_total_system, :uint64_t,
67
+ :pti_threads_user, :uint64_t,
68
+ :pti_threads_system, :uint64_t,
69
+ :pti_policy, :int32_t,
70
+ :pti_faults, :int32_t,
71
+ :pti_pageins, :int32_t,
72
+ :pti_cow_faults, :int32_t,
73
+ :pti_messages_sent, :int32_t,
74
+ :pti_messages_received, :int32_t,
75
+ :pti_syscalls_mach, :int32_t,
76
+ :pti_syscalls_unix, :int32_t,
77
+ :pti_csw, :int32_t,
78
+ :pti_threadnum, :int32_t,
79
+ :pti_numrunning, :int32_t,
80
+ :pti_priority, :int32_t
81
+ )
82
+ end
83
+
84
+ class ProcThreadInfo < FFI::Struct
85
+ layout(
86
+ :pth_user_time, :uint64_t,
87
+ :pth_system_time, :uint64_t,
88
+ :pth_cpu_usage, :int32_t,
89
+ :pth_policy, :int32_t,
90
+ :pth_run_state, :int32_t,
91
+ :pth_flags, :int32_t,
92
+ :pth_sleep_time, :int32_t,
93
+ :pth_curpri, :int32_t,
94
+ :pth_priority, :int32_t,
95
+ :pth_maxpriority, :int32_t,
96
+ :pth_name, [:char, MAXTHREADNAMESIZE]
97
+ )
98
+ end
99
+
100
+ # Map the fields from the FFI::Structs to the Sys::ProcTable struct on
101
+ # class load to reduce the amount of objects needing to be generated for
102
+ # each invocation of Sys::ProcTable.ps
103
+ all_members = ProcBsdInfo.members + ProcTaskInfo.members + ProcThreadInfo.members
104
+ PROC_STRUCT_FIELD_MAP = all_members.map { |member|
105
+ temp = member.to_s.split('_')
106
+ sproperty = temp.size > 1 ? temp[1..-1].join('_') : temp.first
107
+ [member, sproperty.to_sym]
108
+ }.to_h
109
+
110
+ class ProcTaskAllInfo < FFI::Struct
111
+ layout(:pbsd, ProcBsdInfo, :ptinfo, ProcTaskInfo)
112
+ end
113
+
114
+ ffi_lib 'proc'
115
+
116
+ attach_function :proc_listallpids, [:pointer, :int], :int
117
+ attach_function :proc_pidinfo, [:int, :int, :uint64_t, :pointer, :int], :int
118
+
119
+ ffi_lib FFI::Library::LIBC
120
+
121
+ attach_function :sysctl, [:pointer, :uint, :pointer, :pointer, :pointer, :size_t], :int
122
+
123
+ # These mostly mimic the struct members, but we've added a few custom ones as well.
124
+ @fields = %w[
125
+ flags status xstatus pid ppid uid gid ruid rgid svuid svgid rfu1 comm
126
+ name nfiles pgid pjobc tdev tpgid nice start_tvsec start_tvusec
127
+ virtual_size resident_size total_user total_system threads_user
128
+ threads_system policy faults pageins cow_faults messages_sent
129
+ messages_received syscalls_mach syscalls_unix csw threadnum numrunning
130
+ priority cmdline exe environ threadinfo
131
+ ]
132
+
133
+ # Add a couple aliases to make it similar to Linux
134
+ ProcTableStruct = Struct.new("ProcTableStruct", *@fields) do
135
+ alias vsize virtual_size
136
+ alias rss resident_size
137
+ end
138
+
139
+ ThreadInfoStruct = Struct.new("ThreadInfo", :user_time, :system_time,
140
+ :cpu_usage, :policy, :run_state, :flags, :sleep_time, :curpri,
141
+ :priority, :maxpriority, :name
142
+ )
143
+
144
+ public
145
+
146
+ # Returns an array of fields that each ProcTableStruct will contain. This
147
+ # may be useful if you want to know in advance what fields are available
148
+ # without having to perform at least one read of the process table.
149
+ #
150
+ # Example:
151
+ #
152
+ # Sys::ProcTable.fields.each{ |field|
153
+ # puts "Field: #{field}"
154
+ # }
155
+ #
156
+ def self.fields
157
+ @fields
158
+ end
159
+
160
+ # In block form, yields a ProcTableStruct for each process entry that you
161
+ # have rights to. This method returns an array of ProcTableStruct's in
162
+ # non-block form.
163
+ #
164
+ # If a +pid+ is provided, then only a single ProcTableStruct is yielded or
165
+ # returned, or nil if no process information is found for that +pid+.
166
+ #
167
+ # Example:
168
+ #
169
+ # # Iterate over all processes
170
+ # ProcTable.ps do |proc_info|
171
+ # p proc_info
172
+ # end
173
+ #
174
+ # # Print process table information for only pid 1001
175
+ # p ProcTable.ps(pid: 1001)
176
+ #
177
+ # # Same as above, but do not include thread information
178
+ # p ProcTable.ps(pid: 1001, thread_info: false)
179
+ #
180
+ def self.ps(**kwargs)
181
+ pid = kwargs[:pid]
182
+ thread_info = kwargs[:thread_info]
183
+
184
+ if pid
185
+ raise TypeError unless pid.is_a?(Numeric)
186
+ info = ProcTaskAllInfo.new
187
+
188
+ nb = proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, info, info.size)
189
+
190
+ if nb <= 0
191
+ if [Errno::EPERM::Errno, Errno::ESRCH::Errno].include?(FFI.errno)
192
+ return # Either we don't have permission, or the pid no longer exists
193
+ else
194
+ raise SystemCallError.new('proc_pidinfo', FFI.errno)
195
+ end
196
+ end
197
+
198
+ return nil if nb != info.size # Invalid data
199
+
200
+ struct = ProcTableStruct.new
201
+
202
+ # Pass by reference
203
+ get_cmd_args_and_env(pid, struct)
204
+ get_thread_info(pid, struct, info[:ptinfo]) unless thread_info == false
205
+ apply_info_to_struct(info, struct)
206
+
207
+ struct.freeze
208
+ yield struct if block_given?
209
+ struct
210
+ else
211
+ num = proc_listallpids(nil, 0)
212
+ ptr = FFI::MemoryPointer.new(:pid_t, num)
213
+ num = proc_listallpids(ptr, ptr.size)
214
+
215
+ raise SystemCallError.new('proc_listallpids', FFI.errno) if num == 0
216
+
217
+ pids = ptr.get_array_of_int32(0, num).sort
218
+ array = block_given? ? nil : []
219
+
220
+ pids.each do |lpid|
221
+ next unless pid == lpid if pid
222
+ info = ProcTaskAllInfo.new
223
+
224
+ nb = proc_pidinfo(lpid, PROC_PIDTASKALLINFO, 0, info, info.size)
225
+
226
+ if nb <= 0
227
+ if [Errno::EPERM::Errno, Errno::ESRCH::Errno].include?(FFI.errno)
228
+ next # Either we don't have permission, or the pid no longer exists
229
+ else
230
+ raise SystemCallError.new('proc_pidinfo', FFI.errno)
231
+ end
232
+ end
233
+
234
+ # Avoid potentially invalid data
235
+ next if nb != info.size
236
+
237
+ struct = ProcTableStruct.new
238
+
239
+ # Pass by reference
240
+ get_cmd_args_and_env(lpid, struct)
241
+ get_thread_info(lpid, struct, info[:ptinfo]) unless thread_info == false
242
+ apply_info_to_struct(info, struct)
243
+
244
+ struct.freeze
245
+
246
+ if block_given?
247
+ yield struct
248
+ else
249
+ array << struct
250
+ end
251
+ end
252
+
253
+ array
254
+ end
255
+ end
256
+
257
+ private
258
+
259
+ # Pass by reference method that updates the Ruby struct based on the FFI struct.
260
+ #
261
+ def self.apply_info_to_struct(info, struct)
262
+ # Chop the leading xx_ from the FFI struct members for our ruby struct.
263
+ info.members.each do |nested|
264
+ info[nested].members.each do |member|
265
+ if info[nested][member].is_a?(FFI::StructLayout::CharArray)
266
+ struct[PROC_STRUCT_FIELD_MAP[member]] = info[nested][member].to_s
267
+ else
268
+ struct[PROC_STRUCT_FIELD_MAP[member]] = info[nested][member]
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ # Returns an array of ThreadInfo objects for the given pid.
275
+ #
276
+ def self.get_thread_info(pid, struct, ptinfo)
277
+ buf = FFI::MemoryPointer.new(:uint64_t, ptinfo[:pti_threadnum])
278
+ num = proc_pidinfo(pid, PROC_PIDLISTTHREADS, 0, buf, buf.size)
279
+
280
+ if num <= 0
281
+ if [Errno::EPERM::Errno, Errno::ESRCH::Errno].include?(FFI.errno)
282
+ return # Either we don't have permission, or the pid no longer exists
283
+ else
284
+ raise SystemCallError.new('proc_pidinfo', FFI.errno)
285
+ end
286
+ end
287
+
288
+ max = ptinfo[:pti_threadnum]
289
+ struct[:threadinfo] = []
290
+
291
+ 0.upto(max-1) do |index|
292
+ tinfo = ProcThreadInfo.new
293
+
294
+ # Use read_array_of_uint64 for compatibility with JRuby if necessary.
295
+ if buf[index].respond_to?(:read_uint64)
296
+ nb = proc_pidinfo(pid, PROC_PIDTHREADINFO, buf[index].read_uint64, tinfo, tinfo.size)
297
+ else
298
+ nb = proc_pidinfo(pid, PROC_PIDTHREADINFO, buf[index].read_array_of_uint64(1).first, tinfo, tinfo.size)
299
+ end
300
+
301
+ if nb <= 0
302
+ if [Errno::EPERM::Errno, Errno::ESRCH::Errno].include?(FFI.errno)
303
+ return # Either we don't have permission, or the pid no longer exists
304
+ else
305
+ raise SystemCallError.new('proc_pidinfo', FFI.errno)
306
+ end
307
+ end
308
+
309
+ tinfo_struct = ThreadInfoStruct.new(
310
+ tinfo[:pth_user_time],
311
+ tinfo[:pth_system_time],
312
+ tinfo[:pth_cpu_usage],
313
+ tinfo[:pth_policy],
314
+ tinfo[:pth_run_state],
315
+ tinfo[:pth_flags],
316
+ tinfo[:pth_sleep_time],
317
+ tinfo[:pth_curpri],
318
+ tinfo[:pth_priority],
319
+ tinfo[:pth_maxpriority],
320
+ tinfo[:pth_name].to_s,
321
+ )
322
+
323
+ struct[:threadinfo] << tinfo_struct
324
+ end
325
+ end
326
+
327
+ # Get the command line arguments, as well as the environment settings,
328
+ # for the given PID.
329
+ #
330
+ def self.get_cmd_args_and_env(pid, struct)
331
+ len = FFI::MemoryPointer.new(:size_t)
332
+ mib = FFI::MemoryPointer.new(:int, 3)
333
+
334
+ # Since we may not have access to the process information due
335
+ # to improper privileges, just bail if we see a failure here.
336
+
337
+ # First use KERN_PROCARGS2 to discover the argc value of the running process.
338
+ mib.write_array_of_int([CTL_KERN, KERN_PROCARGS2, pid])
339
+ return if sysctl(mib, 3, nil, len, nil, 0) < 0
340
+
341
+ buf = FFI::MemoryPointer.new(:char, len.read_ulong)
342
+ return if sysctl(mib, 3, buf, len, nil, 0) < 0
343
+
344
+ # The argc value is located in the first byte of buf
345
+ argc = buf.read_bytes(1).ord
346
+ buf.free
347
+
348
+ # Now use KERN_PROCARGS to fetch the rest of the process information
349
+ mib.write_array_of_int([CTL_KERN, KERN_PROCARGS, pid])
350
+ return if sysctl(mib, 3, nil, len, nil, 0) < 0
351
+
352
+ buf = FFI::MemoryPointer.new(:char, len.read_ulong)
353
+ return if sysctl(mib, 3, buf, len, nil, 0) < 0
354
+
355
+ exe = buf.read_string # Read up to first null, does not include args
356
+ struct[:exe] = exe
357
+
358
+ # Parse the rest of the information out of a big, ugly string
359
+ array = buf.read_bytes(len.read_ulong).split(0.chr)
360
+ array.delete('') # Delete empty strings
361
+
362
+ # The format that sysctl outputs is as follows:
363
+ #
364
+ # [full executable path]
365
+ # [executable name]
366
+ # [arguments]
367
+ # [environment variables]
368
+ # ...
369
+ # \FF\BF
370
+ # [full executable path]
371
+ #
372
+ # Strip the first executable path and the last two entries from the array.
373
+ # What is left is the name, arguments, and environment variables
374
+ array = array[1..-3]
375
+
376
+ # It seems that argc sometimes returns a bogus value. In that case, delete
377
+ # any environment variable strings, and reset the argc value.
378
+ #
379
+ if argc > array.size
380
+ array.delete_if{ |e| e.include?('=') }
381
+ argc = array.size
382
+ end
383
+
384
+ cmdline = ''
385
+
386
+ # Extract the full command line and its arguments from the array
387
+ argc.times do
388
+ cmdline << ' ' + array.shift
389
+ end
390
+
391
+ struct[:cmdline] = cmdline.strip
392
+
393
+ # Anything remaining at this point is a collection of key=value
394
+ # pairs which we convert into a hash.
395
+ environ = array.inject({}) do |hash, string|
396
+ if string && string.include?('=')
397
+ key, value = string.split('=')
398
+ hash[key] = value
399
+ end
400
+ hash
401
+ end
402
+
403
+ struct[:environ] = environ
404
+ end
405
+ end
406
+ 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