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,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