zerg_support 0.0.1 → 0.0.2
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 +2 -0
- data/Manifest +6 -4
- data/lib/zerg_support.rb +1 -0
- data/lib/zerg_support/process.rb +173 -0
- data/test/test_gems.rb +1 -1
- data/test/test_process.rb +68 -0
- data/zerg_support.gemspec +7 -9
- metadata +12 -8
data/CHANGELOG
CHANGED
data/Manifest
CHANGED
data/lib/zerg_support.rb
CHANGED
@@ -0,0 +1,173 @@
|
|
1
|
+
# process management
|
2
|
+
# extends Base with process management features
|
3
|
+
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
module Zerg::Support::Process
|
7
|
+
@@no_multiple_pids = false
|
8
|
+
|
9
|
+
# Translates process info given by Sys::ProcTable into our own nicer format.
|
10
|
+
def self.xlate_process_info(low_info)
|
11
|
+
{
|
12
|
+
:pid => low_info.pid,
|
13
|
+
:parent_pid => low_info.ppid,
|
14
|
+
:real_uid => low_info.ruid || -1,
|
15
|
+
:real_gid => low_info.rgid || -1,
|
16
|
+
:start_time => low_info.start,
|
17
|
+
:nice => low_info.nice || 0,
|
18
|
+
:priority => low_info.priority || 0,
|
19
|
+
:syscall_priority => low_info.user_priority || 0,
|
20
|
+
:resident_size => low_info.id_rss || 0,
|
21
|
+
:code_size => low_info.ix_rss || 0,
|
22
|
+
:virtual_size => low_info.is_rss || 0,
|
23
|
+
:percent_cpu => low_info.pctcpu,
|
24
|
+
:percent_ram => low_info.pctmem,
|
25
|
+
:state => low_info.state,
|
26
|
+
:command_line => low_info.cmdline
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Collects information about a single process. Returns nil
|
31
|
+
def self.process_info(pid)
|
32
|
+
pinfo = Sys::ProcTable.ps pid
|
33
|
+
pinfo ? xlate_process_info(pinfo) : nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# Collects information about processes with the given pids.
|
37
|
+
# Returns all processes if no list of pids is given.
|
38
|
+
def self.processes(pids = nil)
|
39
|
+
if @@no_multiple_pids and !pids.empty
|
40
|
+
pids.map { |pid| process_info pid }
|
41
|
+
else
|
42
|
+
begin
|
43
|
+
if pids
|
44
|
+
ps_result = Sys::ProcTable.ps(pids)
|
45
|
+
else
|
46
|
+
ps_result = Sys::ProcTable.ps
|
47
|
+
end
|
48
|
+
ps_result.map { |pinfo| xlate_process_info pinfo }
|
49
|
+
rescue TypeError
|
50
|
+
# we're using the real sys-proctable, and its ps doesn't like multiple
|
51
|
+
# arguments
|
52
|
+
@@no_multiple_pids = true
|
53
|
+
processes pids
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Collects information about processes with the given pids.
|
59
|
+
# The information is returned indexed by the processes' pids.
|
60
|
+
def self.processes_by_id(pids = nil)
|
61
|
+
retval = {}
|
62
|
+
self.processes(pids).each { |pinfo| retval[pinfo[:pid]] = pinfo }
|
63
|
+
return retval
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns information about the descendants of the process with the given pid.
|
67
|
+
def self.process_tree(*root_pids)
|
68
|
+
procs_by_ppid = {}
|
69
|
+
proc_list = self.processes_by_id
|
70
|
+
proc_list.each do |pid, pinfo|
|
71
|
+
procs_by_ppid[pinfo[:parent_pid]] ||= []
|
72
|
+
procs_by_ppid[pinfo[:parent_pid]] << pinfo
|
73
|
+
end
|
74
|
+
|
75
|
+
proc_queue = root_pids.map { |pid| proc_list[pid] }.select { |pinfo| pinfo }
|
76
|
+
|
77
|
+
index = 0
|
78
|
+
while index < proc_queue.length
|
79
|
+
pid, index = proc_queue[index][:pid], index + 1
|
80
|
+
next unless procs_by_ppid.has_key? pid
|
81
|
+
proc_queue += procs_by_ppid[pid]
|
82
|
+
end
|
83
|
+
return proc_queue
|
84
|
+
end
|
85
|
+
|
86
|
+
# Kills the process with the given pid.
|
87
|
+
def self.kill(pid)
|
88
|
+
begin
|
89
|
+
Process.kill "TERM", pid
|
90
|
+
Thread.new(pid) do |victim_pid|
|
91
|
+
Kernel.sleep 0.2
|
92
|
+
Process.kill "KILL", victim_pid
|
93
|
+
end
|
94
|
+
rescue
|
95
|
+
# we probably don't have the right to kill the process
|
96
|
+
print "#{$!.class.name}: #{$!}\n"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Kills all the processes descending from the process with the given pid.
|
101
|
+
def self.kill_tree(root_pid)
|
102
|
+
self.process_tree(root_pid).each do |pinfo|
|
103
|
+
self.kill pinfo[:pid]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
## Backend for process information listing
|
110
|
+
|
111
|
+
begin
|
112
|
+
raise 'Use ps' unless RUBY_PLAYFORM =~ /win/ and RUBY_PLATFORM !~ /darwin/
|
113
|
+
require 'sys/proctable'
|
114
|
+
rescue Exception
|
115
|
+
# Emulate the sys-proctable gem using the ps command.
|
116
|
+
# This will be slower than having native syscalls, but at least it doesn't
|
117
|
+
# crash ruby. (yes, sys-proctable 0.7.6, I mean you!)
|
118
|
+
|
119
|
+
#:nodoc: all
|
120
|
+
module Sys; end
|
121
|
+
|
122
|
+
#:nodoc: all
|
123
|
+
module Sys::ProcTable
|
124
|
+
class ProcInfo
|
125
|
+
def initialize(pid, ppid, ruid, rgid, uid, gid, start, nice, rss, rssize,
|
126
|
+
text_size, vsz, user_time, total_time, pctcpu, pctmem,
|
127
|
+
priority, user_priority, state, cmdline)
|
128
|
+
@pid, @ppid, @ruid, @rgid, @uid, @gid, @nice, @priority,
|
129
|
+
@user_priority = *([pid, ppid, ruid, rgid, uid, gid, nice,
|
130
|
+
priority, user_priority].map { |s| s.to_i })
|
131
|
+
@start = Time.parse start
|
132
|
+
@ix_rss, @id_rss, @is_rss = text_size.to_f, rssize.to_f, vsz.to_f
|
133
|
+
@pctcpu, @pctmem = *([pctcpu, pctmem].map { |s| s.to_f })
|
134
|
+
@cmdline = cmdline
|
135
|
+
end
|
136
|
+
attr_reader :pid, :ppid, :ruid, :rgid, :uid, :gid, :nice, :priority,
|
137
|
+
:user_priority, :start, :ix_rss, :id_rss, :is_rss, :pctcpu,
|
138
|
+
:pctmem, :state, :cmdline
|
139
|
+
end
|
140
|
+
|
141
|
+
@@ps_root_cmdline = 'ps -o pid,ppid,ruid,rgid,uid,gid' +
|
142
|
+
',lstart=STARTED+++++++++++++++++++++++++++++++++' +
|
143
|
+
',nice,rss,rssize,tsiz,vsz,utime,cputime,pcpu=%CPU+,' +
|
144
|
+
',pmem=%MEM+,pri,usrpri,state,command'
|
145
|
+
|
146
|
+
@@ps_all_cmdline = @@ps_root_cmdline + ' -A'
|
147
|
+
@@ps_some_cmdline = @@ps_root_cmdline + ' -p '
|
148
|
+
|
149
|
+
def self.ps(pid = nil)
|
150
|
+
pids = pid ? (pid.kind_of?(Enumerable) ? pid : [pid]) : []
|
151
|
+
retval = []
|
152
|
+
ps_cmdline = pids.empty? ? @@ps_all_cmdline :
|
153
|
+
@@ps_some_cmdline + pids.join(',')
|
154
|
+
ps_output = Kernel.` ps_cmdline
|
155
|
+
header_splits = nil
|
156
|
+
ps_output.each_line do |pline|
|
157
|
+
if header_splits
|
158
|
+
# result line, break it up
|
159
|
+
retval << ProcInfo.new(*(header_splits.map { |hs| pline[hs].strip }))
|
160
|
+
else
|
161
|
+
# first line, compute headers
|
162
|
+
lengths = pline.split(/\S\s/).map { |h| h.length + 2 }
|
163
|
+
header_splits, sum = [], 0
|
164
|
+
lengths.each_index do |i|
|
165
|
+
header_splits[i] = Range.new sum, sum += lengths[i], true
|
166
|
+
end
|
167
|
+
header_splits[-1] = Range.new header_splits[-1].begin, -1, false
|
168
|
+
end
|
169
|
+
end
|
170
|
+
return pids.length == 1 ? retval.first : retval
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
data/test/test_gems.rb
CHANGED
@@ -11,7 +11,7 @@ class TestGems < Test::Unit::TestCase
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def test_source_is_manually_tested
|
14
|
-
golden_hash = '
|
14
|
+
golden_hash = '7fca02be12fb30415a492544d91f977e8de0e440'
|
15
15
|
source_hash = hash_gems_file
|
16
16
|
|
17
17
|
assert_equal golden_hash, source_hash, <<END_MESSAGE
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'English'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
require 'zerg_support'
|
5
|
+
|
6
|
+
class TestProcess < Test::Unit::TestCase
|
7
|
+
def test_process_info
|
8
|
+
info = Zerg::Support::Process.process_info($PID)
|
9
|
+
assert info, 'Failed to find current process'
|
10
|
+
|
11
|
+
$ARGV.each do |arg|
|
12
|
+
assert info[:command_line].index(arg),
|
13
|
+
"Command line for current process does not contain argument #{arg}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_process_list_contains_current_process
|
18
|
+
list = Zerg::Support::Process.processes_by_id
|
19
|
+
|
20
|
+
assert list[$PID], 'Process list does not contain current process'
|
21
|
+
end
|
22
|
+
|
23
|
+
# spawn 2^exp children, and their command lines will contain "cookie"
|
24
|
+
def spawn_sleepers(exp, cookie)
|
25
|
+
unless child_pid = fork
|
26
|
+
exp.times { fork }
|
27
|
+
exec 'ruby', '-e', 'sleep 100', '--', cookie
|
28
|
+
end
|
29
|
+
sleep 0.5
|
30
|
+
return child_pid
|
31
|
+
end
|
32
|
+
|
33
|
+
def now_cookie
|
34
|
+
"zerg_test_#{Time.now.to_f}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_process_tree
|
38
|
+
cookie = now_cookie
|
39
|
+
root_pid = spawn_sleepers 3, cookie
|
40
|
+
|
41
|
+
tree_processes = Zerg::Support::Process.process_tree root_pid
|
42
|
+
assert_equal 8, tree_processes.length, 'Missed some processes'
|
43
|
+
assert tree_processes.all? { |info| info[:command_line].index cookie },
|
44
|
+
'Got the wrong processes'
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_kill
|
48
|
+
cookie = now_cookie
|
49
|
+
victim_pid = spawn_sleepers 0, cookie
|
50
|
+
|
51
|
+
Zerg::Support::Process.kill victim_pid
|
52
|
+
sleep 0.5
|
53
|
+
processes = Zerg::Support::Process.processes
|
54
|
+
assert_equal nil, processes[victim_pid], 'Victim was not killed'
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_kill_tree
|
58
|
+
cookie = now_cookie
|
59
|
+
root_pid = spawn_sleepers 3, cookie
|
60
|
+
|
61
|
+
Zerg::Support::Process.kill_tree root_pid
|
62
|
+
sleep 0.5
|
63
|
+
processes = Zerg::Support::Process.processes
|
64
|
+
escaped = processes.select { |info| info[:command_line].index cookie }
|
65
|
+
|
66
|
+
assert_equal [], escaped, 'Some process(es) escaped'
|
67
|
+
end
|
68
|
+
end
|
data/zerg_support.gemspec
CHANGED
@@ -1,30 +1,28 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
1
|
Gem::Specification.new do |s|
|
4
2
|
s.name = %q{zerg_support}
|
5
|
-
s.version = "0.0.
|
3
|
+
s.version = "0.0.2"
|
6
4
|
|
7
5
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
6
|
s.authors = ["Victor Costan"]
|
9
|
-
s.date = %q{2008-11-
|
7
|
+
s.date = %q{2008-11-13}
|
10
8
|
s.description = %q{Support libraries used by the Zerg system.}
|
11
9
|
s.email = %q{victor@zergling.net}
|
12
|
-
s.extra_rdoc_files = ["lib/zerg_support/gems.rb", "lib/zerg_support.rb", "
|
13
|
-
s.files = ["lib/zerg_support/gems.rb", "lib/zerg_support.rb", "
|
10
|
+
s.extra_rdoc_files = ["CHANGELOG", "lib/zerg_support/gems.rb", "lib/zerg_support/process.rb", "lib/zerg_support.rb", "LICENSE", "README"]
|
11
|
+
s.files = ["CHANGELOG", "lib/zerg_support/gems.rb", "lib/zerg_support/process.rb", "lib/zerg_support.rb", "LICENSE", "Manifest", "Rakefile", "README", "test/test_gems.rb", "test/test_process.rb", "zerg_support.gemspec"]
|
14
12
|
s.has_rdoc = true
|
15
13
|
s.homepage = %q{http://www.zergling.net}
|
16
14
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Zerg_support", "--main", "README"]
|
17
15
|
s.require_paths = ["lib"]
|
18
16
|
s.rubyforge_project = %q{rails-pwnage}
|
19
|
-
s.rubygems_version = %q{1.
|
17
|
+
s.rubygems_version = %q{1.2.0}
|
20
18
|
s.summary = %q{Support libraries used by the Zerg system.}
|
21
|
-
s.test_files = ["test/test_gems.rb"]
|
19
|
+
s.test_files = ["test/test_gems.rb", "test/test_process.rb"]
|
22
20
|
|
23
21
|
if s.respond_to? :specification_version then
|
24
22
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
23
|
s.specification_version = 2
|
26
24
|
|
27
|
-
if
|
25
|
+
if current_version >= 3 then
|
28
26
|
s.add_development_dependency(%q<echoe>, [">= 0"])
|
29
27
|
else
|
30
28
|
s.add_dependency(%q<echoe>, [">= 0"])
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zerg_support
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Costan
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-11-
|
12
|
+
date: 2008-11-13 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -29,20 +29,23 @@ executables: []
|
|
29
29
|
extensions: []
|
30
30
|
|
31
31
|
extra_rdoc_files:
|
32
|
+
- CHANGELOG
|
32
33
|
- lib/zerg_support/gems.rb
|
34
|
+
- lib/zerg_support/process.rb
|
33
35
|
- lib/zerg_support.rb
|
34
|
-
- CHANGELOG
|
35
36
|
- LICENSE
|
36
37
|
- README
|
37
38
|
files:
|
39
|
+
- CHANGELOG
|
38
40
|
- lib/zerg_support/gems.rb
|
41
|
+
- lib/zerg_support/process.rb
|
39
42
|
- lib/zerg_support.rb
|
40
|
-
- test/test_gems.rb
|
41
|
-
- CHANGELOG
|
42
43
|
- LICENSE
|
43
|
-
- README
|
44
|
-
- Rakefile
|
45
44
|
- Manifest
|
45
|
+
- Rakefile
|
46
|
+
- README
|
47
|
+
- test/test_gems.rb
|
48
|
+
- test/test_process.rb
|
46
49
|
- zerg_support.gemspec
|
47
50
|
has_rdoc: true
|
48
51
|
homepage: http://www.zergling.net
|
@@ -71,9 +74,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
74
|
requirements: []
|
72
75
|
|
73
76
|
rubyforge_project: rails-pwnage
|
74
|
-
rubygems_version: 1.
|
77
|
+
rubygems_version: 1.2.0
|
75
78
|
signing_key:
|
76
79
|
specification_version: 2
|
77
80
|
summary: Support libraries used by the Zerg system.
|
78
81
|
test_files:
|
79
82
|
- test/test_gems.rb
|
83
|
+
- test/test_process.rb
|