raindrops 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +2 -1
- data/.gitignore +4 -0
- data/.wrongdoc.yml +4 -0
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +2 -196
- data/Gemfile +7 -0
- data/LICENSE +1 -1
- data/README +17 -47
- data/Rakefile +0 -104
- data/examples/linux-listener-stats.rb +123 -0
- data/examples/{config.ru → middleware.ru} +1 -1
- data/examples/watcher.ru +4 -0
- data/examples/watcher_demo.ru +13 -0
- data/examples/zbatery.conf.rb +13 -0
- data/ext/raindrops/extconf.rb +5 -0
- data/ext/raindrops/linux_inet_diag.c +449 -151
- data/ext/raindrops/linux_tcp_info.c +170 -0
- data/ext/raindrops/my_fileno.h +36 -0
- data/ext/raindrops/raindrops.c +232 -20
- data/lib/raindrops.rb +20 -7
- data/lib/raindrops/aggregate.rb +8 -0
- data/lib/raindrops/aggregate/last_data_recv.rb +86 -0
- data/lib/raindrops/aggregate/pmq.rb +239 -0
- data/lib/raindrops/last_data_recv.rb +100 -0
- data/lib/raindrops/linux.rb +26 -16
- data/lib/raindrops/middleware.rb +112 -41
- data/lib/raindrops/middleware/proxy.rb +34 -0
- data/lib/raindrops/struct.rb +15 -0
- data/lib/raindrops/watcher.rb +362 -0
- data/pkg.mk +171 -0
- data/raindrops.gemspec +10 -20
- data/test/ipv6_enabled.rb +10 -0
- data/test/rack_unicorn.rb +12 -0
- data/test/test_aggregate_pmq.rb +65 -0
- data/test/test_inet_diag_socket.rb +13 -0
- data/test/test_last_data_recv_unicorn.rb +69 -0
- data/test/test_linux.rb +55 -57
- 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 +158 -0
- data/test/test_linux_tcp_info.rb +61 -0
- data/test/test_middleware.rb +15 -2
- data/test/test_middleware_unicorn.rb +37 -0
- data/test/test_middleware_unicorn_ipv6.rb +37 -0
- data/test/test_raindrops.rb +65 -1
- data/test/test_raindrops_gc.rb +23 -1
- data/test/test_watcher.rb +85 -0
- metadata +69 -22
- data/examples/linux-tcp-listener-stats.rb +0 -44
data/lib/raindrops.rb
CHANGED
@@ -1,9 +1,20 @@
|
|
1
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@librelist.com
|
2
16
|
class Raindrops
|
3
17
|
|
4
|
-
# Raindrops is currently at version 0.4.1
|
5
|
-
VERSION = '0.4.1'
|
6
|
-
|
7
18
|
# Used to represent the number of +active+ and +queued+ sockets for
|
8
19
|
# a single listen socket across all threads and processes on a
|
9
20
|
# machine.
|
@@ -18,7 +29,7 @@ class Raindrops
|
|
18
29
|
# +queued+ connections is the number of un-accept()-ed sockets in the
|
19
30
|
# queue of a given listen socket.
|
20
31
|
#
|
21
|
-
# These stats are currently only available under Linux
|
32
|
+
# These stats are currently only available under \Linux
|
22
33
|
class ListenStats < Struct.new(:active, :queued)
|
23
34
|
|
24
35
|
# the sum of +active+ and +queued+ sockets
|
@@ -27,9 +38,11 @@ class Raindrops
|
|
27
38
|
end
|
28
39
|
end
|
29
40
|
|
30
|
-
|
31
|
-
require 'raindrops_ext'
|
32
|
-
|
41
|
+
autoload :Linux, 'raindrops/linux'
|
33
42
|
autoload :Struct, 'raindrops/struct'
|
34
43
|
autoload :Middleware, 'raindrops/middleware'
|
44
|
+
autoload :Aggregate, 'raindrops/aggregate'
|
45
|
+
autoload :LastDataRecv, 'raindrops/last_data_recv'
|
46
|
+
autoload :Watcher, 'raindrops/watcher'
|
35
47
|
end
|
48
|
+
require 'raindrops_ext'
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
#
|
3
|
+
# raindrops may use the {aggregate}[http://github.com/josephruscio/aggregate]
|
4
|
+
# RubyGem to aggregate statistics from TCP_Info lookups.
|
5
|
+
module Raindrops::Aggregate
|
6
|
+
autoload :PMQ, "raindrops/aggregate/pmq"
|
7
|
+
autoload :LastDataRecv, "raindrops/aggregate/last_data_recv"
|
8
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require "socket"
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# This module is used to extend TCPServer and Kgio::TCPServer objects
|
6
|
+
# and aggregate +last_data_recv+ times for all accepted clients. It
|
7
|
+
# is designed to be used with Raindrops::LastDataRecv Rack application
|
8
|
+
# but can be easily changed to work with other stats collection devices.
|
9
|
+
#
|
10
|
+
# Methods wrapped include:
|
11
|
+
# - TCPServer#accept
|
12
|
+
# - TCPServer#accept_nonblock
|
13
|
+
# - Kgio::TCPServer#kgio_accept
|
14
|
+
# - Kgio::TCPServer#kgio_tryaccept
|
15
|
+
module Raindrops::Aggregate::LastDataRecv
|
16
|
+
# :stopdoc:
|
17
|
+
TCP_Info = Raindrops::TCP_Info
|
18
|
+
# :startdoc:
|
19
|
+
|
20
|
+
# The integer value of +last_data_recv+ is sent to this object.
|
21
|
+
# This is usually a duck type compatible with the \Aggregate class,
|
22
|
+
# but can be *anything* that accepts the *<<* method.
|
23
|
+
attr_accessor :raindrops_aggregate
|
24
|
+
|
25
|
+
@@default_aggregate = nil
|
26
|
+
|
27
|
+
# By default, this is a Raindrops::Aggregate::PMQ object
|
28
|
+
# It may be anything that responds to *<<*
|
29
|
+
def self.default_aggregate
|
30
|
+
@@default_aggregate ||= Raindrops::Aggregate::PMQ.new
|
31
|
+
end
|
32
|
+
|
33
|
+
# Assign any object that responds to *<<*
|
34
|
+
def self.default_aggregate=(agg)
|
35
|
+
@@default_aggregate = agg
|
36
|
+
end
|
37
|
+
|
38
|
+
# automatically extends any TCPServer objects used by Unicorn
|
39
|
+
def self.cornify!
|
40
|
+
Unicorn::HttpServer::LISTENERS.each do |sock|
|
41
|
+
sock.extend(self) if TCPServer === sock
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# each extended object needs to have TCP_DEFER_ACCEPT enabled
|
46
|
+
# for accuracy.
|
47
|
+
def self.extended(obj)
|
48
|
+
obj.raindrops_aggregate = default_aggregate
|
49
|
+
obj.setsockopt Socket::SOL_TCP, tcp_defer_accept = 9, seconds = 60
|
50
|
+
end
|
51
|
+
|
52
|
+
# :stopdoc:
|
53
|
+
|
54
|
+
def kgio_tryaccept(*args)
|
55
|
+
count! super
|
56
|
+
end
|
57
|
+
|
58
|
+
def kgio_accept(*args)
|
59
|
+
count! super
|
60
|
+
end
|
61
|
+
|
62
|
+
def accept
|
63
|
+
count! super
|
64
|
+
end
|
65
|
+
|
66
|
+
def accept_nonblock
|
67
|
+
count! super
|
68
|
+
end
|
69
|
+
|
70
|
+
# :startdoc:
|
71
|
+
|
72
|
+
# The +last_data_recv+ member of Raindrops::TCP_Info can be used to
|
73
|
+
# infer the time a client spent in the listen queue before it was
|
74
|
+
# accepted.
|
75
|
+
#
|
76
|
+
# We require TCP_DEFER_ACCEPT on the listen socket for
|
77
|
+
# +last_data_recv+ to be accurate
|
78
|
+
def count!(io)
|
79
|
+
if io
|
80
|
+
x = TCP_Info.new(io)
|
81
|
+
@raindrops_aggregate << x.last_data_recv
|
82
|
+
end
|
83
|
+
io
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,239 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require "tempfile"
|
3
|
+
require "aggregate"
|
4
|
+
require "posix_mq"
|
5
|
+
require "fcntl"
|
6
|
+
require "io/extra"
|
7
|
+
require "thread"
|
8
|
+
|
9
|
+
# \Aggregate + POSIX message queues support for Ruby 1.9 and \Linux
|
10
|
+
#
|
11
|
+
# This class is duck-type compatible with \Aggregate and allows us to
|
12
|
+
# aggregate and share statistics from multiple processes/threads aided
|
13
|
+
# POSIX message queues. This is designed to be used with the
|
14
|
+
# Raindrops::LastDataRecv Rack application, but can be used independently
|
15
|
+
# on compatible Runtimes.
|
16
|
+
#
|
17
|
+
# Unlike the core of raindrops, this is only supported on Ruby 1.9 and
|
18
|
+
# Linux 2.6. Using this class requires the following additional RubyGems
|
19
|
+
# or libraries:
|
20
|
+
#
|
21
|
+
# * aggregate (tested with 0.2.2)
|
22
|
+
# * io-extra (tested with 1.2.3)
|
23
|
+
# * posix_mq (tested with 1.0.0)
|
24
|
+
#
|
25
|
+
# == Design
|
26
|
+
#
|
27
|
+
# There is one master thread which aggregates statistics. Individual
|
28
|
+
# worker processes or threads will write to a shared POSIX message
|
29
|
+
# queue (default: "/raindrops") that the master reads from. At a
|
30
|
+
# predefined interval, the master thread will write out to a shared,
|
31
|
+
# anonymous temporary file that workers may read from
|
32
|
+
#
|
33
|
+
# Setting +:worker_interval+ and +:master_interval+ to +1+ will result
|
34
|
+
# in perfect accuracy but at the cost of a high synchronization
|
35
|
+
# overhead. Larger intervals mean less frequent messaging for higher
|
36
|
+
# performance but lower accuracy.
|
37
|
+
class Raindrops::Aggregate::PMQ
|
38
|
+
|
39
|
+
# :stopdoc:
|
40
|
+
# These constants are for Linux. This is designed for aggregating
|
41
|
+
# TCP_INFO.
|
42
|
+
RDLOCK = [ Fcntl::F_RDLCK ].pack("s @256")
|
43
|
+
WRLOCK = [ Fcntl::F_WRLCK ].pack("s @256")
|
44
|
+
UNLOCK = [ Fcntl::F_UNLCK ].pack("s @256")
|
45
|
+
# :startdoc:
|
46
|
+
|
47
|
+
# returns the number of dropped messages sent to a POSIX message
|
48
|
+
# queue if non-blocking operation was desired with :lossy
|
49
|
+
attr_reader :nr_dropped
|
50
|
+
|
51
|
+
#
|
52
|
+
# Creates a new Raindrops::Aggregate::PMQ object
|
53
|
+
#
|
54
|
+
# Raindrops::Aggregate::PMQ.new(options = {}) -> aggregate
|
55
|
+
#
|
56
|
+
# +options+ is a hash that accepts the following keys:
|
57
|
+
#
|
58
|
+
# * :queue - name of the POSIX message queue (default: "/raindrops")
|
59
|
+
# * :worker_interval - interval to send to the master (default: 10)
|
60
|
+
# * :master_interval - interval to for the master to write out (default: 5)
|
61
|
+
# * :lossy - workers drop packets if master cannot keep up (default: false)
|
62
|
+
# * :aggregate - \Aggregate object (default: \Aggregate.new)
|
63
|
+
# * :mq_umask - umask for creatingthe POSIX message queue (default: 0666)
|
64
|
+
#
|
65
|
+
def initialize(params = {})
|
66
|
+
opts = {
|
67
|
+
:queue => ENV["RAINDROPS_MQUEUE"] || "/raindrops",
|
68
|
+
:worker_interval => 10,
|
69
|
+
:master_interval => 5,
|
70
|
+
:lossy => false,
|
71
|
+
:mq_attr => nil,
|
72
|
+
:mq_umask => 0666,
|
73
|
+
:aggregate => Aggregate.new,
|
74
|
+
}.merge! params
|
75
|
+
@master_interval = opts[:master_interval]
|
76
|
+
@worker_interval = opts[:worker_interval]
|
77
|
+
@aggregate = opts[:aggregate]
|
78
|
+
@worker_queue = @worker_interval ? [] : nil
|
79
|
+
@mutex = Mutex.new
|
80
|
+
|
81
|
+
@mq_name = opts[:queue]
|
82
|
+
mq = POSIX_MQ.new @mq_name, :w, opts[:mq_umask], opts[:mq_attr]
|
83
|
+
Tempfile.open("raindrops_pmq") do |t|
|
84
|
+
@wr = File.open(t.path, "wb")
|
85
|
+
@rd = File.open(t.path, "rb")
|
86
|
+
end
|
87
|
+
@cached_aggregate = @aggregate
|
88
|
+
flush_master
|
89
|
+
@mq_send = if opts[:lossy]
|
90
|
+
@nr_dropped = 0
|
91
|
+
mq.nonblock = true
|
92
|
+
mq.method :trysend
|
93
|
+
else
|
94
|
+
mq.method :send
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# adds a sample to the underlying \Aggregate object
|
99
|
+
def << val
|
100
|
+
if q = @worker_queue
|
101
|
+
q << val
|
102
|
+
if q.size >= @worker_interval
|
103
|
+
mq_send(q) or @nr_dropped += 1
|
104
|
+
q.clear
|
105
|
+
end
|
106
|
+
else
|
107
|
+
mq_send(val) or @nr_dropped += 1
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def mq_send(val) # :nodoc:
|
112
|
+
@cached_aggregate = nil
|
113
|
+
@mq_send.call Marshal.dump(val)
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Starts running a master loop, usually in a dedicated thread or process:
|
118
|
+
#
|
119
|
+
# Thread.new { agg.master_loop }
|
120
|
+
#
|
121
|
+
# Any worker can call +agg.stop_master_loop+ to stop the master loop
|
122
|
+
# (possibly causing the thread or process to exit)
|
123
|
+
def master_loop
|
124
|
+
buf = ""
|
125
|
+
a = @aggregate
|
126
|
+
nr = 0
|
127
|
+
mq = POSIX_MQ.new @mq_name, :r # this one is always blocking
|
128
|
+
begin
|
129
|
+
if (nr -= 1) < 0
|
130
|
+
nr = @master_interval
|
131
|
+
flush_master
|
132
|
+
end
|
133
|
+
mq.shift(buf)
|
134
|
+
data = begin
|
135
|
+
Marshal.load(buf) or return
|
136
|
+
rescue ArgumentError, TypeError
|
137
|
+
next
|
138
|
+
end
|
139
|
+
Array === data ? data.each { |x| a << x } : a << data
|
140
|
+
rescue Errno::EINTR
|
141
|
+
rescue => e
|
142
|
+
warn "Unhandled exception in #{__FILE__}:#{__LINE__}: #{e}"
|
143
|
+
break
|
144
|
+
end while true
|
145
|
+
ensure
|
146
|
+
flush_master
|
147
|
+
end
|
148
|
+
|
149
|
+
# Loads the last shared \Aggregate from the master thread/process
|
150
|
+
def aggregate
|
151
|
+
@cached_aggregate ||= begin
|
152
|
+
flush
|
153
|
+
Marshal.load(synchronize(@rd, RDLOCK) do |rd|
|
154
|
+
IO.pread rd.fileno, rd.stat.size, 0
|
155
|
+
end)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Flushes the currently aggregate statistics to a temporary file.
|
160
|
+
# There is no need to call this explicitly as +:worker_interval+ defines
|
161
|
+
# how frequently your data will be flushed for workers to read.
|
162
|
+
def flush_master
|
163
|
+
dump = Marshal.dump @aggregate
|
164
|
+
synchronize(@wr, WRLOCK) do |wr|
|
165
|
+
wr.truncate 0
|
166
|
+
IO.pwrite wr.fileno, dump, 0
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# stops the currently running master loop, may be called from any
|
171
|
+
# worker thread or process
|
172
|
+
def stop_master_loop
|
173
|
+
sleep 0.1 until mq_send(false)
|
174
|
+
rescue Errno::EINTR
|
175
|
+
retry
|
176
|
+
end
|
177
|
+
|
178
|
+
def lock! io, type # :nodoc:
|
179
|
+
io.fcntl Fcntl::F_SETLKW, type
|
180
|
+
rescue Errno::EINTR
|
181
|
+
retry
|
182
|
+
end
|
183
|
+
|
184
|
+
# we use both a mutex for thread-safety and fcntl lock for process-safety
|
185
|
+
def synchronize io, type # :nodoc:
|
186
|
+
@mutex.synchronize do
|
187
|
+
begin
|
188
|
+
lock! io, type
|
189
|
+
yield io
|
190
|
+
ensure
|
191
|
+
lock! io, UNLOCK
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# flushes the local queue of the worker process, sending all pending
|
197
|
+
# data to the master. There is no need to call this explicitly as
|
198
|
+
# +:worker_interval+ defines how frequently your queue will be flushed
|
199
|
+
def flush
|
200
|
+
if q = @local_queue && ! q.empty?
|
201
|
+
mq_send q
|
202
|
+
q.clear
|
203
|
+
end
|
204
|
+
nil
|
205
|
+
end
|
206
|
+
|
207
|
+
# proxy for \Aggregate#count
|
208
|
+
def count; aggregate.count; end
|
209
|
+
|
210
|
+
# proxy for \Aggregate#max
|
211
|
+
def max; aggregate.max; end
|
212
|
+
|
213
|
+
# proxy for \Aggregate#min
|
214
|
+
def min; aggregate.min; end
|
215
|
+
|
216
|
+
# proxy for \Aggregate#sum
|
217
|
+
def sum; aggregate.sum; end
|
218
|
+
|
219
|
+
# proxy for \Aggregate#mean
|
220
|
+
def mean; aggregate.mean; end
|
221
|
+
|
222
|
+
# proxy for \Aggregate#std_dev
|
223
|
+
def std_dev; aggregate.std_dev; end
|
224
|
+
|
225
|
+
# proxy for \Aggregate#outliers_low
|
226
|
+
def outliers_low; aggregate.outliers_low; end
|
227
|
+
|
228
|
+
# proxy for \Aggregate#outliers_high
|
229
|
+
def outliers_high; aggregate.outliers_high; end
|
230
|
+
|
231
|
+
# proxy for \Aggregate#to_s
|
232
|
+
def to_s(*args); aggregate.to_s *args; end
|
233
|
+
|
234
|
+
# proxy for \Aggregate#each
|
235
|
+
def each; aggregate.each { |*args| yield *args }; end
|
236
|
+
|
237
|
+
# proxy for \Aggregate#each_nonzero
|
238
|
+
def each_nonzero; aggregate.each_nonzero { |*args| yield *args }; end
|
239
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require "raindrops"
|
3
|
+
|
4
|
+
# This is highly experimental!
|
5
|
+
#
|
6
|
+
# A self-contained Rack application for aggregating in the
|
7
|
+
# +tcpi_last_data_recv+ field in +struct tcp_info+ if
|
8
|
+
# /usr/include/linux/tcp.h. This is only useful for \Linux 2.6 and later.
|
9
|
+
# This primarily supports Unicorn and derived servers, but may also be
|
10
|
+
# used with any Ruby web server using the core TCPServer class in Ruby.
|
11
|
+
#
|
12
|
+
# Hitting the Rack endpoint configured for this application will return
|
13
|
+
# a an ASCII histogram response body with the following headers:
|
14
|
+
#
|
15
|
+
# - X-Count - number of requests received
|
16
|
+
#
|
17
|
+
# The following headers are only present if X-Count is greater than one.
|
18
|
+
#
|
19
|
+
# - X-Min - lowest last_data_recv time recorded (in milliseconds)
|
20
|
+
# - X-Max - highest last_data_recv time recorded (in milliseconds)
|
21
|
+
# - X-Mean - mean last_data_recv time recorded (rounded, in milliseconds)
|
22
|
+
# - X-Std-Dev - standard deviation of last_data_recv times
|
23
|
+
# - X-Outliers-Low - number of low outliers (hopefully many!)
|
24
|
+
# - X-Outliers-High - number of high outliers (hopefully zero!)
|
25
|
+
#
|
26
|
+
# == To use with Unicorn and derived servers (preload_app=false):
|
27
|
+
#
|
28
|
+
# Put the following in our Unicorn config file (not config.ru):
|
29
|
+
#
|
30
|
+
# require "raindrops/last_data_recv"
|
31
|
+
#
|
32
|
+
# Then follow the instructions below for config.ru:
|
33
|
+
#
|
34
|
+
# == To use with any Rack server using TCPServer
|
35
|
+
#
|
36
|
+
# Setup a route for Raindrops::LastDataRecv in your Rackup config file
|
37
|
+
# (typically config.ru):
|
38
|
+
#
|
39
|
+
# require "raindrops"
|
40
|
+
# map "/raindrops/last_data_recv" do
|
41
|
+
# run Raindrops::LastDataRecv.new
|
42
|
+
# end
|
43
|
+
# map "/" do
|
44
|
+
# use SomeMiddleware
|
45
|
+
# use MoreMiddleware
|
46
|
+
# # ...
|
47
|
+
# run YourAppHere.new
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# == To use with any other Ruby web server that uses TCPServer
|
51
|
+
#
|
52
|
+
# Put the following in any piece of Ruby code loaded after the server has
|
53
|
+
# bound its TCP listeners:
|
54
|
+
#
|
55
|
+
# ObjectSpace.each_object(TCPServer) do |s|
|
56
|
+
# s.extend Raindrops::Aggregate::LastDataRecv
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# Thread.new do
|
60
|
+
# Raindrops::Aggregate::LastDataRecv.default_aggregate.master_loop
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# Then follow the above instructions for config.ru
|
64
|
+
#
|
65
|
+
class Raindrops::LastDataRecv
|
66
|
+
# :stopdoc:
|
67
|
+
|
68
|
+
# trigger autoloads
|
69
|
+
if defined?(Unicorn)
|
70
|
+
agg = Raindrops::Aggregate::LastDataRecv.default_aggregate
|
71
|
+
AGGREGATE_THREAD = Thread.new { agg.master_loop }
|
72
|
+
end
|
73
|
+
# :startdoc
|
74
|
+
|
75
|
+
def initialize(opts = {})
|
76
|
+
Raindrops::Aggregate::LastDataRecv.cornify! if defined?(Unicorn)
|
77
|
+
@aggregate =
|
78
|
+
opts[:aggregate] || Raindrops::Aggregate::LastDataRecv.default_aggregate
|
79
|
+
end
|
80
|
+
|
81
|
+
def call(_)
|
82
|
+
a = @aggregate
|
83
|
+
count = a.count
|
84
|
+
headers = {
|
85
|
+
"Content-Type" => "text/plain",
|
86
|
+
"X-Count" => count.to_s,
|
87
|
+
}
|
88
|
+
if count > 1
|
89
|
+
headers["X-Min"] = a.min.to_s
|
90
|
+
headers["X-Max"] = a.max.to_s
|
91
|
+
headers["X-Mean"] = a.mean.round.to_s
|
92
|
+
headers["X-Std-Dev"] = a.std_dev.round.to_s
|
93
|
+
headers["X-Outliers-Low"] = a.outliers_low.to_s
|
94
|
+
headers["X-Outliers-High"] = a.outliers_high.to_s
|
95
|
+
end
|
96
|
+
body = a.to_s
|
97
|
+
headers["Content-Length"] = body.size.to_s
|
98
|
+
[ 200, headers, [ body ] ]
|
99
|
+
end
|
100
|
+
end
|