Date: Sat Sep 25 06:25:42 2010 +0000
linux: workaround missing RSTRUCT* macros in rbx
Rubinius does not include macros for accessing
Struct members in the C API.
ref: http://github.com/evanphx/rubinius/issues/494
=== raindrops 0.4.0 - flowing into new systems! / 2010-09-21 22:32 UTC
Non-GCC 4.x users may use the libatomic_ops[1] package to
compile Raindrops. Memory efficiency is improved for modern
glibc users with run-time cache line size detection, we no
longer assume 128 byte cache lines.
[1] - http://www.hpl.hp.com/research/linux/atomic_ops/
=== raindrops v0.3.0 - LGPL v2.1 and v3.0 / 2010-07-10 22:29 UTC
Raindrops is now licensed under the LGPLv2.1 or LGPLv3 (from
LGPLv3-only) to allow bundling in GPLv2-only applications.
There are small documentation updates and updated examples
at http://raindrops.bogomips.org/examples/
=== raindrops 0.2.0 - raining penguins! / 2010-05-05 00:35 UTC
For servers running Unicorn 0.98.0 (and derivative servers)
under Linux, :listeners no longer needs to be passed explicitly
when configuring the Rack middleware.
Some small documentation updates and cleanups, too.
=== raindrops 0.1.0 / 2010-04-08 00:45 UTC
initial release
raindrops-0.19.0/lib/ 0000755 0000041 0000041 00000000000 13145550250 014400 5 ustar www-data www-data raindrops-0.19.0/lib/raindrops.rb 0000644 0000041 0000041 00000003266 13145550250 016735 0 ustar www-data www-data # -*- encoding: binary -*-
#
# Each Raindrops object is a container that holds several counters.
# It is internally a page-aligned, shared memory area that allows
# atomic increments, decrements, assignments and reads without any
# locking.
#
# rd = Raindrops.new 4
# rd.incr(0, 1) -> 1
# rd.to_ary -> [ 1, 0, 0, 0 ]
#
# Unlike many classes in this package, the core Raindrops class is
# intended to be portable to all reasonably modern *nix systems
# supporting mmap(). Please let us know if you have portability
# issues, patches or pull requests at mailto:raindrops-public@bogomips.org
class Raindrops
# Used to represent the number of +active+ and +queued+ sockets for
# a single listen socket across all threads and processes on a
# machine.
#
# For TCP listeners, only sockets in the TCP_ESTABLISHED state are
# accounted for. For Unix domain listeners, only CONNECTING and
# CONNECTED Unix domain sockets are accounted for.
#
# +active+ connections is the number of accept()-ed but not-yet-closed
# sockets in all threads/processes sharing the given listener.
#
# +queued+ connections is the number of un-accept()-ed sockets in the
# queue of a given listen socket.
#
# These stats are currently only available under \Linux
class ListenStats < Struct.new(:active, :queued)
# the sum of +active+ and +queued+ sockets
def total
active + queued
end
end
autoload :Linux, 'raindrops/linux'
autoload :Struct, 'raindrops/struct'
autoload :Middleware, 'raindrops/middleware'
autoload :Aggregate, 'raindrops/aggregate'
autoload :LastDataRecv, 'raindrops/last_data_recv'
autoload :Watcher, 'raindrops/watcher'
end
require 'raindrops_ext'
raindrops-0.19.0/lib/raindrops/ 0000755 0000041 0000041 00000000000 13145550250 016401 5 ustar www-data www-data raindrops-0.19.0/lib/raindrops/middleware/ 0000755 0000041 0000041 00000000000 13145550250 020516 5 ustar www-data www-data raindrops-0.19.0/lib/raindrops/middleware/proxy.rb 0000644 0000041 0000041 00000002165 13145550250 022230 0 ustar www-data www-data # -*- encoding: binary -*-
# :stopdoc:
# This class is used by Raindrops::Middleware to proxy application
# response bodies. There should be no need to use it directly.
class Raindrops::Middleware::Proxy
def initialize(body, stats)
@body, @stats = body, stats
end
# yield to the Rack server here for writing
def each
@body.each { |x| yield x }
end
# the Rack server should call this after #each (usually ensure-d)
def close
@stats.decr_writing
@body.close if @body.respond_to?(:close)
end
# Some Rack servers can optimize response processing if it responds
# to +to_path+ via the sendfile(2) system call, we proxy +to_path+
# to the underlying body if possible.
def to_path
@body.to_path
end
# Rack servers use +respond_to?+ to check for the presence of +close+
# and +to_path+ methods.
def respond_to?(m, include_all = false)
m = m.to_sym
:close == m || @body.respond_to?(m, include_all)
end
# Avoid breaking users of non-standard extensions (e.g. #body)
# Rack::BodyProxy does the same.
def method_missing(*args, &block)
@body.__send__(*args, &block)
end
end
raindrops-0.19.0/lib/raindrops/watcher.rb 0000644 0000041 0000041 00000031104 13145550250 020362 0 ustar www-data www-data # -*- encoding: binary -*-
require "thread"
require "time"
require "socket"
require "rack"
require "aggregate"
# Raindrops::Watcher is a stand-alone Rack application for watching
# any number of TCP and UNIX listeners (all of them by default).
#
# It depends on the {Aggregate RubyGem}[https://rubygems.org/gems/aggregate]
#
# In your Rack config.ru:
#
# run Raindrops::Watcher(options = {})
#
# It takes the following options hash:
#
# - :listeners - an array of listener names, (e.g. %w(0.0.0.0:80 /tmp/sock))
# - :delay - interval between stats updates in seconds (default: 1)
#
# Raindrops::Watcher is compatible any thread-safe/thread-aware Rack
# middleware. It does not work well with multi-process web servers
# but can be used to monitor them. It consumes minimal resources
# with the default :delay.
#
# == HTTP endpoints
#
# === GET /
#
# Returns an HTML summary listing of all listen interfaces watched on
#
# === GET /active/$LISTENER.txt
#
# Returns a plain text summary + histogram with X-* HTTP headers for
# active connections.
#
# e.g.: curl https://raindrops-demo.bogomips.org/active/0.0.0.0%3A80.txt
#
# === GET /active/$LISTENER.html
#
# Returns an HTML summary + histogram with X-* HTTP headers for
# active connections.
#
# e.g.: curl https://raindrops-demo.bogomips.org/active/0.0.0.0%3A80.html
#
# === GET /queued/$LISTENER.txt
#
# Returns a plain text summary + histogram with X-* HTTP headers for
# queued connections.
#
# e.g.: curl https://raindrops-demo.bogomips.org/queued/0.0.0.0%3A80.txt
#
# === GET /queued/$LISTENER.html
#
# Returns an HTML summary + histogram with X-* HTTP headers for
# queued connections.
#
# e.g.: curl https://raindrops-demo.bogomips.org/queued/0.0.0.0%3A80.html
#
# === POST /reset/$LISTENER
#
# Resets the active and queued statistics for the given listener.
#
# === GET /tail/$LISTENER.txt?active_min=1&queued_min=1
#
# Streams chunked a response to the client.
# Interval is the preconfigured +:delay+ of the application (default 1 second)
#
# The response is plain text in the following format:
#
# ISO8601_TIMESTAMP LISTENER_NAME ACTIVE_COUNT QUEUED_COUNT LINEFEED
#
# Query parameters:
#
# - active_min - do not stream a line until this active count is reached
# - queued_min - do not stream a line until this queued count is reached
#
# == Response headers (mostly the same names as Raindrops::LastDataRecv)
#
# - X-Count - number of samples polled
# - X-Last-Reset - date since the last reset
#
# The following headers are only present if X-Count is greater than one.
#
# - X-Min - lowest number of connections recorded
# - X-Max - highest number of connections recorded
# - X-Mean - mean number of connections recorded
# - X-Std-Dev - standard deviation of connection count
# - X-Outliers-Low - number of low outliers (hopefully many for queued)
# - X-Outliers-High - number of high outliers (hopefully zero for queued)
# - X-Current - current number of connections
# - X-First-Peak-At - date of when X-Max was first reached
# - X-Last-Peak-At - date of when X-Max was last reached
#
# = Demo Server
#
# There is a server running this app at https://raindrops-demo.bogomips.org/
# The Raindrops::Middleware demo is also accessible at
# https://raindrops-demo.bogomips.org/_raindrops
#
# The demo server is only limited to 30 users, so be sure not to abuse it
# by using the /tail/ endpoint too much.
class Raindrops::Watcher
# :stopdoc:
attr_reader :snapshot
include Rack::Utils
include Raindrops::Linux
DOC_URL = "https://bogomips.org/raindrops/Raindrops/Watcher.html"
Peak = Struct.new(:first, :last)
def initialize(opts = {})
@tcp_listeners = @unix_listeners = nil
if l = opts[:listeners]
tcp, unix = [], []
Array(l).each { |addr| (addr =~ %r{\A/} ? unix : tcp) << addr }
unless tcp.empty? && unix.empty?
@tcp_listeners = tcp
@unix_listeners = unix
end
end
@agg_class = opts[:agg_class] || Aggregate
@start_time = Time.now.utc
@active = Hash.new { |h,k| h[k] = @agg_class.new }
@queued = Hash.new { |h,k| h[k] = @agg_class.new }
@resets = Hash.new { |h,k| h[k] = @start_time }
@peak_active = Hash.new { |h,k| h[k] = Peak.new(@start_time, @start_time) }
@peak_queued = Hash.new { |h,k| h[k] = Peak.new(@start_time, @start_time) }
@snapshot = [ @start_time, {} ]
@delay = opts[:delay] || 1
@lock = Mutex.new
@start = Mutex.new
@cond = ConditionVariable.new
@thr = nil
end
def hostname
Socket.gethostname
end
# rack endpoint
def call(env)
@start.synchronize { @thr ||= aggregator_thread(env["rack.logger"]) }
case env["REQUEST_METHOD"]
when "GET"
get env
when "HEAD"
r = get(env)
r[2] = []
r
when "POST"
post env
else
Rack::Response.new(["Method Not Allowed"], 405).finish
end
end
def aggregate!(agg_hash, peak_hash, addr, number, now)
agg = agg_hash[addr]
if (max = agg.max) && number > 0 && number >= max
peak = peak_hash[addr]
peak.first = now if number > max
peak.last = now
end
agg << number
end
def aggregator_thread(logger) # :nodoc:
@socket = sock = Raindrops::InetDiagSocket.new
thr = Thread.new do
begin
combined = tcp_listener_stats(@tcp_listeners, sock)
combined.merge!(unix_listener_stats(@unix_listeners))
@lock.synchronize do
now = Time.now.utc
combined.each do |addr,stats|
aggregate!(@active, @peak_active, addr, stats.active, now)
aggregate!(@queued, @peak_queued, addr, stats.queued, now)
end
@snapshot = [ now, combined ]
@cond.broadcast
end
rescue => e
logger.error "#{e.class} #{e.inspect}"
end while sleep(@delay) && @socket
sock.close
end
wait_snapshot
thr
end
def non_existent_stats(time)
[ time, @start_time, @agg_class.new, 0, Peak.new(@start_time, @start_time) ]
end
def active_stats(addr) # :nodoc:
@lock.synchronize do
time, combined = @snapshot
stats = combined[addr] or return non_existent_stats(time)
tmp, peak = @active[addr], @peak_active[addr]
[ time, @resets[addr], tmp.dup, stats.active, peak ]
end
end
def queued_stats(addr) # :nodoc:
@lock.synchronize do
time, combined = @snapshot
stats = combined[addr] or return non_existent_stats(time)
tmp, peak = @queued[addr], @peak_queued[addr]
[ time, @resets[addr], tmp.dup, stats.queued, peak ]
end
end
def wait_snapshot
@lock.synchronize do
@cond.wait @lock
@snapshot
end
end
def std_dev(agg)
agg.std_dev.to_s
rescue Errno::EDOM
"NaN"
end
def agg_to_hash(reset_at, agg, current, peak)
{
"X-Count" => agg.count.to_s,
"X-Min" => agg.min.to_s,
"X-Max" => agg.max.to_s,
"X-Mean" => agg.mean.to_s,
"X-Std-Dev" => std_dev(agg),
"X-Outliers-Low" => agg.outliers_low.to_s,
"X-Outliers-High" => agg.outliers_high.to_s,
"X-Last-Reset" => reset_at.httpdate,
"X-Current" => current.to_s,
"X-First-Peak-At" => peak.first.httpdate,
"X-Last-Peak-At" => peak.last.httpdate,
}
end
def histogram_txt(agg)
updated_at, reset_at, agg, current, peak = *agg
headers = agg_to_hash(reset_at, agg, current, peak)
body = agg.to_s # 7-bit ASCII-clean
headers["Content-Type"] = "text/plain"
headers["Expires"] = (updated_at + @delay).httpdate
headers["Content-Length"] = body.size.to_s
[ 200, headers, [ body ] ]
end
def histogram_html(agg, addr)
updated_at, reset_at, agg, current, peak = *agg
headers = agg_to_hash(reset_at, agg, current, peak)
body = "" \
"#{hostname} - #{escape_html addr}" \
"" <<
headers.map { |k,v|
"#{k.gsub(/^X-/, '')} | #{v} |
"
}.join << "
#{escape_html agg}
" \
"" \
""
headers["Content-Type"] = "text/html"
headers["Expires"] = (updated_at + @delay).httpdate
headers["Content-Length"] = body.size.to_s
[ 200, headers, [ body ] ]
end
def get(env)
retried = false
begin
case env["PATH_INFO"]
when "/"
index
when %r{\A/active/(.+)\.txt\z}
histogram_txt(active_stats(unescape($1)))
when %r{\A/active/(.+)\.html\z}
addr = unescape $1
histogram_html(active_stats(addr), addr)
when %r{\A/queued/(.+)\.txt\z}
histogram_txt(queued_stats(unescape($1)))
when %r{\A/queued/(.+)\.html\z}
addr = unescape $1
histogram_html(queued_stats(addr), addr)
when %r{\A/tail/(.+)\.txt\z}
tail(unescape($1), env)
else
not_found
end
rescue Errno::EDOM
raise if retried
retried = true
wait_snapshot
retry
end
end
def not_found
Rack::Response.new(["Not Found"], 404).finish
end
def post(env)
case env["PATH_INFO"]
when %r{\A/reset/(.+)\z}
reset!(env, unescape($1))
else
not_found
end
end
def reset!(env, addr)
@lock.synchronize do
@active.include?(addr) or return not_found
@active.delete addr
@queued.delete addr
@resets[addr] = Time.now.utc
@cond.wait @lock
end
req = Rack::Request.new(env)
res = Rack::Response.new
url = req.referer || "#{req.host_with_port}/"
res.redirect(url)
res["Content-Type"] = "text/plain"
res.write "Redirecting to #{url}"
res.finish
end
def index
updated_at, all = snapshot
headers = {
"Content-Type" => "text/html",
"Last-Modified" => updated_at.httpdate,
"Expires" => (updated_at + @delay).httpdate,
}
body = "" \
"#{hostname} - all interfaces" \
"Updated at #{updated_at.iso8601}
" \
"" \
"" \
"This is running the #{self.class} service, see " \
"#{DOC_URL} " \
"for more information and options." \
"
" \
""
headers["Content-Length"] = body.size.to_s
[ 200, headers, [ body ] ]
end
def tail(addr, env)
Tailer.new(self, addr, env).finish
end
# This is the response body returned for "/tail/$ADDRESS.txt". This
# must use a multi-threaded Rack server with streaming response support.
# It is an internal class and not expected to be used directly
class Tailer
def initialize(rdmon, addr, env) # :nodoc:
@rdmon = rdmon
@addr = addr
q = Rack::Utils.parse_query env["QUERY_STRING"]
@active_min = q["active_min"].to_i
@queued_min = q["queued_min"].to_i
len = addr.size
len = 35 if len > 35
@fmt = "%20s % #{len}s % 10u % 10u\n"
case env["HTTP_VERSION"]
when "HTTP/1.0", nil
@chunk = false
else
@chunk = true
end
end
def finish
headers = {
"Content-Type" => "text/plain",
"Cache-Control" => "no-transform",
"Expires" => Time.at(0).httpdate,
}
headers["Transfer-Encoding"] = "chunked" if @chunk
[ 200, headers, self ]
end
# called by the Rack server
def each # :nodoc:
begin
time, all = @rdmon.wait_snapshot
stats = all[@addr] or next
stats.queued >= @queued_min or next
stats.active >= @active_min or next
body = sprintf(@fmt, time.iso8601, @addr, stats.active, stats.queued)
body = "#{body.size.to_s(16)}\r\n#{body}\r\n" if @chunk
yield body
end while true
yield "0\r\n\r\n" if @chunk
end
end
# shuts down the background thread, only for tests
def shutdown
@socket = nil
@thr.join if @thr
@thr = nil
end
# :startdoc:
end
raindrops-0.19.0/lib/raindrops/struct.rb 0000644 0000041 0000041 00000003133 13145550250 020252 0 ustar www-data www-data # -*- encoding: binary -*-
# This is a wrapper around Raindrops objects much like the core Ruby
# \Struct can be seen as a wrapper around the core \Array class.
# It's usage is similar to the core \Struct class, except its fields
# may only be used to house unsigned long integers.
#
# class Foo < Raindrops::Struct.new(:readers, :writers)
# end
#
# foo = Foo.new 0, 0
#
# foo.incr_writers -> 1
# foo.incr_readers -> 1
#
class Raindrops::Struct
# returns a new class derived from Raindrops::Struct and supporting
# the given +members+ as fields, just like \Struct.new in core Ruby.
def self.new(*members)
members = members.map { |x| x.to_sym }.freeze
str = <= values.size) or raise ArgumentError, "too many arguments"
@raindrops = Raindrops.new(MEMBERS.size)
values.each_with_index { |val,i| @raindrops[i] = values[i] }
end
def initialize_copy(src)
@raindrops = src.instance_variable_get(:@raindrops).dup
end
def []=(index, value)
@raindrops[index] = value
end
def [](index)
@raindrops[index]
end
def to_hash
ary = @raindrops.to_ary
rv = {}
MEMBERS.each_with_index { |member, i| rv[member] = ary[i] }
rv
end
EOS
members.each_with_index do |member, i|
str << "def incr_#{member}; @raindrops.incr(#{i}); end; " \
"def decr_#{member}; @raindrops.decr(#{i}); end; " \
"def #{member}; @raindrops[#{i}]; end; " \
"def #{member}=(val); @raindrops[#{i}] = val; end; "
end
klass = Class.new
klass.const_set(:MEMBERS, members)
klass.class_eval(str)
klass
end
end
raindrops-0.19.0/lib/raindrops/linux.rb 0000644 0000041 0000041 00000005663 13145550250 020077 0 ustar www-data www-data # -*- encoding: binary -*-
# For reporting TCP ListenStats, users of older \Linux kernels need to ensure
# that the the "inet_diag" and "tcp_diag" kernel modules are loaded as they do
# not autoload correctly. The inet_diag facilities of \Raindrops is useful
# for periodic snapshot reporting of listen queue sizes.
#
# Instead of snapshotting, Raindrops::Aggregate::LastDataRecv may be used
# to aggregate statistics from +all+ accepted sockets as they arrive
# based on the +last_data_recv+ field in Raindrops::TCP_Info
module Raindrops::Linux
# The standard proc path for active UNIX domain sockets, feel free to call
# String#replace on this if your /proc is mounted in a non-standard location
# for whatever reason
PROC_NET_UNIX_ARGS = %w(/proc/net/unix)
defined?(::Encoding) and PROC_NET_UNIX_ARGS.push({ :encoding => "binary" })
# Get ListenStats from an array of +paths+
#
# Socket state mapping from integer => symbol, based on socket_state
# enum from include/linux/net.h in the \Linux kernel:
# typedef enum {
# SS_FREE = 0, /* not allocated */
# SS_UNCONNECTED, /* unconnected to any socket */
# SS_CONNECTING, /* in process of connecting */
# SS_CONNECTED, /* connected to socket */
# SS_DISCONNECTING /* in process of disconnecting */
# } socket_state;
# * SS_CONNECTING maps to ListenStats#queued
# * SS_CONNECTED maps to ListenStats#active
#
# This method may be significantly slower than its tcp_listener_stats
# counterpart due to the latter being able to use inet_diag via netlink.
# This parses /proc/net/unix as there is no other (known) way
# to expose Unix domain socket statistics over netlink.
def unix_listener_stats(paths = nil)
rv = Hash.new { |h,k| h[k.freeze] = Raindrops::ListenStats.new(0, 0) }
if nil == paths
paths = [ '[^\n]+' ]
else
paths = paths.map do |path|
path = path.dup
path.force_encoding(Encoding::BINARY) if defined?(Encoding)
if File.symlink?(path)
link = path
path = File.readlink(link)
path.force_encoding(Encoding::BINARY) if defined?(Encoding)
rv[link] = rv[path] # vivify ListenerStats
else
rv[path] # vivify ListenerStats
end
Regexp.escape(path)
end
end
paths = /^\w+: \d+ \d+ (\d+) \d+ (\d+)\s+\d+ (#{paths.join('|')})$/n
# no point in pread since we can't stat for size on this file
File.read(*PROC_NET_UNIX_ARGS).scan(paths) do |s|
path = s[-1]
case s[0]
when "00000000" # client sockets
case s[1].to_i
when 2 then rv[path].queued += 1
when 3 then rv[path].active += 1
end
else
# listeners, vivify empty stats
rv[path]
end
end
rv
end
module_function :unix_listener_stats
end # Raindrops::Linux
raindrops-0.19.0/lib/raindrops/middleware.rb 0000644 0000041 0000041 00000011457 13145550250 021053 0 ustar www-data www-data # -*- encoding: binary -*-
require 'raindrops'
# Raindrops::Middleware is Rack middleware that allows snapshotting
# current activity from an HTTP request. For all operating systems,
# it returns at least the following fields:
#
# * calling - the number of application dispatchers on your machine
# * writing - the number of clients being written to on your machine
#
# Additional fields are available for \Linux users.
#
# It should be loaded at the top of Rack middleware stack before other
# middlewares for maximum accuracy.
#
# === Usage (Rainbows!/Unicorn preload_app=false)
#
# If you're using preload_app=false (the default) in your Rainbows!/Unicorn
# config file, you'll need to create the global Stats object before
# forking.
#
# require 'raindrops'
# $stats ||= Raindrops::Middleware::Stats.new
#
# In your Rack config.ru:
#
# use Raindrops::Middleware, :stats => $stats
#
# === Usage (Rainbows!/Unicorn preload_app=true)
#
# If you're using preload_app=true in your Rainbows!/Unicorn
# config file, just add the middleware to your stack:
#
# In your Rack config.ru:
#
# use Raindrops::Middleware
#
# === Linux-only extras!
#
# To get bound listener statistics under \Linux, you need to specify the
# listener names for your server. You can even include listen sockets for
# *other* servers on the same machine. This can be handy for monitoring
# your nginx proxy as well.
#
# In your Rack config.ru, just pass the :listeners argument as an array of
# strings (along with any other arguments). You can specify any
# combination of TCP or Unix domain socket names:
#
# use Raindrops::Middleware, :listeners => %w(0.0.0.0:80 /tmp/.sock)
#
# If you're running Unicorn 0.98.0 or later, you don't have to pass in
# the :listeners array, Raindrops will automatically detect the listeners
# used by Unicorn master process. This does not detect listeners in
# different processes, of course.
#
# The response body includes the following stats for each listener
# (see also Raindrops::ListenStats):
#
# * active - total number of active clients on that listener
# * queued - total number of queued (pre-accept()) clients on that listener
#
# = Demo Server
#
# There is a server running this middleware (and Watcher) at
# https://raindrops-demo.bogomips.org/_raindrops
#
# Also check out the Watcher demo at https://raindrops-demo.bogomips.org/
#
# The demo server is only limited to 30 users, so be sure not to abuse it
# by using the /tail/ endpoint too much.
#
class Raindrops::Middleware
attr_accessor :app, :stats, :path, :tcp, :unix # :nodoc:
# A Raindrops::Struct used to count the number of :calling and :writing
# clients. This struct is intended to be shared across multiple processes
# and both counters are updated atomically.
#
# This is supported on all operating systems supported by Raindrops
Stats = Raindrops::Struct.new(:calling, :writing)
# :stopdoc:
require "raindrops/middleware/proxy"
# :startdoc:
# +app+ may be any Rack application, this middleware wraps it.
# +opts+ is a hash that understands the following members:
#
# * :stats - Raindrops::Middleware::Stats struct (default: Stats.new)
# * :path - HTTP endpoint used for reading the stats (default: "/_raindrops")
# * :listeners - array of host:port or socket paths (default: from Unicorn)
def initialize(app, opts = {})
@app = app
@stats = opts[:stats] || Stats.new
@path = opts[:path] || "/_raindrops"
tmp = opts[:listeners]
if tmp.nil? && defined?(Unicorn) && Unicorn.respond_to?(:listener_names)
tmp = Unicorn.listener_names
end
@tcp = @unix = nil
if tmp
@tcp = tmp.grep(/\A.+:\d+\z/)
@unix = tmp.grep(%r{\A/})
@tcp = nil if @tcp.empty?
@unix = nil if @unix.empty?
end
end
# standard Rack endpoint
def call(env) # :nodoc:
env['PATH_INFO'] == @path and return stats_response
begin
@stats.incr_calling
status, headers, body = @app.call(env)
rv = [ status, headers, Proxy.new(body, @stats) ]
# the Rack server will start writing headers soon after this method
@stats.incr_writing
rv
ensure
@stats.decr_calling
end
end
def stats_response # :nodoc:
body = "calling: #{@stats.calling}\n" \
"writing: #{@stats.writing}\n"
if defined?(Raindrops::Linux.tcp_listener_stats)
Raindrops::Linux.tcp_listener_stats(@tcp).each do |addr,stats|
body << "#{addr} active: #{stats.active}\n" \
"#{addr} queued: #{stats.queued}\n"
end if @tcp
Raindrops::Linux.unix_listener_stats(@unix).each do |addr,stats|
body << "#{addr} active: #{stats.active}\n" \
"#{addr} queued: #{stats.queued}\n"
end if @unix
end
headers = {
"Content-Type" => "text/plain",
"Content-Length" => body.size.to_s,
}
[ 200, headers, [ body ] ]
end
end
raindrops-0.19.0/lib/raindrops/aggregate.rb 0000644 0000041 0000041 00000000457 13145550250 020662 0 ustar www-data www-data # -*- encoding: binary -*-
#
# raindrops may use the {aggregate}[https://github.com/josephruscio/aggregate]
# RubyGem to aggregate statistics from TCP_Info lookups.
module Raindrops::Aggregate
autoload :PMQ, "raindrops/aggregate/pmq"
autoload :LastDataRecv, "raindrops/aggregate/last_data_recv"
end
raindrops-0.19.0/lib/raindrops/aggregate/ 0000755 0000041 0000041 00000000000 13145550250 020327 5 ustar www-data www-data raindrops-0.19.0/lib/raindrops/aggregate/pmq.rb 0000644 0000041 0000041 00000015563 13145550250 021463 0 ustar www-data www-data # -*- encoding: binary -*-
require "tempfile"
require "aggregate"
require "posix_mq"
require "fcntl"
require "thread"
require "stringio"
# \Aggregate + POSIX message queues support for Ruby 1.9+ and \Linux
#
# This class is duck-type compatible with \Aggregate and allows us to
# aggregate and share statistics from multiple processes/threads aided
# POSIX message queues. This is designed to be used with the
# Raindrops::LastDataRecv Rack application, but can be used independently
# on compatible Runtimes.
#
# Unlike the core of raindrops, this is only supported on Ruby 1.9+ and
# Linux 2.6+. Using this class requires the following additional RubyGems
# or libraries:
#
# * aggregate (tested with 0.2.2)
# * posix_mq (tested with 1.0.0)
#
# == Design
#
# There is one master thread which aggregates statistics. Individual
# worker processes or threads will write to a shared POSIX message
# queue (default: "/raindrops") that the master reads from. At a
# predefined interval, the master thread will write out to a shared,
# anonymous temporary file that workers may read from
#
# Setting +:worker_interval+ and +:master_interval+ to +1+ will result
# in perfect accuracy but at the cost of a high synchronization
# overhead. Larger intervals mean less frequent messaging for higher
# performance but lower accuracy.
class Raindrops::Aggregate::PMQ
# :stopdoc:
# These constants are for Linux. This is designed for aggregating
# TCP_INFO.
RDLOCK = [ Fcntl::F_RDLCK ].pack("s @256".freeze).freeze
WRLOCK = [ Fcntl::F_WRLCK ].pack("s @256".freeze).freeze
UNLOCK = [ Fcntl::F_UNLCK ].pack("s @256".freeze).freeze
# :startdoc:
# returns the number of dropped messages sent to a POSIX message
# queue if non-blocking operation was desired with :lossy
attr_reader :nr_dropped
#
# Creates a new Raindrops::Aggregate::PMQ object
#
# Raindrops::Aggregate::PMQ.new(options = {}) -> aggregate
#
# +options+ is a hash that accepts the following keys:
#
# * :queue - name of the POSIX message queue (default: "/raindrops")
# * :worker_interval - interval to send to the master (default: 10)
# * :master_interval - interval to for the master to write out (default: 5)
# * :lossy - workers drop packets if master cannot keep up (default: false)
# * :aggregate - \Aggregate object (default: \Aggregate.new)
# * :mq_umask - umask for creatingthe POSIX message queue (default: 0666)
#
def initialize(params = {})
opts = {
:queue => ENV["RAINDROPS_MQUEUE"] || "/raindrops",
:worker_interval => 10,
:master_interval => 5,
:lossy => false,
:mq_attr => nil,
:mq_umask => 0666,
:aggregate => Aggregate.new,
}.merge! params
@master_interval = opts[:master_interval]
@worker_interval = opts[:worker_interval]
@aggregate = opts[:aggregate]
@worker_queue = @worker_interval ? [] : nil
@mutex = Mutex.new
@mq_name = opts[:queue]
mq = POSIX_MQ.new @mq_name, :w, opts[:mq_umask], opts[:mq_attr]
Tempfile.open("raindrops_pmq") do |t|
@wr = File.open(t.path, "wb")
@rd = File.open(t.path, "rb")
end
@wr.sync = true
@cached_aggregate = @aggregate
flush_master
@mq_send = if opts[:lossy]
@nr_dropped = 0
mq.nonblock = true
mq.method :trysend
else
mq.method :send
end
end
# adds a sample to the underlying \Aggregate object
def << val
if q = @worker_queue
q << val
if q.size >= @worker_interval
mq_send(q) or @nr_dropped += 1
q.clear
end
else
mq_send(val) or @nr_dropped += 1
end
end
def mq_send(val) # :nodoc:
@cached_aggregate = nil
@mq_send.call Marshal.dump(val)
end
#
# Starts running a master loop, usually in a dedicated thread or process:
#
# Thread.new { agg.master_loop }
#
# Any worker can call +agg.stop_master_loop+ to stop the master loop
# (possibly causing the thread or process to exit)
def master_loop
buf = ""
a = @aggregate
nr = 0
mq = POSIX_MQ.new @mq_name, :r # this one is always blocking
begin
if (nr -= 1) < 0
nr = @master_interval
flush_master
end
mq.shift(buf)
data = begin
Marshal.load(buf) or return
rescue ArgumentError, TypeError
next
end
Array === data ? data.each { |x| a << x } : a << data
rescue Errno::EINTR
rescue => e
warn "Unhandled exception in #{__FILE__}:#{__LINE__}: #{e}"
break
end while true
ensure
flush_master
end
# Loads the last shared \Aggregate from the master thread/process
def aggregate
@cached_aggregate ||= begin
flush
Marshal.load(synchronize(@rd, RDLOCK) do |rd|
dst = StringIO.new
dst.binmode
IO.copy_stream(rd, dst, rd.size, 0)
dst.string
end)
end
end
# Flushes the currently aggregate statistics to a temporary file.
# There is no need to call this explicitly as +:worker_interval+ defines
# how frequently your data will be flushed for workers to read.
def flush_master
dump = Marshal.dump @aggregate
synchronize(@wr, WRLOCK) do |wr|
wr.truncate 0
wr.rewind
wr.write(dump)
end
end
# stops the currently running master loop, may be called from any
# worker thread or process
def stop_master_loop
sleep 0.1 until mq_send(false)
rescue Errno::EINTR
retry
end
def lock! io, type # :nodoc:
io.fcntl Fcntl::F_SETLKW, type
rescue Errno::EINTR
retry
end
# we use both a mutex for thread-safety and fcntl lock for process-safety
def synchronize io, type # :nodoc:
@mutex.synchronize do
begin
type = type.dup
lock! io, type
yield io
ensure
lock! io, type.replace(UNLOCK)
type.clear
end
end
end
# flushes the local queue of the worker process, sending all pending
# data to the master. There is no need to call this explicitly as
# +:worker_interval+ defines how frequently your queue will be flushed
def flush
if q = @local_queue && ! q.empty?
mq_send q
q.clear
end
nil
end
# proxy for \Aggregate#count
def count; aggregate.count; end
# proxy for \Aggregate#max
def max; aggregate.max; end
# proxy for \Aggregate#min
def min; aggregate.min; end
# proxy for \Aggregate#sum
def sum; aggregate.sum; end
# proxy for \Aggregate#mean
def mean; aggregate.mean; end
# proxy for \Aggregate#std_dev
def std_dev; aggregate.std_dev; end
# proxy for \Aggregate#outliers_low
def outliers_low; aggregate.outliers_low; end
# proxy for \Aggregate#outliers_high
def outliers_high; aggregate.outliers_high; end
# proxy for \Aggregate#to_s
def to_s(*args); aggregate.to_s(*args); end
# proxy for \Aggregate#each
def each; aggregate.each { |*args| yield(*args) }; end
# proxy for \Aggregate#each_nonzero
def each_nonzero; aggregate.each_nonzero { |*args| yield(*args) }; end
end
raindrops-0.19.0/lib/raindrops/aggregate/last_data_recv.rb 0000644 0000041 0000041 00000004166 13145550250 023636 0 ustar www-data www-data # -*- encoding: binary -*-
require "socket"
#
#
# This module is used to extend TCPServer and Kgio::TCPServer objects
# and aggregate +last_data_recv+ times for all accepted clients. It
# is designed to be used with Raindrops::LastDataRecv Rack application
# but can be easily changed to work with other stats collection devices.
#
# Methods wrapped include:
# - TCPServer#accept
# - TCPServer#accept_nonblock
# - Kgio::TCPServer#kgio_accept
# - Kgio::TCPServer#kgio_tryaccept
module Raindrops::Aggregate::LastDataRecv
# The integer value of +last_data_recv+ is sent to this object.
# This is usually a duck type compatible with the \Aggregate class,
# but can be *anything* that accepts the *<<* method.
attr_accessor :raindrops_aggregate
@@default_aggregate = nil
# By default, this is a Raindrops::Aggregate::PMQ object
# It may be anything that responds to *<<*
def self.default_aggregate
@@default_aggregate ||= Raindrops::Aggregate::PMQ.new
end
# Assign any object that responds to *<<*
def self.default_aggregate=(agg)
@@default_aggregate = agg
end
# automatically extends any TCPServer objects used by Unicorn
def self.cornify!
Unicorn::HttpServer::LISTENERS.each do |sock|
sock.extend(self) if TCPServer === sock
end
end
# each extended object needs to have TCP_DEFER_ACCEPT enabled
# for accuracy.
def self.extended(obj)
obj.raindrops_aggregate = default_aggregate
# obj.setsockopt Socket::SOL_TCP, tcp_defer_accept = 9, seconds = 60
obj.setsockopt Socket::SOL_TCP, 9, 60
end
# :stopdoc:
def kgio_tryaccept(*args)
count! super
end
def kgio_accept(*args)
count! super
end
def accept
count! super
end
def accept_nonblock
count! super
end
# :startdoc:
# The +last_data_recv+ member of Raindrops::TCP_Info can be used to
# infer the time a client spent in the listen queue before it was
# accepted.
#
# We require TCP_DEFER_ACCEPT on the listen socket for
# +last_data_recv+ to be accurate
def count!(io)
if io
x = Raindrops::TCP_Info.new(io)
@raindrops_aggregate << x.last_data_recv
end
io
end
end
raindrops-0.19.0/lib/raindrops/last_data_recv.rb 0000644 0000041 0000041 00000006075 13145550250 021711 0 ustar www-data www-data # -*- encoding: binary -*-
require "raindrops"
# This is highly experimental!
#
# A self-contained Rack application for aggregating in the
# +tcpi_last_data_recv+ field in +struct+ +tcp_info+ defined in
# +/usr/include/linux/tcp.h+. This is only useful for \Linux 2.6 and later.
# This primarily supports Unicorn and derived servers, but may also be
# used with any Ruby web server using the core TCPServer class in Ruby.
#
# Hitting the Rack endpoint configured for this application will return
# a an ASCII histogram response body with the following headers:
#
# - X-Count - number of requests received
#
# The following headers are only present if X-Count is greater than one.
#
# - X-Min - lowest last_data_recv time recorded (in milliseconds)
# - X-Max - highest last_data_recv time recorded (in milliseconds)
# - X-Mean - mean last_data_recv time recorded (rounded, in milliseconds)
# - X-Std-Dev - standard deviation of last_data_recv times
# - X-Outliers-Low - number of low outliers (hopefully many!)
# - X-Outliers-High - number of high outliers (hopefully zero!)
#
# == To use with Unicorn and derived servers (preload_app=false):
#
# Put the following in our Unicorn config file (not config.ru):
#
# require "raindrops/last_data_recv"
#
# Then follow the instructions below for config.ru:
#
# == To use with any Rack server using TCPServer
#
# Setup a route for Raindrops::LastDataRecv in your Rackup config file
# (typically config.ru):
#
# require "raindrops"
# map "/raindrops/last_data_recv" do
# run Raindrops::LastDataRecv.new
# end
# map "/" do
# use SomeMiddleware
# use MoreMiddleware
# # ...
# run YourAppHere.new
# end
#
# == To use with any other Ruby web server that uses TCPServer
#
# Put the following in any piece of Ruby code loaded after the server has
# bound its TCP listeners:
#
# ObjectSpace.each_object(TCPServer) do |s|
# s.extend Raindrops::Aggregate::LastDataRecv
# end
#
# Thread.new do
# Raindrops::Aggregate::LastDataRecv.default_aggregate.master_loop
# end
#
# Then follow the above instructions for config.ru
#
class Raindrops::LastDataRecv
# :stopdoc:
# trigger autoloads
if defined?(Unicorn)
agg = Raindrops::Aggregate::LastDataRecv.default_aggregate
AGGREGATE_THREAD = Thread.new { agg.master_loop }
end
# :startdoc
def initialize(opts = {})
if defined?(Unicorn::HttpServer::LISTENERS)
Raindrops::Aggregate::LastDataRecv.cornify!
end
@aggregate =
opts[:aggregate] || Raindrops::Aggregate::LastDataRecv.default_aggregate
end
def call(_)
a = @aggregate
count = a.count
headers = {
"Content-Type" => "text/plain",
"X-Count" => count.to_s,
}
if count > 1
headers["X-Min"] = a.min.to_s
headers["X-Max"] = a.max.to_s
headers["X-Mean"] = a.mean.round.to_s
headers["X-Std-Dev"] = a.std_dev.round.to_s
headers["X-Outliers-Low"] = a.outliers_low.to_s
headers["X-Outliers-High"] = a.outliers_high.to_s
end
body = a.to_s
headers["Content-Length"] = body.size.to_s
[ 200, headers, [ body ] ]
end
end
raindrops-0.19.0/.manifest 0000644 0000041 0000041 00000002443 13145550250 015444 0 ustar www-data www-data .document
.gitattributes
.gitignore
.manifest
.olddoc.yml
COPYING
GIT-VERSION-FILE
GIT-VERSION-GEN
GNUmakefile
LATEST
LICENSE
NEWS
README
TODO
archive/.gitignore
archive/slrnpull.conf
examples/linux-listener-stats.rb
examples/middleware.ru
examples/watcher.ru
examples/watcher_demo.ru
examples/yahns.conf.rb
examples/zbatery.conf.rb
ext/raindrops/extconf.rb
ext/raindrops/linux_inet_diag.c
ext/raindrops/my_fileno.h
ext/raindrops/raindrops.c
ext/raindrops/raindrops_atomic.h
ext/raindrops/tcp_info.c
lib/raindrops.rb
lib/raindrops/aggregate.rb
lib/raindrops/aggregate/last_data_recv.rb
lib/raindrops/aggregate/pmq.rb
lib/raindrops/last_data_recv.rb
lib/raindrops/linux.rb
lib/raindrops/middleware.rb
lib/raindrops/middleware/proxy.rb
lib/raindrops/struct.rb
lib/raindrops/watcher.rb
pkg.mk
raindrops.gemspec
setup.rb
test/ipv6_enabled.rb
test/rack_unicorn.rb
test/test_aggregate_pmq.rb
test/test_inet_diag_socket.rb
test/test_last_data_recv_unicorn.rb
test/test_linux.rb
test/test_linux_all_tcp_listen_stats.rb
test/test_linux_all_tcp_listen_stats_leak.rb
test/test_linux_ipv6.rb
test/test_linux_middleware.rb
test/test_middleware.rb
test/test_middleware_unicorn.rb
test/test_middleware_unicorn_ipv6.rb
test/test_raindrops.rb
test/test_raindrops_gc.rb
test/test_struct.rb
test/test_tcp_info.rb
test/test_watcher.rb
raindrops-0.19.0/test/ 0000755 0000041 0000041 00000000000 13145550250 014611 5 ustar www-data www-data raindrops-0.19.0/test/test_raindrops.rb 0000644 0000041 0000041 00000007651 13145550250 020207 0 ustar www-data www-data # -*- encoding: binary -*-
require 'test/unit'
require 'raindrops'
class TestRaindrops < Test::Unit::TestCase
def test_raindrop_counter_max
assert_kind_of Integer, Raindrops::MAX
assert Raindrops::MAX > 0
printf "Raindrops::MAX = 0x%x\n", Raindrops::MAX
end
def test_raindrop_size
assert_kind_of Integer, Raindrops::SIZE
assert Raindrops::SIZE > 0
puts "Raindrops::SIZE = #{Raindrops::SIZE}"
end
def test_page_size
assert_kind_of Integer, Raindrops::PAGE_SIZE
assert Raindrops::PAGE_SIZE > Raindrops::SIZE
end
def test_size_and_capa
rd = Raindrops.new(4)
assert_equal 4, rd.size
assert rd.capa >= rd.size
end
def test_ary
rd = Raindrops.new(4)
assert_equal [0, 0, 0, 0] , rd.to_ary
end
def test_incr_no_args
rd = Raindrops.new(4)
assert_equal 1, rd.incr(0)
assert_equal [1, 0, 0, 0], rd.to_ary
end
def test_incr_args
rd = Raindrops.new(4)
assert_equal 6, rd.incr(3, 6)
assert_equal [0, 0, 0, 6], rd.to_ary
end
def test_decr_args
rd = Raindrops.new(4)
rd[3] = 6
assert_equal 5, rd.decr(3, 1)
assert_equal [0, 0, 0, 5], rd.to_ary
end
def test_incr_shared
rd = Raindrops.new(2)
5.times do
pid = fork { rd.incr(1) }
_, status = Process.waitpid2(pid)
assert status.success?
end
assert_equal [0, 5], rd.to_ary
end
def test_incr_decr
rd = Raindrops.new(1)
fork { 1000000.times { rd.incr(0) } }
1000.times { rd.decr(0) }
statuses = Process.waitall
statuses.each { |pid, status| assert status.success? }
assert_equal [999000], rd.to_ary
end
def test_bad_incr
rd = Raindrops.new(1)
assert_raises(ArgumentError) { rd.incr(-1) }
assert_raises(ArgumentError) { rd.incr(2) }
assert_raises(ArgumentError) { rd.incr(0xffffffff) }
end
def test_dup
@rd = Raindrops.new(1)
rd = @rd.dup
assert_equal 1, @rd.incr(0)
assert_equal 1, rd.incr(0)
assert_equal 2, rd.incr(0)
assert_equal 2, rd[0]
assert_equal 1, @rd[0]
end
def test_clone
@rd = Raindrops.new(1)
rd = @rd.clone
assert_equal 1, @rd.incr(0)
assert_equal 1, rd.incr(0)
assert_equal 2, rd.incr(0)
assert_equal 2, rd[0]
assert_equal 1, @rd[0]
end
def test_big
expect = (1..256).map { 0 }
rd = Raindrops.new(256)
assert_equal expect, rd.to_ary
assert_nothing_raised { rd[255] = 5 }
assert_equal 5, rd[255]
assert_nothing_raised { rd[2] = 2 }
expect[255] = 5
expect[2] = 2
assert_equal expect, rd.to_ary
end
def test_resize
rd = Raindrops.new(4)
assert_equal 4, rd.size
assert_equal rd.capa, rd.size = rd.capa
assert_equal rd.capa, rd.to_ary.size
assert_equal 0, rd[rd.capa - 1]
assert_equal 1, rd.incr(rd.capa - 1)
assert_raises(ArgumentError) { rd[rd.capa] }
end
def test_resize_mremap
rd = Raindrops.new(4)
assert_equal 4, rd.size
old_capa = rd.capa
rd.size = rd.capa + 1
assert_equal old_capa * 2, rd.capa
# mremap() is currently broken with MAP_SHARED
# https://bugzilla.kernel.org/show_bug.cgi?id=8691
assert_equal 0, rd[old_capa]
assert_equal rd.capa, rd.to_ary.size
assert_equal 0, rd[rd.capa - 1]
assert_equal 1, rd.incr(rd.capa - 1)
assert_raises(ArgumentError) { rd[rd.capa] }
rescue RangeError
end # if RUBY_PLATFORM =~ /linux/
def test_evaporate
rd = Raindrops.new 1
assert_nil rd.evaporate!
assert_raises(StandardError) { rd.evaporate! }
end
def test_evaporate_with_fork
tmp = Raindrops.new 2
pid = fork do
tmp.incr 0
exit(tmp.evaporate! == nil)
end
_, status = Process.waitpid2(pid)
assert status.success?
assert_equal [ 1, 0 ], tmp.to_ary
tmp.incr 1
assert_equal [ 1, 1 ], tmp.to_ary
pid = fork do
tmp.incr 1
exit([ 1, 2 ] == tmp.to_ary)
end
_, status = Process.waitpid2(pid)
assert status.success?
assert_equal [ 1, 2 ], tmp.to_ary
end
end
raindrops-0.19.0/test/test_inet_diag_socket.rb 0000644 0000041 0000041 00000000714 13145550250 021472 0 ustar www-data www-data # -*- encoding: binary -*-
require 'test/unit'
require 'raindrops'
require 'fcntl'
$stderr.sync = $stdout.sync = true
class TestInetDiagSocket < Test::Unit::TestCase
def test_new
sock = Raindrops::InetDiagSocket.new
assert_kind_of Socket, sock
assert_kind_of Integer, sock.fileno
flags = sock.fcntl(Fcntl::F_GETFD)
assert_equal Fcntl::FD_CLOEXEC, flags & Fcntl::FD_CLOEXEC
assert_nil sock.close
end
end if RUBY_PLATFORM =~ /linux/
raindrops-0.19.0/test/ipv6_enabled.rb 0000644 0000041 0000041 00000000333 13145550250 017473 0 ustar www-data www-data def ipv6_enabled?
tmp = TCPServer.new(ENV["TEST_HOST6"] || '::1', 0)
tmp.close
true
rescue => e
warn "skipping IPv6 tests, host does not seem to be IPv6 enabled:"
warn " #{e.class}: #{e}"
false
end
raindrops-0.19.0/test/test_tcp_info.rb 0000644 0000041 0000041 00000004461 13145550250 020003 0 ustar www-data www-data # -*- encoding: binary -*-
require 'test/unit'
require 'tempfile'
require 'raindrops'
require 'socket'
require 'pp'
$stderr.sync = $stdout.sync = true
class TestTCP_Info < Test::Unit::TestCase
TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
# Linux kernel commit 5ee3afba88f5a79d0bff07ddd87af45919259f91
TCP_INFO_useful_listenq = `uname -r`.strip >= '2.6.24'
def test_tcp_server_unacked
return if RUBY_PLATFORM !~ /linux/ # unacked not implemented on others...
s = TCPServer.new(TEST_ADDR, 0)
rv = Raindrops::TCP_Info.new s
c = TCPSocket.new TEST_ADDR, s.addr[1]
tmp = Raindrops::TCP_Info.new s
TCP_INFO_useful_listenq and assert_equal 1, tmp.unacked
assert_equal 0, rv.unacked
a = s.accept
tmp = Raindrops::TCP_Info.new s
assert_equal 0, tmp.unacked
before = tmp.object_id
tmp.get!(s)
assert_equal before, tmp.object_id
ensure
[ c, a, s ].compact.each(&:close)
end
def test_accessors
s = TCPServer.new TEST_ADDR, 0
tmp = Raindrops::TCP_Info.new s
tcp_info_methods = tmp.methods - Object.new.methods
assert tcp_info_methods.size >= 32
tcp_info_methods.each do |m|
next if m.to_sym == :get!
next if ! tmp.respond_to?(m)
val = tmp.__send__ m
assert_kind_of Integer, val
assert val >= 0
end
assert tmp.respond_to?(:state), 'every OS knows about TCP state, right?'
ensure
s.close
end
def test_tcp_server_delayed
delay = 0.010
delay_ms = (delay * 1000).to_i
s = TCPServer.new(TEST_ADDR, 0)
c = TCPSocket.new TEST_ADDR, s.addr[1]
c.syswrite "."
sleep(delay * 1.2)
a = s.accept
i = Raindrops::TCP_Info.new(a)
assert i.last_data_recv >= delay_ms, "#{i.last_data_recv} < #{delay_ms}"
ensure
c.close if c
a.close if a
s.close
end
def test_tcp_server_state_closed
s = TCPServer.new(TEST_ADDR, 0)
c = TCPSocket.new(TEST_ADDR, s.addr[1])
i = Raindrops::TCP_Info.allocate
a = s.accept
i.get!(a)
state = i.state
if Raindrops.const_defined?(:TCP)
assert_equal state, Raindrops::TCP[:ESTABLISHED]
end
c = c.close
sleep(0.01) # wait for kernel to update state
i.get!(a)
assert_not_equal state, i.state
ensure
s.close if s
c.close if c
a.close if a
end
end if defined? Raindrops::TCP_Info
raindrops-0.19.0/test/test_middleware.rb 0000644 0000041 0000041 00000006706 13145550250 020323 0 ustar www-data www-data # -*- encoding: binary -*-
require 'test/unit'
require 'raindrops'
class TestMiddleware < Test::Unit::TestCase
def setup
@resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' }
@response = [ 200, @resp_headers, [] ]
@app = lambda { |env| @response }
end
def test_setup
app = Raindrops::Middleware.new(@app)
response = app.call({})
assert_equal @response[0,2], response[0,2]
assert response.last.kind_of?(Raindrops::Middleware::Proxy)
assert response.last.object_id != app.object_id
tmp = []
response.last.each { |y| tmp << y }
assert tmp.empty?
end
def test_alt_stats
stats = Raindrops::Middleware::Stats.new
app = lambda { |env|
if (stats.writing == 0 && stats.calling == 1)
@app.call(env)
else
[ 500, @resp_headers, [] ]
end
}
app = Raindrops::Middleware.new(app, :stats => stats)
response = app.call({})
assert_equal 0, stats.calling
assert_equal 1, stats.writing
assert_equal 200, response[0]
assert response.last.kind_of?(Raindrops::Middleware::Proxy)
tmp = []
response.last.each do |y|
assert_equal 1, stats.writing
tmp << y
end
assert tmp.empty?
end
def test_default_endpoint
app = Raindrops::Middleware.new(@app)
response = app.call("PATH_INFO" => "/_raindrops")
expect = [
200,
{ "Content-Type" => "text/plain", "Content-Length" => "22" },
[ "calling: 0\nwriting: 0\n" ]
]
assert_equal expect, response
end
def test_alt_endpoint
app = Raindrops::Middleware.new(@app, :path => "/foo")
response = app.call("PATH_INFO" => "/foo")
expect = [
200,
{ "Content-Type" => "text/plain", "Content-Length" => "22" },
[ "calling: 0\nwriting: 0\n" ]
]
assert_equal expect, response
end
def test_concurrent
rda, wra = IO.pipe
rdb, wrb = IO.pipe
app = lambda do |env|
wrb.close
wra.syswrite('.')
wra.close
# wait until parent has run app.call for stats endpoint
rdb.read
@app.call(env)
end
app = Raindrops::Middleware.new(app)
pid = fork { app.call({}) }
rdb.close
# wait til child is running in app.call
assert_equal '.', rda.sysread(1)
rda.close
response = app.call("PATH_INFO" => "/_raindrops")
expect = [
200,
{ "Content-Type" => "text/plain", "Content-Length" => "22" },
[ "calling: 1\nwriting: 0\n" ]
]
assert_equal expect, response
wrb.close # unblock child process
assert Process.waitpid2(pid).last.success?
# we didn't call close the body in the forked child, so it'll always be
# marked as writing, a real server would close the body
response = app.call("PATH_INFO" => "/_raindrops")
expect = [
200,
{ "Content-Type" => "text/plain", "Content-Length" => "22" },
[ "calling: 0\nwriting: 1\n" ]
]
assert_equal expect, response
end
def test_middleware_proxy_to_path_missing
app = Raindrops::Middleware.new(@app)
response = app.call({})
body = response[2]
assert_kind_of Raindrops::Middleware::Proxy, body
assert ! body.respond_to?(:to_path)
assert body.respond_to?(:close)
orig_body = @response[2]
def orig_body.to_path; "/dev/null"; end
assert body.respond_to?(:to_path)
assert_equal "/dev/null", body.to_path
def orig_body.body; "this is a body"; end
assert body.respond_to?(:body)
assert_equal "this is a body", body.body
end
end
raindrops-0.19.0/test/test_last_data_recv_unicorn.rb 0000644 0000041 0000041 00000003746 13145550250 022717 0 ustar www-data www-data # -*- encoding: binary -*-
require "./test/rack_unicorn"
require "tempfile"
require "net/http"
$stderr.sync = $stdout.sync = true
pmq = begin
Raindrops::Aggregate::PMQ
rescue LoadError => e
warn "W: #{e} skipping #{__FILE__}"
false
end
if RUBY_VERSION.to_f < 1.9
pmq = false
warn "W: skipping test=#{__FILE__}, only Ruby 1.9 supported for now"
end
class TestLastDataRecvUnicorn < Test::Unit::TestCase
def setup
@queue = "/test.#{rand}"
@host = ENV["UNICORN_TEST_ADDR"] || "127.0.0.1"
@sock = TCPServer.new @host, 0
@port = @sock.addr[1]
ENV["UNICORN_FD"] = @sock.fileno.to_s
@host_with_port = "#@host:#@port"
@cfg = Tempfile.new 'unicorn_config_file'
@cfg.puts "require 'raindrops'"
@cfg.puts "preload_app true"
ENV['RAINDROPS_MQUEUE'] = @queue
# @cfg.puts "worker_processes 4"
@opts = { :listeners => [ @host_with_port ], :config_file => @cfg.path }
end
def test_auto_listener
@srv = fork {
Thread.abort_on_exception = true
app = %q!Rack::Builder.new do
map("/ldr") { run Raindrops::LastDataRecv.new }
map("/") { run Rack::Lobster.new }
end.to_app!
def app.arity; 0; end
def app.call; eval self; end
Unicorn::HttpServer.new(app, @opts).start.join
}
400.times { assert_kind_of Net::HTTPSuccess, get("/") }
resp = get("/ldr")
# # p(resp.methods - Object.methods)
# resp.each_header { |k,v| p [k, "=" , v] }
assert resp.header["x-count"]
assert resp.header["x-min"]
assert resp.header["x-max"]
assert resp.header["x-mean"]
assert resp.header["x-std-dev"]
assert resp.header["x-outliers-low"]
assert resp.header["x-outliers-high"]
assert resp.body.size > 0
end
def get(path)
Net::HTTP.start(@host, @port) { |http| http.get path }
end
def teardown
Process.kill :QUIT, @srv
_, status = Process.waitpid2 @srv
assert status.success?
POSIX_MQ.unlink @queue
end
end if defined?(Unicorn) && RUBY_PLATFORM =~ /linux/ && pmq
raindrops-0.19.0/test/rack_unicorn.rb 0000644 0000041 0000041 00000000414 13145550250 017612 0 ustar www-data www-data # -*- encoding: binary -*-
require "test/unit"
require "raindrops"
require "rack"
require "rack/lobster"
require "open-uri"
begin
require "unicorn"
require "rack/lobster"
rescue LoadError => e
warn "W: #{e} skipping test since Rack or Unicorn was not found"
end
raindrops-0.19.0/test/test_linux_middleware.rb 0000644 0000041 0000041 00000003212 13145550250 021527 0 ustar www-data www-data # -*- encoding: binary -*-
require 'test/unit'
require 'tempfile'
require 'raindrops'
require 'socket'
$stderr.sync = $stdout.sync = true
class TestLinuxMiddleware < Test::Unit::TestCase
def setup
@resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' }
@response = [ 200, @resp_headers, [] ]
@app = lambda { |env| @response }
@to_close = []
end
def teardown
@to_close.each { |io| io.close unless io.closed? }
end
def test_unix_listener
tmp = Tempfile.new("")
File.unlink(tmp.path)
@to_close << UNIXServer.new(tmp.path)
app = Raindrops::Middleware.new(@app, :listeners => [tmp.path])
linux_extra = "#{tmp.path} active: 0\n#{tmp.path} queued: 0\n"
response = app.call("PATH_INFO" => "/_raindrops")
expect = [
200,
{
"Content-Type" => "text/plain",
"Content-Length" => (22 + linux_extra.size).to_s
},
[
"calling: 0\nwriting: 0\n#{linux_extra}" \
]
]
assert_equal expect, response
end
def test_unix_listener_queued
tmp = Tempfile.new("")
File.unlink(tmp.path)
@to_close << UNIXServer.new(tmp.path)
@to_close << UNIXSocket.new(tmp.path)
app = Raindrops::Middleware.new(@app, :listeners => [tmp.path])
linux_extra = "#{tmp.path} active: 0\n#{tmp.path} queued: 1\n"
response = app.call("PATH_INFO" => "/_raindrops")
expect = [
200,
{
"Content-Type" => "text/plain",
"Content-Length" => (22 + linux_extra.size).to_s
},
[
"calling: 0\nwriting: 0\n#{linux_extra}" \
]
]
assert_equal expect, response
end
end if RUBY_PLATFORM =~ /linux/
raindrops-0.19.0/test/test_linux_ipv6.rb 0000644 0000041 0000041 00000011321 13145550250 020276 0 ustar www-data www-data # -*- encoding: binary -*-
require 'test/unit'
require 'tempfile'
require 'raindrops'
require 'socket'
require 'pp'
require "./test/ipv6_enabled"
$stderr.sync = $stdout.sync = true
class TestLinuxIPv6 < Test::Unit::TestCase
include Raindrops::Linux
TEST_ADDR = ENV["TEST_HOST6"] || "::1"
def setup
@to_close = []
end
def teardown
@to_close.each { |io| io.close unless io.closed? }
end
def test_tcp
s = TCPServer.new(TEST_ADDR, 0)
port = s.addr[1]
addr = "[#{TEST_ADDR}]:#{port}"
addrs = [ addr ]
stats = tcp_listener_stats(addrs)
assert_equal 1, stats.size
assert_equal 0, stats[addr].queued
assert_equal 0, stats[addr].active
@to_close << TCPSocket.new(TEST_ADDR, port)
stats = tcp_listener_stats(addrs)
assert_equal 1, stats.size
assert_equal 1, stats[addr].queued
assert_equal 0, stats[addr].active
@to_close << s.accept
stats = tcp_listener_stats(addrs)
assert_equal 1, stats.size
assert_equal 0, stats[addr].queued
assert_equal 1, stats[addr].active
end
def test_tcp_multi
s1 = TCPServer.new(TEST_ADDR, 0)
s2 = TCPServer.new(TEST_ADDR, 0)
port1, port2 = s1.addr[1], s2.addr[1]
addr1, addr2 = "[#{TEST_ADDR}]:#{port1}", "[#{TEST_ADDR}]:#{port2}"
addrs = [ addr1, addr2 ]
stats = tcp_listener_stats(addrs)
assert_equal 2, stats.size
assert_equal 0, stats[addr1].queued
assert_equal 0, stats[addr1].active
assert_equal 0, stats[addr2].queued
assert_equal 0, stats[addr2].active
@to_close << TCPSocket.new(TEST_ADDR, port1)
stats = tcp_listener_stats(addrs)
assert_equal 2, stats.size
assert_equal 1, stats[addr1].queued
assert_equal 0, stats[addr1].active
assert_equal 0, stats[addr2].queued
assert_equal 0, stats[addr2].active
sc1 = s1.accept
stats = tcp_listener_stats(addrs)
assert_equal 2, stats.size
assert_equal 0, stats[addr1].queued
assert_equal 1, stats[addr1].active
assert_equal 0, stats[addr2].queued
assert_equal 0, stats[addr2].active
@to_close << TCPSocket.new(TEST_ADDR, port2)
stats = tcp_listener_stats(addrs)
assert_equal 2, stats.size
assert_equal 0, stats[addr1].queued
assert_equal 1, stats[addr1].active
assert_equal 1, stats[addr2].queued
assert_equal 0, stats[addr2].active
@to_close << TCPSocket.new(TEST_ADDR, port2)
stats = tcp_listener_stats(addrs)
assert_equal 2, stats.size
assert_equal 0, stats[addr1].queued
assert_equal 1, stats[addr1].active
assert_equal 2, stats[addr2].queued
assert_equal 0, stats[addr2].active
@to_close << s2.accept
stats = tcp_listener_stats(addrs)
assert_equal 2, stats.size
assert_equal 0, stats[addr1].queued
assert_equal 1, stats[addr1].active
assert_equal 1, stats[addr2].queued
assert_equal 1, stats[addr2].active
sc1.close
stats = tcp_listener_stats(addrs)
assert_equal 0, stats[addr1].queued
assert_equal 0, stats[addr1].active
assert_equal 1, stats[addr2].queued
assert_equal 1, stats[addr2].active
end
def test_invalid_addresses
assert_raises(ArgumentError) { tcp_listener_stats(%w([1:::5)) }
assert_raises(ArgumentError) { tcp_listener_stats(%w([1:::]5)) }
end
# tries to overflow buffers
def test_tcp_stress_test
nr_proc = 32
nr_sock = 500
s = TCPServer.new(TEST_ADDR, 0)
port = s.addr[1]
addr = "[#{TEST_ADDR}]:#{port}"
addrs = [ addr ]
rda, wra = IO.pipe
rdb, wrb = IO.pipe
nr_proc.times do
fork do
rda.close
wrb.close
@to_close.concat((1..nr_sock).map { s.accept })
wra.syswrite('.')
wra.close
rdb.sysread(1) # wait for parent to nuke us
end
end
nr_proc.times do
fork do
rda.close
wrb.close
@to_close.concat((1..nr_sock).map { TCPSocket.new(TEST_ADDR, port) })
wra.syswrite('.')
wra.close
rdb.sysread(1) # wait for parent to nuke us
end
end
assert_equal('.' * (nr_proc * 2), rda.read(nr_proc * 2))
rda.close
stats = tcp_listener_stats(addrs)
expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 0] }
assert_equal expect, stats
@to_close << TCPSocket.new(TEST_ADDR, port)
stats = tcp_listener_stats(addrs)
expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 1] }
assert_equal expect, stats
if ENV["BENCHMARK"].to_i != 0
require 'benchmark'
puts(Benchmark.measure{1000.times { tcp_listener_stats(addrs) }})
end
wrb.syswrite('.' * (nr_proc * 2)) # broadcast a wakeup
statuses = Process.waitall
statuses.each { |(_,status)| assert status.success?, status.inspect }
end if ENV["STRESS"].to_i != 0
end if RUBY_PLATFORM =~ /linux/ && ipv6_enabled?
raindrops-0.19.0/test/test_middleware_unicorn_ipv6.rb 0000644 0000041 0000041 00000002123 13145550250 023011 0 ustar www-data www-data # -*- encoding: binary -*-
require "./test/rack_unicorn"
require "./test/ipv6_enabled"
$stderr.sync = $stdout.sync = true
class TestMiddlewareUnicornIPv6 < Test::Unit::TestCase
def setup
@host = ENV["TEST_HOST6"] || "::1"
sock = TCPServer.new @host, 0
@port = sock.addr[1]
ENV["UNICORN_FD"] = sock.fileno.to_s
@host_with_port = "[#@host]:#@port"
@opts = { :listeners => [ @host_with_port ] }
@addr_regexp = Regexp.escape @host_with_port
end
def test_auto_listener
@app = Rack::Builder.new do
use Raindrops::Middleware
run Rack::Lobster.new
end
@srv = fork { Unicorn::HttpServer.new(@app, @opts).start.join }
s = TCPSocket.new @host, @port
s.write "GET /_raindrops HTTP/1.0\r\n\r\n"
resp = s.read
_, body = resp.split(/\r\n\r\n/, 2)
assert_match %r{^#@addr_regexp active: 1$}, body
assert_match %r{^#@addr_regexp queued: 0$}, body
end
def teardown
Process.kill :QUIT, @srv
_, status = Process.waitpid2 @srv
assert status.success?
end
end if defined?(Unicorn) && RUBY_PLATFORM =~ /linux/ && ipv6_enabled?
raindrops-0.19.0/test/test_linux_all_tcp_listen_stats_leak.rb 0000644 0000041 0000041 00000001764 13145550250 024632 0 ustar www-data www-data # -*- encoding: binary -*-
require 'test/unit'
require 'raindrops'
require 'socket'
require 'benchmark'
$stderr.sync = $stdout.sync = true
class TestLinuxAllTcpListenStatsLeak < Test::Unit::TestCase
TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
def rss_kb
File.readlines("/proc/#$$/status").grep(/VmRSS:/)[0].split(/\s+/)[1].to_i
end
def test_leak
s = TCPServer.new(TEST_ADDR, 0)
start_kb = rss_kb
p [ :start_kb, start_kb ]
assert_nothing_raised do
p(Benchmark.measure {
1000.times { Raindrops::Linux.all_tcp_listener_stats }
})
end
cur_kb = rss_kb
p [ :cur_kb, cur_kb ]
now = Time.now.to_i
fin = now + 60
assert_nothing_raised do
1000000000.times { |i|
if (i % 1024) == 0
now = Time.now.to_i
break if now > fin
end
Raindrops::Linux.all_tcp_listener_stats
}
end
cur_kb = rss_kb
p [ :cur_kb, cur_kb ]
ensure
s.close
end
end if ENV["STRESS"].to_i != 0
raindrops-0.19.0/test/test_watcher.rb 0000644 0000041 0000041 00000013067 13145550250 017641 0 ustar www-data www-data # -*- encoding: binary -*-
require "test/unit"
require "rack"
require "raindrops"
begin
require 'aggregate'
rescue LoadError => e
warn "W: #{e} skipping #{__FILE__}"
end
class TestWatcher < Test::Unit::TestCase
TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
def check_headers(headers)
%w(X-Count X-Std-Dev X-Min X-Max X-Mean
X-Outliers-Low X-Outliers-Low X-Last-Reset).each { |x|
assert_kind_of String, headers[x], "#{x} missing"
}
end
def teardown
@app.shutdown
@ios.each { |io| io.close unless io.closed? }
end
def setup
@ios = []
@srv = TCPServer.new TEST_ADDR, 0
@ios << @srv
@port = @srv.addr[1]
@client = TCPSocket.new TEST_ADDR, @port
@addr = "#{TEST_ADDR}:#{@port}"
@ios << @client
@app = Raindrops::Watcher.new :delay => 0.001
@req = Rack::MockRequest.new @app
end
def test_index
resp = @req.get "/"
assert_equal 200, resp.status.to_i
t = Time.parse resp.headers["Last-Modified"]
assert_in_delta Time.now.to_f, t.to_f, 2.0
end
def test_active_txt
resp = @req.get "/active/#@addr.txt"
assert_equal 200, resp.status.to_i
assert_equal "text/plain", resp.headers["Content-Type"]
check_headers(resp.headers)
end
def test_invalid
assert_nothing_raised do
@req.get("/active/666.666.666.666%3A666.txt")
@req.get("/queued/666.666.666.666%3A666.txt")
@req.get("/active/666.666.666.666%3A666.html")
@req.get("/queued/666.666.666.666%3A666.html")
end
addr = @app.instance_eval do
@peak_active.keys + @peak_queued.keys +
@resets.keys + @active.keys + @queued.keys
end
assert addr.grep(/666\.666\.666\.666/).empty?, addr.inspect
end
def test_active_html
resp = @req.get "/active/#@addr.html"
assert_equal 200, resp.status.to_i
assert_equal "text/html", resp.headers["Content-Type"]
check_headers(resp.headers)
end
def test_queued_txt
resp = @req.get "/queued/#@addr.txt"
assert_equal 200, resp.status.to_i
assert_equal "text/plain", resp.headers["Content-Type"]
check_headers(resp.headers)
end
def test_queued_html
resp = @req.get "/queued/#@addr.html"
assert_equal 200, resp.status.to_i
assert_equal "text/html", resp.headers["Content-Type"]
check_headers(resp.headers)
end
def test_reset
resp = @req.post "/reset/#@addr"
assert_equal 302, resp.status.to_i
end
def test_tail
env = @req.class.env_for "/tail/#@addr.txt"
status, headers, body = @app.call env
assert_equal "text/plain", headers["Content-Type"]
assert_equal 200, status.to_i
tmp = []
body.each do |x|
assert_kind_of String, x
tmp << x
break if tmp.size > 1
end
end
def test_tail_queued_min
env = @req.class.env_for "/tail/#@addr.txt?queued_min=1"
status, headers, body = @app.call env
assert_equal "text/plain", headers["Content-Type"]
assert_equal 200, status.to_i
tmp = []
body.each do |x|
tmp = TCPSocket.new TEST_ADDR, @port
@ios << tmp
assert_kind_of String, x
assert_equal 1, x.strip.split(/\s+/).last.to_i
break
end
end
def test_x_current_header
env = @req.class.env_for "/active/#@addr.txt"
status, headers, body = @app.call(env)
assert_equal "0", headers["X-Current"], headers.inspect
env = @req.class.env_for "/queued/#@addr.txt"
status, headers, body = @app.call(env)
assert_equal "1", headers["X-Current"], headers.inspect
@ios << @srv.accept
sleep 0.1
env = @req.class.env_for "/queued/#@addr.txt"
status, headers, body = @app.call(env)
assert_equal "0", headers["X-Current"], headers.inspect
env = @req.class.env_for "/active/#@addr.txt"
status, headers, body = @app.call(env)
assert_equal "1", headers["X-Current"], headers.inspect
end
def test_peaks
env = @req.class.env_for "/active/#@addr.txt"
status, headers, body = @app.call(env.dup)
start = headers["X-First-Peak-At"]
assert headers["X-First-Peak-At"], headers.inspect
assert headers["X-Last-Peak-At"], headers.inspect
assert_nothing_raised { Time.parse(headers["X-First-Peak-At"]) }
assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) }
before = headers["X-Last-Peak-At"]
env = @req.class.env_for "/queued/#@addr.txt"
status, headers, body = @app.call(env)
assert_nothing_raised { Time.parse(headers["X-First-Peak-At"]) }
assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) }
assert_equal before, headers["X-Last-Peak-At"], "should not change"
sleep 2
env = @req.class.env_for "/active/#@addr.txt"
status, headers, body = @app.call(env.dup)
assert_equal before, headers["X-Last-Peak-At"], headers.inspect
@ios << @srv.accept
begin
@srv.accept_nonblock
assert false, "we should not get here"
rescue => e
assert_kind_of Errno::EAGAIN, e
end
sleep 0.1
env = @req.class.env_for "/queued/#@addr.txt"
status, headers, body = @app.call(env.dup)
assert headers["X-Last-Peak-At"], headers.inspect
assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) }
assert before != headers["X-Last-Peak-At"]
queued_before = headers["X-Last-Peak-At"]
sleep 2
env = @req.class.env_for "/queued/#@addr.txt"
status, headers, body = @app.call(env)
assert_equal "0", headers["X-Current"]
assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) }
assert_equal queued_before, headers["X-Last-Peak-At"], "should not change"
assert_equal start, headers["X-First-Peak-At"]
end
end if RUBY_PLATFORM =~ /linux/ && defined?(Aggregate)
raindrops-0.19.0/test/test_struct.rb 0000644 0000041 0000041 00000002141 13145550250 017517 0 ustar www-data www-data require 'test/unit'
require 'raindrops'
class TestRaindrops < Test::Unit::TestCase
def test_struct_new
@rw = Raindrops::Struct.new(:r, :w)
assert @rw.kind_of?(Class)
end
TMP = Raindrops::Struct.new(:r, :w)
def test_init_basic
tmp = TMP.new
assert_equal 0, tmp.r
assert_equal 1, tmp.incr_r
assert_equal 1, tmp.r
assert_equal({ :r => 1, :w => 0 }, tmp.to_hash)
assert_equal 1, tmp[0]
assert_equal 0, tmp[1]
assert_equal [ :r, :w ], TMP::MEMBERS
end
def test_init
tmp = TMP.new(5, 6)
assert_equal({ :r => 5, :w => 6 }, tmp.to_hash)
end
def test_dup
a = TMP.new(5, 6)
b = a.dup
assert_equal({ :r => 5, :w => 6 }, b.to_hash)
assert_nothing_raised { 4.times { b.decr_r } }
assert_equal({ :r => 1, :w => 6 }, b.to_hash)
assert_equal({ :r => 5, :w => 6 }, a.to_hash)
end
class Foo < Raindrops::Struct.new(:a, :b, :c, :d)
def to_ary
@raindrops.to_ary
end
def hello
"world"
end
end
def test_subclass
assert_equal [0, 0, 0, 0], Foo.new.to_ary
assert_equal "world", Foo.new.hello
end
end
raindrops-0.19.0/test/test_linux.rb 0000644 0000041 0000041 00000020337 13145550250 017341 0 ustar www-data www-data # -*- encoding: binary -*-
require 'test/unit'
require 'tempfile'
require 'raindrops'
require 'socket'
require 'pp'
$stderr.sync = $stdout.sync = true
class TestLinux < Test::Unit::TestCase
include Raindrops::Linux
TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
def setup
@to_close = []
end
def teardown
@to_close.each { |io| io.close unless io.closed? }
end
def test_unix
tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :)
File.unlink(tmp.path)
us = UNIXServer.new(tmp.path)
stats = unix_listener_stats([tmp.path])
assert_equal 1, stats.size
assert_equal 0, stats[tmp.path].active
assert_equal 0, stats[tmp.path].queued
@to_close << UNIXSocket.new(tmp.path)
stats = unix_listener_stats([tmp.path])
assert_equal 1, stats.size
assert_equal 0, stats[tmp.path].active
assert_equal 1, stats[tmp.path].queued
@to_close << UNIXSocket.new(tmp.path)
stats = unix_listener_stats([tmp.path])
assert_equal 1, stats.size
assert_equal 0, stats[tmp.path].active
assert_equal 2, stats[tmp.path].queued
@to_close << us.accept
stats = unix_listener_stats([tmp.path])
assert_equal 1, stats.size
assert_equal 1, stats[tmp.path].active
assert_equal 1, stats[tmp.path].queued
end
def test_unix_all
tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :)
File.unlink(tmp.path)
us = UNIXServer.new(tmp.path)
@to_close << UNIXSocket.new(tmp.path)
stats = unix_listener_stats
assert_equal 0, stats[tmp.path].active
assert_equal 1, stats[tmp.path].queued
@to_close << UNIXSocket.new(tmp.path)
stats = unix_listener_stats
assert_equal 0, stats[tmp.path].active
assert_equal 2, stats[tmp.path].queued
@to_close << us.accept
stats = unix_listener_stats
assert_equal 1, stats[tmp.path].active
assert_equal 1, stats[tmp.path].queued
end
def test_unix_all_unused
tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :)
File.unlink(tmp.path)
us = UNIXServer.new(tmp.path)
stats = unix_listener_stats
assert stats.keys.include?(tmp.path), stats.inspect
assert_equal 0, stats[tmp.path].active
assert_equal 0, stats[tmp.path].queued
end
def test_unix_resolves_symlinks
tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :)
File.unlink(tmp.path)
us = UNIXServer.new(tmp.path)
# Create a symlink
link = Tempfile.new("somethingelse")
File.unlink(link.path) # We need an available name, not an actual file
File.symlink(tmp.path, link.path)
@to_close << UNIXSocket.new(tmp.path)
stats = unix_listener_stats
assert_equal 0, stats[tmp.path].active
assert_equal 1, stats[tmp.path].queued
@to_close << UNIXSocket.new(link.path)
stats = unix_listener_stats([link.path])
assert_equal 0, stats[link.path].active
assert_equal 2, stats[link.path].queued
assert_equal stats[link.path].object_id, stats[tmp.path].object_id
@to_close << us.accept
stats = unix_listener_stats
assert_equal 1, stats[tmp.path].active
assert_equal 1, stats[tmp.path].queued
end
def test_tcp
s = TCPServer.new(TEST_ADDR, 0)
port = s.addr[1]
addr = "#{TEST_ADDR}:#{port}"
addrs = [ addr ]
stats = tcp_listener_stats(addrs)
assert_equal 1, stats.size
assert_equal 0, stats[addr].queued
assert_equal 0, stats[addr].active
@to_close << TCPSocket.new(TEST_ADDR, port)
stats = tcp_listener_stats(addrs)
assert_equal 1, stats.size
assert_equal 1, stats[addr].queued
assert_equal 0, stats[addr].active
@to_close << s.accept
stats = tcp_listener_stats(addrs)
assert_equal 1, stats.size
assert_equal 0, stats[addr].queued
assert_equal 1, stats[addr].active
end
def test_tcp_reuse_sock
nlsock = Raindrops::InetDiagSocket.new
s = TCPServer.new(TEST_ADDR, 0)
port = s.addr[1]
addr = "#{TEST_ADDR}:#{port}"
addrs = [ addr ]
stats = tcp_listener_stats(addrs, nlsock)
assert_equal 1, stats.size
assert_equal 0, stats[addr].queued
assert_equal 0, stats[addr].active
@to_close << TCPSocket.new(TEST_ADDR, port)
stats = tcp_listener_stats(addrs, nlsock)
assert_equal 1, stats.size
assert_equal 1, stats[addr].queued
assert_equal 0, stats[addr].active
@to_close << s.accept
stats = tcp_listener_stats(addrs, nlsock)
assert_equal 1, stats.size
assert_equal 0, stats[addr].queued
assert_equal 1, stats[addr].active
ensure
nlsock.close
end
def test_tcp_multi
s1 = TCPServer.new(TEST_ADDR, 0)
s2 = TCPServer.new(TEST_ADDR, 0)
port1, port2 = s1.addr[1], s2.addr[1]
addr1, addr2 = "#{TEST_ADDR}:#{port1}", "#{TEST_ADDR}:#{port2}"
addrs = [ addr1, addr2 ]
stats = tcp_listener_stats(addrs)
assert_equal 2, stats.size
assert_equal 0, stats[addr1].queued
assert_equal 0, stats[addr1].active
assert_equal 0, stats[addr2].queued
assert_equal 0, stats[addr2].active
@to_close << TCPSocket.new(TEST_ADDR, port1)
stats = tcp_listener_stats(addrs)
assert_equal 2, stats.size
assert_equal 1, stats[addr1].queued
assert_equal 0, stats[addr1].active
assert_equal 0, stats[addr2].queued
assert_equal 0, stats[addr2].active
sc1 = s1.accept
stats = tcp_listener_stats(addrs)
assert_equal 2, stats.size
assert_equal 0, stats[addr1].queued
assert_equal 1, stats[addr1].active
assert_equal 0, stats[addr2].queued
assert_equal 0, stats[addr2].active
@to_close << TCPSocket.new(TEST_ADDR, port2)
stats = tcp_listener_stats(addrs)
assert_equal 2, stats.size
assert_equal 0, stats[addr1].queued
assert_equal 1, stats[addr1].active
assert_equal 1, stats[addr2].queued
assert_equal 0, stats[addr2].active
@to_close << TCPSocket.new(TEST_ADDR, port2)
stats = tcp_listener_stats(addrs)
assert_equal 2, stats.size
assert_equal 0, stats[addr1].queued
assert_equal 1, stats[addr1].active
assert_equal 2, stats[addr2].queued
assert_equal 0, stats[addr2].active
@to_close << s2.accept
stats = tcp_listener_stats(addrs)
assert_equal 2, stats.size
assert_equal 0, stats[addr1].queued
assert_equal 1, stats[addr1].active
assert_equal 1, stats[addr2].queued
assert_equal 1, stats[addr2].active
sc1.close
stats = tcp_listener_stats(addrs)
assert_equal 0, stats[addr1].queued
assert_equal 0, stats[addr1].active
assert_equal 1, stats[addr2].queued
assert_equal 1, stats[addr2].active
# make sure we don't leave "true" placeholders in results if a
# listener becomes invalid (even momentarily).
s2.close
stats = tcp_listener_stats(addrs)
assert stats.values.all? { |x| x.instance_of?(Raindrops::ListenStats) },
"placeholders left: #{stats.inspect}"
end
# tries to overflow buffers
def test_tcp_stress_test
nr_proc = 32
nr_sock = 500
s = TCPServer.new(TEST_ADDR, 0)
port = s.addr[1]
addr = "#{TEST_ADDR}:#{port}"
addrs = [ addr ]
rda, wra = IO.pipe
rdb, wrb = IO.pipe
nr_proc.times do
fork do
rda.close
wrb.close
@to_close.concat((1..nr_sock).map { s.accept })
wra.syswrite('.')
wra.close
rdb.sysread(1) # wait for parent to nuke us
end
end
nr_proc.times do
fork do
rda.close
wrb.close
@to_close.concat((1..nr_sock).map { TCPSocket.new(TEST_ADDR, port) })
wra.syswrite('.')
wra.close
rdb.sysread(1) # wait for parent to nuke us
end
end
assert_equal('.' * (nr_proc * 2), rda.read(nr_proc * 2))
rda.close
stats = tcp_listener_stats(addrs)
expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 0] }
assert_equal expect, stats
@to_close << TCPSocket.new(TEST_ADDR, port)
stats = tcp_listener_stats(addrs)
expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 1] }
assert_equal expect, stats
if ENV["BENCHMARK"].to_i != 0
require 'benchmark'
puts(Benchmark.measure{1000.times { tcp_listener_stats(addrs) }})
end
wrb.syswrite('.' * (nr_proc * 2)) # broadcast a wakeup
statuses = Process.waitall
statuses.each { |(_,status)| assert status.success?, status.inspect }
end if ENV["STRESS"].to_i != 0
end if RUBY_PLATFORM =~ /linux/
raindrops-0.19.0/test/test_middleware_unicorn.rb 0000644 0000041 0000041 00000002057 13145550250 022053 0 ustar www-data www-data # -*- encoding: binary -*-
require "./test/rack_unicorn"
$stderr.sync = $stdout.sync = true
class TestMiddlewareUnicorn < Test::Unit::TestCase
def setup
@host = ENV["UNICORN_TEST_ADDR"] || "127.0.0.1"
@sock = TCPServer.new @host, 0
@port = @sock.addr[1]
ENV["UNICORN_FD"] = @sock.fileno.to_s
@host_with_port = "#@host:#@port"
@opts = { :listeners => [ @host_with_port ] }
@addr_regexp = Regexp.escape @host_with_port
end
def test_auto_listener
@app = Rack::Builder.new do
use Raindrops::Middleware
run Rack::Lobster.new
end
@srv = fork { Unicorn::HttpServer.new(@app, @opts).start.join }
s = TCPSocket.new @host, @port
s.write "GET /_raindrops HTTP/1.0\r\n\r\n"
resp = s.read
_, body = resp.split(/\r\n\r\n/, 2)
assert_match %r{^#@addr_regexp active: 1$}, body
assert_match %r{^#@addr_regexp queued: 0$}, body
end
def teardown
Process.kill :QUIT, @srv
_, status = Process.waitpid2 @srv
assert status.success?
end
end if defined?(Unicorn) && RUBY_PLATFORM =~ /linux/
raindrops-0.19.0/test/test_raindrops_gc.rb 0000644 0000041 0000041 00000001676 13145550250 020661 0 ustar www-data www-data # -*- encoding: binary -*-
require 'test/unit'
require 'raindrops'
class TestRaindropsGc < Test::Unit::TestCase
# we may need to create more garbage as GC may be less aggressive
# about expiring things. This is completely unrealistic code,
# though...
def test_gc
assert_nothing_raised do
1000000.times { |i| Raindrops.new(24); [] }
end
end
def test_gc_postfork
tmp = Raindrops.new 2
pid = fork do
1000000.times do
tmp = Raindrops.new 2
tmp.to_ary
end
end
_, status = Process.waitpid2(pid)
assert status.success?
assert_equal [ 0, 0 ], tmp.to_ary
tmp.incr 1
assert_equal [ 0, 1 ], tmp.to_ary
pid = fork do
tmp.incr 1
exit([ 0, 2 ] == tmp.to_ary)
end
_, status = Process.waitpid2(pid)
assert status.success?
assert_equal [ 0, 2 ], tmp.to_ary
end
end if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" &&
ENV["STRESS"].to_i != 0
raindrops-0.19.0/test/test_aggregate_pmq.rb 0000644 0000041 0000041 00000003170 13145550250 021001 0 ustar www-data www-data require "test/unit"
require "raindrops"
pmq = begin
Raindrops::Aggregate::PMQ
rescue LoadError => e
warn "W: #{e} skipping #{__FILE__}"
false
end
if RUBY_VERSION.to_f < 1.9
pmq = false
warn "W: skipping #{__FILE__}, only Ruby 1.9 supported for now"
end
Thread.abort_on_exception = true
class TestAggregatePMQ < Test::Unit::TestCase
def setup
@queue = "/test.#{rand}"
end
def teardown
POSIX_MQ.unlink @queue
end
def test_run
pmq = Raindrops::Aggregate::PMQ.new :queue => @queue
thr = Thread.new { pmq.master_loop }
agg = Aggregate.new
(1..10).each { |i| pmq << i; agg << i }
pmq.stop_master_loop
assert thr.join
assert_equal agg.count, pmq.count
assert_equal agg.mean, pmq.mean
assert_equal agg.std_dev, pmq.std_dev
assert_equal agg.min, pmq.min
assert_equal agg.max, pmq.max
assert_equal agg.to_s, pmq.to_s
end
def test_multi_process
nr_workers = 4
nr = 100
pmq = Raindrops::Aggregate::PMQ.new :queue => @queue
pid = fork { pmq.master_loop }
workers = (1..nr_workers).map {
fork {
(1..nr).each { |i| pmq << i }
pmq.flush
}
}
workers.each { |wpid| assert Process.waitpid2(wpid).last.success? }
pmq.stop_master_loop
assert Process.waitpid2(pid).last.success?
assert_equal 400, pmq.count
agg = Aggregate.new
(1..nr_workers).map { (1..nr).each { |i| agg << i } }
assert_equal agg.to_s, pmq.to_s
assert_equal agg.mean, pmq.mean
assert_equal agg.std_dev, pmq.std_dev
assert_equal agg.min, pmq.min
assert_equal agg.max, pmq.max
assert_equal agg.to_s, pmq.to_s
end
end if pmq
raindrops-0.19.0/test/test_linux_all_tcp_listen_stats.rb 0000644 0000041 0000041 00000002562 13145550250 023633 0 ustar www-data www-data # -*- encoding: binary -*-
require 'test/unit'
require 'socket'
require 'raindrops'
require 'pp'
$stderr.sync = $stdout.sync = true
class TestLinuxAllTcpListenStats < Test::Unit::TestCase
include Raindrops::Linux
TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
def test_print_all
puts "EVERYTHING"
pp Raindrops::Linux.tcp_listener_stats
puts("-" * 72)
end if $stdout.tty?
def setup
@socks = []
end
def teardown
@socks.each { |io| io.closed? or io.close }
end
def new_server
s = TCPServer.new TEST_ADDR, 0
@socks << s
[ s, s.addr[1] ]
end
def new_client(port)
s = TCPSocket.new("127.0.0.1", port)
@socks << s
s
end
def new_accept(srv)
c = srv.accept
@socks << c
c
end
def test_all_ports
srv, port = new_server
addr = "#{TEST_ADDR}:#{port}"
all = Raindrops::Linux.tcp_listener_stats
assert_equal [0,0], all[addr].to_a
new_client(port)
all = Raindrops::Linux.tcp_listener_stats
assert_equal [0,1], all[addr].to_a
new_client(port)
all = Raindrops::Linux.tcp_listener_stats
assert_equal [0,2], all[addr].to_a
new_accept(srv)
all = Raindrops::Linux.tcp_listener_stats
assert_equal [1,1], all[addr].to_a
new_accept(srv)
all = Raindrops::Linux.tcp_listener_stats
assert_equal [2,0], all[addr].to_a
end
end if RUBY_PLATFORM =~ /linux/
raindrops-0.19.0/archive/ 0000755 0000041 0000041 00000000000 13145550250 015253 5 ustar www-data www-data raindrops-0.19.0/archive/slrnpull.conf 0000644 0000041 0000041 00000000276 13145550250 020002 0 ustar www-data www-data # group_name max expire headers_only
gmane.comp.lang.ruby.raindrops.general 1000000000 1000000000 0
# usage: slrnpull -d $PWD -h news.gmane.org --no-post
raindrops-0.19.0/archive/.gitignore 0000644 0000041 0000041 00000000026 13145550250 017241 0 ustar www-data www-data /data
/news
/requests
raindrops-0.19.0/.gitignore 0000644 0000041 0000041 00000000171 13145550250 015621 0 ustar www-data www-data *.o
*.so
*.log
*.rbc
Makefile
/GIT-VERSION-FILE
/local.mk
/NEWS*
/ChangeLog
/.manifest
/man
/pkg
/doc
/LATEST
/tmp
/.rbx
raindrops-0.19.0/raindrops.gemspec 0000644 0000041 0000041 00000002012 13145550250 017173 0 ustar www-data www-data # -*- encoding: binary -*-
manifest = File.exist?('.manifest') ?
IO.readlines('.manifest').map!(&:chomp!) : `git ls-files`.split("\n")
test_files = manifest.grep(%r{\Atest/test_.*\.rb\z})
Gem::Specification.new do |s|
s.name = %q{raindrops}
s.version = (ENV["VERSION"] ||= '0.18.0').dup
s.authors = ["raindrops hackers"]
s.description = File.read('README').split("\n\n")[1]
s.email = %q{raindrops-public@bogomips.org}
s.extensions = %w(ext/raindrops/extconf.rb)
s.extra_rdoc_files = IO.readlines('.document').map!(&:chomp!).keep_if do |f|
File.exist?(f)
end
s.files = manifest
s.homepage = 'https://bogomips.org/raindrops/'
s.summary = 'real-time stats for preforking Rack servers'
s.required_ruby_version = '>= 1.9.3'
s.test_files = test_files
s.add_development_dependency('aggregate', '~> 0.2')
s.add_development_dependency('test-unit', '~> 3.0')
s.add_development_dependency('posix_mq', '~> 2.0')
s.add_development_dependency('rack', [ '>= 1.2', '< 3.0' ])
s.licenses = %w(LGPL-2.1+)
end
raindrops-0.19.0/pkg.mk 0000644 0000041 0000041 00000007737 13145550250 014762 0 ustar www-data www-data RUBY = ruby
RAKE = rake
RSYNC = rsync
OLDDOC = olddoc
RDOC = rdoc
GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
@./GIT-VERSION-GEN
-include GIT-VERSION-FILE
-include local.mk
DLEXT := $(shell $(RUBY) -rrbconfig -e 'puts RbConfig::CONFIG["DLEXT"]')
RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
RUBY_ENGINE := $(shell $(RUBY) -e 'puts((RUBY_ENGINE rescue "ruby"))')
lib := lib
ext := $(firstword $(wildcard ext/*))
ifneq ($(ext),)
ext_pfx := tmp/ext/$(RUBY_ENGINE)-$(RUBY_VERSION)
ext_h := $(wildcard $(ext)/*/*.h $(ext)/*.h)
ext_src := $(wildcard $(ext)/*.c $(ext_h))
ext_pfx_src := $(addprefix $(ext_pfx)/,$(ext_src))
ext_d := $(ext_pfx)/$(ext)/.d
$(ext)/extconf.rb: $(wildcard $(ext)/*.h)
@>> $@
$(ext_d):
@mkdir -p $(@D)
@> $@
$(ext_pfx)/$(ext)/%: $(ext)/% $(ext_d)
install -m 644 $< $@
$(ext_pfx)/$(ext)/Makefile: $(ext)/extconf.rb $(ext_d) $(ext_h)
$(RM) -f $(@D)/*.o
cd $(@D) && $(RUBY) $(CURDIR)/$(ext)/extconf.rb $(EXTCONF_ARGS)
ext_sfx := _ext.$(DLEXT)
ext_dl := $(ext_pfx)/$(ext)/$(notdir $(ext)_ext.$(DLEXT))
$(ext_dl): $(ext_src) $(ext_pfx_src) $(ext_pfx)/$(ext)/Makefile
@echo $^ == $@
$(MAKE) -C $(@D)
lib := $(lib):$(ext_pfx)/$(ext)
build: $(ext_dl)
else
build:
endif
pkg_extra += GIT-VERSION-FILE NEWS LATEST
NEWS: GIT-VERSION-FILE .olddoc.yml
$(OLDDOC) prepare
LATEST: NEWS
manifest:
$(RM) .manifest
$(MAKE) .manifest
.manifest: $(pkg_extra)
(git ls-files && for i in $@ $(pkg_extra); do echo $$i; done) | \
LC_ALL=C sort > $@+
cmp $@+ $@ || mv $@+ $@
$(RM) $@+
doc:: .document .olddoc.yml $(pkg_extra) $(PLACEHOLDERS)
-find lib -type f -name '*.rbc' -exec rm -f '{}' ';'
-find ext -type f -name '*.rbc' -exec rm -f '{}' ';'
$(RM) -r doc
$(RDOC) -f oldweb
$(OLDDOC) merge
install -m644 COPYING doc/COPYING
install -m644 NEWS doc/NEWS
install -m644 NEWS.atom.xml doc/NEWS.atom.xml
install -m644 $(shell LC_ALL=C grep '^[A-Z]' .document) doc/
ifneq ($(VERSION),)
pkggem := pkg/$(rfpackage)-$(VERSION).gem
pkgtgz := pkg/$(rfpackage)-$(VERSION).tgz
# ensures we're actually on the tagged $(VERSION), only used for release
verify:
test x"$(shell umask)" = x0022
git rev-parse --verify refs/tags/v$(VERSION)^{}
git diff-index --quiet HEAD^0
test $$(git rev-parse --verify HEAD^0) = \
$$(git rev-parse --verify refs/tags/v$(VERSION)^{})
fix-perms:
-git ls-tree -r HEAD | awk '/^100644 / {print $$NF}' | xargs chmod 644
-git ls-tree -r HEAD | awk '/^100755 / {print $$NF}' | xargs chmod 755
gem: $(pkggem)
install-gem: $(pkggem)
gem install --local $(CURDIR)/$<
$(pkggem): manifest fix-perms
gem build $(rfpackage).gemspec
mkdir -p pkg
mv $(@F) $@
$(pkgtgz): distdir = $(basename $@)
$(pkgtgz): HEAD = v$(VERSION)
$(pkgtgz): manifest fix-perms
@test -n "$(distdir)"
$(RM) -r $(distdir)
mkdir -p $(distdir)
tar cf - $$(cat .manifest) | (cd $(distdir) && tar xf -)
cd pkg && tar cf - $(basename $(@F)) | gzip -9 > $(@F)+
mv $@+ $@
package: $(pkgtgz) $(pkggem)
release:: verify package
# push gem to RubyGems.org
gem push $(pkggem)
else
gem install-gem: GIT-VERSION-FILE
$(MAKE) $@ VERSION=$(GIT_VERSION)
endif
all:: check
test_units := $(wildcard test/test_*.rb)
test: check
check: test-unit
test-unit: $(test_units)
$(test_units): build
$(RUBY) -I $(lib) $@ $(RUBY_TEST_OPTS)
# this requires GNU coreutils variants
ifneq ($(RSYNC_DEST),)
publish_doc:
-git set-file-times
$(MAKE) doc
$(MAKE) doc_gz
$(RSYNC) -av doc/ $(RSYNC_DEST)/
git ls-files | xargs touch
endif
# Create gzip variants of the same timestamp as the original so nginx
# "gzip_static on" can serve the gzipped versions directly.
doc_gz: docs = $(shell find doc -type f ! -regex '^.*\.gz$$')
doc_gz:
for i in $(docs); do \
gzip --rsyncable -9 < $$i > $$i.gz; touch -r $$i $$i.gz; done
check-warnings:
@(for i in $$(git ls-files '*.rb'| grep -v '^setup\.rb$$'); \
do $(RUBY) -d -W2 -c $$i; done) | grep -v '^Syntax OK$$' || :
ifneq ($(PLACEHOLDERS),)
$(PLACEHOLDERS):
echo olddoc_placeholder > $@
endif
.PHONY: all .FORCE-GIT-VERSION-FILE doc check test $(test_units) manifest
.PHONY: check-warnings
raindrops-0.19.0/GIT-VERSION-FILE 0000644 0000041 0000041 00000000025 13145550250 016035 0 ustar www-data www-data GIT_VERSION = 0.19.0
raindrops-0.19.0/LICENSE 0000644 0000041 0000041 00000001505 13145550250 014640 0 ustar www-data www-data raindrops is copyrighted Free Software by all contributors, see logs in
revision control for names and email addresses of all of them.
You can redistribute it and/or modify it under the terms of the GNU
Lesser General Public License (LGPL) as published by the Free Software
Foundation, version {2.1}[https://www.gnu.org/licenses/lgpl-2.1.txt] or
later. Currently version {3}[https://www.gnu.org/licenses/lgpl-3.0.txt],
is preferred (see link:COPYING).
raindrops is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the raindrops; if not, see
raindrops-0.19.0/setup.rb 0000644 0000041 0000041 00000106526 13145550250 015331 0 ustar www-data www-data # -*- encoding: binary -*-
#
# setup.rb
#
# Copyright (c) 2000-2005 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the terms of
# the GNU LGPL, Lesser General Public License version 2.1.
#
unless Enumerable.method_defined?(:map) # Ruby 1.4.6
module Enumerable
alias map collect
end
end
unless File.respond_to?(:read) # Ruby 1.6
def File.read(fname)
open(fname) {|f|
return f.read
}
end
end
unless Errno.const_defined?(:ENOTEMPTY) # Windows?
module Errno
class ENOTEMPTY
# We do not raise this exception, implementation is not needed.
end
end
end
def File.binread(fname)
open(fname, 'rb') {|f|
return f.read
}
end
# for corrupted Windows' stat(2)
def File.dir?(path)
File.directory?((path[-1,1] == '/') ? path : path + '/')
end
class ConfigTable
include Enumerable
def initialize(rbconfig)
@rbconfig = rbconfig
@items = []
@table = {}
# options
@install_prefix = nil
@config_opt = nil
@verbose = true
@no_harm = false
end
attr_accessor :install_prefix
attr_accessor :config_opt
attr_writer :verbose
def verbose?
@verbose
end
attr_writer :no_harm
def no_harm?
@no_harm
end
def [](key)
lookup(key).resolve(self)
end
def []=(key, val)
lookup(key).set val
end
def names
@items.map {|i| i.name }
end
def each(&block)
@items.each(&block)
end
def key?(name)
@table.key?(name)
end
def lookup(name)
@table[name] or setup_rb_error "no such config item: #{name}"
end
def add(item)
@items.push item
@table[item.name] = item
end
def remove(name)
item = lookup(name)
@items.delete_if {|i| i.name == name }
@table.delete_if {|name, i| i.name == name }
item
end
def load_script(path, inst = nil)
if File.file?(path)
MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
end
end
def savefile
'.config'
end
def load_savefile
begin
File.foreach(savefile()) do |line|
k, v = *line.split(/=/, 2)
self[k] = v.strip
end
rescue Errno::ENOENT
setup_rb_error $!.message + "\n#{File.basename($0)} config first"
end
end
def save
@items.each {|i| i.value }
File.open(savefile(), 'w') {|f|
@items.each do |i|
f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
end
}
end
def load_standard_entries
standard_entries(@rbconfig).each do |ent|
add ent
end
end
def standard_entries(rbconfig)
c = rbconfig
rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
major = c['MAJOR'].to_i
minor = c['MINOR'].to_i
teeny = c['TEENY'].to_i
version = "#{major}.#{minor}"
# ruby ver. >= 1.4.4?
newpath_p = ((major >= 2) or
((major == 1) and
((minor >= 5) or
((minor == 4) and (teeny >= 4)))))
if c['rubylibdir']
# V > 1.6.3
libruby = "#{c['prefix']}/lib/ruby"
librubyver = c['rubylibdir']
librubyverarch = c['archdir']
siteruby = c['sitedir']
siterubyver = c['sitelibdir']
siterubyverarch = c['sitearchdir']
elsif newpath_p
# 1.4.4 <= V <= 1.6.3
libruby = "#{c['prefix']}/lib/ruby"
librubyver = "#{c['prefix']}/lib/ruby/#{version}"
librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
siteruby = c['sitedir']
siterubyver = "$siteruby/#{version}"
siterubyverarch = "$siterubyver/#{c['arch']}"
else
# V < 1.4.4
libruby = "#{c['prefix']}/lib/ruby"
librubyver = "#{c['prefix']}/lib/ruby/#{version}"
librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
siterubyver = siteruby
siterubyverarch = "$siterubyver/#{c['arch']}"
end
parameterize = lambda {|path|
path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
}
if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
else
makeprog = 'make'
end
[
ExecItem.new('installdirs', 'std/site/home',
'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
{|val, table|
case val
when 'std'
table['rbdir'] = '$librubyver'
table['sodir'] = '$librubyverarch'
when 'site'
table['rbdir'] = '$siterubyver'
table['sodir'] = '$siterubyverarch'
when 'home'
setup_rb_error '$HOME was not set' unless ENV['HOME']
table['prefix'] = ENV['HOME']
table['rbdir'] = '$libdir/ruby'
table['sodir'] = '$libdir/ruby'
end
},
PathItem.new('prefix', 'path', c['prefix'],
'path prefix of target environment'),
PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
'the directory for commands'),
PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
'the directory for libraries'),
PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
'the directory for shared data'),
PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
'the directory for man pages'),
PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
'the directory for system configuration files'),
PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
'the directory for local state data'),
PathItem.new('libruby', 'path', libruby,
'the directory for ruby libraries'),
PathItem.new('librubyver', 'path', librubyver,
'the directory for standard ruby libraries'),
PathItem.new('librubyverarch', 'path', librubyverarch,
'the directory for standard ruby extensions'),
PathItem.new('siteruby', 'path', siteruby,
'the directory for version-independent aux ruby libraries'),
PathItem.new('siterubyver', 'path', siterubyver,
'the directory for aux ruby libraries'),
PathItem.new('siterubyverarch', 'path', siterubyverarch,
'the directory for aux ruby binaries'),
PathItem.new('rbdir', 'path', '$siterubyver',
'the directory for ruby scripts'),
PathItem.new('sodir', 'path', '$siterubyverarch',
'the directory for ruby extentions'),
PathItem.new('rubypath', 'path', rubypath,
'the path to set to #! line'),
ProgramItem.new('rubyprog', 'name', rubypath,
'the ruby program using for installation'),
ProgramItem.new('makeprog', 'name', makeprog,
'the make program to compile ruby extentions'),
SelectItem.new('shebang', 'all/ruby/never', 'ruby',
'shebang line (#!) editing mode'),
BoolItem.new('without-ext', 'yes/no', 'no',
'does not compile/install ruby extentions')
]
end
private :standard_entries
def load_multipackage_entries
multipackage_entries().each do |ent|
add ent
end
end
def multipackage_entries
[
PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
'package names that you want to install'),
PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
'package names that you do not want to install')
]
end
private :multipackage_entries
ALIASES = {
'std-ruby' => 'librubyver',
'stdruby' => 'librubyver',
'rubylibdir' => 'librubyver',
'archdir' => 'librubyverarch',
'site-ruby-common' => 'siteruby', # For backward compatibility
'site-ruby' => 'siterubyver', # For backward compatibility
'bin-dir' => 'bindir',
'bin-dir' => 'bindir',
'rb-dir' => 'rbdir',
'so-dir' => 'sodir',
'data-dir' => 'datadir',
'ruby-path' => 'rubypath',
'ruby-prog' => 'rubyprog',
'ruby' => 'rubyprog',
'make-prog' => 'makeprog',
'make' => 'makeprog'
}
def fixup
ALIASES.each do |ali, name|
@table[ali] = @table[name]
end
@items.freeze
@table.freeze
@options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
end
def parse_opt(opt)
m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
m.to_a[1,2]
end
def dllext
@rbconfig['DLEXT']
end
def value_config?(name)
lookup(name).value?
end
class Item
def initialize(name, template, default, desc)
@name = name.freeze
@template = template
@value = default
@default = default
@description = desc
end
attr_reader :name
attr_reader :description
attr_accessor :default
alias help_default default
def help_opt
"--#{@name}=#{@template}"
end
def value?
true
end
def value
@value
end
def resolve(table)
@value.gsub(%r<\$([^/]+)>) { table[$1] }
end
def set(val)
@value = check(val)
end
private
def check(val)
setup_rb_error "config: --#{name} requires argument" unless val
val
end
end
class BoolItem < Item
def config_type
'bool'
end
def help_opt
"--#{@name}"
end
private
def check(val)
return 'yes' unless val
case val
when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
when /\An(o)?\z/i, /\Af(alse)\z/i then 'no'
else
setup_rb_error "config: --#{@name} accepts only yes/no for argument"
end
end
end
class PathItem < Item
def config_type
'path'
end
private
def check(path)
setup_rb_error "config: --#{@name} requires argument" unless path
path[0,1] == '$' ? path : File.expand_path(path)
end
end
class ProgramItem < Item
def config_type
'program'
end
end
class SelectItem < Item
def initialize(name, selection, default, desc)
super
@ok = selection.split('/')
end
def config_type
'select'
end
private
def check(val)
unless @ok.include?(val.strip)
setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
end
val.strip
end
end
class ExecItem < Item
def initialize(name, selection, desc, &block)
super name, selection, nil, desc
@ok = selection.split('/')
@action = block
end
def config_type
'exec'
end
def value?
false
end
def resolve(table)
setup_rb_error "$#{name()} wrongly used as option value"
end
undef set
def evaluate(val, table)
v = val.strip.downcase
unless @ok.include?(v)
setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
end
@action.call v, table
end
end
class PackageSelectionItem < Item
def initialize(name, template, default, help_default, desc)
super name, template, default, desc
@help_default = help_default
end
attr_reader :help_default
def config_type
'package'
end
private
def check(val)
unless File.dir?("packages/#{val}")
setup_rb_error "config: no such package: #{val}"
end
val
end
end
class MetaConfigEnvironment
def initialize(config, installer)
@config = config
@installer = installer
end
def config_names
@config.names
end
def config?(name)
@config.key?(name)
end
def bool_config?(name)
@config.lookup(name).config_type == 'bool'
end
def path_config?(name)
@config.lookup(name).config_type == 'path'
end
def value_config?(name)
@config.lookup(name).config_type != 'exec'
end
def add_config(item)
@config.add item
end
def add_bool_config(name, default, desc)
@config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
end
def add_path_config(name, default, desc)
@config.add PathItem.new(name, 'path', default, desc)
end
def set_config_default(name, default)
@config.lookup(name).default = default
end
def remove_config(name)
@config.remove(name)
end
# For only multipackage
def packages
raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
@installer.packages
end
# For only multipackage
def declare_packages(list)
raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
@installer.packages = list
end
end
end # class ConfigTable
# This module requires: #verbose?, #no_harm?
module FileOperations
def mkdir_p(dirname, prefix = nil)
dirname = prefix + File.expand_path(dirname) if prefix
$stderr.puts "mkdir -p #{dirname}" if verbose?
return if no_harm?
# Does not check '/', it's too abnormal.
dirs = File.expand_path(dirname).split(%r<(?=/)>)
if /\A[a-z]:\z/i =~ dirs[0]
disk = dirs.shift
dirs[0] = disk + dirs[0]
end
dirs.each_index do |idx|
path = dirs[0..idx].join('')
Dir.mkdir path unless File.dir?(path)
end
end
def rm_f(path)
$stderr.puts "rm -f #{path}" if verbose?
return if no_harm?
force_remove_file path
end
def rm_rf(path)
$stderr.puts "rm -rf #{path}" if verbose?
return if no_harm?
remove_tree path
end
def remove_tree(path)
if File.symlink?(path)
remove_file path
elsif File.dir?(path)
remove_tree0 path
else
force_remove_file path
end
end
def remove_tree0(path)
Dir.foreach(path) do |ent|
next if ent == '.'
next if ent == '..'
entpath = "#{path}/#{ent}"
if File.symlink?(entpath)
remove_file entpath
elsif File.dir?(entpath)
remove_tree0 entpath
else
force_remove_file entpath
end
end
begin
Dir.rmdir path
rescue Errno::ENOTEMPTY
# directory may not be empty
end
end
def move_file(src, dest)
force_remove_file dest
begin
File.rename src, dest
rescue
File.open(dest, 'wb') {|f|
f.write File.binread(src)
}
File.chmod File.stat(src).mode, dest
File.unlink src
end
end
def force_remove_file(path)
begin
remove_file path
rescue
end
end
def remove_file(path)
File.chmod 0777, path
File.unlink path
end
def install(from, dest, mode, prefix = nil)
$stderr.puts "install #{from} #{dest}" if verbose?
return if no_harm?
realdest = prefix ? prefix + File.expand_path(dest) : dest
realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
str = File.binread(from)
if diff?(str, realdest)
verbose_off {
rm_f realdest if File.exist?(realdest)
}
File.open(realdest, 'wb') {|f|
f.write str
}
File.chmod mode, realdest
File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
if prefix
f.puts realdest.sub(prefix, '')
else
f.puts realdest
end
}
end
end
def diff?(new_content, path)
return true unless File.exist?(path)
new_content != File.binread(path)
end
def command(*args)
$stderr.puts args.join(' ') if verbose?
system(*args) or raise RuntimeError,
"system(#{args.map{|a| a.inspect }.join(' ')}) failed"
end
def ruby(*args)
command config('rubyprog'), *args
end
def make(task = nil)
command(*[config('makeprog'), task].compact)
end
def extdir?(dir)
File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
end
def files_of(dir)
Dir.open(dir) {|d|
return d.select {|ent| File.file?("#{dir}/#{ent}") }
}
end
DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
def directories_of(dir)
Dir.open(dir) {|d|
return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
}
end
end
# This module requires: #srcdir_root, #objdir_root, #relpath
module HookScriptAPI
def get_config(key)
@config[key]
end
alias config get_config
# obsolete: use metaconfig to change configuration
def set_config(key, val)
@config[key] = val
end
#
# srcdir/objdir (works only in the package directory)
#
def curr_srcdir
"#{srcdir_root()}/#{relpath()}"
end
def curr_objdir
"#{objdir_root()}/#{relpath()}"
end
def srcfile(path)
"#{curr_srcdir()}/#{path}"
end
def srcexist?(path)
File.exist?(srcfile(path))
end
def srcdirectory?(path)
File.dir?(srcfile(path))
end
def srcfile?(path)
File.file?(srcfile(path))
end
def srcentries(path = '.')
Dir.open("#{curr_srcdir()}/#{path}") {|d|
return d.to_a - %w(. ..)
}
end
def srcfiles(path = '.')
srcentries(path).select {|fname|
File.file?(File.join(curr_srcdir(), path, fname))
}
end
def srcdirectories(path = '.')
srcentries(path).select {|fname|
File.dir?(File.join(curr_srcdir(), path, fname))
}
end
end
class ToplevelInstaller
Version = '3.4.1'
Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
TASKS = [
[ 'all', 'do config, setup, then install' ],
[ 'config', 'saves your configurations' ],
[ 'show', 'shows current configuration' ],
[ 'setup', 'compiles ruby extentions and others' ],
[ 'install', 'installs files' ],
[ 'test', 'run all tests in test/' ],
[ 'clean', "does `make clean' for each extention" ],
[ 'distclean',"does `make distclean' for each extention" ]
]
def ToplevelInstaller.invoke
config = ConfigTable.new(load_rbconfig())
config.load_standard_entries
config.load_multipackage_entries if multipackage?
config.fixup
klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
klass.new(File.dirname($0), config).invoke
end
def ToplevelInstaller.multipackage?
File.dir?(File.dirname($0) + '/packages')
end
def ToplevelInstaller.load_rbconfig
if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
ARGV.delete(arg)
load File.expand_path(arg.split(/=/, 2)[1])
$".push 'rbconfig.rb'
else
require 'rbconfig'
end
::Config::CONFIG
end
def initialize(ardir_root, config)
@ardir = File.expand_path(ardir_root)
@config = config
# cache
@valid_task_re = nil
end
def config(key)
@config[key]
end
def inspect
"#<#{self.class} #{__id__()}>"
end
def invoke
run_metaconfigs
case task = parsearg_global()
when nil, 'all'
parsearg_config
init_installers
exec_config
exec_setup
exec_install
else
case task
when 'config', 'test'
;
when 'clean', 'distclean'
@config.load_savefile if File.exist?(@config.savefile)
else
@config.load_savefile
end
__send__ "parsearg_#{task}"
init_installers
__send__ "exec_#{task}"
end
end
def run_metaconfigs
@config.load_script "#{@ardir}/metaconfig"
end
def init_installers
@installer = Installer.new(@config, @ardir, File.expand_path('.'))
end
#
# Hook Script API bases
#
def srcdir_root
@ardir
end
def objdir_root
'.'
end
def relpath
'.'
end
#
# Option Parsing
#
def parsearg_global
while arg = ARGV.shift
case arg
when /\A\w+\z/
setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
return arg
when '-q', '--quiet'
@config.verbose = false
when '--verbose'
@config.verbose = true
when '--help'
print_usage $stdout
exit 0
when '--version'
puts "#{File.basename($0)} version #{Version}"
exit 0
when '--copyright'
puts Copyright
exit 0
else
setup_rb_error "unknown global option '#{arg}'"
end
end
nil
end
def valid_task?(t)
valid_task_re() =~ t
end
def valid_task_re
@valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
end
def parsearg_no_options
unless ARGV.empty?
task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
end
end
alias parsearg_show parsearg_no_options
alias parsearg_setup parsearg_no_options
alias parsearg_test parsearg_no_options
alias parsearg_clean parsearg_no_options
alias parsearg_distclean parsearg_no_options
def parsearg_config
evalopt = []
set = []
@config.config_opt = []
while i = ARGV.shift
if /\A--?\z/ =~ i
@config.config_opt = ARGV.dup
break
end
name, value = *@config.parse_opt(i)
if @config.value_config?(name)
@config[name] = value
else
evalopt.push [name, value]
end
set.push name
end
evalopt.each do |name, value|
@config.lookup(name).evaluate value, @config
end
# Check if configuration is valid
set.each do |n|
@config[n] if @config.value_config?(n)
end
end
def parsearg_install
@config.no_harm = false
@config.install_prefix = ''
while a = ARGV.shift
case a
when '--no-harm'
@config.no_harm = true
when /\A--prefix=/
path = a.split(/=/, 2)[1]
path = File.expand_path(path) unless path[0,1] == '/'
@config.install_prefix = path
else
setup_rb_error "install: unknown option #{a}"
end
end
end
def print_usage(out)
out.puts 'Typical Installation Procedure:'
out.puts " $ ruby #{File.basename $0} config"
out.puts " $ ruby #{File.basename $0} setup"
out.puts " # ruby #{File.basename $0} install (may require root privilege)"
out.puts
out.puts 'Detailed Usage:'
out.puts " ruby #{File.basename $0} "
out.puts " ruby #{File.basename $0} [] []"
fmt = " %-24s %s\n"
out.puts
out.puts 'Global options:'
out.printf fmt, '-q,--quiet', 'suppress message outputs'
out.printf fmt, ' --verbose', 'output messages verbosely'
out.printf fmt, ' --help', 'print this message'
out.printf fmt, ' --version', 'print version and quit'
out.printf fmt, ' --copyright', 'print copyright and quit'
out.puts
out.puts 'Tasks:'
TASKS.each do |name, desc|
out.printf fmt, name, desc
end
fmt = " %-24s %s [%s]\n"
out.puts
out.puts 'Options for CONFIG or ALL:'
@config.each do |item|
out.printf fmt, item.help_opt, item.description, item.help_default
end
out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
out.puts
out.puts 'Options for INSTALL:'
out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
out.printf fmt, '--prefix=path', 'install path prefix', ''
out.puts
end
#
# Task Handlers
#
def exec_config
@installer.exec_config
@config.save # must be final
end
def exec_setup
@installer.exec_setup
end
def exec_install
@installer.exec_install
end
def exec_test
@installer.exec_test
end
def exec_show
@config.each do |i|
printf "%-20s %s\n", i.name, i.value if i.value?
end
end
def exec_clean
@installer.exec_clean
end
def exec_distclean
@installer.exec_distclean
end
end # class ToplevelInstaller
class ToplevelInstallerMulti < ToplevelInstaller
include FileOperations
def initialize(ardir_root, config)
super
@packages = directories_of("#{@ardir}/packages")
raise 'no package exists' if @packages.empty?
@root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
end
def run_metaconfigs
@config.load_script "#{@ardir}/metaconfig", self
@packages.each do |name|
@config.load_script "#{@ardir}/packages/#{name}/metaconfig"
end
end
attr_reader :packages
def packages=(list)
raise 'package list is empty' if list.empty?
list.each do |name|
raise "directory packages/#{name} does not exist"\
unless File.dir?("#{@ardir}/packages/#{name}")
end
@packages = list
end
def init_installers
@installers = {}
@packages.each do |pack|
@installers[pack] = Installer.new(@config,
"#{@ardir}/packages/#{pack}",
"packages/#{pack}")
end
with = extract_selection(config('with'))
without = extract_selection(config('without'))
@selected = @installers.keys.select {|name|
(with.empty? or with.include?(name)) \
and not without.include?(name)
}
end
def extract_selection(list)
a = list.split(/,/)
a.each do |name|
setup_rb_error "no such package: #{name}" unless @installers.key?(name)
end
a
end
def print_usage(f)
super
f.puts 'Inluded packages:'
f.puts ' ' + @packages.sort.join(' ')
f.puts
end
#
# Task Handlers
#
def exec_config
run_hook 'pre-config'
each_selected_installers {|inst| inst.exec_config }
run_hook 'post-config'
@config.save # must be final
end
def exec_setup
run_hook 'pre-setup'
each_selected_installers {|inst| inst.exec_setup }
run_hook 'post-setup'
end
def exec_install
run_hook 'pre-install'
each_selected_installers {|inst| inst.exec_install }
run_hook 'post-install'
end
def exec_test
run_hook 'pre-test'
each_selected_installers {|inst| inst.exec_test }
run_hook 'post-test'
end
def exec_clean
rm_f @config.savefile
run_hook 'pre-clean'
each_selected_installers {|inst| inst.exec_clean }
run_hook 'post-clean'
end
def exec_distclean
rm_f @config.savefile
run_hook 'pre-distclean'
each_selected_installers {|inst| inst.exec_distclean }
run_hook 'post-distclean'
end
#
# lib
#
def each_selected_installers
Dir.mkdir 'packages' unless File.dir?('packages')
@selected.each do |pack|
$stderr.puts "Processing the package `#{pack}' ..." if verbose?
Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
Dir.chdir "packages/#{pack}"
yield @installers[pack]
Dir.chdir '../..'
end
end
def run_hook(id)
@root_installer.run_hook id
end
# module FileOperations requires this
def verbose?
@config.verbose?
end
# module FileOperations requires this
def no_harm?
@config.no_harm?
end
end # class ToplevelInstallerMulti
class Installer
FILETYPES = %w( bin lib ext data conf man )
include FileOperations
include HookScriptAPI
def initialize(config, srcroot, objroot)
@config = config
@srcdir = File.expand_path(srcroot)
@objdir = File.expand_path(objroot)
@currdir = '.'
end
def inspect
"#<#{self.class} #{File.basename(@srcdir)}>"
end
def noop(rel)
end
#
# Hook Script API base methods
#
def srcdir_root
@srcdir
end
def objdir_root
@objdir
end
def relpath
@currdir
end
#
# Config Access
#
# module FileOperations requires this
def verbose?
@config.verbose?
end
# module FileOperations requires this
def no_harm?
@config.no_harm?
end
def verbose_off
begin
save, @config.verbose = @config.verbose?, false
yield
ensure
@config.verbose = save
end
end
#
# TASK config
#
def exec_config
exec_task_traverse 'config'
end
alias config_dir_bin noop
alias config_dir_lib noop
def config_dir_ext(rel)
extconf if extdir?(curr_srcdir())
end
alias config_dir_data noop
alias config_dir_conf noop
alias config_dir_man noop
def extconf
ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
end
#
# TASK setup
#
def exec_setup
exec_task_traverse 'setup'
end
def setup_dir_bin(rel)
files_of(curr_srcdir()).each do |fname|
update_shebang_line "#{curr_srcdir()}/#{fname}"
end
end
alias setup_dir_lib noop
def setup_dir_ext(rel)
make if extdir?(curr_srcdir())
end
alias setup_dir_data noop
alias setup_dir_conf noop
alias setup_dir_man noop
def update_shebang_line(path)
return if no_harm?
return if config('shebang') == 'never'
old = Shebang.load(path)
if old
$stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1
new = new_shebang(old)
return if new.to_s == old.to_s
else
return unless config('shebang') == 'all'
new = Shebang.new(config('rubypath'))
end
$stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
open_atomic_writer(path) {|output|
File.open(path, 'rb') {|f|
f.gets if old # discard
output.puts new.to_s
output.print f.read
}
}
end
def new_shebang(old)
if /\Aruby/ =~ File.basename(old.cmd)
Shebang.new(config('rubypath'), old.args)
elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
Shebang.new(config('rubypath'), old.args[1..-1])
else
return old unless config('shebang') == 'all'
Shebang.new(config('rubypath'))
end
end
def open_atomic_writer(path, &block)
tmpfile = File.basename(path) + '.tmp'
begin
File.open(tmpfile, 'wb', &block)
File.rename tmpfile, File.basename(path)
ensure
File.unlink tmpfile if File.exist?(tmpfile)
end
end
class Shebang
def Shebang.load(path)
line = nil
File.open(path) {|f|
line = f.gets
}
return nil unless /\A#!/ =~ line
parse(line)
end
def Shebang.parse(line)
cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
new(cmd, args)
end
def initialize(cmd, args = [])
@cmd = cmd
@args = args
end
attr_reader :cmd
attr_reader :args
def to_s
"#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
end
end
#
# TASK install
#
def exec_install
rm_f 'InstalledFiles'
exec_task_traverse 'install'
end
def install_dir_bin(rel)
install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
end
def install_dir_lib(rel)
install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
end
def install_dir_ext(rel)
return unless extdir?(curr_srcdir())
install_files rubyextentions('.'),
"#{config('sodir')}/#{File.dirname(rel)}",
0555
end
def install_dir_data(rel)
install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
end
def install_dir_conf(rel)
# FIXME: should not remove current config files
# (rename previous file to .old/.org)
install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
end
def install_dir_man(rel)
install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
end
def install_files(list, dest, mode)
mkdir_p dest, @config.install_prefix
list.each do |fname|
install fname, dest, mode, @config.install_prefix
end
end
def libfiles
glob_reject(%w(*.y *.output), targetfiles())
end
def rubyextentions(dir)
ents = glob_select("*.#{@config.dllext}", targetfiles())
if ents.empty?
setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
end
ents
end
def targetfiles
mapdir(existfiles() - hookfiles())
end
def mapdir(ents)
ents.map {|ent|
if File.exist?(ent)
then ent # objdir
else "#{curr_srcdir()}/#{ent}" # srcdir
end
}
end
# picked up many entries from cvs-1.11.1/src/ignore.c
JUNK_FILES = %w(
core RCSLOG tags TAGS .make.state
.nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
*~ *.old *.bak *.BAK *.orig *.rej _$* *$
*.org *.in .*
)
def existfiles
glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
end
def hookfiles
%w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
%w( config setup install clean ).map {|t| sprintf(fmt, t) }
}.flatten
end
def glob_select(pat, ents)
re = globs2re([pat])
ents.select {|ent| re =~ ent }
end
def glob_reject(pats, ents)
re = globs2re(pats)
ents.reject {|ent| re =~ ent }
end
GLOB2REGEX = {
'.' => '\.',
'$' => '\$',
'#' => '\#',
'*' => '.*'
}
def globs2re(pats)
/\A(?:#{
pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
})\z/
end
#
# TASK test
#
TESTDIR = 'test'
def exec_test
unless File.directory?('test')
$stderr.puts 'no test in this package' if verbose?
return
end
$stderr.puts 'Running tests...' if verbose?
begin
require 'test/unit'
rescue LoadError
setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.'
end
runner = Test::Unit::AutoRunner.new(true)
runner.to_run << TESTDIR
runner.run
end
#
# TASK clean
#
def exec_clean
exec_task_traverse 'clean'
rm_f @config.savefile
rm_f 'InstalledFiles'
end
alias clean_dir_bin noop
alias clean_dir_lib noop
alias clean_dir_data noop
alias clean_dir_conf noop
alias clean_dir_man noop
def clean_dir_ext(rel)
return unless extdir?(curr_srcdir())
make 'clean' if File.file?('Makefile')
end
#
# TASK distclean
#
def exec_distclean
exec_task_traverse 'distclean'
rm_f @config.savefile
rm_f 'InstalledFiles'
end
alias distclean_dir_bin noop
alias distclean_dir_lib noop
def distclean_dir_ext(rel)
return unless extdir?(curr_srcdir())
make 'distclean' if File.file?('Makefile')
end
alias distclean_dir_data noop
alias distclean_dir_conf noop
alias distclean_dir_man noop
#
# Traversing
#
def exec_task_traverse(task)
run_hook "pre-#{task}"
FILETYPES.each do |type|
if type == 'ext' and config('without-ext') == 'yes'
$stderr.puts 'skipping ext/* by user option' if verbose?
next
end
traverse task, type, "#{task}_dir_#{type}"
end
run_hook "post-#{task}"
end
def traverse(task, rel, mid)
dive_into(rel) {
run_hook "pre-#{task}"
__send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
directories_of(curr_srcdir()).each do |d|
traverse task, "#{rel}/#{d}", mid
end
run_hook "post-#{task}"
}
end
def dive_into(rel)
return unless File.dir?("#{@srcdir}/#{rel}")
dir = File.basename(rel)
Dir.mkdir dir unless File.dir?(dir)
prevdir = Dir.pwd
Dir.chdir dir
$stderr.puts '---> ' + rel if verbose?
@currdir = rel
yield
Dir.chdir prevdir
$stderr.puts '<--- ' + rel if verbose?
@currdir = File.dirname(rel)
end
def run_hook(id)
path = [ "#{curr_srcdir()}/#{id}",
"#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
return unless path
begin
instance_eval File.read(path), path, 1
rescue
raise if $DEBUG
setup_rb_error "hook #{path} failed:\n" + $!.message
end
end
end # class Installer
class SetupError < StandardError; end
def setup_rb_error(msg)
raise SetupError, msg
end
if $0 == __FILE__
begin
ToplevelInstaller.invoke
rescue SetupError
raise if $DEBUG
$stderr.puts $!.message
$stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
exit 1
end
end
raindrops-0.19.0/TODO 0000644 0000041 0000041 00000000231 13145550250 014316 0 ustar www-data www-data * fix IPv6 inet_diag reporting for Raindrops::Middleware
* pure Ruby version for non-forking servers (patches welcome!)
* unix_diag and udp_diag support
raindrops-0.19.0/ext/ 0000755 0000041 0000041 00000000000 13145550250 014432 5 ustar www-data www-data raindrops-0.19.0/ext/raindrops/ 0000755 0000041 0000041 00000000000 13145550250 016433 5 ustar www-data www-data raindrops-0.19.0/ext/raindrops/tcp_info.c 0000644 0000041 0000041 00000015356 13145550250 020412 0 ustar www-data www-data #include
#include
#include
#if defined(HAVE_LINUX_TCP_H)
# include
#else
# if defined(HAVE_NETINET_TCP_H)
# include
# endif
# if defined(HAVE_NETINET_TCP_FSM_H)
# include
# endif
#endif
#ifdef HAVE_TYPE_STRUCT_TCP_INFO
#include "my_fileno.h"
CFUNC_tcp_info_tcpi_state
CFUNC_tcp_info_tcpi_ca_state
CFUNC_tcp_info_tcpi_retransmits
CFUNC_tcp_info_tcpi_probes
CFUNC_tcp_info_tcpi_backoff
CFUNC_tcp_info_tcpi_options
CFUNC_tcp_info_tcpi_snd_wscale
CFUNC_tcp_info_tcpi_rcv_wscale
CFUNC_tcp_info_tcpi_rto
CFUNC_tcp_info_tcpi_ato
CFUNC_tcp_info_tcpi_snd_mss
CFUNC_tcp_info_tcpi_rcv_mss
CFUNC_tcp_info_tcpi_unacked
CFUNC_tcp_info_tcpi_sacked
CFUNC_tcp_info_tcpi_lost
CFUNC_tcp_info_tcpi_retrans
CFUNC_tcp_info_tcpi_fackets
CFUNC_tcp_info_tcpi_last_data_sent
CFUNC_tcp_info_tcpi_last_ack_sent
CFUNC_tcp_info_tcpi_last_data_recv
CFUNC_tcp_info_tcpi_last_ack_recv
CFUNC_tcp_info_tcpi_pmtu
CFUNC_tcp_info_tcpi_rcv_ssthresh
CFUNC_tcp_info_tcpi_rtt
CFUNC_tcp_info_tcpi_rttvar
CFUNC_tcp_info_tcpi_snd_ssthresh
CFUNC_tcp_info_tcpi_snd_cwnd
CFUNC_tcp_info_tcpi_advmss
CFUNC_tcp_info_tcpi_reordering
CFUNC_tcp_info_tcpi_rcv_rtt
CFUNC_tcp_info_tcpi_rcv_space
CFUNC_tcp_info_tcpi_total_retrans
static size_t tcpi_memsize(const void *ptr)
{
return sizeof(struct tcp_info);
}
static const rb_data_type_t tcpi_type = {
"tcp_info",
{ NULL, RUBY_TYPED_DEFAULT_FREE, tcpi_memsize, /* reserved */ },
/* parent, data, [ flags ] */
};
static VALUE alloc(VALUE klass)
{
struct tcp_info *info;
return TypedData_Make_Struct(klass, struct tcp_info, &tcpi_type, info);
}
/*
* call-seq:
*
* Raindrops::TCP_Info.new(tcp_socket) -> TCP_Info object
*
* Reads a TCP_Info object from any given +tcp_socket+. See the tcp(7)
* manpage and /usr/include/linux/tcp.h for more details.
*/
static VALUE init(VALUE self, VALUE io)
{
int fd = my_fileno(io);
struct tcp_info *info = DATA_PTR(self);
socklen_t len = (socklen_t)sizeof(struct tcp_info);
int rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, info, &len);
if (rc != 0)
rb_sys_fail("getsockopt");
return self;
}
void Init_raindrops_tcp_info(void)
{
VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject);
VALUE cTCP_Info;
/*
* Document-class: Raindrops::TCP_Info
*
* This is used to wrap "struct tcp_info" as described in tcp(7)
* and /usr/include/linux/tcp.h. The following readers methods
* are defined corresponding to the "tcpi_" fields in the
* tcp_info struct.
*
* As of raindrops 0.18.0+, this is supported on FreeBSD and OpenBSD
* systems as well as Linux, although not all fields exist or
* match the documentation, below.
*
* In particular, the +last_data_recv+ field is useful for measuring
* the amount of time a client spent in the listen queue before
* +accept()+, but only if +TCP_DEFER_ACCEPT+ is used with the
* listen socket (it is on by default in Unicorn).
*
* - state
* - ca_state
* - retransmits
* - probes
* - backoff
* - options
* - snd_wscale
* - rcv_wscale
* - rto
* - ato
* - snd_mss
* - rcv_mss
* - unacked
* - sacked
* - lost
* - retrans
* - fackets
* - last_data_sent
* - last_ack_sent
* - last_data_recv
* - last_ack_recv
* - pmtu
* - rcv_ssthresh
* - rtt
* - rttvar
* - snd_ssthresh
* - snd_cwnd
* - advmss
* - reordering
* - rcv_rtt
* - rcv_space
* - total_retrans
*
* https://kernel.org/doc/man-pages/online/pages/man7/tcp.7.html
*/
cTCP_Info = rb_define_class_under(cRaindrops, "TCP_Info", rb_cObject);
rb_define_alloc_func(cTCP_Info, alloc);
rb_define_private_method(cTCP_Info, "initialize", init, 1);
/*
* Document-method: Raindrops::TCP_Info#get!
*
* call-seq:
*
* info = Raindrops::TCP_Info.new(tcp_socket)
* info.get!(tcp_socket)
*
* Update an existing TCP_Info objects with the latest stats
* from the given socket. This even allows sharing TCP_Info
* objects between different sockets to avoid garbage.
*/
rb_define_method(cTCP_Info, "get!", init, 1);
DEFINE_METHOD_tcp_info_tcpi_state;
DEFINE_METHOD_tcp_info_tcpi_ca_state;
DEFINE_METHOD_tcp_info_tcpi_retransmits;
DEFINE_METHOD_tcp_info_tcpi_probes;
DEFINE_METHOD_tcp_info_tcpi_backoff;
DEFINE_METHOD_tcp_info_tcpi_options;
DEFINE_METHOD_tcp_info_tcpi_snd_wscale;
DEFINE_METHOD_tcp_info_tcpi_rcv_wscale;
DEFINE_METHOD_tcp_info_tcpi_rto;
DEFINE_METHOD_tcp_info_tcpi_ato;
DEFINE_METHOD_tcp_info_tcpi_snd_mss;
DEFINE_METHOD_tcp_info_tcpi_rcv_mss;
DEFINE_METHOD_tcp_info_tcpi_unacked;
DEFINE_METHOD_tcp_info_tcpi_sacked;
DEFINE_METHOD_tcp_info_tcpi_lost;
DEFINE_METHOD_tcp_info_tcpi_retrans;
DEFINE_METHOD_tcp_info_tcpi_fackets;
DEFINE_METHOD_tcp_info_tcpi_last_data_sent;
DEFINE_METHOD_tcp_info_tcpi_last_ack_sent;
DEFINE_METHOD_tcp_info_tcpi_last_data_recv;
DEFINE_METHOD_tcp_info_tcpi_last_ack_recv;
DEFINE_METHOD_tcp_info_tcpi_pmtu;
DEFINE_METHOD_tcp_info_tcpi_rcv_ssthresh;
DEFINE_METHOD_tcp_info_tcpi_rtt;
DEFINE_METHOD_tcp_info_tcpi_rttvar;
DEFINE_METHOD_tcp_info_tcpi_snd_ssthresh;
DEFINE_METHOD_tcp_info_tcpi_snd_cwnd;
DEFINE_METHOD_tcp_info_tcpi_advmss;
DEFINE_METHOD_tcp_info_tcpi_reordering;
DEFINE_METHOD_tcp_info_tcpi_rcv_rtt;
DEFINE_METHOD_tcp_info_tcpi_rcv_space;
DEFINE_METHOD_tcp_info_tcpi_total_retrans;
#ifdef RAINDROPS_TCP_STATES_ALL_KNOWN
/*
* Document-const: Raindrops::TCP
*
* This is a frozen hash storing the numeric values
* maps platform-independent symbol keys to
* platform-dependent numeric values. These states
* are all valid values for the Raindrops::TCP_Info#state field.
*
* The platform-independent names of the keys in this hash are:
*
* - :ESTABLISHED
* - :SYN_SENT
* - :SYN_RECV
* - :FIN_WAIT1
* - :FIN_WAIT2
* - :TIME_WAIT
* - :CLOSE
* - :CLOSE_WAIT
* - :LAST_ACK
* - :LISTEN
* - :CLOSING
*
* This is only supported on platforms where TCP_Info is supported,
* currently FreeBSD, OpenBSD, and Linux-based systems.
*/
{
#define TCPSET(n,v) rb_hash_aset(tcp, ID2SYM(rb_intern(#n)), INT2NUM(v))
VALUE tcp = rb_hash_new();
TCPSET(ESTABLISHED, RAINDROPS_TCP_ESTABLISHED);
TCPSET(SYN_SENT, RAINDROPS_TCP_SYN_SENT);
TCPSET(SYN_RECV, RAINDROPS_TCP_SYN_RECV);
TCPSET(FIN_WAIT1, RAINDROPS_TCP_FIN_WAIT1);
TCPSET(FIN_WAIT2, RAINDROPS_TCP_FIN_WAIT2);
TCPSET(TIME_WAIT, RAINDROPS_TCP_TIME_WAIT);
TCPSET(CLOSE, RAINDROPS_TCP_CLOSE);
TCPSET(CLOSE_WAIT, RAINDROPS_TCP_CLOSE_WAIT);
TCPSET(LAST_ACK, RAINDROPS_TCP_LAST_ACK);
TCPSET(LISTEN, RAINDROPS_TCP_LISTEN);
TCPSET(CLOSING, RAINDROPS_TCP_CLOSING);
#undef TCPSET
OBJ_FREEZE(tcp);
rb_define_const(cRaindrops, "TCP", tcp);
}
#endif
}
#endif /* HAVE_STRUCT_TCP_INFO */
raindrops-0.19.0/ext/raindrops/my_fileno.h 0000644 0000041 0000041 00000001263 13145550250 020567 0 ustar www-data www-data #include
#ifdef HAVE_RUBY_IO_H
# include
#else
# include
# include
#endif
#if ! HAVE_RB_IO_T
# define rb_io_t OpenFile
#endif
#ifdef GetReadFile
# define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr)))
#else
# if !HAVE_RB_IO_T || (RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 8)
# define FPTR_TO_FD(fptr) fileno(fptr->f)
# else
# define FPTR_TO_FD(fptr) fptr->fd
# endif
#endif
static int my_fileno(VALUE io)
{
rb_io_t *fptr;
int fd;
if (TYPE(io) != T_FILE)
io = rb_convert_type(io, T_FILE, "IO", "to_io");
GetOpenFile(io, fptr);
fd = FPTR_TO_FD(fptr);
if (fd < 0)
rb_raise(rb_eIOError, "closed stream");
return fd;
}
raindrops-0.19.0/ext/raindrops/raindrops_atomic.h 0000644 0000041 0000041 00000001142 13145550250 022137 0 ustar www-data www-data /*
* use wrappers around libatomic-ops for folks that don't have GCC
* or a new enough version of GCC
*/
#ifndef HAVE_GCC_ATOMIC_BUILTINS
#include
static inline unsigned long
__sync_add_and_fetch(unsigned long *dst, unsigned long incr)
{
AO_t tmp = AO_fetch_and_add((AO_t *)dst, (AO_t)incr);
return (unsigned long)tmp + incr;
}
static inline unsigned long
__sync_sub_and_fetch(unsigned long *dst, unsigned long incr)
{
AO_t tmp = AO_fetch_and_add((AO_t *)dst, (AO_t)(-(long)incr));
return (unsigned long)tmp - incr;
}
#endif /* HAVE_GCC_ATOMIC_BUILTINS */
raindrops-0.19.0/ext/raindrops/extconf.rb 0000644 0000041 0000041 00000010411 13145550250 020423 0 ustar www-data www-data require 'mkmf'
require 'shellwords'
dir_config('atomic_ops')
have_func('mmap', 'sys/mman.h') or abort 'mmap() not found'
have_func('munmap', 'sys/mman.h') or abort 'munmap() not found'
$CPPFLAGS += " -D_GNU_SOURCE "
have_func('mremap', 'sys/mman.h')
headers = %w(sys/types.h netdb.h string.h sys/socket.h netinet/in.h)
if have_header('linux/tcp.h')
headers << 'linux/tcp.h'
else
%w(netinet/tcp.h netinet/tcp_fsm.h).each { |h|
have_header(h, headers) and headers << h
}
end
$CPPFLAGS += " -D_BSD_SOURCE "
if have_type("struct tcp_info", headers)
%w(
tcpi_state
tcpi_ca_state
tcpi_retransmits
tcpi_probes
tcpi_backoff
tcpi_options
tcpi_snd_wscale
tcpi_rcv_wscale
tcpi_rto
tcpi_ato
tcpi_snd_mss
tcpi_rcv_mss
tcpi_unacked
tcpi_sacked
tcpi_lost
tcpi_retrans
tcpi_fackets
tcpi_last_data_sent
tcpi_last_ack_sent
tcpi_last_data_recv
tcpi_last_ack_recv
tcpi_pmtu
tcpi_rcv_ssthresh
tcpi_rtt
tcpi_rttvar
tcpi_snd_ssthresh
tcpi_snd_cwnd
tcpi_advmss
tcpi_reordering
tcpi_rcv_rtt
tcpi_rcv_space
tcpi_total_retrans
tcpi_snd_wnd
tcpi_snd_bwnd
tcpi_snd_nxt
tcpi_rcv_nxt
tcpi_toe_tid
tcpi_snd_rexmitpack
tcpi_rcv_ooopack
tcpi_snd_zerowin
).each do |field|
cfunc = "tcp_info_#{field}"
if have_struct_member('struct tcp_info', field, headers)
func_body = <#{field});
}
EOF
func_body.delete!("\n")
$defs << "-DCFUNC_#{cfunc}=#{Shellwords.shellescape(func_body)}"
else
func_body = "static inline void #{cfunc}(void) {}"
$defs << "-DCFUNC_#{cfunc}=#{Shellwords.shellescape(func_body)}"
cfunc = 'rb_f_notimplement'.freeze
end
rbmethod = %Q("#{field.sub(/\Atcpi_/, ''.freeze)}")
$defs << "-DDEFINE_METHOD_tcp_info_#{field}=" \
"#{Shellwords.shellescape(
%Q[rb_define_method(cTCP_Info,#{rbmethod},#{cfunc},0)])}"
end
tcp_state_map = {
ESTABLISHED: %w(TCP_ESTABLISHED TCPS_ESTABLISHED),
SYN_SENT: %w(TCP_SYN_SENT TCPS_SYN_SENT),
SYN_RECV: %w(TCP_SYN_RECV TCPS_SYN_RECEIVED),
FIN_WAIT1: %w(TCP_FIN_WAIT1 TCPS_FIN_WAIT_1),
FIN_WAIT2: %w(TCP_FIN_WAIT2 TCPS_FIN_WAIT_2),
TIME_WAIT: %w(TCP_TIME_WAIT TCPS_TIME_WAIT),
CLOSE: %w(TCP_CLOSE TCPS_CLOSED),
CLOSE_WAIT: %w(TCP_CLOSE_WAIT TCPS_CLOSE_WAIT),
LAST_ACK: %w(TCP_LAST_ACK TCPS_LAST_ACK),
LISTEN: %w(TCP_LISTEN TCPS_LISTEN),
CLOSING: %w(TCP_CLOSING TCPS_CLOSING),
}
nstate = 0
tcp_state_map.each do |state, try|
try.each do |os_name|
have_const(os_name, headers) or next
tcp_state_map[state] = os_name
nstate += 1
end
end
if nstate == tcp_state_map.size
$defs << '-DRAINDROPS_TCP_STATES_ALL_KNOWN=1'
tcp_state_map.each do |state, name|
$defs << "-DRAINDROPS_TCP_#{state}=#{name}"
end
end
end
have_func("getpagesize", "unistd.h")
have_func('rb_thread_call_without_gvl')
have_func('rb_thread_blocking_region')
have_func('rb_thread_io_blocking_region')
checking_for "GCC 4+ atomic builtins" do
# we test CMPXCHG anyways even though we don't need it to filter out
# ancient i386-only targets without CMPXCHG
src = <