1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

mongrel_rails now uses the RailsConfigurator. All rails.rb are now in Mongrel::Rails (like Camping). Configurator has many improvements. Signals on mongrel_rails now work better.

git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@122 19e92222-5c0b-0410-8929-a290d50e31e9
This commit is contained in:
zedshaw 2006-03-26 20:01:50 +00:00
parent f4a5c938d4
commit f2b53a3a4b
8 changed files with 604 additions and 349 deletions

View file

@ -1,6 +1,9 @@
require 'rubygems'
require 'yaml'
require 'mongrel'
require 'mongrel/rails'
class Start < GemPlugin::Plugin "/commands"
include Mongrel::Command::Base
@ -18,6 +21,7 @@ class Start < GemPlugin::Plugin "/commands"
['-m', '--mime PATH', "A YAML file that lists additional MIME types", :@mime_map, nil],
['-c', '--chdir PATH', "Change to dir before starting (will be expanded)", :@cwd, Dir.pwd],
['-r', '--root PATH', "Set the document root (default 'public')", :@docroot, "public"],
['-B', '--debug', "Enable debugging mode", :@debug, false],
]
end
@ -36,121 +40,62 @@ class Start < GemPlugin::Plugin "/commands"
return @valid
end
def daemonize
# save this for later since daemonize will hose it
if @daemon and RUBY_PLATFORM !~ /mswin/
require 'daemons/daemonize'
puts "Started Mongrel server in #@environment mode at #@address:#@port"
Daemonize.daemonize(log_file=File.join(@cwd, @log_file))
# change back to the original starting directory
Dir.chdir(@cwd)
open(@pid_file,"w") {|f| f.write(Process.pid) }
else
puts "Running Mongrel server in #@environment mode at #@address:#@port"
end
end
def load_mime_map
mime = {}
# configure any requested mime map
if @mime_map
puts "Loading additional MIME types from #@mime_map"
mime.merge!(YAML.load_file(@mime_map))
# check all the mime types to make sure they are the right format
mime.each {|k,v| puts "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 }
end
return mime
end
def configure_rails
# need this later for safe reloading
$orig_dollar_quote = $".clone
ENV['RAILS_ENV'] = @environment
require 'config/environment'
require 'dispatcher'
require 'mongrel/rails'
# configure the rails handler
rails = RailsHandler.new(@docroot, load_mime_map)
return rails
end
def start_mongrel(rails)
@restart = false
server = Mongrel::HttpServer.new(@address, @port, @num_procs.to_i, @timeout.to_i)
server.register("/", rails)
# signal trapping just applies to posix systems
# TERM is a valid signal, but still doesn't gracefuly shutdown on win32.
if RUBY_PLATFORM !~ /mswin/
# graceful shutdown
trap("TERM") {
server.stop
File.unlink @pid_file if File.exist?(@pid_file)
}
# rails reload
trap("HUP") {
STDERR.puts "Reloading rails..."
rails.reload!
STDERR.puts "Done reloading rails."
}
# restart
trap("USR2") {
server.stop
File.unlink @pid_file if File.exist?(@pid_file)
@restart = true
}
trap("INT") {
server.stop
File.unlink @pid_file if File.exist?(@pid_file)
@restart = false
}
end
# hook up any rails specific plugins
GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE
begin
# start mongrel processing thread
server.run
if RUBY_PLATFORM !~ /mswin/
puts "Server Ready. Use CTRL-C to quit."
else
puts "Server Ready. Use CTRL-Pause/Break to quit."
end
server.acceptor.join
rescue Interrupt
STDERR.puts "Interrupted."
raise
end
# daemonize makes restart easy
run if @restart
end
def run
daemonize
rails = configure_rails
start_mongrel(rails)
settings = { :host => @address, :port => @port, :cwd => @cwd,
:log_file => @log_file, :pid_file => @pid_file, :environment => @environment,
:docroot => @docroot, :mime_map => @mime_map, :daemon => @daemon,
:debug => @debug, :includes => ["mongrel"]
}
config = Mongrel::Rails::RailsConfigurator.new(settings) do
log "Starting Mongrel in #{settings[:environment]} mode at #{settings[:host]}:#{settings[:port]}"
if defaults[:daemon]
log "Daemonizing, any open files are closed. Look at #{settings[:pid_file]} and #{settings[:log_file]} for info."
daemonize
end
listener do
mime = {}
if defaults[:mime_map]
log "Loading additional MIME types from #{settings[:mime_map]}"
mime = load_mime_map(defaults[:mime_map], mime)
end
if defaults[:debug]
log "Installing debugging prefixed filters. Look in log/mongrel_debug for the files."
debug "/"
end
log "Starting Rails in environment #{settings[:environment]} ..."
uri "/", :handler => rails
log "Rails loaded."
log "Loading any Rails specific GemPlugins"
load_plugins
setup_rails_signals
end
end
config.run
config.log "Mongrel available at #{settings[:host]}:#{settings[:port]}"
config.join
if config.needs_restart
if RUBY_PLATFORM !~ /mswin/
cmd = "ruby #{__FILE__} start #{original_args.join(' ')}"
config.log "Restarting with arguments: #{cmd}"
exec cmd
else
config.log "Win32 does not support restarts. Exiting."
end
end
end
end
def send_signal(signal, pid_file)
pid = open(pid_file).read.to_i
print "Sending #{signal} to Mongrel at PID #{pid}..."
@ -232,12 +177,7 @@ end
GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE, "rails" => GemPlugin::EXCLUDE
require 'mongrel/debug'
ObjectTracker.configure
MongrelDbg.configure
Mongrel::Command::Registry.instance.run ARGV
END {
Class.report_object_creations
}

View file

@ -438,7 +438,7 @@ module Mongrel
client = @socket.accept
worker_list = @workers.list
if worker_list.length >= @num_processors
STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max)."
STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max). Dropping connection."
client.close
reap_dead_workers(worker_list)
else
@ -541,11 +541,13 @@ module Mongrel
class Configurator
attr_reader :listeners
attr_reader :defaults
attr_reader :needs_restart
# You pass in initial defaults and then a block to continue configuring.
def initialize(defaults={}, &blk)
@listeners = {}
@defaults = defaults
@needs_restart = false
if blk
cloaker(&blk).bind(self).call
@ -577,11 +579,15 @@ module Mongrel
#
# * :host => Host name to bind.
# * :port => Port to bind.
# * :num_processors => The maximum number of concurrent threads allowed. (950 default)
# * :timeout => 1/100th of a second timeout between requests. (10 is 1/10th, 0 is not timeout)
#
def listener(options={},&blk)
ops = resolve_defaults(options)
@listener = Mongrel::HttpServer.new(ops[:host], ops[:port].to_i)
ops[:num_processors] ||= 950
ops[:timeout] ||= 0
@listener = Mongrel::HttpServer.new(ops[:host], ops[:port].to_i, ops[:num_processors].to_i, ops[:timeout].to_i)
@listener_name = "#{ops[:host]}:#{ops[:port]}"
@listeners[@listener_name] = @listener
@ -620,17 +626,22 @@ module Mongrel
# * :log_file => Where to write STDOUT and STDERR.
# * :pid_file => Where to write the process ID.
#
# It is safe to call this on win32 as it will only require daemons
# if NOT win32.
def daemonize(options={})
ops = resolve_defaults(options)
# save this for later since daemonize will hose it
if RUBY_PLATFORM !~ /mswin/
require 'daemons/daemonize'
Daemonize.daemonize(log_file=File.join(options[:cwd], options[:log_file]))
Daemonize.daemonize(log_file=File.join(ops[:cwd], ops[:log_file]))
# change back to the original starting directory
Dir.chdir(options[:cwd])
Dir.chdir(ops[:cwd])
open(options[:pid_file],"w") {|f| f.write(Process.pid) }
open(ops[:pid_file],"w") {|f| f.write(Process.pid) }
else
log "WARNING: Win32 does not support daemon mode."
end
end
@ -640,16 +651,17 @@ module Mongrel
# :excludes => [] setting listing the names of plugins to include
# or exclude from the loading.
def load_plugins(options={})
ops = resolve_defaults(options)
load_settings = {}
if options[:includes]
options[:includes].each do |plugin|
if ops[:includes]
ops[:includes].each do |plugin|
load_settings[plugin] = GemPlugin::INCLUDE
end
end
if options[:excludes]
options[:excludes].each do |plugin|
if ops[:excludes]
ops[:excludes].each do |plugin|
load_settings[plugin] = GemPlugin::EXCLUDE
end
end
@ -672,11 +684,11 @@ module Mongrel
# is organized.
def load_mime_map(file, mime={})
# configure any requested mime map
STDERR.puts "Loading additional MIME types from #{file}"
log "Loading additional MIME types from #{file}"
mime = load_yaml(file, mime)
# check all the mime types to make sure they are the right format
mime.each {|k,v| STDERR.puts "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 }
mime.each {|k,v| log "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 }
return mime
end
@ -696,7 +708,7 @@ module Mongrel
# to prevent Ruby from exiting until each one is done.
def run
@listeners.each {|name,s|
STDERR.puts "Running #{name} listener."
log "Running #{name} listener."
s.run
}
@ -706,7 +718,7 @@ module Mongrel
# stop processing requests (gracefully).
def stop
@listeners.each {|name,s|
STDERR.puts "Stopping #{name} listener."
log "Stopping #{name} listener."
s.stop
}
end
@ -718,6 +730,74 @@ module Mongrel
def join
@listeners.values.each {|s| s.acceptor.join }
end
# Calling this before you register your URIs to the given location
# will setup a set of handlers that log open files, objects, and the
# parameters for each request. This helps you track common problems
# found in Rails applications that are either slow or become unresponsive
# after a little while.
def debug(location)
require 'mongrel/debug'
ObjectTracker.configure
MongrelDbg.configure
MongrelDbg.begin_trace :objects
MongrelDbg.begin_trace :rails
MongrelDbg.begin_trace :files
uri "/", :handler => plugin("/handlers/requestlog::files")
uri "/", :handler => plugin("/handlers/requestlog::objects")
uri "/", :handler => plugin("/handlers/requestlog::params")
end
# Sets up the standard signal handlers that are used on most Ruby
# It only configures if the platform is not win32 and doesn't do
# a HUP signal since this is typically framework specific.
#
# Requires a :pid_file option to indicate a file to delete.
# It sets the MongrelConfig.needs_restart attribute if
# the start command should reload. It's up to you to detect this
# and do whatever is needed for a "restart".
#
# This command is safely ignored if the platform is win32 (with a warning)
def setup_signals(options={})
ops = resolve_defaults(options)
if RUBY_PLATFORM !~ /mswin/
# graceful shutdown
trap("TERM") {
log "TERM signal received."
stop
File.unlink ops[:pid_file] if File.exist?(ops[:pid_file])
}
# restart
trap("USR2") {
log "USR2 signal received."
stop
File.unlink ops[:pid_file] if File.exist?(ops[:pid_file])
@needs_restart = true
}
trap("INT") {
log "INT signal received."
stop
File.unlink ops[:pid_file] if File.exist?(ops[:pid_file])
@needs_restart = false
}
log "Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart)."
else
log "WARNING: Win32 does not have signals support."
end
end
# Logs a simple message to STDERR (or the mongrel log if in daemon mode).
def log(msg)
STDERR.print "** ", msg, "\n"
end
end
end

View file

@ -15,7 +15,7 @@ module Mongrel
# user's bidding.
module Base
attr_reader :valid, :done_validating
attr_reader :valid, :done_validating, :original_args
# Called by the implemented command to set the options for that command.
# Every option has a short and long version, a description, a variable to

View file

@ -1,6 +1,6 @@
require 'logger'
require 'set'
require 'socket'
$mongrel_debugging=true
@ -8,14 +8,14 @@ module MongrelDbg
SETTINGS = { :tracing => {}}
LOGGING = { }
def MongrelDbg::configure(log_dir = "mongrel_debug")
def MongrelDbg::configure(log_dir = "log/mongrel_debug")
Dir.mkdir(log_dir) if not File.exist?(log_dir)
@log_dir = log_dir
end
def MongrelDbg::trace(target, message)
if SETTINGS[:tracing][target]
if SETTINGS[:tracing][target] and LOGGING[target]
LOGGING[target].log(Logger::DEBUG, message)
end
end
@ -34,32 +34,28 @@ module MongrelDbg
LOGGING[target].close
LOGGING[target] = nil
end
def MongrelDbg::tracing?(target)
SETTINGS[:tracing][target]
end
end
module ObjectTracker
@active_objects = nil
@live_object_tracking = false
@live_object_tracking = true
def ObjectTracker.configure
@active_objects = Set.new
ObjectSpace.each_object do |obj|
@active_objects << obj.object_id
end
srand @active_objects.object_id
@sample_thread = Thread.new do
loop do
sleep(rand(3) + (rand(100)/100.0))
ObjectTracker.sample
end
end
@sample_thread.priority = 20
end
def ObjectTracker.start
@stopit = true
@live_object_tracking = true
@stopit = false
end
def ObjectTracker.stop
@ -67,78 +63,197 @@ module ObjectTracker
end
def ObjectTracker.sample
ospace = Set.new
ObjectSpace.each_object do |obj|
ospace << obj.object_id
end
dead_objects = @active_objects - ospace
new_objects = ospace - @active_objects
live_objects = ospace & @active_objects
STDERR.puts "#{dead_objects.length},#{new_objects.length},#{live_objects.length}"
Class.stopit do
ospace = Set.new
counts = {}
# Strings can't be tracked easily and are so numerous that they drown out all else
# so we just ignore them in the counts.
ObjectSpace.each_object do |obj|
if not obj.kind_of? String
ospace << obj.object_id
counts[obj.class] ||= 0
counts[obj.class] += 1
end
end
dead_objects = @active_objects - ospace
new_objects = ospace - @active_objects
live_objects = ospace & @active_objects
MongrelDbg::trace(:objects, "COUNTS: #{dead_objects.length},#{new_objects.length},#{live_objects.length}")
if MongrelDbg::tracing? :objects
top_20 = counts.sort{|a,b| b[1] <=> a[1]}[0..20]
MongrelDbg::trace(:objects,"TOP 20: #{top_20.inspect}")
end
@active_objects = live_objects + new_objects
@active_objects = live_objects + new_objects
[@active_objects, top_20]
end
end
end
$open_files = {}
class IO
alias_method :orig_open, :open
alias_method :orig_close, :close
def open(*arg, &blk)
$open_files[self] = args.inspect
orig_open(*arg,&blk)
end
def close(*arg,&blk)
$open_files.delete self
orig_close(*arg,&blk)
end
end
module Kernel
alias_method :orig_open, :open
def open(*arg, &blk)
$open_files[self] = arg[0]
orig_open(*arg,&blk)
end
def log_open_files
Class.stopit do
open_counts = {}
$open_files.each do |f,args|
open_counts[args] ||= 0
open_counts[args] += 1
end
MongrelDbg::trace(:files, open_counts.to_yaml)
end
end
end
class Class
alias_method :orig_new, :new
@@count = 0
@@stoppit = false
@@stopit = false
@@class_caller_count = Hash.new{|hash,key| hash[key] = Hash.new(0)}
def new(*arg,&blk)
unless @@stoppit
@@stoppit = true
unless @@stopit
@@stopit = true
@@count += 1
@@class_caller_count[self][caller[0]] += 1
@@stoppit = false
@@class_caller_count[self][caller.join("\n\t")] += 1
@@stopit = false
end
orig_new(*arg,&blk)
end
def Class.report_object_creations
@@stoppit = true
puts "Number of objects created = #{@@count}"
total = Hash.new(0)
@@class_caller_count.each_key do |klass|
caller_count = @@class_caller_count[klass]
caller_count.each_value do |count|
total[klass] += count
def Class.report_object_creations(out=$stderr, more_than=20)
Class.stopit do
out.puts "Number of objects created = #{@@count}"
total = Hash.new(0)
@@class_caller_count.each_key do |klass|
caller_count = @@class_caller_count[klass]
caller_count.each_value do |count|
total[klass] += count
end
end
end
klass_list = total.keys.sort{|klass_a, klass_b|
a = total[klass_a]
b = total[klass_b]
if a != b
-1* (a <=> b)
else
klass_a.to_s <=> klass_b.to_s
klass_list = total.keys.sort{|klass_a, klass_b|
a = total[klass_a]
b = total[klass_b]
if a != b
-1* (a <=> b)
else
klass_a.to_s <=> klass_b.to_s
end
}
below_count = 0
klass_list.each do |klass|
below_calls = 0
if total[klass] > more_than
out.puts "#{total[klass]}\t#{klass} objects created."
caller_count = @@class_caller_count[ klass]
caller_count.keys.sort_by{|call| -1*caller_count[call]}.each do |call|
if caller_count[call] > more_than
out.puts "\t** #{caller_count[call]} #{klass} objects AT:"
out.puts "\t#{call}\n\n"
else
below_calls += 1
end
end
out.puts "\t#{below_calls} more objects had calls less that #{more_than} limit.\n\n" if below_calls > 0
else
below_count += 1
end
end
}
klass_list.each do |klass|
puts "#{total[klass]}\t#{klass} objects created."
caller_count = @@class_caller_count[ klass]
caller_count.keys.sort_by{|call| -1*caller_count[call]}.each do |call|
puts "\t#{call}\tCreated #{caller_count[call]} #{klass} objects."
end
puts
out.puts "\t** #{below_count} More objects were created but the count was below the #{more_than} limit." if below_count > 0
end
end
def Class.reset_object_creations
Class.stopit do
@@count = 0
@@class_caller_count = Hash.new{|hash,key| hash[key] = Hash.new(0)}
end
end
def Class.stopit
@@stopit = true
@@count = 0
@@class_caller_count = Hash.new{|hash,key| hash[key] = Hash.new(0)}
@@stoppit = false
yield
@@stopit = false
end
end
module RequestLog
class Files < GemPlugin::Plugin "/handlers"
include Mongrel::HttpHandlerPlugin
def process(request, response)
MongrelDbg::trace(:files, "#{Time.now} FILES OPEN BEFORE REQUEST #{request.params['PATH_INFO']}")
log_open_files
end
end
class Objects < GemPlugin::Plugin "/handlers"
include Mongrel::HttpHandlerPlugin
def process(request, response)
MongrelDbg::trace(:objects, "#{Time.now} OBJECT STATS BEFORE REQUEST #{request.params['PATH_INFO']}")
ObjectTracker.sample
end
end
class Params < GemPlugin::Plugin "/handlers"
include Mongrel::HttpHandlerPlugin
def process(request, response)
MongrelDbg::trace(:rails, "#{Time.now} REQUEST #{request.params['PATH_INFO']}")
MongrelDbg::trace(:rails, request.params.to_yaml)
end
end
end
END {
open("log/mongrel_debug/object_tracking.log", "w") {|f| Class.report_object_creations(f) }
MongrelDbg::trace(:files, "FILES OPEN AT EXIT")
log_open_files
}

View file

@ -1,159 +1,176 @@
require 'mongrel'
require 'cgi'
# Creates Rails specific configuration options for people to use
# instead of the base Configurator.
class RailsConfigurator < Mongrel::Configurator
module Mongrel
module Rails
# Used instead of Mongrel::Configurator.uri to setup
# a rails application at a particular URI. Requires
# the following options:
#
# * :docroot => The public dir to serve from.
# * :environment => Rails environment to use.
#
# And understands the following optional settings:
#
# * :mime => A map of mime types.
#
# Because of how Rails is designed you can only have
# one installed per Ruby interpreter (talk to them
# about thread safety). This function will abort
# with an exception if called more than once.
def rails(location, options={})
ops = resolve_defaults(options)
# fix up some defaults
ops[:environment] ||= "development"
ops[:docroot] ||= "public"
ops[:mime] ||= {}
if @rails_handler
raise "You can only register one RailsHandler for the whole Ruby interpreter. Complain to the ordained Rails core about thread safety."
end
$orig_dollar_quote = $".clone
ENV['RAILS_ENV'] = ops[:environment]
require 'config/environment'
require 'dispatcher'
require 'mongrel/rails'
@rails_handler = RailsHandler.new(ops[:docroot], ops[:mime])
end
# Reloads rails. This isn't too reliable really, but
# should work for most minimal reload purposes. Only reliable
# way it so stop then start the process.
def reload!
if not @rails_handler
raise "Rails was not configured. Read the docs for RailsConfigurator."
end
STDERR.puts "Reloading rails..."
@rails_handler.reload!
STDERR.puts "Done reloading rails."
end
end
# Implements a handler that can run Rails and serve files out of the
# Rails application's public directory. This lets you run your Rails
# application with Mongrel during development and testing, then use it
# also in production behind a server that's better at serving the
# static files.
#
# The RailsHandler takes a mime_map parameter which is a simple suffix=mimetype
# mapping that it should add to the list of valid mime types.
#
# It also supports page caching directly and will try to resolve a request
# in the following order:
#
# * If the requested exact PATH_INFO exists as a file then serve it.
# * If it exists at PATH_INFO+".html" exists then serve that.
# * Finally, construct a Mongrel::CGIWrapper and run Dispatcher.dispath to have Rails go.
#
# This means that if you are using page caching it will actually work with Mongrel
# and you should see a decent speed boost (but not as fast as if you use lighttpd).
#
# An additional feature you can use is
class RailsHandler < Mongrel::HttpHandler
attr_reader :files
attr_reader :guard
def initialize(dir, mime_map = {})
@files = Mongrel::DirHandler.new(dir,false)
@guard = Mutex.new
# register the requested mime types
mime_map.each {|k,v| Mongrel::DirHandler::add_mime_type(k,v) }
end
# Attempts to resolve the request as follows:
#
#
# * If the requested exact PATH_INFO exists as a file then serve it.
# * If it exists at PATH_INFO+".html" exists then serve that.
# * Finally, construct a Mongrel::CGIWrapper and run Dispatcher.dispath to have Rails go.
def process(request, response)
return if response.socket.closed?
path_info = request.params[Mongrel::Const::PATH_INFO]
page_cached = request.params[Mongrel::Const::PATH_INFO] + ".html"
if @files.can_serve(path_info)
# File exists as-is so serve it up
@files.process(request,response)
elsif @files.can_serve(page_cached)
# possible cached page, serve it up
request.params[Mongrel::Const::PATH_INFO] = page_cached
@files.process(request,response)
else
begin
cgi = Mongrel::CGIWrapper.new(request, response)
cgi.handler = self
@guard.synchronize do
# Rails is not thread safe so must be run entirely within synchronize
Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, response.body)
# Creates Rails specific configuration options for people to use
# instead of the base Configurator.
class RailsConfigurator < Mongrel::Configurator
# Creates a single rails handler and returns it so you
# can add it to a uri. You can actually attach it to
# as many URIs as you want, but this returns the
# same RailsHandler for each call.
#
# Requires the following options:
#
# * :docroot => The public dir to serve from.
# * :environment => Rails environment to use.
#
# And understands the following optional settings:
#
# * :mime => A map of mime types.
#
# Because of how Rails is designed you can only have
# one installed per Ruby interpreter (talk to them
# about thread safety). Because of this the first
# time you call this function it does all the config
# needed to get your rails working. After that
# it returns the one handler you've configured.
# This lets you attach Rails to any URI (and mulitple)
# you want, but still protects you from threads destroying
# your handler.
def rails(options={})
return @rails_handler if @rails_handler
ops = resolve_defaults(options)
# fix up some defaults
ops[:environment] ||= "development"
ops[:docroot] ||= "public"
ops[:mime] ||= {}
$orig_dollar_quote = $".clone
ENV['RAILS_ENV'] = ops[:environment]
require 'config/environment'
require 'dispatcher'
require 'mongrel/rails'
@rails_handler = RailsHandler.new(ops[:docroot], ops[:mime])
end
# Reloads rails. This isn't too reliable really, but
# should work for most minimal reload purposes. Only reliable
# way it so stop then start the process.
def reload!
if not @rails_handler
raise "Rails was not configured. Read the docs for RailsConfigurator."
end
log "Reloading rails..."
@rails_handler.reload!
log "Done reloading rails."
end
# Takes the exact same configuration as Mongrel::Configurator (and actually calls that)
# but sets up the additional HUP handler to call reload!.
def setup_rails_signals(options={})
ops = resolve_defaults(options)
if RUBY_PLATFORM !~ /mswin/
setup_signals(options)
# rails reload
trap("HUP") {
log "HUP signal received."
reload!
}
log "Rails signals registered. HUP => reload (without restart). It might not work well."
else
log "WARNING: Rails does not support signals on Win32."
end
end
# Implements a handler that can run Rails and serve files out of the
# Rails application's public directory. This lets you run your Rails
# application with Mongrel during development and testing, then use it
# also in production behind a server that's better at serving the
# static files.
#
# The RailsHandler takes a mime_map parameter which is a simple suffix=mimetype
# mapping that it should add to the list of valid mime types.
#
# It also supports page caching directly and will try to resolve a request
# in the following order:
#
# * If the requested exact PATH_INFO exists as a file then serve it.
# * If it exists at PATH_INFO+".html" exists then serve that.
# * Finally, construct a Mongrel::CGIWrapper and run Dispatcher.dispath to have Rails go.
#
# This means that if you are using page caching it will actually work with Mongrel
# and you should see a decent speed boost (but not as fast as if you use lighttpd).
#
# An additional feature you can use is
class RailsHandler < Mongrel::HttpHandler
attr_reader :files
attr_reader :guard
def initialize(dir, mime_map = {})
@files = Mongrel::DirHandler.new(dir,false)
@guard = Mutex.new
# register the requested mime types
mime_map.each {|k,v| Mongrel::DirHandler::add_mime_type(k,v) }
end
# Attempts to resolve the request as follows:
#
#
# * If the requested exact PATH_INFO exists as a file then serve it.
# * If it exists at PATH_INFO+".html" exists then serve that.
# * Finally, construct a Mongrel::CGIWrapper and run Dispatcher.dispath to have Rails go.
def process(request, response)
return if response.socket.closed?
path_info = request.params[Mongrel::Const::PATH_INFO]
page_cached = request.params[Mongrel::Const::PATH_INFO] + ".html"
if @files.can_serve(path_info)
# File exists as-is so serve it up
@files.process(request,response)
elsif @files.can_serve(page_cached)
# possible cached page, serve it up
request.params[Mongrel::Const::PATH_INFO] = page_cached
@files.process(request,response)
else
begin
cgi = Mongrel::CGIWrapper.new(request, response)
cgi.handler = self
@guard.synchronize do
# Rails is not thread safe so must be run entirely within synchronize
Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, response.body)
end
# This finalizes the output using the proper HttpResponse way
cgi.out {""}
rescue Errno::EPIPE
# ignored
rescue Object => rails_error
log "Error calling Dispatcher.dispatch #{rails_error.inspect}"
log rails_error.backtrace.join("\n")
end
end
end
# Does the internal reload for Rails. It might work for most cases, but
# sometimes you get exceptions. In that case just do a real restart.
def reload!
@guard.synchronize do
$".replace $orig_dollar_quote
GC.start
Dispatcher.reset_application!
ActionController::Routing::Routes.reload
end
end
# This finalizes the output using the proper HttpResponse way
cgi.out {""}
rescue Errno::EPIPE
# ignored
rescue Object => rails_error
STDERR.puts "Error calling Dispatcher.dispatch #{rails_error.inspect}"
STDERR.puts rails_error.backtrace.join("\n")
end
end
end
def reload!
@guard.synchronize do
$".replace $orig_dollar_quote
GC.start
Dispatcher.reset_application!
ActionController::Routing::Routes.reload
end
end
end
if $mongrel_debugging
# Tweak the rails handler to allow for tracing
class RailsHandler
alias :real_process :process
def process(request, response)
MongrelDbg::trace(:rails, "REQUEST #{Time.now}\n" + request.params.to_yaml)
real_process(request, response)
MongrelDbg::trace(:rails, "REQUEST #{Time.now}\n" + request.params.to_yaml)
end
end
end

