costan-zerg_support 0.0.9

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