raindrops-maintained 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.document +7 -0
  3. data/.gitattributes +4 -0
  4. data/.gitignore +16 -0
  5. data/.manifest +62 -0
  6. data/.olddoc.yml +16 -0
  7. data/COPYING +165 -0
  8. data/GIT-VERSION-FILE +1 -0
  9. data/GIT-VERSION-GEN +40 -0
  10. data/GNUmakefile +4 -0
  11. data/LATEST +9 -0
  12. data/LICENSE +16 -0
  13. data/NEWS +384 -0
  14. data/README +101 -0
  15. data/TODO +3 -0
  16. data/archive/.gitignore +3 -0
  17. data/archive/slrnpull.conf +4 -0
  18. data/examples/linux-listener-stats.rb +122 -0
  19. data/examples/middleware.ru +5 -0
  20. data/examples/watcher.ru +4 -0
  21. data/examples/watcher_demo.ru +13 -0
  22. data/examples/yahns.conf.rb +30 -0
  23. data/examples/zbatery.conf.rb +16 -0
  24. data/ext/raindrops/extconf.rb +163 -0
  25. data/ext/raindrops/linux_inet_diag.c +713 -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 +94 -0
  31. data/lib/raindrops/aggregate/pmq.rb +245 -0
  32. data/lib/raindrops/aggregate.rb +8 -0
  33. data/lib/raindrops/last_data_recv.rb +102 -0
  34. data/lib/raindrops/linux.rb +77 -0
  35. data/lib/raindrops/middleware/proxy.rb +40 -0
  36. data/lib/raindrops/middleware.rb +153 -0
  37. data/lib/raindrops/struct.rb +62 -0
  38. data/lib/raindrops/watcher.rb +428 -0
  39. data/lib/raindrops.rb +72 -0
  40. data/pkg.mk +151 -0
  41. data/raindrops-maintained.gemspec +1 -0
  42. data/raindrops.gemspec +26 -0
  43. data/setup.rb +1586 -0
  44. data/test/ipv6_enabled.rb +9 -0
  45. data/test/rack_unicorn.rb +11 -0
  46. data/test/test_aggregate_pmq.rb +65 -0
  47. data/test/test_inet_diag_socket.rb +16 -0
  48. data/test/test_last_data_recv.rb +57 -0
  49. data/test/test_last_data_recv_unicorn.rb +69 -0
  50. data/test/test_linux.rb +281 -0
  51. data/test/test_linux_all_tcp_listen_stats.rb +66 -0
  52. data/test/test_linux_all_tcp_listen_stats_leak.rb +43 -0
  53. data/test/test_linux_ipv6.rb +166 -0
  54. data/test/test_linux_middleware.rb +64 -0
  55. data/test/test_linux_reuseport_tcp_listen_stats.rb +51 -0
  56. data/test/test_middleware.rb +128 -0
  57. data/test/test_middleware_unicorn.rb +37 -0
  58. data/test/test_middleware_unicorn_ipv6.rb +37 -0
  59. data/test/test_raindrops.rb +207 -0
  60. data/test/test_raindrops_gc.rb +38 -0
  61. data/test/test_struct.rb +54 -0
  62. data/test/test_tcp_info.rb +88 -0
  63. data/test/test_watcher.rb +186 -0
  64. metadata +193 -0
