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 +17 -0
- data/LICENSE +21 -0
- data/Manifest +22 -0
- data/README +0 -0
- data/RUBYFORGE +40 -0
- data/Rakefile +26 -0
- data/lib/zerg_support/event_machine/connection_mocks.rb +46 -0
- data/lib/zerg_support/event_machine/frame_protocol.rb +68 -0
- data/lib/zerg_support/event_machine/object_protocol.rb +24 -0
- data/lib/zerg_support/gems.rb +60 -0
- data/lib/zerg_support/open_ssh.rb +104 -0
- data/lib/zerg_support/process.rb +225 -0
- data/lib/zerg_support/spawn.rb +185 -0
- data/lib/zerg_support.rb +21 -0
- data/test/fork_tree.rb +34 -0
- data/test/test_connection_mocks.rb +57 -0
- data/test/test_frame_protocol.rb +119 -0
- data/test/test_gems.rb +29 -0
- data/test/test_object_protocol.rb +55 -0
- data/test/test_open_ssh.rb +125 -0
- data/test/test_process.rb +78 -0
- data/test/test_spawn.rb +163 -0
- data/zerg_support.gemspec +41 -0
- metadata +125 -0
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
|