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

A simple status and statistics handler for people.

git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@209 19e92222-5c0b-0410-8929-a290d50e31e9
This commit is contained in:
zedshaw 2006-05-23 09:49:28 +00:00
parent e6e7c3a058
commit 7022bfab09
4 changed files with 133 additions and 11 deletions

View file

@ -28,13 +28,19 @@ if ARGV.length != 3
exit(1)
end
stats = Mongrel::StatisticsFilter.new(:sample_rate => 1)
config = Mongrel::Configurator.new :host => ARGV[0], :port => ARGV[1] do
listener do
uri "/", :handler => SimpleHandler.new
uri "/", :handler => Mongrel::DeflateFilter.new
uri "/", :handler => stats
uri "/dumb", :handler => DumbHandler.new
uri "/dumb", :handler => Mongrel::DeflateFilter.new
uri "/dumb", :handler => stats
uri "/files", :handler => Mongrel::DirHandler.new(ARGV[2])
uri "/files", :handler => stats
uri "/status", :handler => Mongrel::StatusHandler.new(:stats_filter => stats)
end
trap("INT") { stop }

View file

@ -45,6 +45,8 @@ end
module Mongrel
class URIClassifier
attr_reader :handler_map
# Returns the URIs that have been registered with this classifier so far.
# The URIs returned should not be modified as this will cause a memory leak.
# You can use this to inspect the contents of the URIClassifier.
@ -457,6 +459,8 @@ module Mongrel
attr_reader :classifier
attr_reader :host
attr_reader :port
attr_reader :timeout
attr_reader :num_processors
# Creates a working server on host:port (strange things happen if port isn't a Number).
# Use HttpServer::run to start the server and HttpServer.acceptor.join to
@ -650,6 +654,8 @@ module Mongrel
@classifier.register(uri, [handler])
end
end
handler.listener = self
end
# Removes any handlers registered at the given URI. See Mongrel::URIClassifier#unregister

View file

