raindrops 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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