@@ -0,0 +1,428 @@
1
+ # -*- encoding: binary -*-
2
+ require "thread"
3
+ require "time"
4
+ require "socket"
5
+ require "rack"
6
+ require "aggregate"
7
+
8
+ # Raindrops::Watcher is a stand-alone Rack application for watching
9
+ # any number of TCP and UNIX listeners (all of them by default).
10
+ #
11
+ # It depends on the {Aggregate RubyGem}[https://rubygems.org/gems/aggregate]
12
+ #
13
+ # In your Rack config.ru:
14
+ #
15
+ # run Raindrops::Watcher(options = {})
16
+ #
17
+ # It takes the following options hash:
18
+ #
19
+ # - :listeners - an array of listener names, (e.g. %w(0.0.0.0:80 /tmp/sock))
20
+ # - :delay - interval between stats updates in seconds (default: 1)
21
+ #
22
+ # Raindrops::Watcher is compatible any thread-safe/thread-aware Rack
23
+ # middleware. It does not work well with multi-process web servers
24
+ # but can be used to monitor them. It consumes minimal resources
25
+ # with the default :delay.
26
+ #
27
+ # == HTTP endpoints
28
+ #
29
+ # === GET /
30
+ #
31
+ # Returns an HTML summary listing of all listen interfaces watched on
32
+ #
33
+ # === GET /active/$LISTENER.txt
34
+ #
35
+ # Returns a plain text summary + histogram with X-* HTTP headers for
36
+ # active connections.
37
+ #
38
+ # e.g.: curl https://yhbt.net/raindrops-demo/active/0.0.0.0%3A80.txt
39
+ #
40
+ # === GET /active/$LISTENER.html
41
+ #
42
+ # Returns an HTML summary + histogram with X-* HTTP headers for
43
+ # active connections.
44
+ #
45
+ # e.g.: curl https://yhbt.net/raindrops-demo/active/0.0.0.0%3A80.html
46
+ #
47
+ # === GET /queued/$LISTENER.txt
48
+ #
49
+ # Returns a plain text summary + histogram with X-* HTTP headers for
50
+ # queued connections.
51
+ #
52
+ # e.g.: curl https://yhbt.net/raindrops-demo/queued/0.0.0.0%3A80.txt
53
+ #
54
+ # === GET /queued/$LISTENER.html
55
+ #
56
+ # Returns an HTML summary + histogram with X-* HTTP headers for
57
+ # queued connections.
58
+ #
59
+ # e.g.: curl https://yhbt.net/raindrops-demo/queued/0.0.0.0%3A80.html
60
+ #
61
+ # === POST /reset/$LISTENER
62
+ #
63
+ # Resets the active and queued statistics for the given listener.
64
+ #
65
+ # === GET /tail/$LISTENER.txt?active_min=1&queued_min=1
66
+ #
67
+ # Streams chunked a response to the client.
68
+ # Interval is the preconfigured +:delay+ of the application (default 1 second)
69
+ #
70
+ # The response is plain text in the following format:
71
+ #
72
+ # ISO8601_TIMESTAMP LISTENER_NAME ACTIVE_COUNT QUEUED_COUNT LINEFEED
73
+ #
74
+ # Query parameters:
75
+ #
76
+ # - active_min - do not stream a line until this active count is reached
77
+ # - queued_min - do not stream a line until this queued count is reached
78
+ #
79
+ # == Response headers (mostly the same names as Raindrops::LastDataRecv)
80
+ #
81
+ # - X-Count - number of samples polled
82
+ # - X-Last-Reset - date since the last reset
83
+ #
84
+ # The following headers are only present if X-Count is greater than one.
85
+ #
86
+ # - X-Min - lowest number of connections recorded
87
+ # - X-Max - highest number of connections recorded
88
+ # - X-Mean - mean number of connections recorded
89
+ # - X-Std-Dev - standard deviation of connection count
90
+ # - X-Outliers-Low - number of low outliers (hopefully many for queued)
91
+ # - X-Outliers-High - number of high outliers (hopefully zero for queued)
92
+ # - X-Current - current number of connections
93
+ # - X-First-Peak-At - date of when X-Max was first reached
94
+ # - X-Last-Peak-At - date of when X-Max was last reached
95
+ #
96
+ # = Demo Server
97
+ #
98
+ # There is a server running this app at https://yhbt.net/raindrops-demo/
99
+ # The Raindrops::Middleware demo is also accessible at
100
+ # https://yhbt.net/raindrops-demo/_raindrops
101
+ #
102
+ # The demo server is only limited to 30 users, so be sure not to abuse it
103
+ # by using the /tail/ endpoint too much.
104
+ class Raindrops::Watcher
105
+ # :stopdoc:
106
+ attr_reader :snapshot
107
+ include Rack::Utils
108
+ include Raindrops::Linux
109
+ DOC_URL = "https://yhbt.net/raindrops/Raindrops/Watcher.html"
110
+ Peak = Struct.new(:first, :last)
111
+
112
+ def initialize(opts = {})
113
+ @tcp_listeners = @unix_listeners = nil
114
+ if l = opts[:listeners]
115
+ tcp, unix = [], []
116
+ Array(l).each { |addr| (addr =~ %r{\A/} ? unix : tcp) << addr }
117
+ unless tcp.empty? && unix.empty?
118
+ @tcp_listeners = tcp
119
+ @unix_listeners = unix
120
+ end
121
+ end
122
+
123
+ @agg_class = opts[:agg_class] || Aggregate
124
+ @start_time = Time.now.utc
125
+ @active = Hash.new { |h,k| h[k] = @agg_class.new }
126
+ @queued = Hash.new { |h,k| h[k] = @agg_class.new }
127
+ @resets = Hash.new { |h,k| h[k] = @start_time }
128
+ @peak_active = Hash.new { |h,k| h[k] = Peak.new(@start_time, @start_time) }
129
+ @peak_queued = Hash.new { |h,k| h[k] = Peak.new(@start_time, @start_time) }
130
+ @snapshot = [ @start_time, {} ]
131
+ @delay = opts[:delay] || 1
132
+ @lock = Mutex.new
133
+ @start = Mutex.new
134
+ @cond = ConditionVariable.new
135
+ @thr = nil
136
+ end
137
+
138
+ def hostname
139
+ Socket.gethostname
140
+ end
141
+
142
+ # rack endpoint
143
+ def call(env)
144
+ @start.synchronize { @thr ||= aggregator_thread(env["rack.logger"]) }
145
+ case env["REQUEST_METHOD"]
146
+ when "GET"
147
+ get env
148
+ when "HEAD"
149
+ r = get(env)
150
+ r[2] = []
151
+ r
152
+ when "POST"
153
+ post env
154
+ else
155
+ Rack::Response.new(["Method Not Allowed"], 405).finish
156
+ end
157
+ end
158
+
159
+ def aggregate!(agg_hash, peak_hash, addr, number, now)
160
+ agg = agg_hash[addr]
161
+ if (max = agg.max) && number > 0 && number >= max
162
+ peak = peak_hash[addr]
163
+ peak.first = now if number > max
164
+ peak.last = now
165
+ end
166
+ agg << number
167
+ end
168
+
169
+ def aggregator_thread(logger) # :nodoc:
170
+ @socket = sock = Raindrops::InetDiagSocket.new
171
+ thr = Thread.new do
172
+ begin
173
+ combined = tcp_listener_stats(@tcp_listeners, sock)
174
+ combined.merge!(unix_listener_stats(@unix_listeners))
175
+ @lock.synchronize do
176
+ now = Time.now.utc
177
+ combined.each do |addr,stats|
178
+ aggregate!(@active, @peak_active, addr, stats.active, now)
179
+ aggregate!(@queued, @peak_queued, addr, stats.queued, now)
180
+ end
181
+ @snapshot = [ now, combined ]
182
+ @cond.broadcast
183
+ end
184
+ rescue => e
185
+ logger.error "#{e.class} #{e.inspect}"
186
+ end while sleep(@delay) && @socket
187
+ sock.close
188
+ end
189
+ wait_snapshot
190
+ thr
191
+ end
192
+
193
+ def non_existent_stats(time)
194
+ [ time, @start_time, @agg_class.new, 0, Peak.new(@start_time, @start_time) ]
195
+ end
196
+
197
+ def active_stats(addr) # :nodoc:
198
+ @lock.synchronize do
199
+ time, combined = @snapshot
200
+ stats = combined[addr] or return non_existent_stats(time)
201
+ tmp, peak = @active[addr], @peak_active[addr]
202
+ [ time, @resets[addr], tmp.dup, stats.active, peak ]
203
+ end
204
+ end
205
+
206
+ def queued_stats(addr) # :nodoc:
207
+ @lock.synchronize do
208
+ time, combined = @snapshot
209
+ stats = combined[addr] or return non_existent_stats(time)
210
+ tmp, peak = @queued[addr], @peak_queued[addr]
211
+ [ time, @resets[addr], tmp.dup, stats.queued, peak ]
212
+ end
213
+ end
214
+
215
+ def wait_snapshot
216
+ @lock.synchronize do
217
+ @cond.wait @lock
218
+ @snapshot
219
+ end
220
+ end
221
+
222
+ def std_dev(agg)
223
+ agg.std_dev.to_s
224
+ rescue Errno::EDOM
225
+ "NaN"
226
+ end
227
+
228
+ def agg_to_hash(reset_at, agg, current, peak)
229
+ {
230
+ "X-Count" => agg.count.to_s,
231
+ "X-Min" => agg.min.to_s,
232
+ "X-Max" => agg.max.to_s,
233
+ "X-Mean" => agg.mean.to_s,
234
+ "X-Std-Dev" => std_dev(agg),
235
+ "X-Outliers-Low" => agg.outliers_low.to_s,
236
+ "X-Outliers-High" => agg.outliers_high.to_s,
237
+ "X-Last-Reset" => reset_at.httpdate,
238
+ "X-Current" => current.to_s,
239
+ "X-First-Peak-At" => peak.first.httpdate,
240
+ "X-Last-Peak-At" => peak.last.httpdate,
241
+ }
242
+ end
243
+
244
+ def histogram_txt(agg)
245
+ updated_at, reset_at, agg, current, peak = *agg
246
+ headers = agg_to_hash(reset_at, agg, current, peak)
247
+ body = agg.to_s # 7-bit ASCII-clean
248
+ headers["Content-Type"] = "text/plain"
249
+ headers["Expires"] = (updated_at + @delay).httpdate
250
+ headers["Content-Length"] = body.size.to_s
251
+ [ 200, headers, [ body ] ]
252
+ end
253
+
254
+ def histogram_html(agg, addr)
255
+ updated_at, reset_at, agg, current, peak = *agg
256
+ headers = agg_to_hash(reset_at, agg, current, peak)
257
+ body = "<html>" \
258
+ "<head><title>#{hostname} - #{escape_html addr}</title></head>" \
259
+ "<body><table>" <<
260
+ headers.map { |k,v|
261
+ "<tr><td>#{k.gsub(/^X-/, '')}</td><td>#{v}</td></tr>"
262
+ }.join << "</table><pre>#{escape_html agg}</pre>" \
263
+ "<form action='../reset/#{escape addr}' method='post'>" \
264
+ "<input type='submit' name='x' value='reset' /></form>" \
265
+ "</body>"
266
+ headers["Content-Type"] = "text/html"
267
+ headers["Expires"] = (updated_at + @delay).httpdate
268
+ headers["Content-Length"] = body.size.to_s
269
+ [ 200, headers, [ body ] ]
270
+ end
271
+
272
+ def get(env)
273
+ retried = false
274
+ begin
275
+ case env["PATH_INFO"]
276
+ when "/"
277
+ index
278
+ when %r{\A/active/(.+)\.txt\z}
279
+ histogram_txt(active_stats(unescape($1)))
280
+ when %r{\A/active/(.+)\.html\z}
281
+ addr = unescape $1
282
+ histogram_html(active_stats(addr), addr)
283
+ when %r{\A/queued/(.+)\.txt\z}
284
+ histogram_txt(queued_stats(unescape($1)))
285
+ when %r{\A/queued/(.+)\.html\z}
286
+ addr = unescape $1
287
+ histogram_html(queued_stats(addr), addr)
288
+ when %r{\A/tail/(.+)\.txt\z}
289
+ tail(unescape($1), env)
290
+ else
291
+ not_found
292
+ end
293
+ rescue Errno::EDOM
294
+ raise if retried
295
+ retried = true
296
+ wait_snapshot
297
+ retry
298
+ end
299
+ end
300
+
301
+ def not_found
302
+ Rack::Response.new(["Not Found"], 404).finish
303
+ end
304
+
305
+ def post(env)
306
+ case env["PATH_INFO"]
307
+ when %r{\A/reset/(.+)\z}
308
+ reset!(env, unescape($1))
309
+ else
310
+ not_found
311
+ end
312
+ end
313
+
314
+ def reset!(env, addr)
315
+ @lock.synchronize do
316
+ @active.include?(addr) or return not_found
317
+ @active.delete addr
318
+ @queued.delete addr
319
+ @resets[addr] = Time.now.utc
320
+ @cond.wait @lock
321
+ end
322
+ req = Rack::Request.new(env)
323
+ res = Rack::Response.new
324
+ url = req.referer || "#{req.host_with_port}/"
325
+ res.redirect(url)
326
+ res["Content-Type"] = "text/plain"
327
+ res.write "Redirecting to #{url}"
328
+ res.finish
329
+ end
330
+
331
+ def index
332
+ updated_at, all = snapshot
333
+ headers = {
334
+ "Content-Type" => "text/html",
335
+ "Last-Modified" => updated_at.httpdate,
336
+ "Expires" => (updated_at + @delay).httpdate,
337
+ }
338
+ body = "<html><head>" \
339
+ "<title>#{hostname} - all interfaces</title>" \
340
+ "</head><body><h3>Updated at #{updated_at.iso8601}</h3>" \
341
+ "<table><tr>" \
342
+ "<th>address</th><th>active</th><th>queued</th><th>reset</th>" \
343
+ "</tr>" <<
344
+ all.sort do |a,b|
345
+ a[0] <=> b[0] # sort by addr
346
+ end.map do |addr,stats|
347
+ e_addr = escape addr
348
+ "<tr>" \
349
+ "<td><a href='tail/#{e_addr}.txt' " \
350
+ "title='&quot;tail&quot; output in real time'" \
351
+ ">#{escape_html addr}</a></td>" \
352
+ "<td><a href='active/#{e_addr}.html' " \
353
+ "title='show active connection stats'>#{stats.active}</a></td>" \
354
+ "<td><a href='queued/#{e_addr}.html' " \
355
+ "title='show queued connection stats'>#{stats.queued}</a></td>" \
356
+ "<td><form action='reset/#{e_addr}' method='post'>" \
357
+ "<input title='reset statistics' " \
358
+ "type='submit' name='x' value='x' /></form></td>" \
359
+ "</tr>" \
360
+ end.join << "</table>" \
361
+ "<p>" \
362
+ "This is running the #{self.class}</a> service, see " \
363
+ "<a href='#{DOC_URL}'>#{DOC_URL}</a> " \
364
+ "for more information and options." \
365
+ "</p>" \
366
+ "</body></html>"
367
+ headers["Content-Length"] = body.size.to_s
368
+ [ 200, headers, [ body ] ]
369
+ end
370
+
371
+ def tail(addr, env)
372
+ Tailer.new(self, addr, env).finish
373
+ end
374
+
375
+ # This is the response body returned for "/tail/$ADDRESS.txt". This
376
+ # must use a multi-threaded Rack server with streaming response support.
377
+ # It is an internal class and not expected to be used directly
378
+ class Tailer
379
+ def initialize(rdmon, addr, env) # :nodoc:
380
+ @rdmon = rdmon
381
+ @addr = addr
382
+ q = Rack::Utils.parse_query env["QUERY_STRING"]
383
+ @active_min = q["active_min"].to_i
384
+ @queued_min = q["queued_min"].to_i
385
+ len = addr.size
386
+ len = 35 if len > 35
387
+ @fmt = "%20s % #{len}s % 10u % 10u\n"
388
+ case env["HTTP_VERSION"]
389
+ when "HTTP/1.0", nil
390
+ @chunk = false
391
+ else
392
+ @chunk = true
393
+ end
394
+ end
395
+
396
+ def finish
397
+ headers = {
398
+ "Content-Type" => "text/plain",
399
+ "Cache-Control" => "no-transform",
400
+ "Expires" => Time.at(0).httpdate,
401
+ }
402
+ headers["Transfer-Encoding"] = "chunked" if @chunk
403
+ [ 200, headers, self ]
404
+ end
405
+
406
+ # called by the Rack server
407
+ def each # :nodoc:
408
+ begin
409
+ time, all = @rdmon.wait_snapshot
410
+ stats = all[@addr] or next
411
+ stats.queued >= @queued_min or next
412
+ stats.active >= @active_min or next
413
+ body = sprintf(@fmt, time.iso8601, @addr, stats.active, stats.queued)
414
+ body = "#{body.size.to_s(16)}\r\n#{body}\r\n" if @chunk
415
+ yield body
416
+ end while true
417
+ yield "0\r\n\r\n" if @chunk
418
+ end
419
+ end
420
+
421
+ # shuts down the background thread, only for tests
422
+ def shutdown
423
+ @socket = nil
424
+ @thr.join if @thr
425
+ @thr = nil
426
+ end
427
+ # :startdoc:
428
+ end
data/lib/raindrops.rb ADDED
@@ -0,0 +1,72 @@
1
+ # -*- encoding: binary -*-
2
+ #
3
+ # Each Raindrops object is a container that holds several counters.
4
+ # It is internally a page-aligned, shared memory area that allows
5
+ # atomic increments, decrements, assignments and reads without any
6
+ # locking.
7
+ #
8
+ # rd = Raindrops.new 4
9
+ # rd.incr(0, 1) -> 1
10
+ # rd.to_ary -> [ 1, 0, 0, 0 ]
11
+ #
12
+ # Unlike many classes in this package, the core Raindrops class is
13
+ # intended to be portable to all reasonably modern *nix systems
14
+ # supporting mmap(). Please let us know if you have portability
15
+ # issues, patches or pull requests at mailto:raindrops-public@yhbt.net
16
+ class Raindrops
17
+
18
+ # Used to represent the number of +active+ and +queued+ sockets for
19
+ # a single listen socket across all threads and processes on a
20
+ # machine.
21
+ #
22
+ # For TCP listeners, only sockets in the TCP_ESTABLISHED state are
23
+ # accounted for. For Unix domain listeners, only CONNECTING and
24
+ # CONNECTED Unix domain sockets are accounted for.
25
+ #
26
+ # +active+ connections is the number of accept()-ed but not-yet-closed
27
+ # sockets in all threads/processes sharing the given listener.
28
+ #
29
+ # +queued+ connections is the number of un-accept()-ed sockets in the
30
+ # queue of a given listen socket.
31
+ #
32
+ # These stats are currently only available under \Linux
33
+ class ListenStats < Struct.new(:active, :queued)
34
+
35
+ # the sum of +active+ and +queued+ sockets
36
+ def total
37
+ active + queued
38
+ end
39
+ end unless defined? ListenStats
40
+
41
+ # call-seq:
42
+ # Raindrops.new(size, io: nil) -> raindrops object
43
+ #
44
+ # Initializes a Raindrops object to hold +size+ counters. +size+ is
45
+ # only a hint and the actual number of counters the object has is
46
+ # dependent on the CPU model, number of cores, and page size of
47
+ # the machine. The actual size of the object will always be equal
48
+ # or greater than the specified +size+.
49
+ # If +io+ is provided, then the Raindrops memory will be backed by
50
+ # the specified file; otherwise, it will allocate anonymous memory.
51
+ # The IO object must respond to +truncate+, as this is used to set
52
+ # the size of the file.
53
+ # If +zero+ is provided, then the memory region is zeroed prior to
54
+ # returning. This is only meaningful if +io+ is also provided; in
55
+ # that case it controls whether any existing counter values in +io+
56
+ # are retained (false) or whether it is entirely zeroed (true).
57
+ def initialize(size, io: nil, zero: false)
58
+ # This ruby wrapper exists to handle the keyword-argument handling,
59
+ # which is otherwise kind of awkward in C. We delegate the keyword
60
+ # arguments to the _actual_ initialize implementation as positional
61
+ # args.
62
+ initialize_cimpl(size, io, zero)
63
+ end
64
+
65
+ autoload :Linux, 'raindrops/linux'
66
+ autoload :Struct, 'raindrops/struct'
67
+ autoload :Middleware, 'raindrops/middleware'
68
+ autoload :Aggregate, 'raindrops/aggregate'
69
+ autoload :LastDataRecv, 'raindrops/last_data_recv'
70
+ autoload :Watcher, 'raindrops/watcher'
71
+ end
72
+ require 'raindrops_ext'
data/pkg.mk ADDED
@@ -0,0 +1,151 @@
1
+ RUBY = ruby
2
+ RAKE = rake
3
+ RSYNC = rsync
4
+ OLDDOC = olddoc
5
+ RDOC = rdoc
6
+
7
+ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
8
+ @./GIT-VERSION-GEN
9
+ -include GIT-VERSION-FILE
10
+ -include local.mk
11
+ DLEXT := $(shell $(RUBY) -rrbconfig -e 'puts RbConfig::CONFIG["DLEXT"]')
12
+ RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
13
+ RUBY_ENGINE := $(shell $(RUBY) -e 'puts((RUBY_ENGINE rescue "ruby"))')
14
+ lib := lib
15
+
16
+ ext := $(firstword $(wildcard ext/*))
17
+ ifneq ($(ext),)
18
+ ext_pfx := tmp/ext/$(RUBY_ENGINE)-$(RUBY_VERSION)
19
+ ext_h := $(wildcard $(ext)/*/*.h $(ext)/*.h)
20
+ ext_src := $(wildcard $(ext)/*.c $(ext_h))
21
+ ext_pfx_src := $(addprefix $(ext_pfx)/,$(ext_src))
22
+ ext_d := $(ext_pfx)/$(ext)/.d
23
+ $(ext)/extconf.rb: $(wildcard $(ext)/*.h)
24
+ @>> $@
25
+ $(ext_d):
26
+ @mkdir -p $(@D)
27
+ @> $@
28
+ $(ext_pfx)/$(ext)/%: $(ext)/% $(ext_d)
29
+ install -m 644 $< $@
30
+ $(ext_pfx)/$(ext)/Makefile: $(ext)/extconf.rb $(ext_d) $(ext_h)
31
+ $(RM) -f $(@D)/*.o
32
+ cd $(@D) && $(RUBY) $(CURDIR)/$(ext)/extconf.rb $(EXTCONF_ARGS)
33
+ ext_sfx := _ext.$(DLEXT)
34
+ ext_dl := $(ext_pfx)/$(ext)/$(notdir $(ext)_ext.$(DLEXT))
35
+ $(ext_dl): $(ext_src) $(ext_pfx_src) $(ext_pfx)/$(ext)/Makefile
36
+ @echo $^ == $@
37
+ $(MAKE) -C $(@D)
38
+ lib := $(lib):$(ext_pfx)/$(ext)
39
+ build: $(ext_dl)
40
+ else
41
+ build:
42
+ endif
43
+
44
+ pkg_extra += GIT-VERSION-FILE NEWS LATEST
45
+ NEWS: GIT-VERSION-FILE .olddoc.yml
46
+ $(OLDDOC) prepare
47
+ LATEST: NEWS
48
+
49
+ manifest:
50
+ $(RM) .manifest
51
+ $(MAKE) .manifest
52
+
53
+ .manifest: $(pkg_extra)
54
+ (git ls-files && for i in $@ $(pkg_extra); do echo $$i; done) | \
55
+ LC_ALL=C sort > $@+
56
+ cmp $@+ $@ || mv $@+ $@
57
+ $(RM) $@+
58
+
59
+ doc:: .document .olddoc.yml $(pkg_extra) $(PLACEHOLDERS)
60
+ -find lib -type f -name '*.rbc' -exec rm -f '{}' ';'
61
+ -find ext -type f -name '*.rbc' -exec rm -f '{}' ';'
62
+ $(RM) -r doc
63
+ $(RDOC) -f dark216
64
+ $(OLDDOC) merge
65
+ install -m644 COPYING doc/COPYING
66
+ install -m644 NEWS doc/NEWS
67
+ install -m644 NEWS.atom.xml doc/NEWS.atom.xml
68
+ install -m644 $(shell LC_ALL=C grep '^[A-Z]' .document) doc/
69
+
70
+ ifneq ($(VERSION),)
71
+ pkggem := pkg/$(rfpackage)-$(VERSION).gem
72
+ pkgtgz := pkg/$(rfpackage)-$(VERSION).tgz
73
+
74
+ # ensures we're actually on the tagged $(VERSION), only used for release
75
+ verify:
76
+ test x"$(shell umask)" = x0022
77
+ git rev-parse --verify refs/tags/v$(VERSION)^{}
78
+ git diff-index --quiet HEAD^0
79
+ test $$(git rev-parse --verify HEAD^0) = \
80
+ $$(git rev-parse --verify refs/tags/v$(VERSION)^{})
81
+
82
+ fix-perms:
83
+ -git ls-tree -r HEAD | awk '/^100644 / {print $$NF}' | xargs chmod 644
84
+ -git ls-tree -r HEAD | awk '/^100755 / {print $$NF}' | xargs chmod 755
85
+
86
+ gem: $(pkggem)
87
+
88
+ install-gem: $(pkggem)
89
+ gem install --local $(CURDIR)/$<
90
+
91
+ $(pkggem): manifest fix-perms
92
+ gem build $(rfpackage).gemspec
93
+ mkdir -p pkg
94
+ mv $(@F) $@
95
+
96
+ $(pkgtgz): distdir = $(basename $@)
97
+ $(pkgtgz): HEAD = v$(VERSION)
98
+ $(pkgtgz): manifest fix-perms
99
+ @test -n "$(distdir)"
100
+ $(RM) -r $(distdir)
101
+ mkdir -p $(distdir)
102
+ tar cf - $$(cat .manifest) | (cd $(distdir) && tar xf -)
103
+ cd pkg && tar cf - $(basename $(@F)) | gzip -9 > $(@F)+
104
+ mv $@+ $@
105
+
106
+ package: $(pkgtgz) $(pkggem)
107
+
108
+ release:: verify package
109
+ # push gem to RubyGems.org
110
+ gem push $(pkggem)
111
+ else
112
+ gem install-gem: GIT-VERSION-FILE
113
+ $(MAKE) $@ VERSION=$(GIT_VERSION)
114
+ endif
115
+
116
+ all:: check
117
+ test_units := $(wildcard test/test_*.rb)
118
+ test: check
119
+ check: test-unit
120
+ test-unit: $(test_units)
121
+ $(test_units): build
122
+ $(RUBY) -I $(lib) $@ $(RUBY_TEST_OPTS)
123
+
124
+ # this requires GNU coreutils variants
125
+ ifneq ($(RSYNC_DEST),)
126
+ publish_doc:
127
+ -git set-file-times
128
+ $(MAKE) doc
129
+ $(MAKE) doc_gz
130
+ $(RSYNC) -av doc/ $(RSYNC_DEST)/ \
131
+ --exclude index.html* --exclude created.rid*
132
+ git ls-files | xargs touch
133
+ endif
134
+
135
+ # Create gzip variants of the same timestamp as the original so nginx
136
+ # "gzip_static on" can serve the gzipped versions directly.
137
+ doc_gz: docs = $(shell find doc -type f ! -regex '^.*\.gz$$')
138
+ doc_gz:
139
+ for i in $(docs); do \
140
+ gzip --rsyncable -9 < $$i > $$i.gz; touch -r $$i $$i.gz; done
141
+ check-warnings:
142
+ @(for i in $$(git ls-files '*.rb'| grep -v '^setup\.rb$$'); \
143
+ do $(RUBY) -d -W2 -c $$i; done) | grep -v '^Syntax OK$$' || :
144
+
145
+ ifneq ($(PLACEHOLDERS),)
146
+ $(PLACEHOLDERS):
147
+ echo olddoc_placeholder > $@
148
+ endif
149
+
150
+ .PHONY: all .FORCE-GIT-VERSION-FILE doc check test $(test_units) manifest
151
+ .PHONY: check-warnings
@@ -0,0 +1 @@
1
+ raindrops.gemspec
data/raindrops.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: binary -*-
2
+ manifest = File.exist?('.manifest') ?
3
+ IO.readlines('.manifest').map!(&:chomp!) : `git ls-files`.split("\n")
4
+ test_files = manifest.grep(%r{\Atest/test_.*\.rb\z})
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "raindrops-maintained"
8
+ s.version = (ENV["VERSION"] ||= '0.21.0').dup
9
+ s.authors = ["raindrops hackers"]
10
+ s.description = File.read('README').split("\n\n")[1]
11
+ s.email = %q{raindrops-public@yhbt.net}
12
+ s.extensions = %w(ext/raindrops/extconf.rb)
13
+ s.extra_rdoc_files = IO.readlines('.document').map!(&:chomp!).keep_if do |f|
14
+ File.exist?(f)
15
+ end
16
+ s.files = manifest
17
+ s.homepage = 'https://yhbt.net/raindrops/'
18
+ s.summary = 'real-time stats for preforking Rack servers'
19
+ s.required_ruby_version = '>= 1.9.3'
20
+ s.test_files = test_files
21
+ s.add_development_dependency('aggregate', '~> 0.2')
22
+ s.add_development_dependency('test-unit', '~> 3.0')
23
+ s.add_development_dependency('posix_mq', '~> 2.0')
24
+ s.add_development_dependency('rack', [ '>= 1.2', '< 4' ])
25
+ s.licenses = %w(LGPL-2.1+)
26
+ end