karsthammer-passenger 2.2.4

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.
@@ -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