raindrops-maintained 0.21.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +7 -0
- data/.gitattributes +4 -0
- data/.gitignore +16 -0
- data/.manifest +62 -0
- data/.olddoc.yml +16 -0
- data/COPYING +165 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +4 -0
- data/LATEST +9 -0
- data/LICENSE +16 -0
- data/NEWS +384 -0
- data/README +101 -0
- data/TODO +3 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/examples/linux-listener-stats.rb +122 -0
- data/examples/middleware.ru +5 -0
- data/examples/watcher.ru +4 -0
- data/examples/watcher_demo.ru +13 -0
- data/examples/yahns.conf.rb +30 -0
- data/examples/zbatery.conf.rb +16 -0
- data/ext/raindrops/extconf.rb +163 -0
- data/ext/raindrops/linux_inet_diag.c +713 -0
- data/ext/raindrops/my_fileno.h +16 -0
- data/ext/raindrops/raindrops.c +487 -0
- data/ext/raindrops/raindrops_atomic.h +23 -0
- data/ext/raindrops/tcp_info.c +245 -0
- data/lib/raindrops/aggregate/last_data_recv.rb +94 -0
- data/lib/raindrops/aggregate/pmq.rb +245 -0
- data/lib/raindrops/aggregate.rb +8 -0
- data/lib/raindrops/last_data_recv.rb +102 -0
- data/lib/raindrops/linux.rb +77 -0
- data/lib/raindrops/middleware/proxy.rb +40 -0
- data/lib/raindrops/middleware.rb +153 -0
- data/lib/raindrops/struct.rb +62 -0
- data/lib/raindrops/watcher.rb +428 -0
- data/lib/raindrops.rb +72 -0
- data/pkg.mk +151 -0
- data/raindrops-maintained.gemspec +1 -0
- data/raindrops.gemspec +26 -0
- data/setup.rb +1586 -0
- data/test/ipv6_enabled.rb +9 -0
- data/test/rack_unicorn.rb +11 -0
- data/test/test_aggregate_pmq.rb +65 -0
- data/test/test_inet_diag_socket.rb +16 -0
- data/test/test_last_data_recv.rb +57 -0
- data/test/test_last_data_recv_unicorn.rb +69 -0
- data/test/test_linux.rb +281 -0
- data/test/test_linux_all_tcp_listen_stats.rb +66 -0
- data/test/test_linux_all_tcp_listen_stats_leak.rb +43 -0
- data/test/test_linux_ipv6.rb +166 -0
- data/test/test_linux_middleware.rb +64 -0
- data/test/test_linux_reuseport_tcp_listen_stats.rb +51 -0
- data/test/test_middleware.rb +128 -0
- data/test/test_middleware_unicorn.rb +37 -0
- data/test/test_middleware_unicorn_ipv6.rb +37 -0
- data/test/test_raindrops.rb +207 -0
- data/test/test_raindrops_gc.rb +38 -0
- data/test/test_struct.rb +54 -0
- data/test/test_tcp_info.rb +88 -0
- data/test/test_watcher.rb +186 -0
- metadata +193 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
# For reporting TCP ListenStats, users of older \Linux kernels need to ensure
|
4
|
+
# that the the "inet_diag" and "tcp_diag" kernel modules are loaded as they do
|
5
|
+
# not autoload correctly. The inet_diag facilities of \Raindrops is useful
|
6
|
+
# for periodic snapshot reporting of listen queue sizes.
|
7
|
+
#
|
8
|
+
# Instead of snapshotting, Raindrops::Aggregate::LastDataRecv may be used
|
9
|
+
# to aggregate statistics from +all+ accepted sockets as they arrive
|
10
|
+
# based on the +last_data_recv+ field in Raindrops::TCP_Info
|
11
|
+
|
12
|
+
module Raindrops::Linux
|
13
|
+
|
14
|
+
# The standard proc path for active UNIX domain sockets, feel free to call
|
15
|
+
# String#replace on this if your /proc is mounted in a non-standard location
|
16
|
+
# for whatever reason
|
17
|
+
PROC_NET_UNIX_ARGS = [ '/proc/net/unix', { encoding: "binary" }]
|
18
|
+
|
19
|
+
# Get ListenStats from an array of +paths+
|
20
|
+
#
|
21
|
+
# Socket state mapping from integer => symbol, based on socket_state
|
22
|
+
# enum from include/linux/net.h in the \Linux kernel:
|
23
|
+
# typedef enum {
|
24
|
+
# SS_FREE = 0, /* not allocated */
|
25
|
+
# SS_UNCONNECTED, /* unconnected to any socket */
|
26
|
+
# SS_CONNECTING, /* in process of connecting */
|
27
|
+
# SS_CONNECTED, /* connected to socket */
|
28
|
+
# SS_DISCONNECTING /* in process of disconnecting */
|
29
|
+
# } socket_state;
|
30
|
+
# * SS_CONNECTING maps to ListenStats#queued
|
31
|
+
# * SS_CONNECTED maps to ListenStats#active
|
32
|
+
#
|
33
|
+
# This method may be significantly slower than its tcp_listener_stats
|
34
|
+
# counterpart due to the latter being able to use inet_diag via netlink.
|
35
|
+
# This parses /proc/net/unix as there is no other (known) way
|
36
|
+
# to expose Unix domain socket statistics over netlink.
|
37
|
+
def unix_listener_stats(paths = nil)
|
38
|
+
rv = Hash.new { |h,k| h[k.freeze] = Raindrops::ListenStats.new(0, 0) }
|
39
|
+
if nil == paths
|
40
|
+
paths = [ '[^\n]+' ]
|
41
|
+
else
|
42
|
+
paths = paths.map do |path|
|
43
|
+
path = path.dup
|
44
|
+
path.force_encoding(Encoding::BINARY)
|
45
|
+
if File.symlink?(path)
|
46
|
+
link = path
|
47
|
+
path = File.readlink(link)
|
48
|
+
path.force_encoding(Encoding::BINARY)
|
49
|
+
rv[link] = rv[path] # vivify ListenerStats
|
50
|
+
else
|
51
|
+
rv[path] # vivify ListenerStats
|
52
|
+
end
|
53
|
+
Regexp.escape(path)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
paths = /^\w+: \d+ \d+ (\d+) \d+ (\d+)\s+\d+ (#{paths.join('|')})$/n
|
57
|
+
|
58
|
+
# no point in pread since we can't stat for size on this file
|
59
|
+
File.read(PROC_NET_UNIX_ARGS[0], encoding: 'binary').scan(paths) do |s|
|
60
|
+
path = s[-1]
|
61
|
+
case s[0]
|
62
|
+
when "00000000" # client sockets
|
63
|
+
case s[1].to_i
|
64
|
+
when 2 then rv[path].queued += 1
|
65
|
+
when 3 then rv[path].active += 1
|
66
|
+
end
|
67
|
+
else
|
68
|
+
# listeners, vivify empty stats
|
69
|
+
rv[path]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
rv
|
74
|
+
end
|
75
|
+
module_function :unix_listener_stats
|
76
|
+
|
77
|
+
end # Raindrops::Linux
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :stopdoc:
|
3
|
+
# This class is used by Raindrops::Middleware to proxy application
|
4
|
+
# response bodies. There should be no need to use it directly.
|
5
|
+
class Raindrops::Middleware::Proxy
|
6
|
+
def initialize(body, stats)
|
7
|
+
@body, @stats = body, stats
|
8
|
+
end
|
9
|
+
|
10
|
+
# yield to the Rack server here for writing
|
11
|
+
def each
|
12
|
+
@body.each { |x| yield x }
|
13
|
+
end
|
14
|
+
|
15
|
+
# the Rack server should call this after #each (usually ensure-d)
|
16
|
+
def close
|
17
|
+
@stats.decr_writing
|
18
|
+
@body.close if @body.respond_to?(:close)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Some Rack servers can optimize response processing if it responds
|
22
|
+
# to +to_path+ via the sendfile(2) system call, we proxy +to_path+
|
23
|
+
# to the underlying body if possible.
|
24
|
+
def to_path
|
25
|
+
@body.to_path
|
26
|
+
end
|
27
|
+
|
28
|
+
# Rack servers use +respond_to?+ to check for the presence of +close+
|
29
|
+
# and +to_path+ methods.
|
30
|
+
def respond_to?(m, include_all = false)
|
31
|
+
m = m.to_sym
|
32
|
+
:close == m || @body.respond_to?(m, include_all)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Avoid breaking users of non-standard extensions (e.g. #body)
|
36
|
+
# Rack::BodyProxy does the same.
|
37
|
+
def method_missing(*args, &block)
|
38
|
+
@body.__send__(*args, &block)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'raindrops'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
# Raindrops::Middleware is Rack middleware that allows snapshotting
|
6
|
+
# current activity from an HTTP request. For all operating systems,
|
7
|
+
# it returns at least the following fields:
|
8
|
+
#
|
9
|
+
# * calling - the number of application dispatchers on your machine
|
10
|
+
# * writing - the number of clients being written to on your machine
|
11
|
+
#
|
12
|
+
# Additional fields are available for \Linux users.
|
13
|
+
#
|
14
|
+
# It should be loaded at the top of Rack middleware stack before other
|
15
|
+
# middlewares for maximum accuracy.
|
16
|
+
#
|
17
|
+
# === Usage (Rainbows!/Unicorn preload_app=false)
|
18
|
+
#
|
19
|
+
# If you're using preload_app=false (the default) in your Rainbows!/Unicorn
|
20
|
+
# config file, you'll need to create the global Stats object before
|
21
|
+
# forking.
|
22
|
+
#
|
23
|
+
# require 'raindrops'
|
24
|
+
# $stats ||= Raindrops::Middleware::Stats.new
|
25
|
+
#
|
26
|
+
# In your Rack config.ru:
|
27
|
+
#
|
28
|
+
# use Raindrops::Middleware, :stats => $stats
|
29
|
+
#
|
30
|
+
# === Usage (Rainbows!/Unicorn preload_app=true)
|
31
|
+
#
|
32
|
+
# If you're using preload_app=true in your Rainbows!/Unicorn
|
33
|
+
# config file, just add the middleware to your stack:
|
34
|
+
#
|
35
|
+
# In your Rack config.ru:
|
36
|
+
#
|
37
|
+
# use Raindrops::Middleware
|
38
|
+
#
|
39
|
+
# === Linux-only extras!
|
40
|
+
#
|
41
|
+
# To get bound listener statistics under \Linux, you need to specify the
|
42
|
+
# listener names for your server. You can even include listen sockets for
|
43
|
+
# *other* servers on the same machine. This can be handy for monitoring
|
44
|
+
# your nginx proxy as well.
|
45
|
+
#
|
46
|
+
# In your Rack config.ru, just pass the :listeners argument as an array of
|
47
|
+
# strings (along with any other arguments). You can specify any
|
48
|
+
# combination of TCP or Unix domain socket names:
|
49
|
+
#
|
50
|
+
# use Raindrops::Middleware, :listeners => %w(0.0.0.0:80 /tmp/.sock)
|
51
|
+
#
|
52
|
+
# If you're running Unicorn 0.98.0 or later, you don't have to pass in
|
53
|
+
# the :listeners array, Raindrops will automatically detect the listeners
|
54
|
+
# used by Unicorn master process. This does not detect listeners in
|
55
|
+
# different processes, of course.
|
56
|
+
#
|
57
|
+
# The response body includes the following stats for each listener
|
58
|
+
# (see also Raindrops::ListenStats):
|
59
|
+
#
|
60
|
+
# * active - total number of active clients on that listener
|
61
|
+
# * queued - total number of queued (pre-accept()) clients on that listener
|
62
|
+
#
|
63
|
+
# = Demo Server
|
64
|
+
#
|
65
|
+
# There is a server running this middleware (and Watcher) at
|
66
|
+
# https://yhbt.net/raindrops-demo/_raindrops
|
67
|
+
#
|
68
|
+
# Also check out the Watcher demo at https://yhbt.net/raindrops-demo/
|
69
|
+
#
|
70
|
+
# The demo server is only limited to 30 users, so be sure not to abuse it
|
71
|
+
# by using the /tail/ endpoint too much.
|
72
|
+
#
|
73
|
+
class Raindrops::Middleware
|
74
|
+
attr_accessor :app, :stats, :path, :tcp, :unix # :nodoc:
|
75
|
+
|
76
|
+
# A Raindrops::Struct used to count the number of :calling and :writing
|
77
|
+
# clients. This struct is intended to be shared across multiple processes
|
78
|
+
# and both counters are updated atomically.
|
79
|
+
#
|
80
|
+
# This is supported on all operating systems supported by Raindrops
|
81
|
+
Stats = Raindrops::Struct.new(:calling, :writing)
|
82
|
+
|
83
|
+
# :stopdoc:
|
84
|
+
require "raindrops/middleware/proxy"
|
85
|
+
# :startdoc:
|
86
|
+
|
87
|
+
# +app+ may be any Rack application, this middleware wraps it.
|
88
|
+
# +opts+ is a hash that understands the following members:
|
89
|
+
#
|
90
|
+
# * :stats - Raindrops::Middleware::Stats struct (default: Stats.new)
|
91
|
+
# * :path - HTTP endpoint used for reading the stats (default: "/_raindrops")
|
92
|
+
# * :listeners - array of host:port or socket paths (default: from Unicorn)
|
93
|
+
def initialize(app, opts = {})
|
94
|
+
@app = app
|
95
|
+
@stats = opts[:stats] || Stats.new
|
96
|
+
@path = opts[:path] || "/_raindrops"
|
97
|
+
@mtx = Mutex.new
|
98
|
+
tmp = opts[:listeners]
|
99
|
+
if tmp.nil? && defined?(Unicorn) && Unicorn.respond_to?(:listener_names)
|
100
|
+
tmp = Unicorn.listener_names
|
101
|
+
end
|
102
|
+
@nl_sock = @tcp = @unix = nil
|
103
|
+
|
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
|
111
|
+
|
112
|
+
# standard Rack endpoint
|
113
|
+
def call(env) # :nodoc:
|
114
|
+
env['PATH_INFO'] == @path and return stats_response
|
115
|
+
begin
|
116
|
+
@stats.incr_calling
|
117
|
+
|
118
|
+
status, headers, body = @app.call(env)
|
119
|
+
rv = [ status, headers, Proxy.new(body, @stats) ]
|
120
|
+
|
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
|
128
|
+
|
129
|
+
def stats_response # :nodoc:
|
130
|
+
body = "calling: #{@stats.calling}\n" \
|
131
|
+
"writing: #{@stats.writing}\n"
|
132
|
+
|
133
|
+
if defined?(Raindrops::Linux.tcp_listener_stats)
|
134
|
+
@mtx.synchronize do
|
135
|
+
@nl_sock ||= Raindrops::InetDiagSocket.new
|
136
|
+
Raindrops::Linux.tcp_listener_stats(@tcp, @nl_sock).each do |addr,stats|
|
137
|
+
body << "#{addr} active: #{stats.active}\n" \
|
138
|
+
"#{addr} queued: #{stats.queued}\n"
|
139
|
+
end
|
140
|
+
end if @tcp
|
141
|
+
Raindrops::Linux.unix_listener_stats(@unix).each do |addr,stats|
|
142
|
+
body << "#{addr} active: #{stats.active}\n" \
|
143
|
+
"#{addr} queued: #{stats.queued}\n"
|
144
|
+
end if @unix
|
145
|
+
end
|
146
|
+
|
147
|
+
headers = {
|
148
|
+
"Content-Type" => "text/plain",
|
149
|
+
"Content-Length" => body.size.to_s,
|
150
|
+
}
|
151
|
+
[ 200, headers, [ body ] ]
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
# This is a wrapper around Raindrops objects much like the core Ruby
|
4
|
+
# \Struct can be seen as a wrapper around the core \Array class.
|
5
|
+
# It's usage is similar to the core \Struct class, except its fields
|
6
|
+
# may only be used to house unsigned long integers.
|
7
|
+
#
|
8
|
+
# class Foo < Raindrops::Struct.new(:readers, :writers)
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# foo = Foo.new 0, 0
|
12
|
+
#
|
13
|
+
# foo.incr_writers -> 1
|
14
|
+
# foo.incr_readers -> 1
|
15
|
+
#
|
16
|
+
class Raindrops::Struct
|
17
|
+
|
18
|
+
# returns a new class derived from Raindrops::Struct and supporting
|
19
|
+
# the given +members+ as fields, just like \Struct.new in core Ruby.
|
20
|
+
def self.new(*members)
|
21
|
+
members = members.map { |x| x.to_sym }.freeze
|
22
|
+
str = <<EOS
|
23
|
+
def initialize(*values)
|
24
|
+
(MEMBERS.size >= values.size) or raise ArgumentError, "too many arguments"
|
25
|
+
@raindrops = Raindrops.new(MEMBERS.size)
|
26
|
+
values.each_with_index { |val,i| @raindrops[i] = values[i] }
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize_copy(src)
|
30
|
+
@raindrops = src.instance_variable_get(:@raindrops).dup
|
31
|
+
end
|
32
|
+
|
33
|
+
def []=(index, value)
|
34
|
+
@raindrops[index] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def [](index)
|
38
|
+
@raindrops[index]
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_hash
|
42
|
+
ary = @raindrops.to_ary
|
43
|
+
rv = {}
|
44
|
+
MEMBERS.each_with_index { |member, i| rv[member] = ary[i] }
|
45
|
+
rv
|
46
|
+
end
|
47
|
+
EOS
|
48
|
+
|
49
|
+
members.each_with_index do |member, i|
|
50
|
+
str << "def incr_#{member}; @raindrops.incr(#{i}); end; " \
|
51
|
+
"def decr_#{member}; @raindrops.decr(#{i}); end; " \
|
52
|
+
"def #{member}; @raindrops[#{i}]; end; " \
|
53
|
+
"def #{member}=(val); @raindrops[#{i}] = val; end; "
|
54
|
+
end
|
55
|
+
|
56
|
+
klass = Class.new
|
57
|
+
klass.const_set(:MEMBERS, members)
|
58
|
+
klass.class_eval(str)
|
59
|
+
klass
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|