1 # -*- encoding: binary -*-
4 # Raindrops::Middleware is Rack middleware that allows snapshotting
5 # current activity from an HTTP request. For all operating systems,
6 # it returns at least the following fields:
8 # * calling - the number of application dispatchers on your machine
9 # * writing - the number of clients being written to on your machine
11 # Additional fields are available for \Linux users.
13 # It should be loaded at the top of Rack middleware stack before other
14 # middlewares for maximum accuracy.
16 # === Usage (Rainbows!/Unicorn preload_app=false)
18 # If you're using preload_app=false (the default) in your Rainbows!/Unicorn
19 # config file, you'll need to create the global Stats object before
23 # $stats ||= Raindrops::Middleware::Stats.new
25 # In your Rack config.ru:
27 # use Raindrops::Middleware, :stats => $stats
29 # === Usage (Rainbows!/Unicorn preload_app=true)
31 # If you're using preload_app=true in your Rainbows!/Unicorn
32 # config file, just add the middleware to your stack:
34 # In your Rack config.ru:
36 # use Raindrops::Middleware
38 # === Linux-only extras!
40 # To get bound listener statistics under \Linux, you need to specify the
41 # listener names for your server. You can even include listen sockets for
42 # *other* servers on the same machine. This can be handy for monitoring
43 # your nginx proxy as well.
45 # In your Rack config.ru, just pass the :listeners argument as an array of
46 # strings (along with any other arguments). You can specify any
47 # combination of TCP or Unix domain socket names:
49 # use Raindrops::Middleware, :listeners => %w(0.0.0.0:80 /tmp/.sock)
51 # If you're running Unicorn 0.98.0 or later, you don't have to pass in
52 # the :listeners array, Raindrops will automatically detect the listeners
53 # used by Unicorn master process. This does not detect listeners in
54 # different processes, of course.
56 # The response body includes the following stats for each listener
57 # (see also Raindrops::ListenStats):
59 # * active - total number of active clients on that listener
60 # * queued - total number of queued (pre-accept()) clients on that listener
64 # There is a server running this middleware (and Watcher) at
65 # http://raindrops-demo.bogomips.org/_raindrops
67 # Also check out the Watcher demo at http://raindrops-demo.bogomips.org/
69 # The demo server is only limited to 30 users, so be sure not to abuse it
70 # by using the /tail/ endpoint too much.
72 class Raindrops::Middleware
73 attr_accessor :app, :stats, :path, :tcp, :unix # :nodoc:
75 # A Raindrops::Struct used to count the number of :calling and :writing
76 # clients. This struct is intended to be shared across multiple processes
77 # and both counters are updated atomically.
79 # This is supported on all operating systems supported by Raindrops
80 Stats = Raindrops::Struct.new(:calling, :writing)
83 require "raindrops/middleware/proxy"
86 # +app+ may be any Rack application, this middleware wraps it.
87 # +opts+ is a hash that understands the following members:
89 # * :stats - Raindrops::Middleware::Stats struct (default: Stats.new)
90 # * :path - HTTP endpoint used for reading the stats (default: "/_raindrops")
91 # * :listeners - array of host:port or socket paths (default: from Unicorn)
92 def initialize(app, opts = {})
94 @stats = opts[:stats] || Stats.new
95 @path = opts[:path] || "/_raindrops"
96 tmp = opts[:listeners]
97 if tmp.nil? && defined?(Unicorn) && Unicorn.respond_to?(:listener_names)
98 tmp = Unicorn.listener_names
103 @tcp = tmp.grep(/\A.+:\d+\z/)
104 @unix = tmp.grep(%r{\A/})
105 @tcp = nil if @tcp.empty?
106 @unix = nil if @unix.empty?
110 # standard Rack endpoint
111 def call(env) # :nodoc:
112 env['PATH_INFO'] == @path and return stats_response
116 status, headers, body = @app.call(env)
117 rv = [ status, headers, Proxy.new(body, @stats) ]
119 # the Rack server will start writing headers soon after this method
127 def stats_response # :nodoc:
128 body = "calling: #{@stats.calling}\n" \
129 "writing: #{@stats.writing}\n"
131 if defined?(Raindrops::Linux.tcp_listener_stats)
132 Raindrops::Linux.tcp_listener_stats(@tcp).each do |addr,stats|
133 body << "#{addr} active: #{stats.active}\n" \
134 "#{addr} queued: #{stats.queued}\n"
136 Raindrops::Linux.unix_listener_stats(@unix).each do |addr,stats|
137 body << "#{addr} active: #{stats.active}\n" \
138 "#{addr} queued: #{stats.queued}\n"
143 "Content-Type" => "text/plain",
144 "Content-Length" => body.size.to_s,
146 [ 200, headers, [ body ] ]