sys-proctable 0.7.6 → 1.2.4

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