71
lib/mongrel/stats.rb Normal file
View file

@ -0,0 +1,71 @@
# A very simple little class for doing some basic fast statistics sampling.
# You feed it either samples of numeric data you want measured or you call
# Stats.tick to get it to add a time delta between the last time you called it.
# When you're done either call sum, sumsq, n, min, max, mean or sd to get
# the information. The other option is to just call dump and see everything.
#
# It does all of this very fast and doesn't take up any memory since the samples
# are not stored but instead all the values are calculated on the fly.
class Stats
attr_reader :sum, :sumsq, :n, :min, :max
def initialize(name)
@name = name
reset
end
# Resets the internal counters so you can start sampling again.
def reset
@sum = 0.0
@sumsq = 0.0
@last_time = Time.new
@n = 0.0
@min = 0.0
@max = 0.0
end
# Adds a sampling to the calculations.
def sample(s)
@sum += s
@sumsq += s * s
if @n == 0
@min = @max = s
else
@min = s if @min > s
@max = s if @max < s
end
@n+=1
end
# Dump this Stats object with an optional additional message.
def dump(msg = "")
STDERR.puts "[#{@name}] #{msg} : SUM=#@sum, SUMSQ=#@sumsq, N=#@n, MEAN=#{mean}, SD=#{sd}, MIN=#@min, MAX=#@max"
end
# Calculates and returns the mean for the data passed so far.
def mean
@sum / @n
end
# Calculates the standard deviation of the data so far.
def sd
# (sqrt( ((s).sumsq - ( (s).sum * (s).sum / (s).n)) / ((s).n-1) ))
Math.sqrt( (@sumsq - ( @sum * @sum / @n)) / (@n-1) )
end
# Adds a time delta between now and the last time you called this. This
# will give you the average time between two activities.
#
# An example is:
#
# t = Stats.new("do_stuff")
# 10000.times { do_stuff(); t.tick }
# t.dump("time")
#
def tick
now = Time.now
sample(now - @last_time)
@last_time = now
end
end

