= 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.2/lib/raindrops/watcher.rb 0000644 0000041 0000041 00000031050 14054564151 020371 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://yhbt.net/raindrops-demo/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://yhbt.net/raindrops-demo/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://yhbt.net/raindrops-demo/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://yhbt.net/raindrops-demo/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://yhbt.net/raindrops-demo/
# The Raindrops::Middleware demo is also accessible at
# https://yhbt.net/raindrops-demo/_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://yhbt.net/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.2/lib/raindrops/last_data_recv.rb 0000644 0000041 0000041 00000006075 14054564151 021720 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.2/lib/raindrops/aggregate.rb 0000644 0000041 0000041 00000000457 14054564151 020671 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.2/lib/raindrops/middleware/ 0000755 0000041 0000041 00000000000 14054564151 020525 5 ustar www-data www-data raindrops-0.19.2/lib/raindrops/middleware/proxy.rb 0000644 0000041 0000041 00000002165 14054564151 022237 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.2/lib/raindrops/middleware.rb 0000644 0000041 0000041 00000011447 14054564151 021061 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://yhbt.net/raindrops-demo/_raindrops
#
# Also check out the Watcher demo at https://yhbt.net/raindrops-demo/
#
# 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.2/lib/raindrops/aggregate/ 0000755 0000041 0000041 00000000000 14054564151 020336 5 ustar www-data www-data raindrops-0.19.2/lib/raindrops/aggregate/pmq.rb 0000644 0000041 0000041 00000015547 14054564151 021474 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.2/lib/raindrops/aggregate/last_data_recv.rb 0000644 0000041 0000041 00000004166 14054564151 023645 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.2/pkg.mk 0000644 0000041 0000041 00000010021 14054564151 014745 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 dark216
$(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)/ \
--exclude index.html* --exclude created.rid*
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.2/NEWS 0000644 0000041 0000041 00000032436 14054564151 014350 0 ustar www-data www-data === raindrops 0.19.2 / 2021-05-25 23:13 UTC
This release fixes compatibility with GC.compact on Ruby 3.x
when using ListenStats on Linux. The listener stats
functionality is rarely used and does not affect most users
who just have raindrops installed for shared atomic counters.
=== raindrops 0.19.1 / 2020-01-08 09:31 UTC
This release fixes some warnings on newer Rubies.
We're no longer on bogomips.org since it's due
for expiry and I can't pay extortionists for a .org, so
s/bogomips.org/yhbt.net/ for now, and be prepared to move again
when extortionists move onto extorting the .net TLD.
doc: switch homepage to dark216
ext/raindrops/extconf: fix cflags reset on ancient CC
fixes for newer rubies
replace bogomips.org with yhbt.net
=== raindrops 0.19.0 - Rack 2.x middleware compatibility / 2017-08-09 23:52 UTC
This release fixes Rack 2.x compatibility for the few users of
Raindrops::Middleware
.
Thanks to Dmytro Shteflyuk for this release.
No need to upgrade unless you use Raindrops::Middleware with
Rack 2.x.
There's also a few minor, inconsequential cleanups.
Dmytro Shteflyuk (1):
Properly override respond_to? in Raindrops::Middleware::Proxy
Eric Wong (2):
Ruby thread compatibility updates
tcp_info: remove unnecessary extconf.h include
=== raindrops 0.18.0 / 2017-03-23 02:44 UTC
The most notable feature of this release is the addition of
FreeBSD and OpenBSD TCP_INFO support. This includes the
Raindrops::TCP for portably mapping TCP state names to
platform-dependent numeric values:
https://bogomips.org/raindrops/Raindrops.html#TCP
Thanks to Jeremy Evans and Simon Eskildsen on the
unicorn-public@bogomips.org mailing list for inspiring
these changes to raindrops.
There's also a few internal cleanups, and documentation
improvements, including some fixes to the largely-forgotten
Raindrops::Aggreage::PMQ class:
https://bogomips.org/raindrops/Raindrops/Aggregate/PMQ.html
20 changes since 0.17.0:
test_inet_diag_socket: fix Fixnum deprecation warning
TODO: add item for IPv6 breakage
ext: fix documentation for C ext-defined classes
TCP_Info: custom documentation for #get!
TypedData C-API conversion
test_watcher: disable test correctly when aggregate is missing
tcp_info: support this struct under FreeBSD
define Raindrops::TCP hash for TCP states
linux_inet_diag: reduce stack usage and simplify
avoid reading errno repeatedly
aggregate/pmq: avoid false sharing of lock buffers
aggregate/pmq: remove io-extra requirement
aggregate/pmq: avoid File#stat allocation
Merge remote-tracking branch 'origin/freebsd'
Merge remote-tracking branch 'origin/aggregate-pmq'
doc: remove private email support address
doc: update location of TCP_INFO-related stuff
build: avoid olddoc for building the RubyGem
doc: document Raindrops::TCP hash
aggregate/pmq: update version numbers for Ruby and Linux
=== raindrops 0.17.0 - rack 2.x updates / 2016-07-31 15:19 UTC
This release features minor updates to support rack 2.x
while maintaining support for rack 1.2 and later.
As a result, Ruby 1.8.6 compatibility is gone, but
Ruby 1.8.7 probably still works, for now.
There's also a minor warning fix, doc updates, and
the homepage now supports HTTPS (HTTP remains supported)
5 changes since raindrops 0.16.0:
drop Rack::Utils.bytesize dependency
gemspec: bump Rack dependency
linux_inet_diag: GCC attribute format check
use HTTPS and move homepage to https://bogomips.org/raindrops/
examples: add yahns config, zbatery is abandoned
=== raindrops 0.16.0 - minor fixes and workarounds / 2016-02-29 12:36 UTC
There's mainly a fix/workaround for Ruby 2.3 now returning
locale-aware strings for File.readlink and our test suite
using strange paths allowed by *nix.
https://bugs.ruby-lang.org/issues/12034
tcp_listener_stats won't return "true" object placeholders
if stats are configured for a non-existent listener.
There are also minor optimizations for Ruby 2.2+ (at the expense
of 2.1 and earlier).
And the usual round of minor tweaks and doc updates.
10 changes since v0.15.0:
gemspec: avoid circular dependency on unicorn
remove optimizations which made sense for older rubies
linux: workaround Ruby 2.3 change
linux: remove Pathname stdlib dependency
add .gitattributes for Ruby method detection
middleware: minor bytecode size reduction
doc: update URLs and references
README: remove indentation from URLs in RDoc
linux: tcp_listener_stats drops "true" placeholders
build: use '--local' domain for dev gem install
=== raindrops 0.15.0 - non-glibc compat fix on Linux / 2015-07-22 00:30 UTC
Thanks to Doug Forster for sending us the report.
No other fixes
* check for the existence of linux/tcp.h
=== raindrops 0.14.0 - misc doc updates / 2015-06-25 21:50 UTC
Eric Wong (7):
linux_inet_diag: annotate memory freeing on diag errors
README: trim intro and update license
modernize packaging and documentation
move mailing list to raindrops-public@bogomips.org
linux_inet_diag: clarify *fprintf usage without GVL
TODO: add item for unix_diag and udp_diag
linux_inet_diag: fix Wshorten-64-to-32 warnings
Hleb Valoshka (1):
Add setup and teardown for ipv6 tests
=== raindrops 0.13.0 several minor fixes and improvements / 2014-02-18 20:59 UTC
Most notably, this release is necessary for Ruby 2.2 (dev).
Thanks to Koichi Sasada for the bug report!
Eric Wong (5):
Rakefile: remove raa_update task
last_data_recv: do not assume Unicorn includes all constants
raindrops.gemspec: add wrongdoc dev dependency
linux_inet_diag: fix Ruby 2.2 (dev) build
license: use LGPLv2.1 or later (was LGPL (2.1|3.0)-only)
Hleb Valoshka (1):
Remove Scope IDs from IPv6 addresses.
=== raindrops 0.12.0 - compatibility fixes / 2013-09-02 10:33 UTC
This release fixes builds on systems where compilers target i386
(and not later x86 systems). There are also minor improvements for
Ruby 2.1.0dev and Rubinius.
Eric Wong (5):
doc: add email address to generated doc/site
README: update regarding Ruby support status
extconf: try harder for gcc atomics in i386-configured systems
linux_inet_diag: improve compatibility with newer GCs
test_watcher: fix for Ruby trunk r40195 and later
=== raindrops 0.11.0 - minor fixes improvements / 2013-04-20 23:10 UTC
Eric Wong (7):
raindrops: favor configured processor count over online count
watcher: set Content-Type via assignment
Linux::TCP_Info: implement #get! instance method
linux_inet_diag: avoid unnecessary sockaddr initialization
.gitignore: add .rbx
switch back to gemspec development dependencies
linux_inet_diag: better align listener_stats struct
Lawrence Pit (1):
Watcher: Use relative paths in HTML links
=== raindrops 0.10.0 - minor feature updates / 2012-06-19 08:30 UTC
Improvements to the Unix domain socket handling and small
bugfixes throughout.
Support for the "unix_diag" facility in Linux 3.3+ is planned
but not yet implemented (patches to raindrops@librelist.org
appreciated)
Brian Corrigan (1):
resolve symlinks to Unix domain sockets
Eric Wong (6):
unix_listener_stats follows and remembers symlinks
middleware/proxy: favor __send__ for method dispatch
unix: show zero-value stats for idle listeners
test_watcher: fix incorrect request/date comparison
watcher: sort index of listener listing
watcher: do not require Rack::Head for HEAD response
See "git log v0.9.0..v0.10.0" for full details
=== raindrops 0.9.0 - minor middleware/proxy update / 2012-05-21 00:06 UTC
Raindrops::Middleware::Proxy now forwards method_missing
to the proxied body object. This allows compatibility
with non-standard Rack extensions employed by some
middlewares, applications, or servers.
Thanks to Ben Somers for the feature!
=== raindrops 0.8.1 - compatibility fixes / 2012-05-12 05:58 UTC
This release fixes a build problem found under a current SmartOS. This
release also runs successfully on FreeBSD 9.0 under both x86-64 and
i386.
There are also documentation updates from Aman Gupta and a test suite
fix from Jeremy Evans for OpenBSD.
raindrops fully supports unicorn on recent versions of FreeBSD, OpenBSD,
SmartOS, and possibly other Free Software systems. Portability reports
and fixes for Free Software systems is greatly appreciated at
raindrops@librelist.org
Non-Free systems will never be supported.
raindrops requires the Linux 2.6.18 or later for full functionality
(which unicorn does not require). Future releases will take advantage
of the unix_diag functionality found in the Linux 3.3 (and later)
kernels.
=== raindrops 0.8.0 - watcher updates / 2011-10-14 22:00 UTC
There are various updates to the Raindrops::Watcher Rack app.
Most notably, the timestamp where the a statistic first and last
hit its peak value (active/queued connections) is captured.
As usual, the latest Raindrops::Watcher is running at:
http://raindrops-demo.bogomips.org/
=== raindrops 0.7.0 - FreeBSD fix, code cleanups / 2011-06-27 07:26 UTC
This release fixes a build issue on FreeBSD. There are various
documentation and code cleanups, too.
=== raindrops 0.6.1 - fix build on non-Linux / 2011-03-21 22:31 UTC
TCP_INFO support couldn't compile under non-Linux, this was
broken since 0.5.0 when TCP_INFO support was introduced.
Thanks to Ben Bleything for the report.
=== raindrops 0.6.0 - polishing up the last release / 2011-03-21 21:46 UTC
Following up the huge 0.5.0 release, 0.6.0 makes
some minor improvements:
* minor UI/UX improvements for Watcher Rack app
* set close-on-exec by default for inet_diag sockets
* inet_diag build fixes for newer GNU libc6
* --with-atomic_ops-dir= build option added
=== raindrops 0.5.0 - more Linux extras! / 2011-03-17 03:13 UTC
Portable changes:
* Raindrops are now resizable within the limits of system page size
* Raindrops::Middleware proxies +to_path+ in response bodies
* More documentation
Linux-only changes:
* Raindrops::LastDataRecv[1] Rack application
* Raindrops::Watcher[2] Rack application
* Raindrops::TCP_Info[3] class for capturing TCP connection stats
* IPv6 support for inet_diag
* faster inet_diag stats for multiple sockets
There is also a demo server running the Watcher and Middleware
components. It's capped to 30 concurrent users, so go easy on
it:
Raindrops::Watcher: http://raindrops-demo.bogomips.org/
Raindrops::Middleware: http://raindrops-demo.bogomips.org/_raindrops
[1] http://raindrops.bogomips.org/Raindrops/LastDataRecv.html
[2] http://raindrops.bogomips.org/Raindrops/Watcher.html
[3] http://raindrops.bogomips.org/Raindrops/TCP_Info.html
=== raindrops 0.4.1 - more portability! / 2010-09-26 06:49 UTC
Rubinius 1.1.0 support is complete. Atomic operations
are now available under FreeBSD 7.0 now.
Full changelog below:
commit 8a2a725a4ad074af493e5aa075155eda8b1d6be7
Author: Eric Wong
Date: Sat Sep 25 00:14:48 2010 -0700
force -march=i486 where GCC is targeted for i386
Nobody uses i386 anymore (especially not with Ruby!),
but some systems like FreeBSD 7.0 still target GCC at
i386 by default, so we force GCC to use a slightly
more modern instruction set and allow it to use
atomic builtins.
commit 256cc7c8ffb441dcf2d2a2da3bbbcc82546962d9
Author: Eric Wong
Date: Sat Sep 25 00:01:46 2010 -0700
disable Linux-only code on non-Linux
This allows us to build and link correctly on FreeBSD 7.0
commit 22a5a39d75faa890048d07ae4ea0d494acd414ce
Author: Eric Wong
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.2/setup.rb 0000644 0000041 0000041 00000106526 14054564151 015340 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.2/README 0000644 0000041 0000041 00000006651 14054564151 014531 0 ustar www-data www-data = raindrops - real-time stats for preforking Rack servers
raindrops is a real-time stats toolkit to show statistics for Rack HTTP
servers. It is designed for preforking servers such as unicorn, but
should support any Rack HTTP server on platforms supporting POSIX shared
memory. It may also be used as a generic scoreboard for sharing atomic
counters across multiple processes.
== Features
* counters are shared across all forked children and lock-free
* counters are kept on separate cache lines to reduce contention under SMP
* may expose server statistics as a Rack Middleware endpoint
(default: "/_raindrops")
* middleware displays the number of actively processing and writing
clients from a single request regardless of which worker process
it hits.
== Linux-only Extra Features!
* Middleware response includes extra stats for bound TCP and
Unix domain sockets (configurable, it can include stats from
other TCP or UNIX domain socket servers).
* TCP socket stats use efficient inet_diag facilities via netlink
instead of parsing /proc/net/tcp to minimize overhead.
This was fun to discover and write.
* TCP_Info reporting may be used to check stat for every accepted client
on TCP servers
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
== Install
We recommend GCC 4+ (or compatible) to support the __sync builtins
(__sync_{add,sub}_and_fetch()):
https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
For non-GCC 4+ users, we also support compilation with the libatomic_ops
package starting with Raindrops 0.4.0:
https://github.com/ivmai/libatomic_ops
If you're using a packaged Ruby distribution, make sure you have a C
compiler and the matching Ruby development libraries and headers.
If you use RubyGems:
gem install raindrops
== Usage
See Raindrops::Middleware and Raindrops::LastDataRecv documentation for
use Rack servers. The entire library is fully-documented and we are
responsive on the publically archived mailing list
(mailto:raindrops-public@yhbt.net) if
you have any questions or comments.
== Development
You can get the latest source via git from the following locations:
git://yhbt.net/raindrops.git
git://repo.or.cz/raindrops.git (mirror)
You may browse the code from the web and download the latest snapshot
tarballs here:
* https://yhbt.net/raindrops.git
* http://repo.or.cz/w/raindrops.git (gitweb)
Inline patches (from "git format-patch") to the mailing list are
preferred because they allow code review and comments in the reply to
the patch.
We will adhere to mostly the same conventions for patch submissions as
git itself. See the Documentation/SubmittingPatches document
distributed with git on on patch submission guidelines to follow. Just
don't email the git mailing list or maintainer with raindrops patches.
raindrops is licensed under the LGPL-2.1+
== Contact
All feedback (bug reports, user/development discussion, patches, pull
requests) go to the publically archived mailing list:
mailto:raindrops-public@yhbt.net
Mailing list archives are available over HTTPS and NNTP:
* https://yhbt.net/raindrops-public/
* http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/raindrops-public/
* nntp://news.public-inbox.org/inbox.comp.lang.ruby.raindrops
Since archives are public, scrub sensitive information and
use anonymity tools such as Tor or Mixmaster if you deem necessary.
raindrops-0.19.2/archive/ 0000755 0000041 0000041 00000000000 14054564151 015262 5 ustar www-data www-data raindrops-0.19.2/archive/slrnpull.conf 0000644 0000041 0000041 00000000276 14054564151 020011 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.2/archive/.gitignore 0000644 0000041 0000041 00000000026 14054564151 017250 0 ustar www-data www-data /data
/news
/requests
raindrops-0.19.2/ext/ 0000755 0000041 0000041 00000000000 14054564151 014441 5 ustar www-data www-data raindrops-0.19.2/ext/raindrops/ 0000755 0000041 0000041 00000000000 14054564151 016442 5 ustar www-data www-data raindrops-0.19.2/ext/raindrops/tcp_info.c 0000644 0000041 0000041 00000015356 14054564151 020421 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.2/ext/raindrops/my_fileno.h 0000644 0000041 0000041 00000001263 14054564151 020576 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.2/ext/raindrops/linux_inet_diag.c 0000644 0000041 0000041 00000042554 14054564151 021762 0 ustar www-data www-data #include
#include
#include
#include "my_fileno.h"
#ifdef __linux__
#ifdef HAVE_RB_THREAD_IO_BLOCKING_REGION
/* Ruby 1.9.3 and 2.0.0 */
VALUE rb_thread_io_blocking_region(rb_blocking_function_t *, void *, int);
# define rd_fd_region(fn,data,fd) \
rb_thread_io_blocking_region((fn),(data),(fd))
#elif defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) && \
defined(HAVE_RUBY_THREAD_H) && HAVE_RUBY_THREAD_H
/* in case Ruby 2.0+ ever drops rb_thread_io_blocking_region: */
# include
# define COMPAT_FN (void *(*)(void *))
# define rd_fd_region(fn,data,fd) \
rb_thread_call_without_gvl(COMPAT_FN(fn),(data),RUBY_UBF_IO,NULL)
#else
# error Ruby <= 1.8 not supported
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
union any_addr {
struct sockaddr_storage ss;
struct sockaddr sa;
struct sockaddr_in in;
struct sockaddr_in6 in6;
};
static size_t page_size;
static unsigned g_seq;
static VALUE cListenStats, cIDSock;
static ID id_new;
struct listen_stats {
uint32_t active;
uint32_t queued;
uint32_t listener_p;
};
#define OPLEN (sizeof(struct inet_diag_bc_op) + \
sizeof(struct inet_diag_hostcond) + \
sizeof(struct sockaddr_storage))
struct nogvl_args {
st_table *table;
struct iovec iov[3]; /* last iov holds inet_diag bytecode */
struct listen_stats stats;
int fd;
};
#ifdef SOCK_CLOEXEC
# define my_SOCK_RAW (SOCK_RAW|SOCK_CLOEXEC)
# define FORCE_CLOEXEC(v) (v)
#else
# define my_SOCK_RAW SOCK_RAW
static VALUE FORCE_CLOEXEC(VALUE io)
{
int fd = my_fileno(io);
int flags = fcntl(fd, F_SETFD, FD_CLOEXEC);
if (flags == -1)
rb_sys_fail("fcntl(F_SETFD, FD_CLOEXEC)");
return io;
}
#endif
/*
* call-seq:
* Raindrops::InetDiagSocket.new -> Socket
*
* Creates a new Socket object for the netlink inet_diag facility
*/
static VALUE ids_s_new(VALUE klass)
{
VALUE argv[3];
argv[0] = INT2NUM(AF_NETLINK);
argv[1] = INT2NUM(my_SOCK_RAW);
argv[2] = INT2NUM(NETLINK_INET_DIAG);
return FORCE_CLOEXEC(rb_call_super(3, argv));
}
/* creates a Ruby ListenStats Struct based on our internal listen_stats */
static VALUE rb_listen_stats(struct listen_stats *stats)
{
VALUE active = UINT2NUM(stats->active);
VALUE queued = UINT2NUM(stats->queued);
return rb_struct_new(cListenStats, active, queued);
}
static int st_free_data(st_data_t key, st_data_t value, st_data_t ignored)
{
xfree((void *)key);
xfree((void *)value);
return ST_DELETE;
}
/*
* call-seq:
* remove_scope_id(ip_address)
*
* Returns copy of IP address with Scope ID removed,
* if address has it (only IPv6 actually may have it).
*/
static VALUE remove_scope_id(const char *addr)
{
VALUE rv = rb_str_new2(addr);
long len = RSTRING_LEN(rv);
char *ptr = RSTRING_PTR(rv);
char *pct = memchr(ptr, '%', len);
/*
* remove scoped portion
* Ruby equivalent: rv.sub!(/%([^\]]*)\]/, "]")
*/
if (pct) {
size_t newlen = pct - ptr;
char *rbracket = memchr(pct, ']', len - newlen);
if (rbracket) {
size_t move = len - (rbracket - ptr);
memmove(pct, rbracket, move);
newlen += move;
rb_str_set_len(rv, newlen);
} else {
rb_raise(rb_eArgError,
"']' not found in IPv6 addr=%s", ptr);
}
}
return rv;
}
static int st_to_hash(st_data_t key, st_data_t value, VALUE hash)
{
struct listen_stats *stats = (struct listen_stats *)value;
if (stats->listener_p) {
VALUE k = remove_scope_id((const char *)key);
VALUE v = rb_listen_stats(stats);
OBJ_FREEZE(k);
rb_hash_aset(hash, k, v);
}
return st_free_data(key, value, 0);
}
static int st_AND_hash(st_data_t key, st_data_t value, VALUE hash)
{
struct listen_stats *stats = (struct listen_stats *)value;
if (stats->listener_p) {
VALUE k = remove_scope_id((const char *)key);
if (rb_hash_lookup(hash, k) == Qtrue) {
VALUE v = rb_listen_stats(stats);
OBJ_FREEZE(k);
rb_hash_aset(hash, k, v);
}
}
return st_free_data(key, value, 0);
}
static const char *addr_any(sa_family_t family)
{
static const char ipv4[] = "0.0.0.0";
static const char ipv6[] = "[::]";
if (family == AF_INET)
return ipv4;
assert(family == AF_INET6 && "unknown family");
return ipv6;
}
#ifdef __GNUC__
static void bug_warn_nogvl(const char *, ...)
__attribute__((format(printf,1,2)));
#endif
static void bug_warn_nogvl(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "Please report how you produced this at "\
"raindrops-public@yhbt.net\n");
fflush(stderr);
}
static struct listen_stats *stats_for(st_table *table, struct inet_diag_msg *r)
{
char *host, *key, *port, *old_key;
size_t alloca_len;
struct listen_stats *stats;
socklen_t hostlen;
socklen_t portlen = (socklen_t)sizeof("65535");
int n;
const void *src = r->id.idiag_src;
switch (r->idiag_family) {
case AF_INET: {
hostlen = INET_ADDRSTRLEN;
alloca_len = hostlen + portlen;
host = key = alloca(alloca_len);
break;
}
case AF_INET6: {
hostlen = INET6_ADDRSTRLEN;
alloca_len = 1 + hostlen + 1 + portlen;
key = alloca(alloca_len);
host = key + 1;
break;
}
default:
assert(0 && "unsupported address family, could that be IPv7?!");
}
if (!inet_ntop(r->idiag_family, src, host, hostlen)) {
bug_warn_nogvl("BUG: inet_ntop: %s\n", strerror(errno));
*key = '\0';
*host = '\0';
}
hostlen = (socklen_t)strlen(host);
switch (r->idiag_family) {
case AF_INET:
host[hostlen] = ':';
port = host + hostlen + 1;
break;
case AF_INET6:
key[0] = '[';
host[hostlen] = ']';
host[hostlen + 1] = ':';
port = host + hostlen + 2;
break;
default:
assert(0 && "unsupported address family, could that be IPv7?!");
}
n = snprintf(port, portlen, "%u", ntohs(r->id.idiag_sport));
if (n <= 0) {
bug_warn_nogvl("BUG: snprintf port: %d\n", n);
*key = '\0';
}
if (st_lookup(table, (st_data_t)key, (st_data_t *)&stats))
return stats;
old_key = key;
if (r->idiag_state == TCP_ESTABLISHED) {
n = snprintf(key, alloca_len, "%s:%u",
addr_any(r->idiag_family),
ntohs(r->id.idiag_sport));
if (n <= 0) {
bug_warn_nogvl("BUG: snprintf: %d\n", n);
*key = '\0';
}
if (st_lookup(table, (st_data_t)key, (st_data_t *)&stats))
return stats;
if (n <= 0) {
key = xmalloc(1);
*key = '\0';
} else {
old_key = key;
key = xmalloc(n + 1);
memcpy(key, old_key, n + 1);
}
} else {
size_t old_len = strlen(old_key) + 1;
key = xmalloc(old_len);
memcpy(key, old_key, old_len);
}
stats = xcalloc(1, sizeof(struct listen_stats));
st_insert(table, (st_data_t)key, (st_data_t)stats);
return stats;
}
static void table_incr_active(st_table *table, struct inet_diag_msg *r)
{
struct listen_stats *stats = stats_for(table, r);
++stats->active;
}
static void table_set_queued(st_table *table, struct inet_diag_msg *r)
{
struct listen_stats *stats = stats_for(table, r);
stats->listener_p = 1;
stats->queued = r->idiag_rqueue;
}
/* inner loop of inet_diag, called for every socket returned by netlink */
static inline void r_acc(struct nogvl_args *args, struct inet_diag_msg *r)
{
/*
* inode == 0 means the connection is still in the listen queue
* and has not yet been accept()-ed by the server. The
* inet_diag bytecode cannot filter this for us.
*/
if (r->idiag_inode == 0)
return;
if (r->idiag_state == TCP_ESTABLISHED) {
if (args->table)
table_incr_active(args->table, r);
else
args->stats.active++;
} else { /* if (r->idiag_state == TCP_LISTEN) */
if (args->table)
table_set_queued(args->table, r);
else
args->stats.queued = r->idiag_rqueue;
}
/*
* we wont get anything else because of the idiag_states filter
*/
}
static const char err_sendmsg[] = "sendmsg";
static const char err_recvmsg[] = "recvmsg";
static const char err_nlmsg[] = "nlmsg";
struct diag_req {
struct nlmsghdr nlh;
struct inet_diag_req r;
};
static void prep_msghdr(
struct msghdr *msg,
struct nogvl_args *args,
struct sockaddr_nl *nladdr,
size_t iovlen)
{
memset(msg, 0, sizeof(struct msghdr));
msg->msg_name = (void *)nladdr;
msg->msg_namelen = sizeof(struct sockaddr_nl);
msg->msg_iov = args->iov;
msg->msg_iovlen = iovlen;
}
static void prep_diag_args(
struct nogvl_args *args,
struct sockaddr_nl *nladdr,
struct rtattr *rta,
struct diag_req *req,
struct msghdr *msg)
{
memset(req, 0, sizeof(struct diag_req));
memset(nladdr, 0, sizeof(struct sockaddr_nl));
nladdr->nl_family = AF_NETLINK;
req->nlh.nlmsg_len = (unsigned int)(sizeof(struct diag_req) +
RTA_LENGTH(args->iov[2].iov_len));
req->nlh.nlmsg_type = TCPDIAG_GETSOCK;
req->nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
req->nlh.nlmsg_pid = getpid();
req->r.idiag_states = (1<rta_type = INET_DIAG_REQ_BYTECODE;
rta->rta_len = RTA_LENGTH(args->iov[2].iov_len);
args->iov[0].iov_base = req;
args->iov[0].iov_len = sizeof(struct diag_req);
args->iov[1].iov_base = rta;
args->iov[1].iov_len = sizeof(struct rtattr);
prep_msghdr(msg, args, nladdr, 3);
}
static void prep_recvmsg_buf(struct nogvl_args *args)
{
/* reuse buffer that was allocated for bytecode */
args->iov[0].iov_len = page_size;
args->iov[0].iov_base = args->iov[2].iov_base;
}
/* does the inet_diag stuff with netlink(), this is called w/o GVL */
static VALUE diag(void *ptr)
{
struct nogvl_args *args = ptr;
struct sockaddr_nl nladdr;
struct rtattr rta;
struct diag_req req;
struct msghdr msg;
const char *err = NULL;
unsigned seq = ++g_seq;
prep_diag_args(args, &nladdr, &rta, &req, &msg);
req.nlh.nlmsg_seq = seq;
if (sendmsg(args->fd, &msg, 0) < 0) {
err = err_sendmsg;
goto out;
}
prep_recvmsg_buf(args);
while (1) {
ssize_t readed;
size_t r;
struct nlmsghdr *h = (struct nlmsghdr *)args->iov[0].iov_base;
prep_msghdr(&msg, args, &nladdr, 1);
readed = recvmsg(args->fd, &msg, 0);
if (readed < 0) {
if (errno == EINTR)
continue;
err = err_recvmsg;
goto out;
}
if (readed == 0)
goto out;
r = (size_t)readed;
for ( ; NLMSG_OK(h, r); h = NLMSG_NEXT(h, r)) {
if (h->nlmsg_seq != seq)
continue;
if (h->nlmsg_type == NLMSG_DONE)
goto out;
if (h->nlmsg_type == NLMSG_ERROR) {
err = err_nlmsg;
goto out;
}
r_acc(args, NLMSG_DATA(h));
}
}
out:
/* prepare to raise, free memory before reacquiring GVL */
if (err && args->table) {
int save_errno = errno;
st_foreach(args->table, st_free_data, 0);
st_free_table(args->table);
errno = save_errno;
}
return (VALUE)err;
}
/* populates sockaddr_storage struct by parsing +addr+ */
static void parse_addr(union any_addr *inet, VALUE addr)
{
char *host_ptr;
char *check;
char *colon = NULL;
char *rbracket = NULL;
void *dst;
long host_len;
int af, rc;
uint16_t *portdst;
unsigned long port;
Check_Type(addr, T_STRING);
host_ptr = StringValueCStr(addr);
host_len = RSTRING_LEN(addr);
if (*host_ptr == '[') { /* ipv6 address format (rfc2732) */
rbracket = memchr(host_ptr + 1, ']', host_len - 1);
if (rbracket == NULL)
rb_raise(rb_eArgError, "']' not found in IPv6 addr=%s",
host_ptr);
if (rbracket[1] != ':')
rb_raise(rb_eArgError, "':' not found in IPv6 addr=%s",
host_ptr);
colon = rbracket + 1;
host_ptr++;
*rbracket = 0;
inet->ss.ss_family = af = AF_INET6;
dst = &inet->in6.sin6_addr;
portdst = &inet->in6.sin6_port;
} else { /* ipv4 */
colon = memchr(host_ptr, ':', host_len);
inet->ss.ss_family = af = AF_INET;
dst = &inet->in.sin_addr;
portdst = &inet->in.sin_port;
}
if (!colon)
rb_raise(rb_eArgError, "port not found in: `%s'", host_ptr);
port = strtoul(colon + 1, &check, 10);
*colon = 0;
rc = inet_pton(af, host_ptr, dst);
*colon = ':';
if (rbracket) *rbracket = ']';
if (*check || ((uint16_t)port != port))
rb_raise(rb_eArgError, "invalid port: %s", colon + 1);
if (rc != 1)
rb_raise(rb_eArgError, "inet_pton failed for: `%s' with %d",
host_ptr, rc);
*portdst = ntohs((uint16_t)port);
}
/* generates inet_diag bytecode to match all addrs */
static void gen_bytecode_all(struct iovec *iov)
{
struct inet_diag_bc_op *op;
struct inet_diag_hostcond *cond;
/* iov_len was already set and base allocated in a parent function */
assert(iov->iov_len == OPLEN && iov->iov_base && "iov invalid");
op = iov->iov_base;
op->code = INET_DIAG_BC_S_COND;
op->yes = OPLEN;
op->no = sizeof(struct inet_diag_bc_op) + OPLEN;
cond = (struct inet_diag_hostcond *)(op + 1);
cond->family = AF_UNSPEC;
cond->port = -1;
cond->prefix_len = 0;
}
/* generates inet_diag bytecode to match a single addr */
static void gen_bytecode(struct iovec *iov, union any_addr *inet)
{
struct inet_diag_bc_op *op;
struct inet_diag_hostcond *cond;
/* iov_len was already set and base allocated in a parent function */
assert(iov->iov_len == OPLEN && iov->iov_base && "iov invalid");
op = iov->iov_base;
op->code = INET_DIAG_BC_S_COND;
op->yes = OPLEN;
op->no = sizeof(struct inet_diag_bc_op) + OPLEN;
cond = (struct inet_diag_hostcond *)(op + 1);
cond->family = inet->ss.ss_family;
switch (inet->ss.ss_family) {
case AF_INET: {
cond->port = ntohs(inet->in.sin_port);
cond->prefix_len = inet->in.sin_addr.s_addr == 0 ? 0 :
sizeof(inet->in.sin_addr.s_addr) * CHAR_BIT;
*cond->addr = inet->in.sin_addr.s_addr;
}
break;
case AF_INET6: {
cond->port = ntohs(inet->in6.sin6_port);
cond->prefix_len = memcmp(&in6addr_any, &inet->in6.sin6_addr,
sizeof(struct in6_addr)) == 0 ?
0 : sizeof(inet->in6.sin6_addr) * CHAR_BIT;
memcpy(&cond->addr, &inet->in6.sin6_addr,
sizeof(struct in6_addr));
}
break;
default:
assert(0 && "unsupported address family, could that be IPv7?!");
}
}
/*
* n.b. we may safely raise here because an error will cause diag()
* to free args->table
*/
static void nl_errcheck(VALUE r)
{
const char *err = (const char *)r;
if (err) {
if (err == err_nlmsg)
rb_raise(rb_eRuntimeError, "NLMSG_ERROR");
else
rb_sys_fail(err);
}
}
static VALUE tcp_stats(struct nogvl_args *args, VALUE addr)
{
union any_addr query_addr;
parse_addr(&query_addr, addr);
gen_bytecode(&args->iov[2], &query_addr);
memset(&args->stats, 0, sizeof(struct listen_stats));
nl_errcheck(rd_fd_region(diag, args, args->fd));
return rb_listen_stats(&args->stats);
}
static int drop_placeholders(st_data_t k, st_data_t v, st_data_t ign)
{
if ((VALUE)v == Qtrue)
return ST_DELETE;
return ST_CONTINUE;
}
/*
* call-seq:
* Raindrops::Linux.tcp_listener_stats([addrs[, sock]]) => hash
*
* If specified, +addr+ may be a string or array of strings representing
* listen addresses to filter for. Returns a hash with given addresses as
* keys and ListenStats objects as the values or a hash of all addresses.
*
* addrs = %w(0.0.0.0:80 127.0.0.1:8080)
*
* If +addr+ is nil or not specified, all (IPv4) addresses are returned.
* If +sock+ is specified, it should be a Raindrops::InetDiagSock object.
*/
static VALUE tcp_listener_stats(int argc, VALUE *argv, VALUE self)
{
VALUE rv = rb_hash_new();
struct nogvl_args args;
VALUE addrs, sock;
rb_scan_args(argc, argv, "02", &addrs, &sock);
/*
* allocating page_size instead of OP_LEN since we'll reuse the
* buffer for recvmsg() later, we already checked for
* OPLEN <= page_size at initialization
*/
args.iov[2].iov_len = OPLEN;
args.iov[2].iov_base = alloca(page_size);
args.table = NULL;
if (NIL_P(sock))
sock = rb_funcall(cIDSock, id_new, 0);
args.fd = my_fileno(sock);
switch (TYPE(addrs)) {
case T_STRING:
rb_hash_aset(rv, addrs, tcp_stats(&args, addrs));
return rv;
case T_ARRAY: {
long i;
long len = RARRAY_LEN(addrs);
if (len == 1) {
VALUE cur = rb_ary_entry(addrs, 0);
rb_hash_aset(rv, cur, tcp_stats(&args, cur));
return rv;
}
for (i = 0; i < len; i++) {
union any_addr check;
VALUE cur = rb_ary_entry(addrs, i);
parse_addr(&check, cur);
rb_hash_aset(rv, cur, Qtrue /* placeholder */);
}
/* fall through */
}
case T_NIL:
args.table = st_init_strtable();
gen_bytecode_all(&args.iov[2]);
break;
default:
rb_raise(rb_eArgError,
"addr must be an array of strings, a string, or nil");
}
nl_errcheck(rd_fd_region(diag, &args, args.fd));
st_foreach(args.table, NIL_P(addrs) ? st_to_hash : st_AND_hash, rv);
st_free_table(args.table);
if (RHASH_SIZE(rv) > 1)
rb_hash_foreach(rv, drop_placeholders, Qfalse);
/* let GC deal with corner cases */
if (argc < 2) rb_io_close(sock);
return rv;
}
void Init_raindrops_linux_inet_diag(void)
{
VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject);
VALUE mLinux = rb_define_module_under(cRaindrops, "Linux");
VALUE Socket;
rb_require("socket");
Socket = rb_const_get(rb_cObject, rb_intern("Socket"));
id_new = rb_intern("new");
/*
* Document-class: Raindrops::InetDiagSocket
*
* This is a subclass of +Socket+ specifically for talking
* to the inet_diag facility of Netlink.
*/
cIDSock = rb_define_class_under(cRaindrops, "InetDiagSocket", Socket);
rb_define_singleton_method(cIDSock, "new", ids_s_new, 0);
cListenStats = rb_const_get(cRaindrops, rb_intern("ListenStats"));
rb_gc_register_mark_object(cListenStats); /* pin */
rb_define_module_function(mLinux, "tcp_listener_stats",
tcp_listener_stats, -1);
page_size = getpagesize();
assert(OPLEN <= page_size && "bytecode OPLEN is not <= PAGE_SIZE");
}
#endif /* __linux__ */
raindrops-0.19.2/ext/raindrops/raindrops_atomic.h 0000644 0000041 0000041 00000001142 14054564151 022146 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.2/ext/raindrops/extconf.rb 0000644 0000041 0000041 00000010411 14054564151 020432 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 = <