@ -1,3 +1,5 @@
require 'mongrel/stats'
# Mongrel Web Server - A Mostly Ruby Webserver and Library
#
# Copyright (C) 2005 Zed A. Shaw zedshaw AT zedshaw dot com
@ -29,6 +31,7 @@ module Mongrel
#
class HttpHandler
attr_reader :header_only
attr_accessor :listener
def process(request, response)
end
@ -42,6 +45,7 @@ module Mongrel
module HttpHandlerPlugin
attr_reader :options
attr_reader :header_only
attr_accessor :listener
def initialize(options={})
@options = options
@ -64,7 +68,7 @@ module Mongrel
def initialize(msg)
@response = Const::ERROR_404_RESPONSE + msg
end
# Just kicks back the standard 404 response with your special message.
def process(request, response)
response.socket.write(@response)
@ -165,7 +169,7 @@ module Mongrel
if child == ".."
out << "<a href=\"#{base}/#{child}\">Up to parent..</a><br/>"
else
out << "<a href=\"#{base}/#{child}\">#{child}</a><br/>"
out << "<a href=\"#{base}/#{child}/\">#{child}</a><br/>"
end
end
out << "</body></html>"
@ -177,7 +181,7 @@ module Mongrel
end
end
# Sends the contents of a file back to the user. Not terribly efficient since it's
# opening and closing the file for each read.
def send_file(req_path, request, response, header_only=false)
@ -195,13 +199,13 @@ module Mongrel
# test to see if this is a conditional request, and test if
# the response would be identical to the last response
same_response = case
when unmodified_since && !last_response_time = Time.httpdate(unmodified_since) rescue nil : false
when unmodified_since && last_response_time > Time.now : false
when unmodified_since && mtime > last_response_time : false
when none_match && none_match == '*' : false
when none_match && !none_match.strip.split(/\s*,\s*/).include?(etag) : false
else unmodified_since || none_match # validation successful if we get this far and at least one of the header exists
end
when unmodified_since && !last_response_time = Time.httpdate(unmodified_since) rescue nil : false
when unmodified_since && last_response_time > Time.now : false
when unmodified_since && mtime > last_response_time : false
when none_match && none_match == '*' : false
when none_match && !none_match.strip.split(/\s*,\s*/).include?(etag) : false
else unmodified_since || none_match # validation successful if we get this far and at least one of the header exists
end
header = response.header
header[Const::ETAG] = etag
@ -290,4 +294,104 @@ module Mongrel
end
end
end
# Implements a few basic statistics for a particular URI. Register it anywhere
# you want in the request chain and it'll quickly gather some numbers for you
# to analyze. It is pretty fast, but don't put it out in production.
#
# You should pass the filter to StatusHandler as StatusHandler.new(:stats_filter => stats).
# This lets you then hit the status URI you want and get these stats from a browser.
#
# StatisticsFilter takes an option of :sample_rate. This is a number that's passed to
# rand and if that number gets hit then a sample is taken. This helps reduce the load
# and keeps the statistics valid (since sampling is a part of how they work).
#
# The exception to :sample_rate is that inter-request time is sampled on every request.
# If this wasn't done then it wouldn't be accurate as a measure of time between requests.
class StatisticsFilter < HttpHandler
attr_reader :stats
def initialize(ops={})
@sample_rate = ops[:sample_rate] || 300
@processors = Stats.new("processors")
@reqsize = Stats.new("request Kb")
@headcount = Stats.new("req param count")
@respsize = Stats.new("response Kb")
@interreq = Stats.new("inter-request time")
end
def process(request, response)
if rand(@sample_rate)+1 == @sample_rate
@processors.sample(listener.workers.list.length)
@headcount.sample(request.params.length)
@reqsize.sample(request.body.length / 1024.0)
@respsize.sample((response.body.length + response.header.out.length) / 1024.0)
end
@interreq.tick
end
def dump
"#{@processors.to_s}\n#{@reqsize.to_s}\n#{@headcount.to_s}\n#{@respsize.to_s}\n#{@interreq.to_s}"
end
end
# The :stats_filter is basically any configured stats filter that you've added to this same
# URI. This lets the status handler print out statistics on how Mongrel is doing.
class StatusHandler < HttpHandler
def initialize(ops={})
@stats = ops[:stats_filter]
end
def table(title, rows)
results = "<table border=\"1\"><tr><th colspan=\"#{rows[0].length}\">#{title}</th></tr>"
rows.each do |cols|
results << "<tr>"
cols.each {|col| results << "<td>#{col}</td>" }
results << "</tr>"
end
results + "</table>"
end
def describe_listener
results = ""
results << "<h1>Listener #{listener.host}:#{listener.port}</h1>"
results << table("settings", [
["host",listener.host],
["port",listener.port],
["timeout",listener.timeout],
["workers max",listener.num_processors],
])
if @stats
results << "<h2>Statistics</h2><p>N means the number of samples, pay attention to MEAN, SD, MIN and MAX."
results << "<pre>#{@stats.dump}</pre>"
end
results << "<h2>Registered Handlers</h2>"
uris = listener.classifier.handler_map
results << table("handlers", uris.map {|uri,handlers|
[uri,
"<pre>" +
handlers.map {|h| h.class.to_s }.join("\n") +
"</pre>"
]
})
results
end
def process(request, response)
response.start do |head,out|
out.write <<-END
<html><body><title>Mongrel Server Status</title>
#{describe_listener}
</body></html>
END
end
end
end
end

View file

@ -57,9 +57,15 @@ class Stats
# Dump this Stats object with an optional additional message.
def dump(msg = "", out=STDERR)
out.puts "[#{@name}] #{msg} : SUM=#@sum, SUMSQ=#@sumsq, N=#@n, MEAN=#{mean}, SD=#{sd}, MIN=#@min, MAX=#@max"
out.puts "#{msg}: #{self.to_s}"
end
# Returns a common display (used by dump)
def to_s
"[#{@name}]: SUM=%0.4f, SUMSQ=%0.4f, N=%0.4f, MEAN=%0.4f, SD=%0.4f, MIN=%0.4f, MAX=%0.4f" % [@sum, @sumsq, @n, mean, sd, @min, @max]
end
# Calculates and returns the mean for the data passed so far.
def mean
@sum / @n