View file

@ -1,23 +1,27 @@
require 'fileutils'
FileUtils.mkdir_p "log/mongrel_debug"
require 'test/unit'
require 'mongrel/rails'
require 'mongrel/debug'
require 'fileutils'
class MongrelDbgTest < Test::Unit::TestCase
def setup
FileUtils.rm_rf "mongrel_debug"
FileUtils.rm_rf "log/mongrel_debug"
MongrelDbg::configure
end
def test_tracing_to_log
MongrelDbg::begin_trace(:rails)
MongrelDbg::trace(:rails, "Good stuff")
MongrelDbg::end_trace(:rails)
assert File.exist?("mongrel_debug"), "Didn't make logging directory"
assert File.exist?("mongrel_debug/rails.log"), "Didn't make the rails.log file"
assert File.size("mongrel_debug/rails.log") > 0, "Didn't write anything to the log."
assert File.exist?("log/mongrel_debug"), "Didn't make logging directory"
assert File.exist?("log/mongrel_debug/rails.log"), "Didn't make the rails.log file"
assert File.size("log/mongrel_debug/rails.log") > 0, "Didn't write anything to the log."
Class.report_object_creations
Class.reset_object_creations

28
test/test_stats.rb Normal file
View file

@ -0,0 +1,28 @@
require 'test/unit'
require 'mongrel/stats'
class StatsTest < Test::Unit::TestCase
def test_sampling_speed
s = Stats.new("test")
t = Stats.new("time")
10000.times { s.sample(rand(20)); t.tick }
s.dump("FIRST")
t.dump("FIRST")
old_mean = s.mean
old_sd = s.sd
s.reset
t.reset
10000.times { s.sample(rand(20)); t.tick }
s.dump("SECOND")
t.dump("SECOND")
assert_not_equal old_mean, s.mean
assert_not_equal old_mean, s.sd
end
end