raindrops 0.7.0 → 0.8.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.
data/GIT-VERSION-GEN CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v0.7.0.GIT
4
+ DEF_VER=v0.8.0.GIT
5
5
 
6
6
  LF='
7
7
  '
data/TODO CHANGED
@@ -1 +1 @@
1
- * pure Ruby version for non-forking servers
1
+ * pure Ruby version for non-forking servers (patches welcome!)
@@ -58,6 +58,10 @@ require "aggregate"
58
58
  #
59
59
  # e.g.: curl http://raindrops-demo.bogomips.org/queued/0.0.0.0%3A80.html
60
60
  #
61
+ # === POST /reset/$LISTENER
62
+ #
63
+ # Resets the active and queued statistics for the given listener.
64
+ #
61
65
  # === GET /tail/$LISTENER.txt?active_min=1&queued_min=1
62
66
  #
63
67
  # Streams chunked a response to the client.
@@ -85,6 +89,9 @@ require "aggregate"
85
89
  # - X-Std-Dev - standard deviation of connection count
86
90
  # - X-Outliers-Low - number of low outliers (hopefully many for queued)
87
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
88
95
  #
89
96
  # = Demo Server
90
97
  #
@@ -100,6 +107,7 @@ class Raindrops::Watcher
100
107
  include Rack::Utils
101
108
  include Raindrops::Linux
102
109
  DOC_URL = "http://raindrops.bogomips.org/Raindrops/Watcher.html"
110
+ Peak = Struct.new(:first, :last)
103
111
 
104
112
  def initialize(opts = {})
105
113
  @tcp_listeners = @unix_listeners = nil
@@ -112,12 +120,14 @@ class Raindrops::Watcher
112
120
  end
113
121
  end
114
122
 
115
- agg_class = opts[:agg_class] || Aggregate
116
- start = Time.now.utc
117
- @active = Hash.new { |h,k| h[k] = agg_class.new }
118
- @queued = Hash.new { |h,k| h[k] = agg_class.new }
119
- @resets = Hash.new { |h,k| h[k] = start }
120
- @snapshot = [ start, {} ]
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, {} ]
121
131
  @delay = opts[:delay] || 1
122
132
  @lock = Mutex.new
123
133
  @start = Mutex.new
@@ -142,6 +152,16 @@ class Raindrops::Watcher
142
152
  end
143
153
  end
144
154
 
155
+ def aggregate!(agg_hash, peak_hash, addr, number, now)
156
+ agg = agg_hash[addr]
157
+ if (max = agg.max) && number > 0 && number >= max
158
+ peak = peak_hash[addr]
159
+ peak.first = now if number > max
160
+ peak.last = now
161
+ end
162
+ agg << number
163
+ end
164
+
145
165
  def aggregator_thread(logger) # :nodoc:
146
166
  @socket = sock = Raindrops::InetDiagSocket.new
147
167
  thr = Thread.new do
@@ -149,11 +169,12 @@ class Raindrops::Watcher
149
169
  combined = tcp_listener_stats(@tcp_listeners, sock)
150
170
  combined.merge!(unix_listener_stats(@unix_listeners))
151
171
  @lock.synchronize do
172
+ now = Time.now.utc
152
173
  combined.each do |addr,stats|
153
- @active[addr] << stats.active
154
- @queued[addr] << stats.queued
174
+ aggregate!(@active, @peak_active, addr, stats.active, now)
175
+ aggregate!(@queued, @peak_queued, addr, stats.queued, now)
155
176
  end
156
- @snapshot = [ Time.now.utc, combined ]
177
+ @snapshot = [ now, combined ]
157
178
  @cond.broadcast
158
179
  end
159
180
  rescue => e
@@ -165,17 +186,25 @@ class Raindrops::Watcher
165
186
  thr
166
187
  end
167
188
 
189
+ def non_existent_stats(time)
190
+ [ time, @start_time, @agg_class.new, 0, Peak.new(@start_time, @start_time) ]
191
+ end
192
+
168
193
  def active_stats(addr) # :nodoc:
169
194
  @lock.synchronize do
170
- tmp = @active[addr] or return
171
- [ @snapshot[0], @resets[addr], tmp.dup ]
195
+ time, combined = @snapshot
196
+ stats = combined[addr] or return non_existent_stats(time)
197
+ tmp, peak = @active[addr], @peak_active[addr]
198
+ [ time, @resets[addr], tmp.dup, stats.active, peak ]
172
199
  end
173
200
  end
174
201
 
175
202
  def queued_stats(addr) # :nodoc:
176
203
  @lock.synchronize do
177
- tmp = @queued[addr] or return
178
- [ @snapshot[0], @resets[addr], tmp.dup ]
204
+ time, combined = @snapshot
205
+ stats = combined[addr] or return non_existent_stats(time)
206
+ tmp, peak = @queued[addr], @peak_queued[addr]
207
+ [ time, @resets[addr], tmp.dup, stats.queued, peak ]
179
208
  end
180
209
  end
181
210
 
@@ -186,22 +215,31 @@ class Raindrops::Watcher
186
215
  end
187
216
  end
188
217
 
189
- def agg_to_hash(reset_at, agg)
218
+ def std_dev(agg)
219
+ agg.std_dev.to_s
220
+ rescue Errno::EDOM
221
+ "NaN"
222
+ end
223
+
224
+ def agg_to_hash(reset_at, agg, current, peak)
190
225
  {
191
226
  "X-Count" => agg.count.to_s,
192
227
  "X-Min" => agg.min.to_s,
193
228
  "X-Max" => agg.max.to_s,
194
229
  "X-Mean" => agg.mean.to_s,
195
- "X-Std-Dev" => agg.std_dev.to_s,
230
+ "X-Std-Dev" => std_dev(agg),
196
231
  "X-Outliers-Low" => agg.outliers_low.to_s,
197
232
  "X-Outliers-High" => agg.outliers_high.to_s,
198
233
  "X-Last-Reset" => reset_at.httpdate,
234
+ "X-Current" => current.to_s,
235
+ "X-First-Peak-At" => peak.first.httpdate,
236
+ "X-Last-Peak-At" => peak.last.httpdate,
199
237
  }
200
238
  end
201
239
 
202
240
  def histogram_txt(agg)
203
- updated_at, reset_at, agg = *agg
204
- headers = agg_to_hash(reset_at, agg)
241
+ updated_at, reset_at, agg, current, peak = *agg
242
+ headers = agg_to_hash(reset_at, agg, current, peak)
205
243
  body = agg.to_s
206
244
  headers["Content-Type"] = "text/plain"
207
245
  headers["Expires"] = (updated_at + @delay).httpdate
@@ -210,8 +248,8 @@ class Raindrops::Watcher
210
248
  end
211
249
 
212
250
  def histogram_html(agg, addr)
213
- updated_at, reset_at, agg = *agg
214
- headers = agg_to_hash(reset_at, agg)
251
+ updated_at, reset_at, agg, current, peak = *agg
252
+ headers = agg_to_hash(reset_at, agg, current, peak)
215
253
  body = "<html>" \
216
254
  "<head><title>#{hostname} - #{escape_html addr}</title></head>" \
217
255
  "<body><table>" <<
@@ -229,29 +267,31 @@ class Raindrops::Watcher
229
267
 
230
268
  def get(env)
231
269
  retried = false
