precompiled-raindrops 0.18.0

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