sys-proctable 0.9.3-universal-solaris → 0.9.4-universal-solaris
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
- data/CHANGES +11 -0
- data/MANIFEST +5 -3
- data/README +8 -9
- data/Rakefile +32 -21
- data/lib/sunos/sys/proctable.rb +432 -444
- data/lib/sys/top.rb +6 -4
- data/sys-proctable.gemspec +3 -2
- data/test/test_sys_proctable_all.rb +2 -5
- data/test/test_sys_proctable_sunos.rb +295 -297
- metadata +49 -49
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 493156f7b41233bb2dc91f61f64d668d681369f2
|
4
|
+
data.tar.gz: 73c3ea02760e2a1038a3cb79b5bc1bdd8a7843f5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d47a88803966ff31fbd8b3010ea9856dfe35bb424a18ec87b62972860228bc0467aae6ffdb4cfbb33a07ede4a38938022a6cdce5c11aa9d0497c6e9e621a1abd
|
7
|
+
data.tar.gz: 36c9b04c0f5668a1bd50aa83dc1bec55f2e4fd92178184703db047d805770e85a1c2d6789a0011c29542963b975beed4b18d5969ae4a5a05bbd586f7e1388a75
|
data/CHANGES
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
== 0.9.4 - 4-Mar-2014
|
2
|
+
* Added support for AIX 5.3 or later courtesy of Rick Ohnemus.
|
3
|
+
* The Solaris version now uses FFI structs instead of a packed array.
|
4
|
+
It solved issues with 64-bit versions of Ruby and it's self-documenting.
|
5
|
+
* The FreeBSD version has been converted to use FFI. In addition, additional
|
6
|
+
struct members have been added, and members that previously returned nil
|
7
|
+
now return meaningful data.
|
8
|
+
* Support for NetBSD and OpenBSD has been temporarily dropped. Considering
|
9
|
+
that the C code did not build on those platforms anyway, I doubt most of
|
10
|
+
you will notice. Patches for those platforms are welcome, but only using FFI.
|
11
|
+
|
1
12
|
== 0.9.3 - 17-Mar-2013
|
2
13
|
* Fixed a bug on OSX where a long command string arg could cause
|
3
14
|
a segfault. Thanks go to Nathaniel Bibler for the spot.
|
data/MANIFEST
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
* Rakefile
|
4
4
|
* README
|
5
5
|
* sys-proctable.gemspec
|
6
|
+
* doc/aix.txt
|
6
7
|
* doc/bsd.txt
|
7
8
|
* doc/hpux.txt
|
8
9
|
* doc/linux.txt
|
@@ -10,20 +11,21 @@
|
|
10
11
|
* doc/top.txt
|
11
12
|
* doc/windows.txt
|
12
13
|
* example/example_ps.rb
|
13
|
-
* ext/bsd/extconf.rb
|
14
|
-
* ext/bsd/sys/proctable.c
|
15
14
|
* ext/darwin/extconf.rb
|
16
15
|
* ext/darwin/sys/proctable.c
|
17
16
|
* ext/hpux/extconf.rb
|
18
17
|
* ext/hpux/sys/proctable.c
|
19
18
|
* lib/sys/top.rb
|
19
|
+
* lib/aix/sys/proctable.rb
|
20
|
+
* lib/freebsd/sys/proctable.rb
|
20
21
|
* lib/linux/sys/proctable.rb
|
21
22
|
* lib/sunos/sys/proctable.rb
|
22
23
|
* lib/windows/sys/proctable.rb
|
24
|
+
* test/test_sys_proctable_aix.rb
|
23
25
|
* test/test_sys_proctable_all.rb
|
24
26
|
* test/test_sys_proctable_darwin.rb
|
27
|
+
* test/test_sys_proctable_freebsd.rb
|
25
28
|
* test/test_sys_proctable_hpux.rb
|
26
|
-
* test/test_sys_proctable_bsd.rb
|
27
29
|
* test/test_sys_proctable_linux.rb
|
28
30
|
* test/test_sys_proctable_sunos.rb
|
29
31
|
* test/test_sys_proctable_windows.rb
|
data/README
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
== Supported Platforms
|
8
8
|
* Windows 2000 or later
|
9
9
|
* Linux 2.6+
|
10
|
-
*
|
10
|
+
* FreeBSD
|
11
11
|
* Solaris 8+
|
12
12
|
* HP-UX 10+
|
13
13
|
* OS X 10.4+
|
@@ -20,7 +20,7 @@
|
|
20
20
|
gem install sys-proctable --platform mswin32 # Windows
|
21
21
|
gem install sys-proctable --platform sunos # Solaris
|
22
22
|
gem install sys-proctable --platform linux # Linux
|
23
|
-
gem install sys-proctable --platform freebsd #
|
23
|
+
gem install sys-proctable --platform freebsd # FreeBSD
|
24
24
|
|
25
25
|
== Synopsis
|
26
26
|
require 'sys/proctable'
|
@@ -51,7 +51,7 @@
|
|
51
51
|
information from a different host. This relies on the WMI service running.
|
52
52
|
|
53
53
|
== Known Issues
|
54
|
-
===
|
54
|
+
=== FreeBSD
|
55
55
|
A kvm interface is used. That means the owner of the process using the
|
56
56
|
sys-proctable library needs to be a member of the kvm group (or root).
|
57
57
|
|
@@ -67,13 +67,12 @@
|
|
67
67
|
Using readdir_r() still won't solve all potential thread safety issues anyway.
|
68
68
|
|
69
69
|
== Future Plans
|
70
|
-
|
71
|
-
|
72
|
-
release.
|
70
|
+
Add support for NetBSD and OpenBSD.
|
71
|
+
Convert existing C code to FFI.
|
73
72
|
|
74
73
|
== Acknowledgements
|
75
|
-
This library
|
76
|
-
Dan Urist. Many ideas, as well as large chunks of code, were taken
|
74
|
+
This library was originally based on the Perl module Proc::ProcessTable
|
75
|
+
by Dan Urist. Many ideas, as well as large chunks of code, were taken
|
77
76
|
from his work. So, a big THANK YOU goes out to Dan Urist.
|
78
77
|
|
79
78
|
A big thanks also goes out to Mike Hall who was very helpful with ideas,
|
@@ -105,7 +104,7 @@
|
|
105
104
|
Artistic 2.0
|
106
105
|
|
107
106
|
== Copyright
|
108
|
-
(C) 2003-
|
107
|
+
(C) 2003-2014 Daniel J. Berger
|
109
108
|
All Rights Reserved.
|
110
109
|
|
111
110
|
== Author
|
data/Rakefile
CHANGED
@@ -8,7 +8,7 @@ CLEAN.include(
|
|
8
8
|
'**/*.core', # Core dump files
|
9
9
|
'**/*.gem', # Gem files
|
10
10
|
'**/*.rbc', # Rubinius
|
11
|
-
'
|
11
|
+
'**/*.rbx', # Rubinius
|
12
12
|
'**/*.o', # C object file
|
13
13
|
'**/*.log', # Ruby extension build log
|
14
14
|
'**/Makefile', # C Makefile
|
@@ -27,9 +27,6 @@ task :build => [:clean] do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
case CONFIG['host_os']
|
30
|
-
when /bsd/i
|
31
|
-
dir = 'ext/bsd'
|
32
|
-
ext = '.so'
|
33
30
|
when /darwin/i
|
34
31
|
dir = 'ext/darwin'
|
35
32
|
ext = '.bundle'
|
@@ -38,7 +35,7 @@ task :build => [:clean] do
|
|
38
35
|
ext = '.sl'
|
39
36
|
end
|
40
37
|
|
41
|
-
|
38
|
+
if CONFIG['host_os'] =~ /darwin|hpux/i
|
42
39
|
Dir.chdir(dir) do
|
43
40
|
ruby 'extconf.rb'
|
44
41
|
sh 'make'
|
@@ -61,8 +58,10 @@ task :install => [:build] do
|
|
61
58
|
file = 'lib/linux/sys/proctable.rb'
|
62
59
|
when /sunos|solaris/i
|
63
60
|
file = 'lib/sunos/sys/proctable.rb'
|
64
|
-
when /
|
65
|
-
|
61
|
+
when /aix/i
|
62
|
+
file = 'lib/aix/sys/proctable.rb'
|
63
|
+
when /freebsd/i
|
64
|
+
file = 'lib/freebsd/sys/proctable.rb'
|
66
65
|
when /darwin/i
|
67
66
|
Dir.chdir('ext/darwin'){ sh 'make install' }
|
68
67
|
when /hpux/i
|
@@ -75,12 +74,12 @@ end
|
|
75
74
|
desc 'Uninstall the sys-proctable library'
|
76
75
|
task :uninstall do
|
77
76
|
case CONFIG['host_os']
|
78
|
-
when /
|
79
|
-
dir = File.join(CONFIG['sitelibdir'], 'sys')
|
80
|
-
file = File.join(dir, 'proctable.rb')
|
81
|
-
else
|
77
|
+
when /darwin|hpux/i
|
82
78
|
dir = File.join(CONFIG['sitearchdir'], 'sys')
|
83
79
|
file = File.join(dir, 'proctable.' + CONFIG['DLEXT'])
|
80
|
+
else
|
81
|
+
dir = File.join(CONFIG['sitelibdir'], 'sys')
|
82
|
+
file = File.join(dir, 'proctable.rb')
|
84
83
|
end
|
85
84
|
|
86
85
|
rm(file)
|
@@ -111,12 +110,15 @@ Rake::TestTask.new do |t|
|
|
111
110
|
when /sunos|solaris/i
|
112
111
|
t.test_files = FileList['test/test_sys_proctable_sunos.rb']
|
113
112
|
t.libs << 'lib/sunos'
|
113
|
+
when /aix/i
|
114
|
+
t.test_files = FileList['test/test_sys_proctable_aix.rb']
|
115
|
+
t.libs << 'lib/aix'
|
116
|
+
when /freebsd/i
|
117
|
+
t.test_files = FileList['test/test_sys_proctable_freebsd.rb']
|
118
|
+
t.libs << 'lib/freebsd'
|
114
119
|
when /darwin/i
|
115
120
|
t.libs << 'ext/darwin'
|
116
121
|
t.test_files = FileList['test/test_sys_proctable_darwin.rb']
|
117
|
-
when /bsd/i
|
118
|
-
t.libs << 'ext/bsd'
|
119
|
-
t.test_files = FileList['test/test_sys_proctable_bsd.rb']
|
120
122
|
when /hpux/i
|
121
123
|
t.libs << 'ext/hpux'
|
122
124
|
t.test_files = FileList['test/test_sys_proctable_hpux.rb']
|
@@ -133,13 +135,12 @@ namespace :gem do
|
|
133
135
|
# of some bugginess in Rubygems' platform.rb.
|
134
136
|
#
|
135
137
|
case CONFIG['host_os']
|
136
|
-
when /
|
138
|
+
when /freebsd/i
|
137
139
|
spec.platform = Gem::Platform.new(['universal', 'freebsd'])
|
138
|
-
spec.
|
139
|
-
spec.files
|
140
|
-
spec.
|
141
|
-
spec.
|
142
|
-
spec.extensions = ['ext/bsd/extconf.rb']
|
140
|
+
spec.require_paths = ['lib', 'lib/freebsd']
|
141
|
+
spec.files += ['lib/freebsd/sys/proctable.rb']
|
142
|
+
spec.test_files << 'test/test_sys_proctable_freebsd.rb'
|
143
|
+
spec.add_dependency('ffi')
|
143
144
|
when /darwin/i
|
144
145
|
spec.platform = Gem::Platform.new(['universal', 'darwin'])
|
145
146
|
spec.files << 'ext/darwin/sys/proctable.c'
|
@@ -162,6 +163,11 @@ namespace :gem do
|
|
162
163
|
spec.require_paths = ['lib', 'lib/sunos']
|
163
164
|
spec.files += ['lib/sunos/sys/proctable.rb']
|
164
165
|
spec.test_files << 'test/test_sys_proctable_sunos.rb'
|
166
|
+
when /aix/i
|
167
|
+
spec.platform = Gem::Platform.new(['universal', 'aix5'])
|
168
|
+
spec.require_paths = ['lib', 'lib/aix']
|
169
|
+
spec.files += ['lib/aix/sys/proctable.rb']
|
170
|
+
spec.test_files << 'test/test_sys_proctable_aix.rb'
|
165
171
|
when /mswin|win32|dos|cygwin|mingw|windows/i
|
166
172
|
spec.platform = Gem::Platform.new(['universal', 'mingw32'])
|
167
173
|
spec.require_paths = ['lib', 'lib/windows']
|
@@ -172,7 +178,12 @@ namespace :gem do
|
|
172
178
|
# https://github.com/rubygems/rubygems/issues/147
|
173
179
|
spec.original_platform = spec.platform
|
174
180
|
|
175
|
-
Gem::
|
181
|
+
if Gem::VERSION < "2.0"
|
182
|
+
Gem::Builder.new(spec).build
|
183
|
+
else
|
184
|
+
require 'rubygems/package'
|
185
|
+
Gem::Package.build(spec)
|
186
|
+
end
|
176
187
|
end
|
177
188
|
|
178
189
|
desc 'Install the sys-proctable library as a gem'
|
data/lib/sunos/sys/proctable.rb
CHANGED
@@ -3,460 +3,448 @@
|
|
3
3
|
#
|
4
4
|
# A pure Ruby version of sys-proctable for SunOS 5.8 or later.
|
5
5
|
########################################################################
|
6
|
+
require 'ffi'
|
6
7
|
|
7
8
|
# The Sys module serves as a namespace only.
|
8
9
|
module Sys
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
#
|
205
|
-
#
|
206
|
-
#
|
207
|
-
#
|
208
|
-
#
|
209
|
-
#
|
210
|
-
#
|
211
|
-
#
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
struct.
|
313
|
-
struct.
|
314
|
-
|
315
|
-
struct.
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
address = fd.sysread(struct.argc * 4).unpack("L")[0]
|
323
|
-
|
324
|
-
struct.cmd_args = []
|
325
|
-
|
326
|
-
0.upto(struct.argc - 1){ |i|
|
327
|
-
fd.sysseek(address, IO::SEEK_SET)
|
328
|
-
data = fd.sysread(128)[/^[^\0]*/] # Null strip
|
329
|
-
struct.cmd_args << data
|
330
|
-
address += data.length + 1 # Add 1 for the space
|
331
|
-
}
|
332
|
-
|
333
|
-
# Get the environment hash associated with the process.
|
334
|
-
struct.environ = {}
|
335
|
-
|
336
|
-
fd.sysseek(struct.envp, IO::SEEK_SET)
|
337
|
-
|
338
|
-
env_address = fd.sysread(128).unpack("L")[0]
|
339
|
-
|
340
|
-
# TODO: Optimization potential here.
|
341
|
-
loop do
|
342
|
-
fd.sysseek(env_address, IO::SEEK_SET)
|
343
|
-
data = fd.sysread(1024)[/^[^\0]*/] # Null strip
|
344
|
-
break if data.empty?
|
345
|
-
key, value = data.split('=')
|
346
|
-
struct.environ[key] = value
|
347
|
-
env_address += data.length + 1 # Add 1 for the space
|
348
|
-
end
|
349
|
-
end
|
350
|
-
rescue Errno::EACCES, Errno::EOVERFLOW, EOFError
|
351
|
-
# Skip this if we don't have proper permissions, if there's
|
352
|
-
# no associated environment, or if there's a largefile issue.
|
353
|
-
rescue Errno::ENOENT
|
354
|
-
next # The process has terminated. Bail out!
|
355
|
-
end
|
356
|
-
|
357
|
-
### struct prusage
|
358
|
-
|
359
|
-
begin
|
360
|
-
prusage = 0.chr * 512
|
361
|
-
prusage = IO.read("/proc/#{file}/usage")
|
362
|
-
|
363
|
-
prusage_array = prusage.unpack(@prusage_pack_directive)
|
364
|
-
|
365
|
-
# skip pr_lwpid
|
366
|
-
struct.count = prusage_array[1]
|
367
|
-
struct.tstamp = prusage_array[2]
|
368
|
-
struct.create = prusage_array[4]
|
369
|
-
struct.term = prusage_array[6]
|
370
|
-
struct.rtime = prusage_array[8]
|
371
|
-
struct.utime = prusage_array[10]
|
372
|
-
struct.stime = prusage_array[12]
|
373
|
-
struct.ttime = prusage_array[14]
|
374
|
-
struct.tftime = prusage_array[16]
|
375
|
-
struct.dftime = prusage_array[18]
|
376
|
-
struct.kftime = prusage_array[20]
|
377
|
-
struct.ltime = prusage_array[22]
|
378
|
-
struct.slptime = prusage_array[24]
|
379
|
-
struct.wtime = prusage_array[26]
|
380
|
-
struct.stoptime = prusage_array[28]
|
381
|
-
# skip filltime
|
382
|
-
struct.minf = prusage_array[42]
|
383
|
-
struct.majf = prusage_array[43]
|
384
|
-
struct.nswap = prusage_array[44]
|
385
|
-
struct.inblk = prusage_array[45]
|
386
|
-
struct.oublk = prusage_array[46]
|
387
|
-
struct.msnd = prusage_array[47]
|
388
|
-
struct.mrcv = prusage_array[48]
|
389
|
-
struct.sigs = prusage_array[49]
|
390
|
-
struct.vctx = prusage_array[50]
|
391
|
-
struct.ictx = prusage_array[51]
|
392
|
-
struct.sysc = prusage_array[52]
|
393
|
-
struct.ioch = prusage_array[53]
|
394
|
-
rescue Errno::EACCES
|
395
|
-
# Do nothing if we lack permissions. Just move on.
|
396
|
-
rescue Errno::ENOENT
|
397
|
-
next # The process has terminated. Bail out!
|
398
|
-
end
|
399
|
-
|
400
|
-
# Information from /proc/<pid>/path. This is represented as a hash,
|
401
|
-
# with the symbolic link name as the key, and the file it links to
|
402
|
-
# as the value, or nil if it cannot be found.
|
403
|
-
#--
|
404
|
-
# Note that cwd information can be gathered from here, too.
|
405
|
-
struct.path = {}
|
406
|
-
|
407
|
-
Dir["/proc/#{file}/path/*"].each{ |entry|
|
408
|
-
link = File.readlink(entry) rescue nil
|
409
|
-
struct.path[File.basename(entry)] = link
|
11
|
+
# The ProcTable class encapsulates process table information.
|
12
|
+
class ProcTable
|
13
|
+
extend FFI::Library
|
14
|
+
|
15
|
+
class Error < StandardError; end
|
16
|
+
|
17
|
+
# There is no constructor
|
18
|
+
private_class_method :new
|
19
|
+
|
20
|
+
# The version of the sys-proctable library
|
21
|
+
VERSION = '0.9.4'
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
class Timeval < FFI::Struct
|
26
|
+
layout(:tv_sec, :time_t, :tv_usec, :time_t)
|
27
|
+
end
|
28
|
+
|
29
|
+
class LWPSInfo < FFI::Struct
|
30
|
+
layout(
|
31
|
+
:pr_flag, :int,
|
32
|
+
:pr_lwpid, :id_t,
|
33
|
+
:pr_addr, :uintptr_t,
|
34
|
+
:pr_wchan, :uintptr_t,
|
35
|
+
:pr_stype, :char,
|
36
|
+
:pr_state, :char,
|
37
|
+
:pr_sname, :char,
|
38
|
+
:pr_nice, :char,
|
39
|
+
:pr_syscall, :short,
|
40
|
+
:pr_oldpri, :char,
|
41
|
+
:pr_cpu, :char,
|
42
|
+
:pr_pri, :int,
|
43
|
+
:pr_pctcpu, :ushort_t,
|
44
|
+
:pr_pad, :ushort_t,
|
45
|
+
:pr_start, Timeval,
|
46
|
+
:pr_time, Timeval,
|
47
|
+
:pr_clname, [:char, 8],
|
48
|
+
:pr_name, [:char, 16],
|
49
|
+
:pr_onpro, :int,
|
50
|
+
:pr_bindpro, :int,
|
51
|
+
:pr_bindpset, :int,
|
52
|
+
:pr_filler, [:int, 5]
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
class PSInfo < FFI::Struct
|
57
|
+
layout(
|
58
|
+
:pr_flag, :int,
|
59
|
+
:pr_nlwp, :int,
|
60
|
+
:pr_pid, :pid_t,
|
61
|
+
:pr_ppid, :pid_t,
|
62
|
+
:pr_pgid, :pid_t,
|
63
|
+
:pr_sid, :pid_t,
|
64
|
+
:pr_uid, :uid_t,
|
65
|
+
:pr_euid, :uid_t,
|
66
|
+
:pr_gid, :gid_t,
|
67
|
+
:pr_egid, :gid_t,
|
68
|
+
:pr_addr, :uintptr_t,
|
69
|
+
:pr_size, :size_t,
|
70
|
+
:pr_rssize, :size_t,
|
71
|
+
:pr_pad1, :size_t,
|
72
|
+
:pr_ttydev, :dev_t,
|
73
|
+
:pr_pctcpu, :ushort_t,
|
74
|
+
:pr_pctmem, :ushort_t,
|
75
|
+
:pr_start, Timeval,
|
76
|
+
:pr_time, Timeval,
|
77
|
+
:pr_ctime, Timeval,
|
78
|
+
:pr_fname, [:char, 16],
|
79
|
+
:pr_psargs, [:char, 80],
|
80
|
+
:pr_wstat, :int,
|
81
|
+
:pr_argc, :int,
|
82
|
+
:pr_argv, :uintptr_t,
|
83
|
+
:pr_envp, :uintptr_t,
|
84
|
+
:pr_dmodel, :char,
|
85
|
+
:pr_pad2, [:char, 3],
|
86
|
+
:pr_taskid, :taskid_t,
|
87
|
+
:pr_projid, :projid_t,
|
88
|
+
:pr_nzomb, :int,
|
89
|
+
:pr_poolid, :poolid_t,
|
90
|
+
:pr_zoneid, :zoneid_t,
|
91
|
+
:pr_contract, :id_t,
|
92
|
+
:pr_filler, [:int, 1],
|
93
|
+
:pr_lwp, LWPSInfo
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
class PRUsage < FFI::Struct
|
98
|
+
layout(
|
99
|
+
:pr_lwpid, :id_t,
|
100
|
+
:pr_count, :int,
|
101
|
+
:pr_tstamp, Timeval,
|
102
|
+
:pr_create, Timeval,
|
103
|
+
:pr_term, Timeval,
|
104
|
+
:pr_rtime, Timeval,
|
105
|
+
:pr_utime, Timeval,
|
106
|
+
:pr_stime, Timeval,
|
107
|
+
:pr_ttime, Timeval,
|
108
|
+
:pr_tftime, Timeval,
|
109
|
+
:pr_dftime, Timeval,
|
110
|
+
:pr_kftime, Timeval,
|
111
|
+
:pr_ltime, Timeval,
|
112
|
+
:pr_slptime, Timeval,
|
113
|
+
:pr_wtime, Timeval,
|
114
|
+
:pr_stoptime, Timeval,
|
115
|
+
:pr_filetime, [Timeval,6],
|
116
|
+
:pr_minf, :ulong_t,
|
117
|
+
:pr_majf, :ulong_t,
|
118
|
+
:pr_nswap, :ulong_t,
|
119
|
+
:pr_inblk, :ulong_t,
|
120
|
+
:pr_oublk, :ulong_t,
|
121
|
+
:pr_msnd, :ulong_t,
|
122
|
+
:pr_mrcv, :ulong_t,
|
123
|
+
:pr_sigs, :ulong_t,
|
124
|
+
:pr_vctx, :ulong_t,
|
125
|
+
:pr_ictx, :ulong_t,
|
126
|
+
:pr_sysc, :ulong_t,
|
127
|
+
:pr_ioch, :ulong_t,
|
128
|
+
:filler, [:ulong_t, 10]
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
PRNODEV = (1<<FFI::Platform::ADDRESS_SIZE)-1
|
133
|
+
|
134
|
+
@fields = [
|
135
|
+
:flag, # process flags (deprecated)
|
136
|
+
:nlwp, # number of active lwp's in the process
|
137
|
+
:pid, # unique process id
|
138
|
+
:ppid, # process id of parent
|
139
|
+
:pgid, # pid of session leader
|
140
|
+
:sid, # session id
|
141
|
+
:uid, # real user id
|
142
|
+
:euid, # effective user id
|
143
|
+
:gid, # real group id
|
144
|
+
:egid, # effective group id
|
145
|
+
:addr, # address of the process
|
146
|
+
:size, # size of process in kbytes
|
147
|
+
:rssize, # resident set size in kbytes
|
148
|
+
:ttydev, # tty device (or PRNODEV)
|
149
|
+
:pctcpu, # % of recent cpu used by all lwp's
|
150
|
+
:pctmem, # % of system memory used by process
|
151
|
+
:start, # absolute process start time
|
152
|
+
:time, # usr + sys cpu time for this process
|
153
|
+
:ctime, # usr + sys cpu time for reaped children
|
154
|
+
:fname, # name of the exec'd file
|
155
|
+
:psargs, # initial characters argument list - same as cmdline
|
156
|
+
:wstat, # if a zombie, the wait status
|
157
|
+
:argc, # initial argument count
|
158
|
+
:argv, # address of initial argument vector
|
159
|
+
:envp, # address of initial environment vector
|
160
|
+
:dmodel, # data model of the process
|
161
|
+
:taskid, # task id
|
162
|
+
:projid, # project id
|
163
|
+
:nzomb, # number of zombie lwp's in the process
|
164
|
+
:poolid, # pool id
|
165
|
+
:zoneid, # zone id
|
166
|
+
:contract, # process contract
|
167
|
+
:lwpid, # lwp id
|
168
|
+
:wchan, # wait address for sleeping lwp
|
169
|
+
:stype, # synchronization event type
|
170
|
+
:state, # numeric lwp state
|
171
|
+
:sname, # printable character for state
|
172
|
+
:nice, # nice for cpu usage
|
173
|
+
:syscall, # system call number (if in syscall)
|
174
|
+
:pri, # priority
|
175
|
+
:clname, # scheduling class name
|
176
|
+
:name, # name of system lwp
|
177
|
+
:onpro, # processor which last ran thsi lwp
|
178
|
+
:bindpro, # processor to which lwp is bound
|
179
|
+
:bindpset, # processor set to which lwp is bound
|
180
|
+
:count, # number of contributing lwp's
|
181
|
+
:tstamp, # current time stamp
|
182
|
+
:create, # process/lwp creation time stamp
|
183
|
+
:term, # process/lwp termination time stamp
|
184
|
+
:rtime, # total lwp real (elapsed) time
|
185
|
+
:utime, # user level cpu time
|
186
|
+
:stime, # system call cpu time
|
187
|
+
:ttime, # other system trap cpu time
|
188
|
+
:tftime, # text page fault sleep time
|
189
|
+
:dftime, # text page fault sleep time
|
190
|
+
:kftime, # kernel page fault sleep time
|
191
|
+
:ltime, # user lock wait sleep time
|
192
|
+
:slptime, # all other sleep time
|
193
|
+
:wtime, # wait-cpu (latency) time
|
194
|
+
:stoptime, # stopped time
|
195
|
+
:minf, # minor page faults
|
196
|
+
:majf, # major page faults
|
197
|
+
:nswap, # swaps
|
198
|
+
:inblk, # input blocks
|
199
|
+
:oublk, # output blocks
|
200
|
+
:msnd, # messages sent
|
201
|
+
:mrcv, # messages received
|
202
|
+
:sigs, # signals received
|
203
|
+
:vctx, # voluntary context switches
|
204
|
+
:ictx, # involuntary context switches
|
205
|
+
:sysc, # system calls
|
206
|
+
:ioch, # chars read and written
|
207
|
+
:path, # array of symbolic link paths from /proc/<pid>/path
|
208
|
+
:contracts, # array symbolic link paths from /proc/<pid>/contracts
|
209
|
+
:fd, # array of used file descriptors
|
210
|
+
:cmd_args, # array of command line arguments
|
211
|
+
:environ, # hash of environment associated with the process,
|
212
|
+
:cmdline # joined cmd_args if present, otherwise psargs
|
213
|
+
]
|
214
|
+
|
215
|
+
public
|
216
|
+
|
217
|
+
ProcTableStruct = Struct.new("ProcTableStruct", *@fields) do
|
218
|
+
alias comm fname
|
219
|
+
end
|
220
|
+
|
221
|
+
# In block form, yields a ProcTableStruct for each process entry that you
|
222
|
+
# have rights to. This method returns an array of ProcTableStruct's in
|
223
|
+
# non-block form.
|
224
|
+
#
|
225
|
+
# If a +pid+ is provided, then only a single ProcTableStruct is yielded or
|
226
|
+
# returned, or nil if no process information is found for that +pid+.
|
227
|
+
#
|
228
|
+
# Example:
|
229
|
+
#
|
230
|
+
# # Iterate over all processes
|
231
|
+
# ProcTable.ps do |proc_info|
|
232
|
+
# p proc_info
|
233
|
+
# end
|
234
|
+
#
|
235
|
+
# # Print process table information for only pid 1001
|
236
|
+
# p ProcTable.ps(1001)
|
237
|
+
#
|
238
|
+
def self.ps(pid = nil)
|
239
|
+
raise TypeError unless pid.is_a?(Fixnum) if pid
|
240
|
+
|
241
|
+
array = block_given? ? nil : []
|
242
|
+
struct = nil
|
243
|
+
|
244
|
+
Dir.foreach("/proc") do |file|
|
245
|
+
next if file =~ /\D/ # Skip non-numeric entries under /proc
|
246
|
+
|
247
|
+
# Only return information for a given pid, if provided
|
248
|
+
next unless file.to_i == pid if pid
|
249
|
+
|
250
|
+
# Skip over any entries we don't have permissions to read
|
251
|
+
next unless File.readable?("/proc/#{file}/psinfo")
|
252
|
+
|
253
|
+
data = IO.read("/proc/#{file}/psinfo") rescue next
|
254
|
+
psinfo = PSInfo.new(FFI::MemoryPointer.from_string(data))
|
255
|
+
|
256
|
+
struct = ProcTableStruct.new
|
257
|
+
|
258
|
+
struct.flag = psinfo[:pr_flag]
|
259
|
+
struct.nlwp = psinfo[:pr_nlwp]
|
260
|
+
struct.pid = psinfo[:pr_pid]
|
261
|
+
struct.ppid = psinfo[:pr_ppid]
|
262
|
+
struct.pgid = psinfo[:pr_pgid]
|
263
|
+
struct.sid = psinfo[:pr_sid]
|
264
|
+
struct.uid = psinfo[:pr_uid]
|
265
|
+
struct.euid = psinfo[:pr_euid]
|
266
|
+
struct.gid = psinfo[:pr_gid]
|
267
|
+
struct.egid = psinfo[:pr_egid]
|
268
|
+
struct.addr = psinfo[:pr_addr]
|
269
|
+
struct.size = psinfo[:pr_size] * 1024 # bytes
|
270
|
+
struct.rssize = psinfo[:pr_rssize] * 1024 # bytes
|
271
|
+
struct.ttydev = psinfo[:pr_ttydev] == PRNODEV ? -1 : psinfo[:pr_ttydev]
|
272
|
+
struct.pctcpu = (psinfo[:pr_pctcpu] * 100).to_f / 0x8000
|
273
|
+
struct.pctmem = (psinfo[:pr_pctmem] * 100).to_f / 0x8000
|
274
|
+
|
275
|
+
struct.start = Time.at(psinfo[:pr_start][:tv_sec])
|
276
|
+
struct.time = psinfo[:pr_time][:tv_sec]
|
277
|
+
struct.ctime = psinfo[:pr_ctime][:tv_sec]
|
278
|
+
|
279
|
+
struct.fname = psinfo[:pr_fname].to_s
|
280
|
+
struct.psargs = psinfo[:pr_psargs].to_s
|
281
|
+
struct.wstat = psinfo[:pr_wstat]
|
282
|
+
struct.argc = psinfo[:pr_argc]
|
283
|
+
struct.argv = psinfo[:pr_argv]
|
284
|
+
struct.envp = psinfo[:pr_envp]
|
285
|
+
struct.dmodel = psinfo[:pr_dmodel]
|
286
|
+
|
287
|
+
struct.taskid = psinfo[:pr_taskid]
|
288
|
+
struct.projid = psinfo[:pr_projid]
|
289
|
+
struct.nzomb = psinfo[:pr_nzomb]
|
290
|
+
struct.poolid = psinfo[:pr_poolid]
|
291
|
+
struct.zoneid = psinfo[:pr_zoneid]
|
292
|
+
struct.contract = psinfo[:pr_contract]
|
293
|
+
|
294
|
+
### LWPSINFO struct info
|
295
|
+
|
296
|
+
struct.lwpid = psinfo[:pr_lwp][:pr_lwpid]
|
297
|
+
struct.wchan = psinfo[:pr_lwp][:pr_wchan]
|
298
|
+
struct.stype = psinfo[:pr_lwp][:pr_stype]
|
299
|
+
struct.state = psinfo[:pr_lwp][:pr_state]
|
300
|
+
struct.sname = psinfo[:pr_lwp][:pr_sname].chr
|
301
|
+
struct.nice = psinfo[:pr_lwp][:pr_nice]
|
302
|
+
struct.syscall = psinfo[:pr_lwp][:pr_syscall]
|
303
|
+
struct.pri = psinfo[:pr_lwp][:pr_pri]
|
304
|
+
struct.clname = psinfo[:pr_lwp][:pr_clname].to_s
|
305
|
+
struct.name = psinfo[:pr_lwp][:pr_name].to_s
|
306
|
+
struct.onpro = psinfo[:pr_lwp][:pr_onpro]
|
307
|
+
struct.bindpro = psinfo[:pr_lwp][:pr_bindpro]
|
308
|
+
struct.bindpset = psinfo[:pr_lwp][:pr_bindpset]
|
309
|
+
|
310
|
+
# Get the full command line out of /proc/<pid>/as.
|
311
|
+
begin
|
312
|
+
File.open("/proc/#{file}/as") do |fd|
|
313
|
+
fd.sysseek(struct.argv, IO::SEEK_SET)
|
314
|
+
address = fd.sysread(struct.argc * 4).unpack("L")[0]
|
315
|
+
|
316
|
+
struct.cmd_args = []
|
317
|
+
|
318
|
+
0.upto(struct.argc - 1){ |i|
|
319
|
+
fd.sysseek(address, IO::SEEK_SET)
|
320
|
+
data = fd.sysread(128)[/^[^\0]*/] # Null strip
|
321
|
+
struct.cmd_args << data
|
322
|
+
address += data.length + 1 # Add 1 for the space
|
410
323
|
}
|
411
324
|
|
412
|
-
#
|
413
|
-
|
414
|
-
# it links to as the value.
|
415
|
-
struct.contracts = {}
|
325
|
+
# Get the environment hash associated with the process.
|
326
|
+
struct.environ = {}
|
416
327
|
|
417
|
-
|
418
|
-
link = File.readlink(entry) rescue nil
|
419
|
-
struct.contracts[File.basename(entry)] = link
|
420
|
-
}
|
328
|
+
fd.sysseek(struct.envp, IO::SEEK_SET)
|
421
329
|
|
422
|
-
|
423
|
-
# numeric file descriptors used by the process.
|
424
|
-
struct.fd = Dir["/proc/#{file}/fd/*"].map{ |f| File.basename(f).to_i }
|
425
|
-
|
426
|
-
# Use the cmd_args as the cmdline if available. Otherwise use
|
427
|
-
# the psargs. This struct member is provided to provide a measure
|
428
|
-
# of consistency with the other platform implementations.
|
429
|
-
if struct.cmd_args && struct.cmd_args.length > 0
|
430
|
-
struct.cmdline = struct.cmd_args.join(' ')
|
431
|
-
else
|
432
|
-
struct.cmdline = struct.psargs
|
433
|
-
end
|
434
|
-
|
435
|
-
# This is read-only data
|
436
|
-
struct.freeze
|
330
|
+
env_address = fd.sysread(128).unpack("L")[0]
|
437
331
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
332
|
+
# TODO: Optimization potential here.
|
333
|
+
loop do
|
334
|
+
fd.sysseek(env_address, IO::SEEK_SET)
|
335
|
+
data = fd.sysread(1024)[/^[^\0]*/] # Null strip
|
336
|
+
break if data.empty?
|
337
|
+
key, value = data.split('=')
|
338
|
+
struct.environ[key] = value
|
339
|
+
env_address += data.length + 1 # Add 1 for the space
|
442
340
|
end
|
443
|
-
|
444
|
-
|
445
|
-
|
341
|
+
end
|
342
|
+
rescue Errno::EACCES, Errno::EOVERFLOW, EOFError, RangeError
|
343
|
+
# Skip this if we don't have proper permissions, if there's
|
344
|
+
# no associated environment, or if there's a largefile issue.
|
345
|
+
rescue Errno::ENOENT
|
346
|
+
next # The process has terminated. Bail out!
|
347
|
+
end
|
348
|
+
|
349
|
+
### struct prusage
|
350
|
+
|
351
|
+
begin
|
352
|
+
data = IO.read("/proc/#{file}/usage")
|
353
|
+
prusage = PRUsage.new(FFI::MemoryPointer.from_string(data))
|
354
|
+
|
355
|
+
struct.count = prusage[:pr_count]
|
356
|
+
struct.tstamp = prusage[:pr_tstamp][:tv_sec]
|
357
|
+
struct.create = prusage[:pr_create][:tv_sec]
|
358
|
+
struct.term = prusage[:pr_term][:tv_sec]
|
359
|
+
struct.rtime = prusage[:pr_rtime][:tv_sec]
|
360
|
+
struct.utime = prusage[:pr_utime][:tv_sec]
|
361
|
+
struct.stime = prusage[:pr_stime][:tv_sec]
|
362
|
+
struct.ttime = prusage[:pr_ttime][:tv_sec]
|
363
|
+
struct.tftime = prusage[:pr_tftime][:tv_sec]
|
364
|
+
struct.dftime = prusage[:pr_dftime][:tv_sec]
|
365
|
+
struct.kftime = prusage[:pr_kftime][:tv_sec]
|
366
|
+
struct.ltime = prusage[:pr_ltime][:tv_sec]
|
367
|
+
struct.slptime = prusage[:pr_slptime][:tv_sec]
|
368
|
+
struct.wtime = prusage[:pr_wtime][:tv_sec]
|
369
|
+
struct.stoptime = prusage[:pr_stoptime][:tv_sec]
|
370
|
+
struct.minf = prusage[:pr_minf]
|
371
|
+
struct.majf = prusage[:pr_majf]
|
372
|
+
struct.nswap = prusage[:pr_nswap]
|
373
|
+
struct.inblk = prusage[:pr_inblk]
|
374
|
+
struct.oublk = prusage[:pr_oublk]
|
375
|
+
struct.msnd = prusage[:pr_msnd]
|
376
|
+
struct.mrcv = prusage[:pr_mrcv]
|
377
|
+
struct.sigs = prusage[:pr_sigs]
|
378
|
+
struct.vctx = prusage[:pr_vctx]
|
379
|
+
struct.ictx = prusage[:pr_ictx]
|
380
|
+
struct.sysc = prusage[:pr_sysc]
|
381
|
+
struct.ioch = prusage[:pr_ioch]
|
382
|
+
rescue Errno::EACCES
|
383
|
+
# Do nothing if we lack permissions. Just move on.
|
384
|
+
rescue Errno::ENOENT
|
385
|
+
next # The process has terminated. Bail out!
|
386
|
+
end
|
387
|
+
|
388
|
+
# Information from /proc/<pid>/path. This is represented as a hash,
|
389
|
+
# with the symbolic link name as the key, and the file it links to
|
390
|
+
# as the value, or nil if it cannot be found.
|
391
|
+
#--
|
392
|
+
# Note that cwd information can be gathered from here, too.
|
393
|
+
struct.path = {}
|
394
|
+
|
395
|
+
Dir["/proc/#{file}/path/*"].each{ |entry|
|
396
|
+
link = File.readlink(entry) rescue nil
|
397
|
+
struct.path[File.basename(entry)] = link
|
398
|
+
}
|
399
|
+
|
400
|
+
# Information from /proc/<pid>/contracts. This is represented as
|
401
|
+
# a hash, with the symbolic link name as the key, and the file
|
402
|
+
# it links to as the value.
|
403
|
+
struct.contracts = {}
|
404
|
+
|
405
|
+
Dir["/proc/#{file}/contracts/*"].each{ |entry|
|
406
|
+
link = File.readlink(entry) rescue nil
|
407
|
+
struct.contracts[File.basename(entry)] = link
|
408
|
+
}
|
409
|
+
|
410
|
+
# Information from /proc/<pid>/fd. This returns an array of
|
411
|
+
# numeric file descriptors used by the process.
|
412
|
+
struct.fd = Dir["/proc/#{file}/fd/*"].map{ |f| File.basename(f).to_i }
|
413
|
+
|
414
|
+
# Use the cmd_args as the cmdline if available. Otherwise use
|
415
|
+
# the psargs. This struct member is provided to provide a measure
|
416
|
+
# of consistency with the other platform implementations.
|
417
|
+
if struct.cmd_args && struct.cmd_args.length > 0
|
418
|
+
struct.cmdline = struct.cmd_args.join(' ')
|
419
|
+
else
|
420
|
+
struct.cmdline = struct.psargs
|
421
|
+
end
|
422
|
+
|
423
|
+
# This is read-only data
|
424
|
+
struct.freeze
|
425
|
+
|
426
|
+
if block_given?
|
427
|
+
yield struct
|
428
|
+
else
|
429
|
+
array << struct
|
430
|
+
end
|
446
431
|
end
|
447
432
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
433
|
+
pid ? struct : array
|
434
|
+
end
|
435
|
+
|
436
|
+
# Returns an array of fields that each ProcTableStruct will contain. This
|
437
|
+
# may be useful if you want to know in advance what fields are available
|
438
|
+
# without having to perform at least one read of the /proc table.
|
439
|
+
#
|
440
|
+
# Example:
|
441
|
+
#
|
442
|
+
# Sys::ProcTable.fields.each{ |field|
|
443
|
+
# puts "Field: #{field}"
|
444
|
+
# }
|
445
|
+
#
|
446
|
+
def self.fields
|
447
|
+
@fields.map{ |f| f.to_s }
|
448
|
+
end
|
449
|
+
end
|
462
450
|
end
|