232
- case env["PATH_INFO"]
233
- when "/"
234
- index
235
- when %r{\A/active/(.+)\.txt\z}
236
- histogram_txt(active_stats(unescape($1)))
237
- when %r{\A/active/(.+)\.html\z}
238
- addr = unescape $1
239
- histogram_html(active_stats(addr), addr)
240
- when %r{\A/queued/(.+)\.txt\z}
241
- histogram_txt(queued_stats(unescape($1)))
242
- when %r{\A/queued/(.+)\.html\z}
243
- addr = unescape $1
244
- histogram_html(queued_stats(addr), addr)
245
- when %r{\A/tail/(.+)\.txt\z}
246
- tail(unescape($1), env)
247
- else
248
- not_found
249
- end
270
+ begin
271
+ case env["PATH_INFO"]
272
+ when "/"
273
+ index
274
+ when %r{\A/active/(.+)\.txt\z}
275
+ histogram_txt(active_stats(unescape($1)))
276
+ when %r{\A/active/(.+)\.html\z}
277
+ addr = unescape $1
278
+ histogram_html(active_stats(addr), addr)
279
+ when %r{\A/queued/(.+)\.txt\z}
280
+ histogram_txt(queued_stats(unescape($1)))
281
+ when %r{\A/queued/(.+)\.html\z}
282
+ addr = unescape $1
283
+ histogram_html(queued_stats(addr), addr)
284
+ when %r{\A/tail/(.+)\.txt\z}
285
+ tail(unescape($1), env)
286
+ else
287
+ not_found
288
+ end
250
289
  rescue Errno::EDOM
251
290
  raise if retried
252
291
  retried = true
253
292
  wait_snapshot
254
293
  retry
294
+ end
255
295
  end
256
296
 
257
297
  def not_found
@@ -2,7 +2,7 @@ require "test/unit"
2
2
  require "raindrops"
3
3
  pmq = begin
4
4
  Raindrops::Aggregate::PMQ
5
- rescue => LoadError
5
+ rescue LoadError => e
6
6
  warn "W: #{e} skipping test"
7
7
  false
8
8
  end
@@ -6,7 +6,7 @@ require "net/http"
6
6
  $stderr.sync = $stdout.sync = true
7
7
  pmq = begin
8
8
  Raindrops::Aggregate::PMQ
9
- rescue => LoadError
9
+ rescue LoadError => e
10
10
  warn "W: #{e} skipping test"
11
11
  false
12
12
  end
@@ -40,7 +40,7 @@ class TestLastDataRecvUnicorn < Test::Unit::TestCase
40
40
  end.to_app!
41
41
  def app.arity; 0; end
42
42
  def app.call; eval self; end
43
- Unicorn.run(app, @opts)
43
+ Unicorn::HttpServer.new(app, @opts).start.join
44
44
  }
45
45
  400.times { assert_kind_of Net::HTTPSuccess, get("/") }
46
46
  resp = get("/ldr")
@@ -19,7 +19,7 @@ class TestMiddlewareUnicorn < Test::Unit::TestCase
19
19
  use Raindrops::Middleware
20
20
  run Rack::Lobster.new
21
21
  end
22
- @srv = fork { Unicorn.run(@app, @opts) }
22
+ @srv = fork { Unicorn::HttpServer.new(@app, @opts).start.join }
23
23
 
24
24
  s = TCPSocket.new @host, @port
25
25
  s.write "GET /_raindrops HTTP/1.0\r\n\r\n"
@@ -20,7 +20,7 @@ class TestMiddlewareUnicornIPv6 < Test::Unit::TestCase
20
20
  use Raindrops::Middleware
21
21
  run Rack::Lobster.new
22
22
  end
23
- @srv = fork { Unicorn.run(@app, @opts) }
23
+ @srv = fork { Unicorn::HttpServer.new(@app, @opts).start.join }
24
24
  s = TCPSocket.new @host, @port
25
25
  s.write "GET /_raindrops HTTP/1.0\r\n\r\n"
26
26
  resp = s.read
data/test/test_watcher.rb CHANGED
@@ -43,6 +43,20 @@ class TestWatcher < Test::Unit::TestCase
43
43
  check_headers(resp.headers)
44
44
  end
45
45
 
