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:
parent
f4a5c938d4
commit
f2b53a3a4b
8 changed files with 604 additions and 349 deletions
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
|
108
lib/mongrel.rb
108
lib/mongrel.rb
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
71
lib/mongrel/stats.rb
Normal 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
|
|
@ -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
28
test/test_stats.rb
Normal 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
|
Loading…
Add table
Reference in a new issue