raindrops 0.12.0 - compatibility fixes
[raindrops.git] / lib / raindrops / middleware.rb
blob7c647eca515d36baaf382deeb8bdc751ccbbe979
1 # -*- encoding: binary -*-
2 require 'raindrops'
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
20 # forking.
22 #    require 'raindrops'
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
62 # = Demo Server
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.
78   #
79   # This is supported on all operating systems supported by Raindrops
80   class Stats < Raindrops::Struct.new(:calling, :writing)
81   end
83   # :stopdoc:
84   PATH_INFO = "PATH_INFO"
85   require "raindrops/middleware/proxy"
86   # :startdoc:
88   # +app+ may be any Rack application, this middleware wraps it.
89   # +opts+ is a hash that understands the following members:
90   #
91   # * :stats - Raindrops::Middleware::Stats struct (default: Stats.new)
92   # * :path - HTTP endpoint used for reading the stats (default: "/_raindrops")
93   # * :listeners - array of host:port or socket paths (default: from Unicorn)
94   def initialize(app, opts = {})
95     @app = app
96     @stats = opts[:stats] || Stats.new
97     @path = opts[:path] || "/_raindrops"
98     tmp = opts[:listeners]
99     if tmp.nil? && defined?(Unicorn) && Unicorn.respond_to?(:listener_names)
100       tmp = Unicorn.listener_names
101     end
102     @tcp = @unix = nil
104     if tmp
105       @tcp = tmp.grep(/\A.+:\d+\z/)
106       @unix = tmp.grep(%r{\A/})
107       @tcp = nil if @tcp.empty?
108       @unix = nil if @unix.empty?
109     end
110   end
112   # standard Rack endpoint
113   def call(env) # :nodoc:
114     env[PATH_INFO] == @path and return stats_response
115     begin
116       @stats.incr_calling
118       status, headers, body = @app.call(env)
119       rv = [ status, headers, Proxy.new(body, @stats) ]
121       # the Rack server will start writing headers soon after this method
122       @stats.incr_writing
123       rv
124     ensure
125       @stats.decr_calling
126     end
127   end
129   def stats_response  # :nodoc:
130     body = "calling: #{@stats.calling}\n" \
131            "writing: #{@stats.writing}\n"
133     if defined?(Raindrops::Linux.tcp_listener_stats)
134       Raindrops::Linux.tcp_listener_stats(@tcp).each do |addr,stats|
135         body << "#{addr} active: #{stats.active}\n" \
136                 "#{addr} queued: #{stats.queued}\n"
137       end if @tcp
138       Raindrops::Linux.unix_listener_stats(@unix).each do |addr,stats|
139         body << "#{addr} active: #{stats.active}\n" \
140                 "#{addr} queued: #{stats.queued}\n"
141       end if @unix
142     end
144     headers = {
145       "Content-Type" => "text/plain",
146       "Content-Length" => body.size.to_s,
147     }
148     [ 200, headers, [ body ] ]
149   end