46
+ def test_invalid
47
+ assert_nothing_raised do
48
+ @req.get("/active/666.666.666.666%3A666.txt")
49
+ @req.get("/queued/666.666.666.666%3A666.txt")
50
+ @req.get("/active/666.666.666.666%3A666.html")
51
+ @req.get("/queued/666.666.666.666%3A666.html")
52
+ end
53
+ addr = @app.instance_eval do
54
+ @peak_active.keys + @peak_queued.keys +
55
+ @resets.keys + @active.keys + @queued.keys
56
+ end
57
+ assert addr.grep(/666\.666\.666\.666/).empty?, addr.inspect
58
+ end
59
+
46
60
  def test_active_html
47
61
  resp = @req.get "/active/#@addr.html"
48
62
  assert_equal 200, resp.status.to_i
@@ -96,4 +110,66 @@ class TestWatcher < Test::Unit::TestCase
96
110
  break
97
111
  end
98
112
  end
113
+
114
+ def test_x_current_header
115
+ env = @req.class.env_for "/active/#@addr.txt"
116
+ status, headers, body = @app.call(env)
117
+ assert_equal "0", headers["X-Current"], headers.inspect
118
+
119
+ env = @req.class.env_for "/queued/#@addr.txt"
120
+ status, headers, body = @app.call(env)
121
+ assert_equal "1", headers["X-Current"], headers.inspect
122
+
123
+ @ios << @srv.accept
124
+ sleep 0.1
125
+
126
+ env = @req.class.env_for "/queued/#@addr.txt"
127
+ status, headers, body = @app.call(env)
128
+ assert_equal "0", headers["X-Current"], headers.inspect
129
+
130
+ env = @req.class.env_for "/active/#@addr.txt"
131
+ status, headers, body = @app.call(env)
132
+ assert_equal "1", headers["X-Current"], headers.inspect
133
+ end
134
+
135
+ def test_peaks
136
+ env = @req.class.env_for "/active/#@addr.txt"
137
+ status, headers, body = @app.call(env.dup)
138
+ start = headers["X-First-Peak-At"]
139
+ assert headers["X-First-Peak-At"], headers.inspect
140
+ assert headers["X-Last-Peak-At"], headers.inspect
141
+ assert_nothing_raised { Time.parse(headers["X-First-Peak-At"]) }
142
+ assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) }
143
+ before = headers["X-Last-Peak-At"]
144
+
145
+ env = @req.class.env_for "/queued/#@addr.txt"
146
+ status, headers, body = @app.call(env)
147
+ assert_nothing_raised { Time.parse(headers["X-First-Peak-At"]) }
148
+ assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) }
149
+ assert_equal before, headers["X-Last-Peak-At"], "should not change"
150
+
151
+ sleep 2
152
+ env = @req.class.env_for "/active/#@addr.txt"
153
+ status, headers, body = @app.call(env.dup)
154
+ assert_equal before, headers["X-Last-Peak-At"], headers.inspect
155
+
156
+ @ios << @srv.accept
157
+ assert_raises(Errno::EAGAIN) { @srv.accept_nonblock }
158
+ sleep 0.1
159
+ status, headers, body = @app.call(env.dup)
160
+ assert headers["X-Last-Peak-At"], headers.inspect
161
+ assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) }
162
+ assert before != headers["X-Last-Peak-At"]
163
+
164
+ queued_before = headers["X-Last-Peak-At"]
165
+
166
+ sleep 2
167
+
168
+ env = @req.class.env_for "/queued/#@addr.txt"
169
+ status, headers, body = @app.call(env)
170
+ assert_equal "0", headers["X-Current"]
171
+ assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) }
172
+ assert_equal queued_before, headers["X-Last-Peak-At"], "should not change"
173
+ assert_equal start, headers["X-First-Peak-At"]
174
+ end
99
175
  end if RUBY_PLATFORM =~ /linux/
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raindrops
3
3
  version: !ruby/object:Gem::Version
4
- hash: 3
4
+ hash: 63
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 7
8
+ - 8
9
9
  - 0
10
- version: 0.7.0
10
+ version: 0.8.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - raindrops hackers
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-06-27 00:00:00 Z
18
+ date: 2011-10-15 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: bundler