costan-zerg_support 0.0.9

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.
data/CHANGELOG ADDED
@@ -0,0 +1,17 @@
1
+ v0.0.9. Changed ProcTable wrapper to deal with API change in 0.8.0.
2
+
3
+ v0.0.8. Extension emulation works in Windows without a build environment.
4
+
5
+ v0.0.7. Windows support.
6
+
7
+ v0.0.6. Event Machine protocols for talking to zergling daemons.
8
+
9
+ v0.0.5. Added forgotten spawn.rb to Manifest.
10
+
11
+ v0.0.4. Polished implementation and tests for process spawning.
12
+
13
+ v0.0.3. Preliminary implementation of process spawning.
14
+
15
+ v0.0.2. Process management.
16
+
17
+ v0.0.1. Initial release. Support code for zerg* gem installation.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2008 Victor Costan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Manifest ADDED
@@ -0,0 +1,22 @@
1
+ CHANGELOG
2
+ lib/zerg_support/event_machine/connection_mocks.rb
3
+ lib/zerg_support/event_machine/frame_protocol.rb
4
+ lib/zerg_support/event_machine/object_protocol.rb
5
+ lib/zerg_support/gems.rb
6
+ lib/zerg_support/open_ssh.rb
7
+ lib/zerg_support/process.rb
8
+ lib/zerg_support/spawn.rb
9
+ lib/zerg_support.rb
10
+ LICENSE
11
+ Manifest
12
+ Rakefile
13
+ README
14
+ RUBYFORGE
15
+ test/fork_tree.rb
16
+ test/test_connection_mocks.rb
17
+ test/test_frame_protocol.rb
18
+ test/test_gems.rb
19
+ test/test_object_protocol.rb
20
+ test/test_open_ssh.rb
21
+ test/test_process.rb
22
+ test/test_spawn.rb
data/README ADDED
File without changes
data/RUBYFORGE ADDED
@@ -0,0 +1,40 @@
1
+ Quickstart for Rubyforge:
2
+
3
+ 1) Get the code
4
+ git clone git@github.com:costan/zerg_support.git
5
+
6
+ 2) Install the rubyforge gem
7
+ gem install rubyforge
8
+
9
+ 3) Save your rubyforge.org login information
10
+ rubyforge setup
11
+
12
+ 4) Get a login cookie
13
+ rubyforge login
14
+
15
+ 5) Get project configuration from rubyforge
16
+ rubyforge config zerglings
17
+
18
+ 6) Create a package to release under
19
+ rubyforge create_package zerglings zerg_support
20
+
21
+ 7) Install the echoe gem (required for building this gem)
22
+ gem install echoe
23
+
24
+ 8) Release the gem (finally!)
25
+ rake release
26
+
27
+ Releasing a new gemspec to Github
28
+
29
+ 1) Build the gem
30
+ rake package
31
+
32
+ 2) Copy the spec
33
+ cp pkg/zerg_support-*/zerg_support.gemspec .
34
+
35
+ 3) Commit the spec
36
+ git add zerg_support.gemspec
37
+ git commit -m "New gemspec, for Github distribution."
38
+
39
+ 4) Push to Github
40
+ git push
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ gem 'echoe'
3
+ require 'echoe'
4
+
5
+ Echoe.new('zerg_support') do |p|
6
+ p.project = 'zerglings' # rubyforge project
7
+
8
+ p.author = 'Victor Costan'
9
+ p.email = 'victor@zergling.net'
10
+ p.summary = 'Support libraries used by Zergling.Net deployment code.'
11
+ p.url = 'http://www.zergling.net'
12
+
13
+ p.need_tar_gz = !Platform.windows?
14
+ p.need_zip = !Platform.windows?
15
+ p.rdoc_pattern = /^(lib|bin|tasks|ext)|^BUILD|^README|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
16
+
17
+ p.development_dependencies = ["echoe >=3.0.2",
18
+ "event_machine >=0.12.2",
19
+ "flexmock >=0.8.3",
20
+ ]
21
+ end
22
+
23
+ if $0 == __FILE__
24
+ Rake.application = Rake::Application.new
25
+ Rake.application.run
26
+ end
@@ -0,0 +1,46 @@
1
+ # Mocks the sending end of an EventMachine connection.
2
+ # The sent data is concatenated in a string available by calling #string.
3
+ class Zerg::Support::EventMachine::SendMock
4
+ attr_reader :string
5
+
6
+ def initialize
7
+ @string = ''
8
+ end
9
+
10
+ def send_data(data)
11
+ @string << data
12
+ end
13
+ end
14
+
15
+ # Mocks the receiving end of an EventMachine connection.
16
+ # The data to be received is passed as an array of strings to the constructor.
17
+ # Calling #replay mocks receiving the data.
18
+ class Zerg::Support::EventMachine::ReceiveMock
19
+ attr_accessor :strings
20
+ attr_accessor :objects
21
+
22
+ def initialize(strings = [''])
23
+ @strings = strings
24
+ @objects = []
25
+ end
26
+
27
+ # Simulates receiving all the given strings as data from Event Machine.
28
+ def replay
29
+ @strings.each { |str| receive_data str }
30
+ self
31
+ end
32
+
33
+ #:nodoc:
34
+ def receive__object(object)
35
+ @objects << object
36
+ end
37
+
38
+ # Declares the name of the object to be received. For instance, a frame
39
+ # protocol would use :frame for name. This generates a receive_frame method,
40
+ # and a frames accessor.
41
+ def self.object_name(name)
42
+ alias_method "receive_#{name}".to_sym, :receive__object
43
+ return if name == :object
44
+ alias_method "#{name}s".to_sym, :objects
45
+ end
46
+ end
@@ -0,0 +1,68 @@
1
+ #:nodoc: namespace
2
+ module Zerg::Support::EventMachine
3
+
4
+ # Event Machine protocol for sending and receiving discrete-sized frames
5
+ module FrameProtocol
6
+ #:nodoc: This is called by Event Machine when TCP stream data is available.
7
+ def receive_data(data)
8
+ @frame_protocol_varsize ||= ''
9
+
10
+ i = 0
11
+ loop do
12
+ while @frame_protocol_buffer.nil? and i < data.size
13
+ @frame_protocol_varsize << data[i]
14
+ if (data[i] & 0x80) == 0
15
+ @frame_protocol_bytes_left =
16
+ FrameProtocol.decode_natural @frame_protocol_varsize
17
+ @frame_protocol_buffer = ''
18
+ end
19
+ i += 1
20
+ end
21
+
22
+ return if @frame_protocol_buffer.nil?
23
+ break if @frame_protocol_bytes_left > data.size - i
24
+
25
+ receive_frame @frame_protocol_buffer + data[i, @frame_protocol_bytes_left]
26
+ @frame_protocol_varsize, @frame_protocol_buffer = '', nil
27
+ i += @frame_protocol_bytes_left
28
+ end
29
+
30
+ @frame_protocol_buffer << data[i..-1]
31
+ @frame_protocol_bytes_left -= data.size-i
32
+ end
33
+
34
+ # Override to process incoming frames.
35
+ def receive_frame(frame_data); end
36
+
37
+ # Sends a frame via the underlying Event Machine TCP stream.
38
+ def send_frame(frame_data)
39
+ encoded_length = FrameProtocol.encode_natural(frame_data.length)
40
+ send_data encoded_length + frame_data
41
+ end
42
+
43
+ #:nodoc: Encodes a natural (non-negative) integer into a string.
44
+ def self.encode_natural(number)
45
+ string = ''
46
+ loop do
47
+ number, byte = number.divmod(0x80)
48
+ string << (byte | ((number > 0) ? 0x80 : 0x00))
49
+ break if number == 0
50
+ end
51
+ string
52
+ end
53
+
54
+ #:nodoc: Decodes a natural (non-negative) integer from a string.
55
+ def self.decode_natural(string)
56
+ number = 0
57
+ multiplier = 1
58
+ string.each_byte do |byte|
59
+ more, number_bits = byte.divmod 0x80
60
+ number += number_bits * multiplier
61
+ break if more == 0
62
+ multiplier *= 0x80
63
+ end
64
+ return number
65
+ end
66
+ end
67
+
68
+ end # namespace Zerg::Support::EventMachine
@@ -0,0 +1,24 @@
1
+ require 'yaml'
2
+
3
+ #:nodoc: namespace
4
+ module Zerg::Support::EventMachine
5
+
6
+ # Event Machine protocol for sending serializable objects.
7
+ module ObjectProtocol
8
+ include FrameProtocol
9
+
10
+ # Send a serialized object.
11
+ def send_object(object)
12
+ send_frame YAML.dump(object)
13
+ end
14
+
15
+ #:nodoc: Processes an incoming frame and de-serializes the object in it.
16
+ def receive_frame(frame_data)
17
+ receive_object YAML.load(frame_data)
18
+ end
19
+
20
+ # Override to process incoming objects.
21
+ def receive_object(object); end
22
+ end
23
+
24
+ end # namespace Zerg::Support::EventMachine
@@ -0,0 +1,60 @@
1
+ # methods used in gem installation hooks
2
+ module Zerg::Support::Gems
3
+ # called by ensure_on_path for Windows systems
4
+ def self.ensure_on_windows_path(bin_file)
5
+ bat_file = File.expand_path(File.join(ENV["WINDIR"],
6
+ File.basename(bin_file) + ".bat"))
7
+ begin
8
+ File.open(bat_file, 'w') do |f|
9
+ f.write <<END_BATCH
10
+ @ECHO OFF
11
+ IF NOT "%~f0" == "~f0" GOTO :WinNT
12
+ @"ruby.exe" "#{File.expand_path(bin_file)}" %1 %2 %3 %4 %5 %6 %7 %8 %9
13
+ GOTO :EOF
14
+ :WinNT
15
+ @"ruby.exe" "#{File.expand_path(bin_file)}" %*
16
+ END_BATCH
17
+ end
18
+ #rescue
19
+ # if anything goes wrong we probably don't have permissions (hi Vista?)
20
+ end
21
+ end
22
+
23
+ # called by ensure_on_path for UNIX systems
24
+ def self.ensure_on_unix_path(bin_file)
25
+ path = "/usr/bin/#{File.basename bin_file}"
26
+ begin
27
+ # using a link so the gem can be updated and the link still works
28
+ FileUtils.ln_s(bin_file, path, :force)
29
+ rescue
30
+ # if anything goes wrong we probably don't have permissions
31
+ # oh well at least we tried
32
+ end
33
+ end
34
+
35
+ # ensures that bin_file can be invoked from a shell
36
+ def self.ensure_on_path(bin_script)
37
+ caller_trace = Kernel.caller.first
38
+ caller_match = /^(.*)\:\d+\:in /.match(caller_trace) ||
39
+ /^(.*)\:\d+$/.match(caller_trace)
40
+ bin_file = File.expand_path caller_match[1] + '/../../../bin/' + bin_script
41
+ # this is a cheat to get the binary in the right place on stubborn Debians
42
+ if RUBY_PLATFORM =~ /win/ and RUBY_PLATFORM !~ /darwin/
43
+ ensure_on_windows_path bin_file
44
+ else
45
+ ensure_on_unix_path bin_file
46
+ end
47
+ end
48
+
49
+ # tricks rubygems into believeing that the extension compiled and worked out
50
+ def self.emulate_extension_install(extension_name)
51
+ File.open('Makefile', 'w') { |f| f.write "all:\n\ninstall:\n\n" }
52
+ File.open('make', 'w') do |f|
53
+ f.write '#!/bin/sh'
54
+ f.chmod f.stat.mode | 0111
55
+ end
56
+ File.open(extension_name + '.so', 'w') {}
57
+ File.open(extension_name + '.dll', 'w') {}
58
+ File.open('nmake.bat', 'w') { |f| }
59
+ end
60
+ end
@@ -0,0 +1,104 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+
4
+ # Tools for managing openssh cryptographic material
5
+ module Zerg::Support::OpenSSH
6
+ # Extracts the keys from a file of known_hosts format.
7
+ def self.known_hosts_keys(io)
8
+ io.each_line do |line|
9
+
10
+ end
11
+ end
12
+
13
+ # The components in a openssh .pub / known_host RSA public key.
14
+ RSA_COMPONENTS = ['ssh-rsa', :e, :n]
15
+ # The components in a openssh .pub / known_host DSA public key.
16
+ DSA_COMPONENTS = ['ssh-dss', :p, :q, :g, :pub_key]
17
+
18
+ # Encodes a key's public part in the format found in .pub & known_hosts files.
19
+ def self.encode_pubkey(key)
20
+ case key
21
+ when OpenSSL::PKey::RSA
22
+ components = RSA_COMPONENTS
23
+ when OpenSSL::PKey::DSA
24
+ components = DSA_COMPONENTS
25
+ else
26
+ raise "Unsupported key type #{key.class.name}"
27
+ end
28
+ components.map! { |c| c.kind_of?(Symbol) ? encode_mpi(key.send(c)) : c }
29
+ # ruby tries to be helpful and adds new lines every 60 bytes :(
30
+ [pack_pubkey_components(components)].pack('m').gsub("\n", '')
31
+ end
32
+
33
+ # Decodes an openssh public key from the format of .pub & known_hosts files.
34
+ def self.decode_pubkey(string)
35
+ components = unpack_pubkey_components Base64.decode64(string)
36
+ case components.first
37
+ when RSA_COMPONENTS.first
38
+ ops = RSA_COMPONENTS.zip components
39
+ key = OpenSSL::PKey::RSA.new
40
+ when DSA_COMPONENTS.first
41
+ ops = DSA_COMPONENTS.zip components
42
+ key = OpenSSL::PKey::DSA.new
43
+ else
44
+ raise "Unsupported key type #{components.first}"
45
+ end
46
+ ops.each do |o|
47
+ next unless o.first.kind_of? Symbol
48
+ key.send "#{o.first}=", decode_mpi(o.last)
49
+ end
50
+ return key
51
+ end
52
+
53
+ # Loads a serialized key from an IO instance (File, StringIO).
54
+ def self.load_key(io)
55
+ key_from_string io.read
56
+ end
57
+
58
+ # Reads a serialized key from a string.
59
+ def self.key_from_string(serialized_key)
60
+ header = first_line serialized_key
61
+ if header.index 'RSA'
62
+ OpenSSL::PKey::RSA.new serialized_key
63
+ elsif header.index 'DSA'
64
+ OpenSSL::PKey::DSA.new serialized_key
65
+ else
66
+ raise 'Unknown key type'
67
+ end
68
+ end
69
+
70
+ # Extracts the first line of a string.
71
+ def self.first_line(string)
72
+ string[0, string.index(/\r|\n/) || string.len]
73
+ end
74
+
75
+ # Unpacks the string components in an openssh-encoded pubkey.
76
+ def self.unpack_pubkey_components(str)
77
+ cs = []
78
+ i = 0
79
+ while i < str.length
80
+ len = str[i, 4].unpack('N').first
81
+ cs << str[i + 4, len]
82
+ i += 4 + len
83
+ end
84
+ return cs
85
+ end
86
+
87
+ # Packs string components into an openssh-encoded pubkey.
88
+ def self.pack_pubkey_components(strings)
89
+ (strings.map { |s| [s.length].pack('N') }).zip(strings).flatten.join
90
+ end
91
+
92
+ # Decodes an openssh-mpi-encoded integer.
93
+ def self.decode_mpi(mpi_str)
94
+ mpi_str.unpack('C*').inject(0) { |acc, c| (acc << 8) | c }
95
+ end
96
+
97
+ # Encodes an openssh-mpi-encoded integer.
98
+ def self.encode_mpi(n)
99
+ chars, n = [], n.to_i
100
+ chars << (n & 0xff) and n >>= 8 while n != 0
101
+ chars << 0 if chars.empty? or chars.last >= 0x80
102
+ chars.reverse.pack('C*')
103
+ end
104
+ end
@@ -0,0 +1,225 @@
1
+ require 'time'
2
+
3
+ # process management
4
+ module Zerg::Support::Process
5
+ @@no_multiple_pids = false
6
+
7
+ unless RUBY_PLATFORM =~ /win/ and RUBY_PLATFORM !~ /darwin/
8
+ # Translates process info given by Sys::ProcTable into our own nicer format.
9
+ def self.xlate_process_info(low_info)
10
+ {
11
+ :pid => low_info.pid,
12
+ :parent_pid => low_info.ppid,
13
+ :real_uid => low_info.ruid || -1,
14
+ :real_gid => low_info.rgid || -1,
15
+ :start_time => low_info.start,
16
+ :nice => low_info.nice || 0,
17
+ :priority => low_info.priority || 0,
18
+ :syscall_priority => low_info.user_priority || 0,
19
+ :resident_size => (low_info.id_rss || 0) * 1024,
20
+ :code_size => (low_info.ix_rss || 0) * 1024,
21
+ :virtual_size => (low_info.is_rss || 0) * 1024,
22
+ :percent_cpu => low_info.pctcpu,
23
+ :percent_ram => low_info.pctmem,
24
+ :state => low_info.state,
25
+ :command_line => low_info.cmdline
26
+ }
27
+ end
28
+
29
+ else
30
+ #:nodoc:
31
+ def self.xlate_process_info(low_info)
32
+ state = low_info.execution_state.to_s
33
+ state << 's' if low_info.session_id == 0
34
+
35
+ {
36
+ :pid => low_info.pid,
37
+ :parent_pid => low_info.ppid,
38
+ :real_uid => -1,
39
+ :real_gid => -1,
40
+ :start_time => low_info.creation_date,
41
+ :nice => 0,
42
+ :priority => low_info.priority || 0,
43
+ :syscall_priority => 0,
44
+ :resident_size => low_info.working_set_size || 0,
45
+ :code_size => 0,
46
+ :virtual_size => low_info.virtual_size || 0,
47
+ :percent_cpu => 0,
48
+ :percent_ram => 0,
49
+ :state => state,
50
+ :command_line => low_info.cmdline || low_info.comm || ''
51
+ }
52
+ end
53
+ end
54
+
55
+ # Collects information about a single process. Returns nil
56
+ def self.process_info(pid)
57
+ pinfo = ps pid
58
+ pinfo ? xlate_process_info(pinfo) : nil
59
+ end
60
+
61
+ # Collects information about processes with the given pids.
62
+ # Returns all processes if no list of pids is given.
63
+ def self.processes(pids = nil)
64
+ if @@no_multiple_pids and !pids.empty
65
+ pids.map { |pid| process_info pid }
66
+ else
67
+ begin
68
+ if pids
69
+ ps_result = ps(pids)
70
+ else
71
+ ps_result = ps
72
+ end
73
+ ps_result.map { |pinfo| xlate_process_info pinfo }
74
+ rescue TypeError
75
+ # we're using the real sys-proctable, and its ps doesn't like multiple
76
+ # arguments
77
+ @@no_multiple_pids = true
78
+ processes pids
79
+ end
80
+ end
81
+ end
82
+
83
+ # Collects information about processes with the given pids.
84
+ # The information is returned indexed by the processes' pids.
85
+ def self.processes_by_id(pids = nil)
86
+ retval = {}
87
+ self.processes(pids).each { |pinfo| retval[pinfo[:pid]] = pinfo }
88
+ return retval
89
+ end
90
+
91
+ # Returns information about the descendants of the process with the given pid.
92
+ def self.process_tree(*root_pids)
93
+ procs_by_ppid = {}
94
+ proc_list = self.processes_by_id
95
+ proc_list.each do |pid, pinfo|
96
+ procs_by_ppid[pinfo[:parent_pid]] ||= []
97
+ procs_by_ppid[pinfo[:parent_pid]] << pinfo
98
+ end
99
+
100
+ proc_queue = root_pids.map { |pid| proc_list[pid] }.select { |pinfo| pinfo }
101
+
102
+ index = 0
103
+ while index < proc_queue.length
104
+ pid, index = proc_queue[index][:pid], index + 1
105
+ next unless procs_by_ppid.has_key? pid
106
+ proc_queue += procs_by_ppid[pid]
107
+ end
108
+ return proc_queue
109
+ end
110
+
111
+
112
+ if RUBY_PLATFORM =~ /win/ and RUBY_PLATFORM !~ /darwin/
113
+ #:nodoc: Wrapper around Process.kill that works on all platforms.
114
+ def self.kill_primitive(pid, force = false)
115
+ Process.kill(force ? 9 : 4, pid)
116
+ end
117
+ else
118
+ #:nodoc: Wrapper around Process.kill that works on all platforms.
119
+ def self.kill_primitive(pid, force = false)
120
+ Process.kill(force ? 'KILL' : 'TERM', pid)
121
+ end
122
+ end
123
+
124
+ # Kills the process with the given pid.
125
+ def self.kill(pid)
126
+ begin
127
+ self.kill_primitive pid, false
128
+ Thread.new(pid) do |victim_pid|
129
+ Kernel.sleep 0.2
130
+ self.kill_primitive pid, true
131
+ end
132
+ rescue
133
+ # we probably don't have the right to kill the process
134
+ print "#{$!.class.name}: #{$!}\n"
135
+ end
136
+ end
137
+
138
+ # Kills all the processes descending from the process with the given pid.
139
+ def self.kill_tree(root_pid)
140
+ self.process_tree(root_pid).each do |pinfo|
141
+ self.kill pinfo[:pid]
142
+ end
143
+ end
144
+ end
145
+
146
+
147
+ ## Backend for process information listing
148
+
149
+ begin
150
+ raise 'Use ps' unless RUBY_PLATFORM =~ /win/ and RUBY_PLATFORM !~ /darwin/
151
+
152
+ require 'time'
153
+ require 'sys/proctable'
154
+
155
+ module Zerg::Support::Process
156
+ def self.ps(pid = nil)
157
+ Sys::ProcTable.ps pid
158
+ end
159
+ end
160
+ rescue Exception
161
+ # Emulate the sys-proctable gem using the ps command.
162
+ # This will be slower than having native syscalls, but at least it doesn't
163
+ # crash ruby. (yes, sys-proctable 0.7.6, I mean you!)
164
+
165
+ #:nodoc: all
166
+ module Zerg::Support::ProcTable
167
+ class ProcInfo
168
+ def initialize(pid, ppid, ruid, rgid, uid, gid, start, nice, rss, rssize,
169
+ text_size, vsz, user_time, total_time, pctcpu, pctmem,
170
+ priority, user_priority, state, cmdline)
171
+ @pid, @ppid, @ruid, @rgid, @uid, @gid, @nice, @priority,
172
+ @user_priority = *([pid, ppid, ruid, rgid, uid, gid, nice,
173
+ priority, user_priority].map { |s| s.to_i })
174
+ @start = Time.parse start
175
+ @ix_rss, @id_rss, @is_rss = text_size.to_f, rssize.to_f, vsz.to_f
176
+ @pctcpu, @pctmem = *([pctcpu, pctmem].map { |s| s.to_f })
177
+ @cmdline = cmdline
178
+
179
+ # TODO(victor): translate UNIX strings into something else
180
+ @state = state
181
+ end
182
+ attr_reader :pid, :ppid, :ruid, :rgid, :uid, :gid, :nice, :priority,
183
+ :user_priority, :start, :ix_rss, :id_rss, :is_rss, :pctcpu,
184
+ :pctmem, :state, :cmdline
185
+ end
186
+
187
+ @@ps_root_cmdline = 'ps -o pid,ppid,ruid,rgid,uid,gid' +
188
+ ',lstart="STARTED_________________________________"' +
189
+ ',nice,rss,rssize,tsiz,vsz,utime,cputime,pcpu="PCPU_"' +
190
+ ',pmem="PMEM_",pri,usrpri,stat,command'
191
+
192
+ @@ps_all_cmdline = @@ps_root_cmdline + ' -A'
193
+ @@ps_some_cmdline = @@ps_root_cmdline + ' -p '
194
+
195
+ def self.ps(pid = nil)
196
+ pids = pid ? (pid.kind_of?(Enumerable) ? pid : [pid]) : []
197
+ retval = []
198
+ ps_cmdline = pids.empty? ? @@ps_all_cmdline :
199
+ @@ps_some_cmdline + pids.join(',')
200
+ ps_output = Kernel.` ps_cmdline
201
+ header_splits = nil
202
+ ps_output.each_line do |pline|
203
+ if header_splits
204
+ # result line, break it up
205
+ retval << ProcInfo.new(*(header_splits.map { |hs| pline[hs].strip }))
206
+ else
207
+ # first line, compute headers
208
+ lengths = pline.split(/\S\s/).map { |h| h.length + 2 }
209
+ header_splits, sum = [], 0
210
+ lengths.each_index do |i|
211
+ header_splits[i] = Range.new sum, sum += lengths[i], true
212
+ end
213
+ header_splits[-1] = Range.new header_splits[-1].begin, -1, false
214
+ end
215
+ end
216
+ return pids.length == 1 ? retval.first : retval
217
+ end
218
+ end
219
+
220
+ module Zerg::Support::Process
221
+ def self.ps(pid = nil)
222
+ Zerg::Support::ProcTable.ps pid
223
+ end
224
+ end
225
+ end