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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -0
- data/CHANGES +165 -0
- data/MANIFEST +33 -41
- data/README +115 -135
- data/Rakefile +94 -0
- data/benchmarks/bench_ps.rb +21 -0
- data/doc/top.txt +5 -11
- data/examples/example_ps.rb +20 -0
- data/lib/aix/sys/proctable.rb +458 -0
- data/lib/darwin/sys/proctable.rb +363 -0
- data/lib/freebsd/sys/proctable.rb +363 -0
- data/lib/linux/sys/proctable.rb +314 -0
- data/lib/linux/sys/proctable/cgroup_entry.rb +50 -0
- data/lib/linux/sys/proctable/smaps.rb +118 -0
- data/lib/sunos/sys/proctable.rb +456 -0
- data/lib/sys-proctable.rb +1 -0
- data/lib/sys-top.rb +1 -0
- data/lib/sys/proctable.rb +18 -0
- data/lib/sys/proctable/version.rb +6 -0
- data/lib/sys/top.rb +28 -19
- data/lib/windows/sys/proctable.rb +208 -0
- data/spec/sys_proctable_aix_spec.rb +328 -0
- data/spec/sys_proctable_all_spec.rb +89 -0
- data/spec/sys_proctable_darwin_spec.rb +120 -0
- data/spec/sys_proctable_freebsd_spec.rb +210 -0
- data/spec/sys_proctable_linux_spec.rb +310 -0
- data/spec/sys_proctable_sunos_spec.rb +316 -0
- data/spec/sys_proctable_windows_spec.rb +317 -0
- data/spec/sys_top_spec.rb +51 -0
- data/sys-proctable.gemspec +38 -0
- metadata +140 -64
- metadata.gz.sig +0 -0
- data/doc/freebsd.txt +0 -90
- data/doc/hpux.txt +0 -77
- data/doc/linux.txt +0 -85
- data/doc/solaris.txt +0 -99
- data/doc/windows.txt +0 -122
- data/ext/extconf.rb +0 -98
- data/ext/sunos/sunos.c +0 -374
- data/ext/sunos/sunos.h +0 -177
- data/ext/version.h +0 -2
- data/test/tc_all.rb +0 -59
- data/test/tc_freebsd.rb +0 -45
- data/test/tc_hpux.rb +0 -49
- data/test/tc_kvm_bsd.rb +0 -31
- data/test/tc_linux.rb +0 -45
- data/test/tc_sunos.rb +0 -52
- data/test/tc_top.rb +0 -26
- data/test/tc_windows.rb +0 -40
- 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
|