karsthammer-passenger 2.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env ruby
2
+ # Phusion Passenger - http://www.modrails.com/
3
+ # Copyright (c) 2008, 2009 Phusion
4
+ #
5
+ # "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in
15
+ # all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ # THE SOFTWARE.
24
+
25
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
26
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../ext"))
27
+ require 'phusion_passenger/admin_tools/control_process'
28
+ require 'optparse'
29
+
30
+ include PhusionPassenger::AdminTools
31
+
32
+ # ANSI color codes
33
+ RESET = "\e[0m"
34
+ BOLD = "\e[1m"
35
+ YELLOW = "\e[33m"
36
+ BLACK_BG = "\e[40m"
37
+ BLUE_BG = "\e[44m"
38
+
39
+ def show_status(control_process, options = {})
40
+ case options[:show]
41
+ when 'pool'
42
+ begin
43
+ text = control_process.status
44
+ rescue SystemCallError => e
45
+ STDERR.puts "*** ERROR: Cannot query status for Passenger instance #{control_process.pid}:"
46
+ STDERR.puts e.to_s
47
+ exit 2
48
+ end
49
+
50
+ # Colorize output
51
+ text.gsub!(/^(----)(.*)$/, YELLOW + BLUE_BG + BOLD + '\1\2' + RESET)
52
+
53
+ puts text
54
+
55
+ when 'backtraces'
56
+ begin
57
+ text = control_process.backtraces
58
+ rescue SystemCallError => e
59
+ STDERR.puts "*** ERROR: Cannot query status for Passenger instance #{control_process.pid}:"
60
+ STDERR.puts e.to_s
61
+ exit 2
62
+ end
63
+
64
+ # Colorize output
65
+ text.gsub!(/^(Thread .*:)$/, BLACK_BG + YELLOW + '\1' + RESET)
66
+ text.gsub!(/^( +in '.*? )(.*?)\(/, '\1' + BOLD + '\2' + RESET + '(')
67
+
68
+ puts text
69
+ end
70
+ end
71
+
72
+ def start
73
+ options = { :show => 'pool' }
74
+ parser = OptionParser.new do |opts|
75
+ opts.banner = "Usage: passenger-status [options] [Phusion Passenger's PID]"
76
+ opts.separator ""
77
+ opts.separator "Tool for inspecting Phusion Passenger's internal status."
78
+ opts.separator ""
79
+
80
+ opts.separator "Options:"
81
+ opts.on("--show=pool|backtraces", String,
82
+ "Whether to show the pool's contents or\n" <<
83
+ "#{' ' * 37}the backtraces of all threads.") do |what|
84
+ if what !~ /\A(pool|backtraces)\Z/
85
+ STDERR.puts "Invalid argument for --show."
86
+ exit 1
87
+ else
88
+ options[:show] = what
89
+ end
90
+ end
91
+ end
92
+ begin
93
+ parser.parse!
94
+ rescue OptionParser::ParseError => e
95
+ puts e
96
+ puts
97
+ puts "Please see '--help' for valid options."
98
+ exit 1
99
+ end
100
+
101
+ if ARGV.empty?
102
+ control_processes = ControlProcess.list
103
+ if control_processes.empty?
104
+ STDERR.puts("ERROR: Phusion Passenger doesn't seem to be running.")
105
+ exit 2
106
+ elsif control_processes.size == 1
107
+ show_status(control_processes.first, options)
108
+ else
109
+ puts "It appears that multiple Passenger instances are running. Please select a"
110
+ puts "specific one by running:"
111
+ puts
112
+ puts " passenger-status <PID>"
113
+ puts
114
+ puts "The following Passenger instances are running:"
115
+ control_processes.each do |control|
116
+ puts " PID: #{control.pid}"
117
+ end
118
+ exit 1
119
+ end
120
+ else
121
+ show_status(ControlProcess.for_pid(ARGV[0].to_i), options)
122
+ end
123
+ end
124
+
125
+ start
@@ -0,0 +1,344 @@
1
+ #!/usr/bin/env ruby
2
+ # Phusion Passenger - http://www.modrails.com/
3
+ # Copyright (c) 2008, 2009 Phusion
4
+ #
5
+ # "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in
15
+ # all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ # THE SOFTWARE.
24
+
25
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
26
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../ext"))
27
+ require 'rubygems'
28
+ require 'optparse'
29
+ require 'socket'
30
+ require 'thread'
31
+ require 'phusion_passenger/platform_info'
32
+ require 'phusion_passenger/message_channel'
33
+ require 'phusion_passenger/utils'
34
+
35
+ include PhusionPassenger
36
+ include PhusionPassenger::Utils
37
+ include PlatformInfo
38
+
39
+ # A thread or a process, depending on the Ruby VM implementation.
40
+ class Subprocess
41
+ attr_accessor :channel
42
+
43
+ def initialize(name, &block)
44
+ if RUBY_PLATFORM == "java"
45
+ a, b = UNIXSocket.pair
46
+ @thread = Thread.new do
47
+ block.call(true, MessageChannel.new(b))
48
+ end
49
+ @channel = MessageChannel.new(a)
50
+ @thread_channel = b
51
+ else
52
+ a, b = UNIXSocket.pair
53
+ @pid = safe_fork(name) do
54
+ a.close
55
+ $0 = name
56
+ Process.setsid
57
+ block.call(false, MessageChannel.new(b))
58
+ end
59
+ b.close
60
+ @channel = MessageChannel.new(a)
61
+ end
62
+ end
63
+
64
+ def stop
65
+ if RUBY_PLATFORM == "java"
66
+ @thread.terminate
67
+ @channel.close
68
+ @thread_channel.close
69
+ else
70
+ Process.kill('SIGKILL', @pid) rescue nil
71
+ Process.waitpid(@pid) rescue nil
72
+ @channel.close
73
+ end
74
+ end
75
+ end
76
+
77
+ class StressTester
78
+ def start
79
+ @options = parse_options
80
+ load_hawler
81
+
82
+ Thread.abort_on_exception = true
83
+ if GC.respond_to?(:copy_on_write_friendly=)
84
+ GC.copy_on_write_friendly = true
85
+ end
86
+ @terminal_height = ENV['LINES'] ? ENV['LINES'].to_i : 24
87
+ @terminal_width = ENV['COLUMNS'] ? ENV['COLUMNS'].to_i : 80
88
+
89
+ if Process.euid != 0
90
+ puts "*** WARNING: This program might not be able to restart " <<
91
+ "Apache because it's not running as root. Please run " <<
92
+ "this tool as root."
93
+ puts
94
+ puts "Press Enter to continue..."
95
+ begin
96
+ STDIN.readline
97
+ rescue Interrupt
98
+ exit 1
99
+ end
100
+ end
101
+
102
+ run_crawlers
103
+ end
104
+
105
+ def parse_options
106
+ options = {
107
+ :concurrency => 20,
108
+ :depth => 20,
109
+ :nice => true,
110
+ :apache_restart_interval => 24 * 60,
111
+ :app_restart_interval => 55
112
+ }
113
+ parser = OptionParser.new do |opts|
114
+ opts.banner = "Usage: passenger-stress-test <hostname> <app_root> [options]\n\n" <<
115
+ "Stress test the given (Passenger-powered) website by:\n" <<
116
+ " * crawling it with multiple concurrently running crawlers.\n" <<
117
+ " * gracefully restarting Apache at random times (please point the 'APXS2'\n" <<
118
+ " variable to your Apache's 'apxs' binary).\n" <<
119
+ " * restarting the target (Passenger-powered) application at random time.\n" <<
120
+ "\n" <<
121
+ "Example:\n" <<
122
+ " passenger-stress-test mywebsite.com /webapps/mywebsite\n" <<
123
+ "\n"
124
+
125
+ opts.separator "Options:"
126
+ opts.on("-c", "--concurrency N", Integer,
127
+ "Number of crawlers to start (default = #{options[:concurrency]})") do |v|
128
+ options[:concurrency] = v
129
+ end
130
+ opts.on("-p", "--apache-restart-interval N", Integer,
131
+ "Gracefully restart Apache after N minutes\n" <<
132
+ (" " * 37) << "(default = #{options[:apache_restart_interval]})") do |v|
133
+ options[:apache_restart_interval] = v
134
+ end
135
+ opts.on("-a", "--app-restart-interval N", Integer,
136
+ "Restart the application after N minutes\n" <<
137
+ (" " * 37) << "(default = #{options[:app_restart_interval]})") do |v|
138
+ options[:app_restart_interval] = v
139
+ end
140
+ opts.on("-h", "--help", "Show this message") do
141
+ puts opts
142
+ exit
143
+ end
144
+ end
145
+ parser.parse!
146
+
147
+ options[:host] = ARGV[0]
148
+ options[:app_root] = ARGV[1]
149
+ if !options[:host] || !options[:app_root]
150
+ puts parser
151
+ exit 1
152
+ end
153
+ return options
154
+ end
155
+
156
+ def load_hawler
157
+ begin
158
+ require 'hawler'
159
+ rescue LoadError
160
+ STDERR.puts "This tool requires Hawler (http://tinyurl.com/ywgk6x). Please install it with:"
161
+ STDERR.puts
162
+ STDERR.puts " gem install --source http://spoofed.org/files/hawler/ hawler"
163
+ exit 1
164
+ end
165
+ end
166
+
167
+ def run_crawlers
168
+ @started = false
169
+ @crawlers = []
170
+
171
+ # Start crawler processes.
172
+ GC.start if GC.copy_on_write_friendly?
173
+ @options[:concurrency].times do |i|
174
+ STDOUT.write("Starting crawler #{i + 1} of #{@options[:concurrency]}...\n")
175
+ STDOUT.flush
176
+ process = Subprocess.new("crawler #{i + 1}") do |is_thread, channel|
177
+ if !is_thread && @options[:nice]
178
+ system("renice 1 #{Process.pid} >/dev/null 2>/dev/null")
179
+ end
180
+ while true
181
+ crawl!(i + 1, channel)
182
+ end
183
+ end
184
+ @crawlers << {
185
+ :id => i + 1,
186
+ :process => process,
187
+ :channel => process.channel,
188
+ :mutex => Mutex.new,
189
+ :current_uri => nil,
190
+ :crawled => 0
191
+ }
192
+ end
193
+
194
+ puts
195
+ if RUBY_PLATFORM != "java"
196
+ # 'sleep' b0rks when running in JRuby?
197
+ sleep 1
198
+ end
199
+ begin
200
+ $0 = "Passenger Crawler: control process"
201
+ io_to_crawler = {}
202
+ ios = []
203
+ @crawlers.each do |crawler|
204
+ io_to_crawler[crawler[:channel].io] = crawler
205
+ ios << crawler[:channel].io
206
+ end
207
+
208
+ # Tell each crawler to start crawling.
209
+ @crawlers.each do |crawler|
210
+ crawler[:channel].write("start")
211
+ end
212
+
213
+ # Show progress periodically.
214
+ @start_time = Time.now
215
+ progress_reporter = Thread.new(&method(:report_progress))
216
+ @next_apache_restart = Time.now + @options[:apache_restart_interval] * 60
217
+ apache_restarter = Thread.new(&method(:restart_apache))
218
+ @next_app_restart = Time.now + @options[:app_restart_interval] * 60
219
+ app_restarter = Thread.new(&method(:restart_app))
220
+
221
+ while true
222
+ note_progress(ios, io_to_crawler)
223
+ end
224
+ rescue Interrupt
225
+ trap('SIGINT') {}
226
+ puts "Shutting down..."
227
+ @done = true
228
+ @crawlers.each do |crawler|
229
+ STDOUT.write("Stopping crawler #{crawler[:id]} of #{@options[:concurrency]}...\r")
230
+ STDOUT.flush
231
+ crawler[:process].stop
232
+ end
233
+ progress_reporter.join if progress_reporter
234
+ apache_restarter.join if apache_restarter
235
+ app_restarter.join if app_restarter
236
+ puts
237
+ end
238
+ end
239
+
240
+ def note_progress(ios, io_to_crawler)
241
+ select(ios)[0].each do |io|
242
+ crawler = io_to_crawler[io]
243
+ uri = crawler[:channel].read[0]
244
+ crawler[:mutex].synchronize do
245
+ crawler[:current_uri] = uri
246
+ crawler[:crawled] += 1
247
+ end
248
+ end
249
+ end
250
+
251
+ def report_progress
252
+ while !@done
253
+ output = "\n" * @terminal_height
254
+ output << "### Running for #{duration(Time.now.to_i - @start_time.to_i)}\n"
255
+ @crawlers.each do |crawler|
256
+ crawler[:mutex].synchronize do
257
+ line = sprintf("Crawler %-2d: %-3d -> %s",
258
+ crawler[:id],
259
+ crawler[:crawled],
260
+ crawler[:current_uri])
261
+ output << sprintf("%-#{@terminal_width}s\n", line)
262
+ end
263
+ end
264
+ output << "Next Apache restart: in #{duration(@next_apache_restart.to_i - Time.now.to_i)}\n"
265
+ output << "Next app restart : in #{duration(@next_app_restart.to_i - Time.now.to_i)}\n"
266
+ STDOUT.write(output)
267
+ sleep 0.5
268
+ end
269
+ end
270
+
271
+ def restart_apache
272
+ while !@done
273
+ if Time.now > @next_apache_restart
274
+ @next_apache_restart = Time.now + @options[:apache_restart_interval] * 60
275
+ system("#{HTTPD} -k graceful")
276
+ end
277
+ end
278
+ end
279
+
280
+ def restart_app
281
+ while !@done
282
+ if Time.now > @next_app_restart
283
+ @next_app_restart = Time.now + @options[:app_restart_interval] * 60
284
+ system("touch #{@options[:app_root]}/tmp/restart.txt")
285
+ end
286
+ end
287
+ end
288
+
289
+ def duration(seconds)
290
+ result = ""
291
+ if seconds >= 60
292
+ minutes = (seconds / 60)
293
+ if minutes >= 60
294
+ hours = minutes / 60
295
+ minutes = minutes % 60
296
+ if hours == 1
297
+ result << "#{hours} hour "
298
+ else
299
+ result << "#{hours} hours "
300
+ end
301
+ end
302
+
303
+ seconds = seconds % 60
304
+ if minutes == 1
305
+ result << "#{minutes} minute "
306
+ else
307
+ result << "#{minutes} minutes "
308
+ end
309
+ end
310
+ result << "#{seconds} seconds"
311
+ return result
312
+ end
313
+
314
+ def crawl!(id, channel)
315
+ progress_reporter = lambda do |uri, referer, response|
316
+ begin
317
+ if !@started
318
+ # At the beginning, wait until the control process
319
+ # tells us to start.
320
+ @started = true
321
+ channel.read
322
+ end
323
+ channel.write(uri, referer, response)
324
+ rescue
325
+ if RUBY_PLATFORM == "java"
326
+ Thread.current.terminate
327
+ else
328
+ Process.kill('SIGKILL', Process.pid)
329
+ end
330
+ end
331
+ end
332
+ crawler = Hawler.new(@options[:host], progress_reporter)
333
+ if RUBY_PLATFORM == "java"
334
+ trap('SIGINT') do
335
+ raise Interrupt, "Interrupted"
336
+ end
337
+ end
338
+ crawler.recurse = true
339
+ crawler.depth = @options[:depth]
340
+ crawler.start
341
+ end
342
+ end
343
+
344
+ StressTester.new.start