raindrops-0.19.0/0000755000004100000410000000000013145550250013632 5ustar www-datawww-dataraindrops-0.19.0/.gitattributes0000644000004100000410000000010513145550250016521 0ustar www-datawww-data*.gemspec diff=ruby *.rb diff=ruby *.ru diff=ruby Rakefile diff=ruby raindrops-0.19.0/examples/0000755000004100000410000000000013145550250015450 5ustar www-datawww-dataraindrops-0.19.0/examples/zbatery.conf.rb0000644000004100000410000000116213145550250020401 0ustar www-datawww-data# Used for running Raindrops::Watcher, which requires a multi-threaded # Rack server capable of streaming a response. Threads must be used, # so any multi-threaded Rack server may be used. # zbatery was recommended in the past, but it is abandoned # . # yahns may work as an alternative (see yahns.conf.rb in this dir) Rainbows! do use :ThreadSpawn end log_dir = "/var/log/zbatery" if File.writable?(log_dir) && File.directory?(log_dir) stderr_path "#{log_dir}/raindrops-demo.stderr.log" stdout_path "#{log_dir}/raindrops-demo.stdout.log" listen "/tmp/.r" pid "/tmp/.raindrops.pid" end raindrops-0.19.0/examples/watcher.ru0000644000004100000410000000021313145550250017451 0ustar www-datawww-data# Sample standalone Rack application, recommended use is with Zbatery # See zbatery.conf.rb require "raindrops" run Raindrops::Watcher.new raindrops-0.19.0/examples/middleware.ru0000644000004100000410000000023513145550250020135 0ustar www-datawww-data# sample stand-alone rackup application for Raindrops::Middleware require 'rack/lobster' require 'raindrops' use Raindrops::Middleware run Rack::Lobster.new raindrops-0.19.0/examples/linux-listener-stats.rb0000755000004100000410000000635513145550250022127 0ustar www-datawww-data#!/usr/bin/ruby # -*- encoding: binary -*- $stdout.sync = $stderr.sync = true # this is used to show or watch the number of active and queued # connections on any listener socket from the command line require 'raindrops' require 'optparse' require 'ipaddr' require 'time' begin require 'sleepy_penguin' rescue LoadError end usage = "Usage: #$0 [-d DELAY] [-t QUEUED_THRESHOLD] ADDR..." ARGV.size > 0 or abort usage delay = false all = false queued_thresh = -1 # "normal" exits when driven on the command-line trap(:INT) { exit 130 } trap(:PIPE) { exit 0 } OptionParser.new('', 24, ' ') do |opts| opts.banner = usage opts.on('-d', '--delay=DELAY', Float) { |n| delay = n } opts.on('-t', '--queued-threshold=INT', Integer) { |n| queued_thresh = n } opts.on('-a', '--all') { all = true } opts.parse! ARGV end begin require 'aggregate' rescue LoadError $stderr.puts "Aggregate missing, USR1 and USR2 handlers unavailable" end if delay if delay && defined?(SleepyPenguin::TimerFD) @tfd = SleepyPenguin::TimerFD.new @tfd.settime nil, delay, delay def delay_for(seconds) @tfd.expirations end else alias delay_for sleep end agg_active = agg_queued = nil if delay && defined?(Aggregate) agg_active = Aggregate.new agg_queued = Aggregate.new def dump_aggregate(label, agg) $stderr.write "--- #{label} ---\n" %w(count min max outliers_low outliers_high mean std_dev).each do |f| $stderr.write "#{f}=#{agg.__send__ f}\n" end $stderr.write "#{agg}\n\n" end trap(:USR1) do dump_aggregate "active", agg_active dump_aggregate "queued", agg_queued end trap(:USR2) do agg_active = Aggregate.new agg_queued = Aggregate.new end $stderr.puts "USR1(dump_aggregate) and USR2(reset) handlers ready for PID=#$$" end ARGV.each do |addr| addr =~ %r{\A(127\..+):(\d+)\z} or next host, port = $1, $2 hex_port = '%X' % port.to_i ip_addr = IPAddr.new(host) hex_host = ip_addr.hton.each_byte.inject('') { |s,o| s << '%02X' % o } socks = File.readlines('/proc/net/tcp') hex_addr = "#{hex_host}:#{hex_port}" if socks.grep(/^\s+\d+:\s+#{hex_addr}\s+/).empty? && ! socks.grep(/^\s+\d+:\s+00000000:#{hex_port}\s+/).empty? warn "W: #{host}:#{port} (#{hex_addr}) not found in /proc/net/tcp" warn "W: Did you mean 0.0.0.0:#{port}?" end end len = "address".size now = nil tcp, unix = [], [] ARGV.each do |addr| bs = addr.respond_to?(:bytesize) ? addr.bytesize : addr.size len = bs if bs > len (addr =~ %r{\A/} ? unix : tcp) << addr end combined = {} tcp_args = unix_args = nil unless tcp.empty? && unix.empty? tcp_args = tcp unix_args = unix end sock = Raindrops::InetDiagSocket.new if tcp len = 35 if len > 35 fmt = "%20s % #{len}s % 10u % 10u\n" $stderr.printf fmt.tr('u','s'), *%w(timestamp address active queued) begin if now combined.clear now = nil end combined.merge! Raindrops::Linux.tcp_listener_stats(tcp_args, sock) combined.merge! Raindrops::Linux.unix_listener_stats(unix_args) combined.each do |addr,stats| active, queued = stats.active, stats.queued if agg_active agg_active << active agg_queued << queued end next if queued < queued_thresh printf fmt, now ||= Time.now.utc.iso8601, addr, active, queued end end while delay && delay_for(delay) raindrops-0.19.0/examples/yahns.conf.rb0000644000004100000410000000212613145550250020044 0ustar www-datawww-data# Inlined rack app using yahns server (git clone git://yhbt.net/yahns.git) # Usage: yahns -c /path/to/this/file.conf.rb # There is no separate config.ru file for this example, # but rack_app may also be a string pointing to the path of a # config.ru file require 'rack' rack_app = Rack::Builder.new do use Rack::Head addr = %w(0.0.0.0:9418 0.0.0.0:443 [::]:443 0.0.0.0:80 [::]:80 127.0.0.1:6081 127.0.0.1:280 0.0.0.0:119 [::]:119) use Raindrops::Middleware, listeners: addr run Raindrops::Watcher.new(listeners: addr) end.to_app # rack_app = '/path/to/config.ru' # a more standard config app(:rack, rack_app) do # I keep IPv4 and IPv6 on separate sockets to avoid ugly # IPv4-mapped-IPv6 addresses: listen 8080 listen '[::]:8080', ipv6only: true client_max_body_size 0 # no POST or any uploads client_timeout 5 output_buffering false # needed for /tail/ endpoint to avoid ENOSPC queue { worker_threads 30 } end # logging is optional, but recommended for diagnosing problems # stderr_path '/var/log/yahns/stderr-raindrops.log' # stdout_path '/var/log/yahns/stdout-raindrops.log' raindrops-0.19.0/examples/watcher_demo.ru0000644000004100000410000000053213145550250020461 0ustar www-datawww-data# This is a snippet of the config that powers # https://raindrops-demo.bogomips.org/ # This may be used with the packaged zbatery.conf.rb # # zbatery -c zbatery.conf.ru watcher_demo.ru -E none require "raindrops" use Raindrops::Middleware listeners = %w( 0.0.0.0:9418 0.0.0.0:80 /tmp/.r ) run Raindrops::Watcher.new :listeners => listeners raindrops-0.19.0/.olddoc.yml0000644000004100000410000000100313145550250015671 0ustar www-datawww-data--- cgit_url: https://bogomips.org/raindrops.git git_url: git://bogomips.org/raindrops.git rdoc_url: https://bogomips.org/raindrops/ public_email: raindrops-public@bogomips.org ml_url: - https://bogomips.org/raindrops-public/ - http://ou63pmih66umazou.onion/raindrops-public nntp_url: - nntp://news.public-inbox.org/inbox.comp.lang.ruby.raindrops - nntp://ou63pmih66umazou.onion/inbox.comp.lang.ruby.raindrops source_code: - git clone git://bogomips.org/raindrops.git - git clone https://bogomips.org/raindrops.git raindrops-0.19.0/LATEST0000644000004100000410000000115513145550250014553 0ustar www-datawww-data=== 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.19.0/GNUmakefile0000644000004100000410000000013713145550250015705 0ustar www-datawww-dataall:: RSYNC_DEST := bogomips.org:/srv/bogomips/raindrops rfpackage := raindrops include pkg.mk raindrops-0.19.0/README0000644000004100000410000000654313145550250014522 0ustar www-datawww-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@bogomips.org) if you have any questions or comments. == Development You can get the latest source via git from the following locations: git://bogomips.org/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://bogomips.org/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@bogomips.org Mailing list archives are available over HTTPS and NNTP: * https://bogomips.org/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.0/NEWS0000644000004100000410000003100413145550250014327 0ustar www-datawww-data=== 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.0/lib/0000755000004100000410000000000013145550250014400 5ustar www-datawww-dataraindrops-0.19.0/lib/raindrops.rb0000644000004100000410000000326613145550250016735 0ustar www-datawww-data# -*- encoding: binary -*- # # Each Raindrops object is a container that holds several counters. # It is internally a page-aligned, shared memory area that allows # atomic increments, decrements, assignments and reads without any # locking. # # rd = Raindrops.new 4 # rd.incr(0, 1) -> 1 # rd.to_ary -> [ 1, 0, 0, 0 ] # # Unlike many classes in this package, the core Raindrops class is # intended to be portable to all reasonably modern *nix systems # supporting mmap(). Please let us know if you have portability # issues, patches or pull requests at mailto:raindrops-public@bogomips.org class Raindrops # Used to represent the number of +active+ and +queued+ sockets for # a single listen socket across all threads and processes on a # machine. # # For TCP listeners, only sockets in the TCP_ESTABLISHED state are # accounted for. For Unix domain listeners, only CONNECTING and # CONNECTED Unix domain sockets are accounted for. # # +active+ connections is the number of accept()-ed but not-yet-closed # sockets in all threads/processes sharing the given listener. # # +queued+ connections is the number of un-accept()-ed sockets in the # queue of a given listen socket. # # These stats are currently only available under \Linux class ListenStats < Struct.new(:active, :queued) # the sum of +active+ and +queued+ sockets def total active + queued end end autoload :Linux, 'raindrops/linux' autoload :Struct, 'raindrops/struct' autoload :Middleware, 'raindrops/middleware' autoload :Aggregate, 'raindrops/aggregate' autoload :LastDataRecv, 'raindrops/last_data_recv' autoload :Watcher, 'raindrops/watcher' end require 'raindrops_ext' raindrops-0.19.0/lib/raindrops/0000755000004100000410000000000013145550250016401 5ustar www-datawww-dataraindrops-0.19.0/lib/raindrops/middleware/0000755000004100000410000000000013145550250020516 5ustar www-datawww-dataraindrops-0.19.0/lib/raindrops/middleware/proxy.rb0000644000004100000410000000216513145550250022230 0ustar www-datawww-data# -*- encoding: binary -*- # :stopdoc: # This class is used by Raindrops::Middleware to proxy application # response bodies. There should be no need to use it directly. class Raindrops::Middleware::Proxy def initialize(body, stats) @body, @stats = body, stats end # yield to the Rack server here for writing def each @body.each { |x| yield x } end # the Rack server should call this after #each (usually ensure-d) def close @stats.decr_writing @body.close if @body.respond_to?(:close) end # Some Rack servers can optimize response processing if it responds # to +to_path+ via the sendfile(2) system call, we proxy +to_path+ # to the underlying body if possible. def to_path @body.to_path end # Rack servers use +respond_to?+ to check for the presence of +close+ # and +to_path+ methods. def respond_to?(m, include_all = false) m = m.to_sym :close == m || @body.respond_to?(m, include_all) end # Avoid breaking users of non-standard extensions (e.g. #body) # Rack::BodyProxy does the same. def method_missing(*args, &block) @body.__send__(*args, &block) end end raindrops-0.19.0/lib/raindrops/watcher.rb0000644000004100000410000003110413145550250020362 0ustar www-datawww-data# -*- encoding: binary -*- require "thread" require "time" require "socket" require "rack" require "aggregate" # Raindrops::Watcher is a stand-alone Rack application for watching # any number of TCP and UNIX listeners (all of them by default). # # It depends on the {Aggregate RubyGem}[https://rubygems.org/gems/aggregate] # # In your Rack config.ru: # # run Raindrops::Watcher(options = {}) # # It takes the following options hash: # # - :listeners - an array of listener names, (e.g. %w(0.0.0.0:80 /tmp/sock)) # - :delay - interval between stats updates in seconds (default: 1) # # Raindrops::Watcher is compatible any thread-safe/thread-aware Rack # middleware. It does not work well with multi-process web servers # but can be used to monitor them. It consumes minimal resources # with the default :delay. # # == HTTP endpoints # # === GET / # # Returns an HTML summary listing of all listen interfaces watched on # # === GET /active/$LISTENER.txt # # Returns a plain text summary + histogram with X-* HTTP headers for # active connections. # # e.g.: curl https://raindrops-demo.bogomips.org/active/0.0.0.0%3A80.txt # # === GET /active/$LISTENER.html # # Returns an HTML summary + histogram with X-* HTTP headers for # active connections. # # e.g.: curl https://raindrops-demo.bogomips.org/active/0.0.0.0%3A80.html # # === GET /queued/$LISTENER.txt # # Returns a plain text summary + histogram with X-* HTTP headers for # queued connections. # # e.g.: curl https://raindrops-demo.bogomips.org/queued/0.0.0.0%3A80.txt # # === GET /queued/$LISTENER.html # # Returns an HTML summary + histogram with X-* HTTP headers for # queued connections. # # e.g.: curl https://raindrops-demo.bogomips.org/queued/0.0.0.0%3A80.html # # === POST /reset/$LISTENER # # Resets the active and queued statistics for the given listener. # # === GET /tail/$LISTENER.txt?active_min=1&queued_min=1 # # Streams chunked a response to the client. # Interval is the preconfigured +:delay+ of the application (default 1 second) # # The response is plain text in the following format: # # ISO8601_TIMESTAMP LISTENER_NAME ACTIVE_COUNT QUEUED_COUNT LINEFEED # # Query parameters: # # - active_min - do not stream a line until this active count is reached # - queued_min - do not stream a line until this queued count is reached # # == Response headers (mostly the same names as Raindrops::LastDataRecv) # # - X-Count - number of samples polled # - X-Last-Reset - date since the last reset # # The following headers are only present if X-Count is greater than one. # # - X-Min - lowest number of connections recorded # - X-Max - highest number of connections recorded # - X-Mean - mean number of connections recorded # - X-Std-Dev - standard deviation of connection count # - X-Outliers-Low - number of low outliers (hopefully many for queued) # - X-Outliers-High - number of high outliers (hopefully zero for queued) # - X-Current - current number of connections # - X-First-Peak-At - date of when X-Max was first reached # - X-Last-Peak-At - date of when X-Max was last reached # # = Demo Server # # There is a server running this app at https://raindrops-demo.bogomips.org/ # The Raindrops::Middleware demo is also accessible at # https://raindrops-demo.bogomips.org/_raindrops # # The demo server is only limited to 30 users, so be sure not to abuse it # by using the /tail/ endpoint too much. class Raindrops::Watcher # :stopdoc: attr_reader :snapshot include Rack::Utils include Raindrops::Linux DOC_URL = "https://bogomips.org/raindrops/Raindrops/Watcher.html" Peak = Struct.new(:first, :last) def initialize(opts = {}) @tcp_listeners = @unix_listeners = nil if l = opts[:listeners] tcp, unix = [], [] Array(l).each { |addr| (addr =~ %r{\A/} ? unix : tcp) << addr } unless tcp.empty? && unix.empty? @tcp_listeners = tcp @unix_listeners = unix end end @agg_class = opts[:agg_class] || Aggregate @start_time = Time.now.utc @active = Hash.new { |h,k| h[k] = @agg_class.new } @queued = Hash.new { |h,k| h[k] = @agg_class.new } @resets = Hash.new { |h,k| h[k] = @start_time } @peak_active = Hash.new { |h,k| h[k] = Peak.new(@start_time, @start_time) } @peak_queued = Hash.new { |h,k| h[k] = Peak.new(@start_time, @start_time) } @snapshot = [ @start_time, {} ] @delay = opts[:delay] || 1 @lock = Mutex.new @start = Mutex.new @cond = ConditionVariable.new @thr = nil end def hostname Socket.gethostname end # rack endpoint def call(env) @start.synchronize { @thr ||= aggregator_thread(env["rack.logger"]) } case env["REQUEST_METHOD"] when "GET" get env when "HEAD" r = get(env) r[2] = [] r when "POST" post env else Rack::Response.new(["Method Not Allowed"], 405).finish end end def aggregate!(agg_hash, peak_hash, addr, number, now) agg = agg_hash[addr] if (max = agg.max) && number > 0 && number >= max peak = peak_hash[addr] peak.first = now if number > max peak.last = now end agg << number end def aggregator_thread(logger) # :nodoc: @socket = sock = Raindrops::InetDiagSocket.new thr = Thread.new do begin combined = tcp_listener_stats(@tcp_listeners, sock) combined.merge!(unix_listener_stats(@unix_listeners)) @lock.synchronize do now = Time.now.utc combined.each do |addr,stats| aggregate!(@active, @peak_active, addr, stats.active, now) aggregate!(@queued, @peak_queued, addr, stats.queued, now) end @snapshot = [ now, combined ] @cond.broadcast end rescue => e logger.error "#{e.class} #{e.inspect}" end while sleep(@delay) && @socket sock.close end wait_snapshot thr end def non_existent_stats(time) [ time, @start_time, @agg_class.new, 0, Peak.new(@start_time, @start_time) ] end def active_stats(addr) # :nodoc: @lock.synchronize do time, combined = @snapshot stats = combined[addr] or return non_existent_stats(time) tmp, peak = @active[addr], @peak_active[addr] [ time, @resets[addr], tmp.dup, stats.active, peak ] end end def queued_stats(addr) # :nodoc: @lock.synchronize do time, combined = @snapshot stats = combined[addr] or return non_existent_stats(time) tmp, peak = @queued[addr], @peak_queued[addr] [ time, @resets[addr], tmp.dup, stats.queued, peak ] end end def wait_snapshot @lock.synchronize do @cond.wait @lock @snapshot end end def std_dev(agg) agg.std_dev.to_s rescue Errno::EDOM "NaN" end def agg_to_hash(reset_at, agg, current, peak) { "X-Count" => agg.count.to_s, "X-Min" => agg.min.to_s, "X-Max" => agg.max.to_s, "X-Mean" => agg.mean.to_s, "X-Std-Dev" => std_dev(agg), "X-Outliers-Low" => agg.outliers_low.to_s, "X-Outliers-High" => agg.outliers_high.to_s, "X-Last-Reset" => reset_at.httpdate, "X-Current" => current.to_s, "X-First-Peak-At" => peak.first.httpdate, "X-Last-Peak-At" => peak.last.httpdate, } end def histogram_txt(agg) updated_at, reset_at, agg, current, peak = *agg headers = agg_to_hash(reset_at, agg, current, peak) body = agg.to_s # 7-bit ASCII-clean headers["Content-Type"] = "text/plain" headers["Expires"] = (updated_at + @delay).httpdate headers["Content-Length"] = body.size.to_s [ 200, headers, [ body ] ] end def histogram_html(agg, addr) updated_at, reset_at, agg, current, peak = *agg headers = agg_to_hash(reset_at, agg, current, peak) body = "" \ "#{hostname} - #{escape_html addr}" \ "" << headers.map { |k,v| "" }.join << "
#{k.gsub(/^X-/, '')}#{v}
#{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}

" \ "" \ "" \ "" << all.sort do |a,b| a[0] <=> b[0] # sort by addr end.map do |addr,stats| e_addr = escape addr "" \ "" \ "" \ "" \ "" \ "" \ end.join << "
addressactivequeuedreset
#{escape_html addr}#{stats.active}#{stats.queued}
" \ "
" \ "

" \ "This is running the #{self.class} service, see " \ "#{DOC_URL} " \ "for more information and options." \ "

" \ "" headers["Content-Length"] = body.size.to_s [ 200, headers, [ body ] ] end def tail(addr, env) Tailer.new(self, addr, env).finish end # This is the response body returned for "/tail/$ADDRESS.txt". This # must use a multi-threaded Rack server with streaming response support. # It is an internal class and not expected to be used directly class Tailer def initialize(rdmon, addr, env) # :nodoc: @rdmon = rdmon @addr = addr q = Rack::Utils.parse_query env["QUERY_STRING"] @active_min = q["active_min"].to_i @queued_min = q["queued_min"].to_i len = addr.size len = 35 if len > 35 @fmt = "%20s % #{len}s % 10u % 10u\n" case env["HTTP_VERSION"] when "HTTP/1.0", nil @chunk = false else @chunk = true end end def finish headers = { "Content-Type" => "text/plain", "Cache-Control" => "no-transform", "Expires" => Time.at(0).httpdate, } headers["Transfer-Encoding"] = "chunked" if @chunk [ 200, headers, self ] end # called by the Rack server def each # :nodoc: begin time, all = @rdmon.wait_snapshot stats = all[@addr] or next stats.queued >= @queued_min or next stats.active >= @active_min or next body = sprintf(@fmt, time.iso8601, @addr, stats.active, stats.queued) body = "#{body.size.to_s(16)}\r\n#{body}\r\n" if @chunk yield body end while true yield "0\r\n\r\n" if @chunk end end # shuts down the background thread, only for tests def shutdown @socket = nil @thr.join if @thr @thr = nil end # :startdoc: end raindrops-0.19.0/lib/raindrops/struct.rb0000644000004100000410000000313313145550250020252 0ustar www-datawww-data# -*- encoding: binary -*- # This is a wrapper around Raindrops objects much like the core Ruby # \Struct can be seen as a wrapper around the core \Array class. # It's usage is similar to the core \Struct class, except its fields # may only be used to house unsigned long integers. # # class Foo < Raindrops::Struct.new(:readers, :writers) # end # # foo = Foo.new 0, 0 # # foo.incr_writers -> 1 # foo.incr_readers -> 1 # class Raindrops::Struct # returns a new class derived from Raindrops::Struct and supporting # the given +members+ as fields, just like \Struct.new in core Ruby. def self.new(*members) members = members.map { |x| x.to_sym }.freeze str = <= values.size) or raise ArgumentError, "too many arguments" @raindrops = Raindrops.new(MEMBERS.size) values.each_with_index { |val,i| @raindrops[i] = values[i] } end def initialize_copy(src) @raindrops = src.instance_variable_get(:@raindrops).dup end def []=(index, value) @raindrops[index] = value end def [](index) @raindrops[index] end def to_hash ary = @raindrops.to_ary rv = {} MEMBERS.each_with_index { |member, i| rv[member] = ary[i] } rv end EOS members.each_with_index do |member, i| str << "def incr_#{member}; @raindrops.incr(#{i}); end; " \ "def decr_#{member}; @raindrops.decr(#{i}); end; " \ "def #{member}; @raindrops[#{i}]; end; " \ "def #{member}=(val); @raindrops[#{i}] = val; end; " end klass = Class.new klass.const_set(:MEMBERS, members) klass.class_eval(str) klass end end raindrops-0.19.0/lib/raindrops/linux.rb0000644000004100000410000000566313145550250020077 0ustar www-datawww-data# -*- encoding: binary -*- # For reporting TCP ListenStats, users of older \Linux kernels need to ensure # that the the "inet_diag" and "tcp_diag" kernel modules are loaded as they do # not autoload correctly. The inet_diag facilities of \Raindrops is useful # for periodic snapshot reporting of listen queue sizes. # # Instead of snapshotting, Raindrops::Aggregate::LastDataRecv may be used # to aggregate statistics from +all+ accepted sockets as they arrive # based on the +last_data_recv+ field in Raindrops::TCP_Info module Raindrops::Linux # The standard proc path for active UNIX domain sockets, feel free to call # String#replace on this if your /proc is mounted in a non-standard location # for whatever reason PROC_NET_UNIX_ARGS = %w(/proc/net/unix) defined?(::Encoding) and PROC_NET_UNIX_ARGS.push({ :encoding => "binary" }) # Get ListenStats from an array of +paths+ # # Socket state mapping from integer => symbol, based on socket_state # enum from include/linux/net.h in the \Linux kernel: # typedef enum { # SS_FREE = 0, /* not allocated */ # SS_UNCONNECTED, /* unconnected to any socket */ # SS_CONNECTING, /* in process of connecting */ # SS_CONNECTED, /* connected to socket */ # SS_DISCONNECTING /* in process of disconnecting */ # } socket_state; # * SS_CONNECTING maps to ListenStats#queued # * SS_CONNECTED maps to ListenStats#active # # This method may be significantly slower than its tcp_listener_stats # counterpart due to the latter being able to use inet_diag via netlink. # This parses /proc/net/unix as there is no other (known) way # to expose Unix domain socket statistics over netlink. def unix_listener_stats(paths = nil) rv = Hash.new { |h,k| h[k.freeze] = Raindrops::ListenStats.new(0, 0) } if nil == paths paths = [ '[^\n]+' ] else paths = paths.map do |path| path = path.dup path.force_encoding(Encoding::BINARY) if defined?(Encoding) if File.symlink?(path) link = path path = File.readlink(link) path.force_encoding(Encoding::BINARY) if defined?(Encoding) rv[link] = rv[path] # vivify ListenerStats else rv[path] # vivify ListenerStats end Regexp.escape(path) end end paths = /^\w+: \d+ \d+ (\d+) \d+ (\d+)\s+\d+ (#{paths.join('|')})$/n # no point in pread since we can't stat for size on this file File.read(*PROC_NET_UNIX_ARGS).scan(paths) do |s| path = s[-1] case s[0] when "00000000" # client sockets case s[1].to_i when 2 then rv[path].queued += 1 when 3 then rv[path].active += 1 end else # listeners, vivify empty stats rv[path] end end rv end module_function :unix_listener_stats end # Raindrops::Linux raindrops-0.19.0/lib/raindrops/middleware.rb0000644000004100000410000001145713145550250021053 0ustar www-datawww-data# -*- encoding: binary -*- require 'raindrops' # Raindrops::Middleware is Rack middleware that allows snapshotting # current activity from an HTTP request. For all operating systems, # it returns at least the following fields: # # * calling - the number of application dispatchers on your machine # * writing - the number of clients being written to on your machine # # Additional fields are available for \Linux users. # # It should be loaded at the top of Rack middleware stack before other # middlewares for maximum accuracy. # # === Usage (Rainbows!/Unicorn preload_app=false) # # If you're using preload_app=false (the default) in your Rainbows!/Unicorn # config file, you'll need to create the global Stats object before # forking. # # require 'raindrops' # $stats ||= Raindrops::Middleware::Stats.new # # In your Rack config.ru: # # use Raindrops::Middleware, :stats => $stats # # === Usage (Rainbows!/Unicorn preload_app=true) # # If you're using preload_app=true in your Rainbows!/Unicorn # config file, just add the middleware to your stack: # # In your Rack config.ru: # # use Raindrops::Middleware # # === Linux-only extras! # # To get bound listener statistics under \Linux, you need to specify the # listener names for your server. You can even include listen sockets for # *other* servers on the same machine. This can be handy for monitoring # your nginx proxy as well. # # In your Rack config.ru, just pass the :listeners argument as an array of # strings (along with any other arguments). You can specify any # combination of TCP or Unix domain socket names: # # use Raindrops::Middleware, :listeners => %w(0.0.0.0:80 /tmp/.sock) # # If you're running Unicorn 0.98.0 or later, you don't have to pass in # the :listeners array, Raindrops will automatically detect the listeners # used by Unicorn master process. This does not detect listeners in # different processes, of course. # # The response body includes the following stats for each listener # (see also Raindrops::ListenStats): # # * active - total number of active clients on that listener # * queued - total number of queued (pre-accept()) clients on that listener # # = Demo Server # # There is a server running this middleware (and Watcher) at # https://raindrops-demo.bogomips.org/_raindrops # # Also check out the Watcher demo at https://raindrops-demo.bogomips.org/ # # The demo server is only limited to 30 users, so be sure not to abuse it # by using the /tail/ endpoint too much. # class Raindrops::Middleware attr_accessor :app, :stats, :path, :tcp, :unix # :nodoc: # A Raindrops::Struct used to count the number of :calling and :writing # clients. This struct is intended to be shared across multiple processes # and both counters are updated atomically. # # This is supported on all operating systems supported by Raindrops Stats = Raindrops::Struct.new(:calling, :writing) # :stopdoc: require "raindrops/middleware/proxy" # :startdoc: # +app+ may be any Rack application, this middleware wraps it. # +opts+ is a hash that understands the following members: # # * :stats - Raindrops::Middleware::Stats struct (default: Stats.new) # * :path - HTTP endpoint used for reading the stats (default: "/_raindrops") # * :listeners - array of host:port or socket paths (default: from Unicorn) def initialize(app, opts = {}) @app = app @stats = opts[:stats] || Stats.new @path = opts[:path] || "/_raindrops" tmp = opts[:listeners] if tmp.nil? && defined?(Unicorn) && Unicorn.respond_to?(:listener_names) tmp = Unicorn.listener_names end @tcp = @unix = nil if tmp @tcp = tmp.grep(/\A.+:\d+\z/) @unix = tmp.grep(%r{\A/}) @tcp = nil if @tcp.empty? @unix = nil if @unix.empty? end end # standard Rack endpoint def call(env) # :nodoc: env['PATH_INFO'] == @path and return stats_response begin @stats.incr_calling status, headers, body = @app.call(env) rv = [ status, headers, Proxy.new(body, @stats) ] # the Rack server will start writing headers soon after this method @stats.incr_writing rv ensure @stats.decr_calling end end def stats_response # :nodoc: body = "calling: #{@stats.calling}\n" \ "writing: #{@stats.writing}\n" if defined?(Raindrops::Linux.tcp_listener_stats) Raindrops::Linux.tcp_listener_stats(@tcp).each do |addr,stats| body << "#{addr} active: #{stats.active}\n" \ "#{addr} queued: #{stats.queued}\n" end if @tcp Raindrops::Linux.unix_listener_stats(@unix).each do |addr,stats| body << "#{addr} active: #{stats.active}\n" \ "#{addr} queued: #{stats.queued}\n" end if @unix end headers = { "Content-Type" => "text/plain", "Content-Length" => body.size.to_s, } [ 200, headers, [ body ] ] end end raindrops-0.19.0/lib/raindrops/aggregate.rb0000644000004100000410000000045713145550250020662 0ustar www-datawww-data# -*- encoding: binary -*- # # raindrops may use the {aggregate}[https://github.com/josephruscio/aggregate] # RubyGem to aggregate statistics from TCP_Info lookups. module Raindrops::Aggregate autoload :PMQ, "raindrops/aggregate/pmq" autoload :LastDataRecv, "raindrops/aggregate/last_data_recv" end raindrops-0.19.0/lib/raindrops/aggregate/0000755000004100000410000000000013145550250020327 5ustar www-datawww-dataraindrops-0.19.0/lib/raindrops/aggregate/pmq.rb0000644000004100000410000001556313145550250021463 0ustar www-datawww-data# -*- encoding: binary -*- require "tempfile" require "aggregate" require "posix_mq" require "fcntl" require "thread" require "stringio" # \Aggregate + POSIX message queues support for Ruby 1.9+ and \Linux # # This class is duck-type compatible with \Aggregate and allows us to # aggregate and share statistics from multiple processes/threads aided # POSIX message queues. This is designed to be used with the # Raindrops::LastDataRecv Rack application, but can be used independently # on compatible Runtimes. # # Unlike the core of raindrops, this is only supported on Ruby 1.9+ and # Linux 2.6+. Using this class requires the following additional RubyGems # or libraries: # # * aggregate (tested with 0.2.2) # * posix_mq (tested with 1.0.0) # # == Design # # There is one master thread which aggregates statistics. Individual # worker processes or threads will write to a shared POSIX message # queue (default: "/raindrops") that the master reads from. At a # predefined interval, the master thread will write out to a shared, # anonymous temporary file that workers may read from # # Setting +:worker_interval+ and +:master_interval+ to +1+ will result # in perfect accuracy but at the cost of a high synchronization # overhead. Larger intervals mean less frequent messaging for higher # performance but lower accuracy. class Raindrops::Aggregate::PMQ # :stopdoc: # These constants are for Linux. This is designed for aggregating # TCP_INFO. RDLOCK = [ Fcntl::F_RDLCK ].pack("s @256".freeze).freeze WRLOCK = [ Fcntl::F_WRLCK ].pack("s @256".freeze).freeze UNLOCK = [ Fcntl::F_UNLCK ].pack("s @256".freeze).freeze # :startdoc: # returns the number of dropped messages sent to a POSIX message # queue if non-blocking operation was desired with :lossy attr_reader :nr_dropped # # Creates a new Raindrops::Aggregate::PMQ object # # Raindrops::Aggregate::PMQ.new(options = {}) -> aggregate # # +options+ is a hash that accepts the following keys: # # * :queue - name of the POSIX message queue (default: "/raindrops") # * :worker_interval - interval to send to the master (default: 10) # * :master_interval - interval to for the master to write out (default: 5) # * :lossy - workers drop packets if master cannot keep up (default: false) # * :aggregate - \Aggregate object (default: \Aggregate.new) # * :mq_umask - umask for creatingthe POSIX message queue (default: 0666) # def initialize(params = {}) opts = { :queue => ENV["RAINDROPS_MQUEUE"] || "/raindrops", :worker_interval => 10, :master_interval => 5, :lossy => false, :mq_attr => nil, :mq_umask => 0666, :aggregate => Aggregate.new, }.merge! params @master_interval = opts[:master_interval] @worker_interval = opts[:worker_interval] @aggregate = opts[:aggregate] @worker_queue = @worker_interval ? [] : nil @mutex = Mutex.new @mq_name = opts[:queue] mq = POSIX_MQ.new @mq_name, :w, opts[:mq_umask], opts[:mq_attr] Tempfile.open("raindrops_pmq") do |t| @wr = File.open(t.path, "wb") @rd = File.open(t.path, "rb") end @wr.sync = true @cached_aggregate = @aggregate flush_master @mq_send = if opts[:lossy] @nr_dropped = 0 mq.nonblock = true mq.method :trysend else mq.method :send end end # adds a sample to the underlying \Aggregate object def << val if q = @worker_queue q << val if q.size >= @worker_interval mq_send(q) or @nr_dropped += 1 q.clear end else mq_send(val) or @nr_dropped += 1 end end def mq_send(val) # :nodoc: @cached_aggregate = nil @mq_send.call Marshal.dump(val) end # # Starts running a master loop, usually in a dedicated thread or process: # # Thread.new { agg.master_loop } # # Any worker can call +agg.stop_master_loop+ to stop the master loop # (possibly causing the thread or process to exit) def master_loop buf = "" a = @aggregate nr = 0 mq = POSIX_MQ.new @mq_name, :r # this one is always blocking begin if (nr -= 1) < 0 nr = @master_interval flush_master end mq.shift(buf) data = begin Marshal.load(buf) or return rescue ArgumentError, TypeError next end Array === data ? data.each { |x| a << x } : a << data rescue Errno::EINTR rescue => e warn "Unhandled exception in #{__FILE__}:#{__LINE__}: #{e}" break end while true ensure flush_master end # Loads the last shared \Aggregate from the master thread/process def aggregate @cached_aggregate ||= begin flush Marshal.load(synchronize(@rd, RDLOCK) do |rd| dst = StringIO.new dst.binmode IO.copy_stream(rd, dst, rd.size, 0) dst.string end) end end # Flushes the currently aggregate statistics to a temporary file. # There is no need to call this explicitly as +:worker_interval+ defines # how frequently your data will be flushed for workers to read. def flush_master dump = Marshal.dump @aggregate synchronize(@wr, WRLOCK) do |wr| wr.truncate 0 wr.rewind wr.write(dump) end end # stops the currently running master loop, may be called from any # worker thread or process def stop_master_loop sleep 0.1 until mq_send(false) rescue Errno::EINTR retry end def lock! io, type # :nodoc: io.fcntl Fcntl::F_SETLKW, type rescue Errno::EINTR retry end # we use both a mutex for thread-safety and fcntl lock for process-safety def synchronize io, type # :nodoc: @mutex.synchronize do begin type = type.dup lock! io, type yield io ensure lock! io, type.replace(UNLOCK) type.clear end end end # flushes the local queue of the worker process, sending all pending # data to the master. There is no need to call this explicitly as # +:worker_interval+ defines how frequently your queue will be flushed def flush if q = @local_queue && ! q.empty? mq_send q q.clear end nil end # proxy for \Aggregate#count def count; aggregate.count; end # proxy for \Aggregate#max def max; aggregate.max; end # proxy for \Aggregate#min def min; aggregate.min; end # proxy for \Aggregate#sum def sum; aggregate.sum; end # proxy for \Aggregate#mean def mean; aggregate.mean; end # proxy for \Aggregate#std_dev def std_dev; aggregate.std_dev; end # proxy for \Aggregate#outliers_low def outliers_low; aggregate.outliers_low; end # proxy for \Aggregate#outliers_high def outliers_high; aggregate.outliers_high; end # proxy for \Aggregate#to_s def to_s(*args); aggregate.to_s(*args); end # proxy for \Aggregate#each def each; aggregate.each { |*args| yield(*args) }; end # proxy for \Aggregate#each_nonzero def each_nonzero; aggregate.each_nonzero { |*args| yield(*args) }; end end raindrops-0.19.0/lib/raindrops/aggregate/last_data_recv.rb0000644000004100000410000000416613145550250023636 0ustar www-datawww-data# -*- encoding: binary -*- require "socket" # # # This module is used to extend TCPServer and Kgio::TCPServer objects # and aggregate +last_data_recv+ times for all accepted clients. It # is designed to be used with Raindrops::LastDataRecv Rack application # but can be easily changed to work with other stats collection devices. # # Methods wrapped include: # - TCPServer#accept # - TCPServer#accept_nonblock # - Kgio::TCPServer#kgio_accept # - Kgio::TCPServer#kgio_tryaccept module Raindrops::Aggregate::LastDataRecv # The integer value of +last_data_recv+ is sent to this object. # This is usually a duck type compatible with the \Aggregate class, # but can be *anything* that accepts the *<<* method. attr_accessor :raindrops_aggregate @@default_aggregate = nil # By default, this is a Raindrops::Aggregate::PMQ object # It may be anything that responds to *<<* def self.default_aggregate @@default_aggregate ||= Raindrops::Aggregate::PMQ.new end # Assign any object that responds to *<<* def self.default_aggregate=(agg) @@default_aggregate = agg end # automatically extends any TCPServer objects used by Unicorn def self.cornify! Unicorn::HttpServer::LISTENERS.each do |sock| sock.extend(self) if TCPServer === sock end end # each extended object needs to have TCP_DEFER_ACCEPT enabled # for accuracy. def self.extended(obj) obj.raindrops_aggregate = default_aggregate # obj.setsockopt Socket::SOL_TCP, tcp_defer_accept = 9, seconds = 60 obj.setsockopt Socket::SOL_TCP, 9, 60 end # :stopdoc: def kgio_tryaccept(*args) count! super end def kgio_accept(*args) count! super end def accept count! super end def accept_nonblock count! super end # :startdoc: # The +last_data_recv+ member of Raindrops::TCP_Info can be used to # infer the time a client spent in the listen queue before it was # accepted. # # We require TCP_DEFER_ACCEPT on the listen socket for # +last_data_recv+ to be accurate def count!(io) if io x = Raindrops::TCP_Info.new(io) @raindrops_aggregate << x.last_data_recv end io end end raindrops-0.19.0/lib/raindrops/last_data_recv.rb0000644000004100000410000000607513145550250021711 0ustar www-datawww-data# -*- encoding: binary -*- require "raindrops" # This is highly experimental! # # A self-contained Rack application for aggregating in the # +tcpi_last_data_recv+ field in +struct+ +tcp_info+ defined in # +/usr/include/linux/tcp.h+. This is only useful for \Linux 2.6 and later. # This primarily supports Unicorn and derived servers, but may also be # used with any Ruby web server using the core TCPServer class in Ruby. # # Hitting the Rack endpoint configured for this application will return # a an ASCII histogram response body with the following headers: # # - X-Count - number of requests received # # The following headers are only present if X-Count is greater than one. # # - X-Min - lowest last_data_recv time recorded (in milliseconds) # - X-Max - highest last_data_recv time recorded (in milliseconds) # - X-Mean - mean last_data_recv time recorded (rounded, in milliseconds) # - X-Std-Dev - standard deviation of last_data_recv times # - X-Outliers-Low - number of low outliers (hopefully many!) # - X-Outliers-High - number of high outliers (hopefully zero!) # # == To use with Unicorn and derived servers (preload_app=false): # # Put the following in our Unicorn config file (not config.ru): # # require "raindrops/last_data_recv" # # Then follow the instructions below for config.ru: # # == To use with any Rack server using TCPServer # # Setup a route for Raindrops::LastDataRecv in your Rackup config file # (typically config.ru): # # require "raindrops" # map "/raindrops/last_data_recv" do # run Raindrops::LastDataRecv.new # end # map "/" do # use SomeMiddleware # use MoreMiddleware # # ... # run YourAppHere.new # end # # == To use with any other Ruby web server that uses TCPServer # # Put the following in any piece of Ruby code loaded after the server has # bound its TCP listeners: # # ObjectSpace.each_object(TCPServer) do |s| # s.extend Raindrops::Aggregate::LastDataRecv # end # # Thread.new do # Raindrops::Aggregate::LastDataRecv.default_aggregate.master_loop # end # # Then follow the above instructions for config.ru # class Raindrops::LastDataRecv # :stopdoc: # trigger autoloads if defined?(Unicorn) agg = Raindrops::Aggregate::LastDataRecv.default_aggregate AGGREGATE_THREAD = Thread.new { agg.master_loop } end # :startdoc def initialize(opts = {}) if defined?(Unicorn::HttpServer::LISTENERS) Raindrops::Aggregate::LastDataRecv.cornify! end @aggregate = opts[:aggregate] || Raindrops::Aggregate::LastDataRecv.default_aggregate end def call(_) a = @aggregate count = a.count headers = { "Content-Type" => "text/plain", "X-Count" => count.to_s, } if count > 1 headers["X-Min"] = a.min.to_s headers["X-Max"] = a.max.to_s headers["X-Mean"] = a.mean.round.to_s headers["X-Std-Dev"] = a.std_dev.round.to_s headers["X-Outliers-Low"] = a.outliers_low.to_s headers["X-Outliers-High"] = a.outliers_high.to_s end body = a.to_s headers["Content-Length"] = body.size.to_s [ 200, headers, [ body ] ] end end raindrops-0.19.0/.manifest0000644000004100000410000000244313145550250015444 0ustar www-datawww-data.document .gitattributes .gitignore .manifest .olddoc.yml COPYING GIT-VERSION-FILE GIT-VERSION-GEN GNUmakefile LATEST LICENSE NEWS README TODO archive/.gitignore archive/slrnpull.conf examples/linux-listener-stats.rb examples/middleware.ru examples/watcher.ru examples/watcher_demo.ru examples/yahns.conf.rb examples/zbatery.conf.rb ext/raindrops/extconf.rb ext/raindrops/linux_inet_diag.c ext/raindrops/my_fileno.h ext/raindrops/raindrops.c ext/raindrops/raindrops_atomic.h ext/raindrops/tcp_info.c lib/raindrops.rb lib/raindrops/aggregate.rb lib/raindrops/aggregate/last_data_recv.rb lib/raindrops/aggregate/pmq.rb lib/raindrops/last_data_recv.rb lib/raindrops/linux.rb lib/raindrops/middleware.rb lib/raindrops/middleware/proxy.rb lib/raindrops/struct.rb lib/raindrops/watcher.rb pkg.mk raindrops.gemspec setup.rb test/ipv6_enabled.rb test/rack_unicorn.rb test/test_aggregate_pmq.rb test/test_inet_diag_socket.rb test/test_last_data_recv_unicorn.rb test/test_linux.rb test/test_linux_all_tcp_listen_stats.rb test/test_linux_all_tcp_listen_stats_leak.rb test/test_linux_ipv6.rb test/test_linux_middleware.rb test/test_middleware.rb test/test_middleware_unicorn.rb test/test_middleware_unicorn_ipv6.rb test/test_raindrops.rb test/test_raindrops_gc.rb test/test_struct.rb test/test_tcp_info.rb test/test_watcher.rb raindrops-0.19.0/test/0000755000004100000410000000000013145550250014611 5ustar www-datawww-dataraindrops-0.19.0/test/test_raindrops.rb0000644000004100000410000000765113145550250020207 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'raindrops' class TestRaindrops < Test::Unit::TestCase def test_raindrop_counter_max assert_kind_of Integer, Raindrops::MAX assert Raindrops::MAX > 0 printf "Raindrops::MAX = 0x%x\n", Raindrops::MAX end def test_raindrop_size assert_kind_of Integer, Raindrops::SIZE assert Raindrops::SIZE > 0 puts "Raindrops::SIZE = #{Raindrops::SIZE}" end def test_page_size assert_kind_of Integer, Raindrops::PAGE_SIZE assert Raindrops::PAGE_SIZE > Raindrops::SIZE end def test_size_and_capa rd = Raindrops.new(4) assert_equal 4, rd.size assert rd.capa >= rd.size end def test_ary rd = Raindrops.new(4) assert_equal [0, 0, 0, 0] , rd.to_ary end def test_incr_no_args rd = Raindrops.new(4) assert_equal 1, rd.incr(0) assert_equal [1, 0, 0, 0], rd.to_ary end def test_incr_args rd = Raindrops.new(4) assert_equal 6, rd.incr(3, 6) assert_equal [0, 0, 0, 6], rd.to_ary end def test_decr_args rd = Raindrops.new(4) rd[3] = 6 assert_equal 5, rd.decr(3, 1) assert_equal [0, 0, 0, 5], rd.to_ary end def test_incr_shared rd = Raindrops.new(2) 5.times do pid = fork { rd.incr(1) } _, status = Process.waitpid2(pid) assert status.success? end assert_equal [0, 5], rd.to_ary end def test_incr_decr rd = Raindrops.new(1) fork { 1000000.times { rd.incr(0) } } 1000.times { rd.decr(0) } statuses = Process.waitall statuses.each { |pid, status| assert status.success? } assert_equal [999000], rd.to_ary end def test_bad_incr rd = Raindrops.new(1) assert_raises(ArgumentError) { rd.incr(-1) } assert_raises(ArgumentError) { rd.incr(2) } assert_raises(ArgumentError) { rd.incr(0xffffffff) } end def test_dup @rd = Raindrops.new(1) rd = @rd.dup assert_equal 1, @rd.incr(0) assert_equal 1, rd.incr(0) assert_equal 2, rd.incr(0) assert_equal 2, rd[0] assert_equal 1, @rd[0] end def test_clone @rd = Raindrops.new(1) rd = @rd.clone assert_equal 1, @rd.incr(0) assert_equal 1, rd.incr(0) assert_equal 2, rd.incr(0) assert_equal 2, rd[0] assert_equal 1, @rd[0] end def test_big expect = (1..256).map { 0 } rd = Raindrops.new(256) assert_equal expect, rd.to_ary assert_nothing_raised { rd[255] = 5 } assert_equal 5, rd[255] assert_nothing_raised { rd[2] = 2 } expect[255] = 5 expect[2] = 2 assert_equal expect, rd.to_ary end def test_resize rd = Raindrops.new(4) assert_equal 4, rd.size assert_equal rd.capa, rd.size = rd.capa assert_equal rd.capa, rd.to_ary.size assert_equal 0, rd[rd.capa - 1] assert_equal 1, rd.incr(rd.capa - 1) assert_raises(ArgumentError) { rd[rd.capa] } end def test_resize_mremap rd = Raindrops.new(4) assert_equal 4, rd.size old_capa = rd.capa rd.size = rd.capa + 1 assert_equal old_capa * 2, rd.capa # mremap() is currently broken with MAP_SHARED # https://bugzilla.kernel.org/show_bug.cgi?id=8691 assert_equal 0, rd[old_capa] assert_equal rd.capa, rd.to_ary.size assert_equal 0, rd[rd.capa - 1] assert_equal 1, rd.incr(rd.capa - 1) assert_raises(ArgumentError) { rd[rd.capa] } rescue RangeError end # if RUBY_PLATFORM =~ /linux/ def test_evaporate rd = Raindrops.new 1 assert_nil rd.evaporate! assert_raises(StandardError) { rd.evaporate! } end def test_evaporate_with_fork tmp = Raindrops.new 2 pid = fork do tmp.incr 0 exit(tmp.evaporate! == nil) end _, status = Process.waitpid2(pid) assert status.success? assert_equal [ 1, 0 ], tmp.to_ary tmp.incr 1 assert_equal [ 1, 1 ], tmp.to_ary pid = fork do tmp.incr 1 exit([ 1, 2 ] == tmp.to_ary) end _, status = Process.waitpid2(pid) assert status.success? assert_equal [ 1, 2 ], tmp.to_ary end end raindrops-0.19.0/test/test_inet_diag_socket.rb0000644000004100000410000000071413145550250021472 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'raindrops' require 'fcntl' $stderr.sync = $stdout.sync = true class TestInetDiagSocket < Test::Unit::TestCase def test_new sock = Raindrops::InetDiagSocket.new assert_kind_of Socket, sock assert_kind_of Integer, sock.fileno flags = sock.fcntl(Fcntl::F_GETFD) assert_equal Fcntl::FD_CLOEXEC, flags & Fcntl::FD_CLOEXEC assert_nil sock.close end end if RUBY_PLATFORM =~ /linux/ raindrops-0.19.0/test/ipv6_enabled.rb0000644000004100000410000000033313145550250017473 0ustar www-datawww-datadef ipv6_enabled? tmp = TCPServer.new(ENV["TEST_HOST6"] || '::1', 0) tmp.close true rescue => e warn "skipping IPv6 tests, host does not seem to be IPv6 enabled:" warn " #{e.class}: #{e}" false end raindrops-0.19.0/test/test_tcp_info.rb0000644000004100000410000000446113145550250020003 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'tempfile' require 'raindrops' require 'socket' require 'pp' $stderr.sync = $stdout.sync = true class TestTCP_Info < Test::Unit::TestCase TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' # Linux kernel commit 5ee3afba88f5a79d0bff07ddd87af45919259f91 TCP_INFO_useful_listenq = `uname -r`.strip >= '2.6.24' def test_tcp_server_unacked return if RUBY_PLATFORM !~ /linux/ # unacked not implemented on others... s = TCPServer.new(TEST_ADDR, 0) rv = Raindrops::TCP_Info.new s c = TCPSocket.new TEST_ADDR, s.addr[1] tmp = Raindrops::TCP_Info.new s TCP_INFO_useful_listenq and assert_equal 1, tmp.unacked assert_equal 0, rv.unacked a = s.accept tmp = Raindrops::TCP_Info.new s assert_equal 0, tmp.unacked before = tmp.object_id tmp.get!(s) assert_equal before, tmp.object_id ensure [ c, a, s ].compact.each(&:close) end def test_accessors s = TCPServer.new TEST_ADDR, 0 tmp = Raindrops::TCP_Info.new s tcp_info_methods = tmp.methods - Object.new.methods assert tcp_info_methods.size >= 32 tcp_info_methods.each do |m| next if m.to_sym == :get! next if ! tmp.respond_to?(m) val = tmp.__send__ m assert_kind_of Integer, val assert val >= 0 end assert tmp.respond_to?(:state), 'every OS knows about TCP state, right?' ensure s.close end def test_tcp_server_delayed delay = 0.010 delay_ms = (delay * 1000).to_i s = TCPServer.new(TEST_ADDR, 0) c = TCPSocket.new TEST_ADDR, s.addr[1] c.syswrite "." sleep(delay * 1.2) a = s.accept i = Raindrops::TCP_Info.new(a) assert i.last_data_recv >= delay_ms, "#{i.last_data_recv} < #{delay_ms}" ensure c.close if c a.close if a s.close end def test_tcp_server_state_closed s = TCPServer.new(TEST_ADDR, 0) c = TCPSocket.new(TEST_ADDR, s.addr[1]) i = Raindrops::TCP_Info.allocate a = s.accept i.get!(a) state = i.state if Raindrops.const_defined?(:TCP) assert_equal state, Raindrops::TCP[:ESTABLISHED] end c = c.close sleep(0.01) # wait for kernel to update state i.get!(a) assert_not_equal state, i.state ensure s.close if s c.close if c a.close if a end end if defined? Raindrops::TCP_Info raindrops-0.19.0/test/test_middleware.rb0000644000004100000410000000670613145550250020323 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'raindrops' class TestMiddleware < Test::Unit::TestCase def setup @resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' } @response = [ 200, @resp_headers, [] ] @app = lambda { |env| @response } end def test_setup app = Raindrops::Middleware.new(@app) response = app.call({}) assert_equal @response[0,2], response[0,2] assert response.last.kind_of?(Raindrops::Middleware::Proxy) assert response.last.object_id != app.object_id tmp = [] response.last.each { |y| tmp << y } assert tmp.empty? end def test_alt_stats stats = Raindrops::Middleware::Stats.new app = lambda { |env| if (stats.writing == 0 && stats.calling == 1) @app.call(env) else [ 500, @resp_headers, [] ] end } app = Raindrops::Middleware.new(app, :stats => stats) response = app.call({}) assert_equal 0, stats.calling assert_equal 1, stats.writing assert_equal 200, response[0] assert response.last.kind_of?(Raindrops::Middleware::Proxy) tmp = [] response.last.each do |y| assert_equal 1, stats.writing tmp << y end assert tmp.empty? end def test_default_endpoint app = Raindrops::Middleware.new(@app) response = app.call("PATH_INFO" => "/_raindrops") expect = [ 200, { "Content-Type" => "text/plain", "Content-Length" => "22" }, [ "calling: 0\nwriting: 0\n" ] ] assert_equal expect, response end def test_alt_endpoint app = Raindrops::Middleware.new(@app, :path => "/foo") response = app.call("PATH_INFO" => "/foo") expect = [ 200, { "Content-Type" => "text/plain", "Content-Length" => "22" }, [ "calling: 0\nwriting: 0\n" ] ] assert_equal expect, response end def test_concurrent rda, wra = IO.pipe rdb, wrb = IO.pipe app = lambda do |env| wrb.close wra.syswrite('.') wra.close # wait until parent has run app.call for stats endpoint rdb.read @app.call(env) end app = Raindrops::Middleware.new(app) pid = fork { app.call({}) } rdb.close # wait til child is running in app.call assert_equal '.', rda.sysread(1) rda.close response = app.call("PATH_INFO" => "/_raindrops") expect = [ 200, { "Content-Type" => "text/plain", "Content-Length" => "22" }, [ "calling: 1\nwriting: 0\n" ] ] assert_equal expect, response wrb.close # unblock child process assert Process.waitpid2(pid).last.success? # we didn't call close the body in the forked child, so it'll always be # marked as writing, a real server would close the body response = app.call("PATH_INFO" => "/_raindrops") expect = [ 200, { "Content-Type" => "text/plain", "Content-Length" => "22" }, [ "calling: 0\nwriting: 1\n" ] ] assert_equal expect, response end def test_middleware_proxy_to_path_missing app = Raindrops::Middleware.new(@app) response = app.call({}) body = response[2] assert_kind_of Raindrops::Middleware::Proxy, body assert ! body.respond_to?(:to_path) assert body.respond_to?(:close) orig_body = @response[2] def orig_body.to_path; "/dev/null"; end assert body.respond_to?(:to_path) assert_equal "/dev/null", body.to_path def orig_body.body; "this is a body"; end assert body.respond_to?(:body) assert_equal "this is a body", body.body end end raindrops-0.19.0/test/test_last_data_recv_unicorn.rb0000644000004100000410000000374613145550250022717 0ustar www-datawww-data# -*- encoding: binary -*- require "./test/rack_unicorn" require "tempfile" require "net/http" $stderr.sync = $stdout.sync = true pmq = begin Raindrops::Aggregate::PMQ rescue LoadError => e warn "W: #{e} skipping #{__FILE__}" false end if RUBY_VERSION.to_f < 1.9 pmq = false warn "W: skipping test=#{__FILE__}, only Ruby 1.9 supported for now" end class TestLastDataRecvUnicorn < Test::Unit::TestCase def setup @queue = "/test.#{rand}" @host = ENV["UNICORN_TEST_ADDR"] || "127.0.0.1" @sock = TCPServer.new @host, 0 @port = @sock.addr[1] ENV["UNICORN_FD"] = @sock.fileno.to_s @host_with_port = "#@host:#@port" @cfg = Tempfile.new 'unicorn_config_file' @cfg.puts "require 'raindrops'" @cfg.puts "preload_app true" ENV['RAINDROPS_MQUEUE'] = @queue # @cfg.puts "worker_processes 4" @opts = { :listeners => [ @host_with_port ], :config_file => @cfg.path } end def test_auto_listener @srv = fork { Thread.abort_on_exception = true app = %q!Rack::Builder.new do map("/ldr") { run Raindrops::LastDataRecv.new } map("/") { run Rack::Lobster.new } end.to_app! def app.arity; 0; end def app.call; eval self; end Unicorn::HttpServer.new(app, @opts).start.join } 400.times { assert_kind_of Net::HTTPSuccess, get("/") } resp = get("/ldr") # # p(resp.methods - Object.methods) # resp.each_header { |k,v| p [k, "=" , v] } assert resp.header["x-count"] assert resp.header["x-min"] assert resp.header["x-max"] assert resp.header["x-mean"] assert resp.header["x-std-dev"] assert resp.header["x-outliers-low"] assert resp.header["x-outliers-high"] assert resp.body.size > 0 end def get(path) Net::HTTP.start(@host, @port) { |http| http.get path } end def teardown Process.kill :QUIT, @srv _, status = Process.waitpid2 @srv assert status.success? POSIX_MQ.unlink @queue end end if defined?(Unicorn) && RUBY_PLATFORM =~ /linux/ && pmq raindrops-0.19.0/test/rack_unicorn.rb0000644000004100000410000000041413145550250017612 0ustar www-datawww-data# -*- encoding: binary -*- require "test/unit" require "raindrops" require "rack" require "rack/lobster" require "open-uri" begin require "unicorn" require "rack/lobster" rescue LoadError => e warn "W: #{e} skipping test since Rack or Unicorn was not found" end raindrops-0.19.0/test/test_linux_middleware.rb0000644000004100000410000000321213145550250021527 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'tempfile' require 'raindrops' require 'socket' $stderr.sync = $stdout.sync = true class TestLinuxMiddleware < Test::Unit::TestCase def setup @resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' } @response = [ 200, @resp_headers, [] ] @app = lambda { |env| @response } @to_close = [] end def teardown @to_close.each { |io| io.close unless io.closed? } end def test_unix_listener tmp = Tempfile.new("") File.unlink(tmp.path) @to_close << UNIXServer.new(tmp.path) app = Raindrops::Middleware.new(@app, :listeners => [tmp.path]) linux_extra = "#{tmp.path} active: 0\n#{tmp.path} queued: 0\n" response = app.call("PATH_INFO" => "/_raindrops") expect = [ 200, { "Content-Type" => "text/plain", "Content-Length" => (22 + linux_extra.size).to_s }, [ "calling: 0\nwriting: 0\n#{linux_extra}" \ ] ] assert_equal expect, response end def test_unix_listener_queued tmp = Tempfile.new("") File.unlink(tmp.path) @to_close << UNIXServer.new(tmp.path) @to_close << UNIXSocket.new(tmp.path) app = Raindrops::Middleware.new(@app, :listeners => [tmp.path]) linux_extra = "#{tmp.path} active: 0\n#{tmp.path} queued: 1\n" response = app.call("PATH_INFO" => "/_raindrops") expect = [ 200, { "Content-Type" => "text/plain", "Content-Length" => (22 + linux_extra.size).to_s }, [ "calling: 0\nwriting: 0\n#{linux_extra}" \ ] ] assert_equal expect, response end end if RUBY_PLATFORM =~ /linux/ raindrops-0.19.0/test/test_linux_ipv6.rb0000644000004100000410000001132113145550250020276 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'tempfile' require 'raindrops' require 'socket' require 'pp' require "./test/ipv6_enabled" $stderr.sync = $stdout.sync = true class TestLinuxIPv6 < Test::Unit::TestCase include Raindrops::Linux TEST_ADDR = ENV["TEST_HOST6"] || "::1" def setup @to_close = [] end def teardown @to_close.each { |io| io.close unless io.closed? } end def test_tcp s = TCPServer.new(TEST_ADDR, 0) port = s.addr[1] addr = "[#{TEST_ADDR}]:#{port}" addrs = [ addr ] stats = tcp_listener_stats(addrs) assert_equal 1, stats.size assert_equal 0, stats[addr].queued assert_equal 0, stats[addr].active @to_close << TCPSocket.new(TEST_ADDR, port) stats = tcp_listener_stats(addrs) assert_equal 1, stats.size assert_equal 1, stats[addr].queued assert_equal 0, stats[addr].active @to_close << s.accept stats = tcp_listener_stats(addrs) assert_equal 1, stats.size assert_equal 0, stats[addr].queued assert_equal 1, stats[addr].active end def test_tcp_multi s1 = TCPServer.new(TEST_ADDR, 0) s2 = TCPServer.new(TEST_ADDR, 0) port1, port2 = s1.addr[1], s2.addr[1] addr1, addr2 = "[#{TEST_ADDR}]:#{port1}", "[#{TEST_ADDR}]:#{port2}" addrs = [ addr1, addr2 ] stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 0, stats[addr1].active assert_equal 0, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << TCPSocket.new(TEST_ADDR, port1) stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 1, stats[addr1].queued assert_equal 0, stats[addr1].active assert_equal 0, stats[addr2].queued assert_equal 0, stats[addr2].active sc1 = s1.accept stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 0, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << TCPSocket.new(TEST_ADDR, port2) stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 1, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << TCPSocket.new(TEST_ADDR, port2) stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 2, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << s2.accept stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 1, stats[addr2].queued assert_equal 1, stats[addr2].active sc1.close stats = tcp_listener_stats(addrs) assert_equal 0, stats[addr1].queued assert_equal 0, stats[addr1].active assert_equal 1, stats[addr2].queued assert_equal 1, stats[addr2].active end def test_invalid_addresses assert_raises(ArgumentError) { tcp_listener_stats(%w([1:::5)) } assert_raises(ArgumentError) { tcp_listener_stats(%w([1:::]5)) } end # tries to overflow buffers def test_tcp_stress_test nr_proc = 32 nr_sock = 500 s = TCPServer.new(TEST_ADDR, 0) port = s.addr[1] addr = "[#{TEST_ADDR}]:#{port}" addrs = [ addr ] rda, wra = IO.pipe rdb, wrb = IO.pipe nr_proc.times do fork do rda.close wrb.close @to_close.concat((1..nr_sock).map { s.accept }) wra.syswrite('.') wra.close rdb.sysread(1) # wait for parent to nuke us end end nr_proc.times do fork do rda.close wrb.close @to_close.concat((1..nr_sock).map { TCPSocket.new(TEST_ADDR, port) }) wra.syswrite('.') wra.close rdb.sysread(1) # wait for parent to nuke us end end assert_equal('.' * (nr_proc * 2), rda.read(nr_proc * 2)) rda.close stats = tcp_listener_stats(addrs) expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 0] } assert_equal expect, stats @to_close << TCPSocket.new(TEST_ADDR, port) stats = tcp_listener_stats(addrs) expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 1] } assert_equal expect, stats if ENV["BENCHMARK"].to_i != 0 require 'benchmark' puts(Benchmark.measure{1000.times { tcp_listener_stats(addrs) }}) end wrb.syswrite('.' * (nr_proc * 2)) # broadcast a wakeup statuses = Process.waitall statuses.each { |(_,status)| assert status.success?, status.inspect } end if ENV["STRESS"].to_i != 0 end if RUBY_PLATFORM =~ /linux/ && ipv6_enabled? raindrops-0.19.0/test/test_middleware_unicorn_ipv6.rb0000644000004100000410000000212313145550250023011 0ustar www-datawww-data# -*- encoding: binary -*- require "./test/rack_unicorn" require "./test/ipv6_enabled" $stderr.sync = $stdout.sync = true class TestMiddlewareUnicornIPv6 < Test::Unit::TestCase def setup @host = ENV["TEST_HOST6"] || "::1" sock = TCPServer.new @host, 0 @port = sock.addr[1] ENV["UNICORN_FD"] = sock.fileno.to_s @host_with_port = "[#@host]:#@port" @opts = { :listeners => [ @host_with_port ] } @addr_regexp = Regexp.escape @host_with_port end def test_auto_listener @app = Rack::Builder.new do use Raindrops::Middleware run Rack::Lobster.new end @srv = fork { Unicorn::HttpServer.new(@app, @opts).start.join } s = TCPSocket.new @host, @port s.write "GET /_raindrops HTTP/1.0\r\n\r\n" resp = s.read _, body = resp.split(/\r\n\r\n/, 2) assert_match %r{^#@addr_regexp active: 1$}, body assert_match %r{^#@addr_regexp queued: 0$}, body end def teardown Process.kill :QUIT, @srv _, status = Process.waitpid2 @srv assert status.success? end end if defined?(Unicorn) && RUBY_PLATFORM =~ /linux/ && ipv6_enabled? raindrops-0.19.0/test/test_linux_all_tcp_listen_stats_leak.rb0000644000004100000410000000176413145550250024632 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'raindrops' require 'socket' require 'benchmark' $stderr.sync = $stdout.sync = true class TestLinuxAllTcpListenStatsLeak < Test::Unit::TestCase TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' def rss_kb File.readlines("/proc/#$$/status").grep(/VmRSS:/)[0].split(/\s+/)[1].to_i end def test_leak s = TCPServer.new(TEST_ADDR, 0) start_kb = rss_kb p [ :start_kb, start_kb ] assert_nothing_raised do p(Benchmark.measure { 1000.times { Raindrops::Linux.all_tcp_listener_stats } }) end cur_kb = rss_kb p [ :cur_kb, cur_kb ] now = Time.now.to_i fin = now + 60 assert_nothing_raised do 1000000000.times { |i| if (i % 1024) == 0 now = Time.now.to_i break if now > fin end Raindrops::Linux.all_tcp_listener_stats } end cur_kb = rss_kb p [ :cur_kb, cur_kb ] ensure s.close end end if ENV["STRESS"].to_i != 0 raindrops-0.19.0/test/test_watcher.rb0000644000004100000410000001306713145550250017641 0ustar www-datawww-data# -*- encoding: binary -*- require "test/unit" require "rack" require "raindrops" begin require 'aggregate' rescue LoadError => e warn "W: #{e} skipping #{__FILE__}" end class TestWatcher < Test::Unit::TestCase TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' def check_headers(headers) %w(X-Count X-Std-Dev X-Min X-Max X-Mean X-Outliers-Low X-Outliers-Low X-Last-Reset).each { |x| assert_kind_of String, headers[x], "#{x} missing" } end def teardown @app.shutdown @ios.each { |io| io.close unless io.closed? } end def setup @ios = [] @srv = TCPServer.new TEST_ADDR, 0 @ios << @srv @port = @srv.addr[1] @client = TCPSocket.new TEST_ADDR, @port @addr = "#{TEST_ADDR}:#{@port}" @ios << @client @app = Raindrops::Watcher.new :delay => 0.001 @req = Rack::MockRequest.new @app end def test_index resp = @req.get "/" assert_equal 200, resp.status.to_i t = Time.parse resp.headers["Last-Modified"] assert_in_delta Time.now.to_f, t.to_f, 2.0 end def test_active_txt resp = @req.get "/active/#@addr.txt" assert_equal 200, resp.status.to_i assert_equal "text/plain", resp.headers["Content-Type"] check_headers(resp.headers) end def test_invalid assert_nothing_raised do @req.get("/active/666.666.666.666%3A666.txt") @req.get("/queued/666.666.666.666%3A666.txt") @req.get("/active/666.666.666.666%3A666.html") @req.get("/queued/666.666.666.666%3A666.html") end addr = @app.instance_eval do @peak_active.keys + @peak_queued.keys + @resets.keys + @active.keys + @queued.keys end assert addr.grep(/666\.666\.666\.666/).empty?, addr.inspect end def test_active_html resp = @req.get "/active/#@addr.html" assert_equal 200, resp.status.to_i assert_equal "text/html", resp.headers["Content-Type"] check_headers(resp.headers) end def test_queued_txt resp = @req.get "/queued/#@addr.txt" assert_equal 200, resp.status.to_i assert_equal "text/plain", resp.headers["Content-Type"] check_headers(resp.headers) end def test_queued_html resp = @req.get "/queued/#@addr.html" assert_equal 200, resp.status.to_i assert_equal "text/html", resp.headers["Content-Type"] check_headers(resp.headers) end def test_reset resp = @req.post "/reset/#@addr" assert_equal 302, resp.status.to_i end def test_tail env = @req.class.env_for "/tail/#@addr.txt" status, headers, body = @app.call env assert_equal "text/plain", headers["Content-Type"] assert_equal 200, status.to_i tmp = [] body.each do |x| assert_kind_of String, x tmp << x break if tmp.size > 1 end end def test_tail_queued_min env = @req.class.env_for "/tail/#@addr.txt?queued_min=1" status, headers, body = @app.call env assert_equal "text/plain", headers["Content-Type"] assert_equal 200, status.to_i tmp = [] body.each do |x| tmp = TCPSocket.new TEST_ADDR, @port @ios << tmp assert_kind_of String, x assert_equal 1, x.strip.split(/\s+/).last.to_i break end end def test_x_current_header env = @req.class.env_for "/active/#@addr.txt" status, headers, body = @app.call(env) assert_equal "0", headers["X-Current"], headers.inspect env = @req.class.env_for "/queued/#@addr.txt" status, headers, body = @app.call(env) assert_equal "1", headers["X-Current"], headers.inspect @ios << @srv.accept sleep 0.1 env = @req.class.env_for "/queued/#@addr.txt" status, headers, body = @app.call(env) assert_equal "0", headers["X-Current"], headers.inspect env = @req.class.env_for "/active/#@addr.txt" status, headers, body = @app.call(env) assert_equal "1", headers["X-Current"], headers.inspect end def test_peaks env = @req.class.env_for "/active/#@addr.txt" status, headers, body = @app.call(env.dup) start = headers["X-First-Peak-At"] assert headers["X-First-Peak-At"], headers.inspect assert headers["X-Last-Peak-At"], headers.inspect assert_nothing_raised { Time.parse(headers["X-First-Peak-At"]) } assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) } before = headers["X-Last-Peak-At"] env = @req.class.env_for "/queued/#@addr.txt" status, headers, body = @app.call(env) assert_nothing_raised { Time.parse(headers["X-First-Peak-At"]) } assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) } assert_equal before, headers["X-Last-Peak-At"], "should not change" sleep 2 env = @req.class.env_for "/active/#@addr.txt" status, headers, body = @app.call(env.dup) assert_equal before, headers["X-Last-Peak-At"], headers.inspect @ios << @srv.accept begin @srv.accept_nonblock assert false, "we should not get here" rescue => e assert_kind_of Errno::EAGAIN, e end sleep 0.1 env = @req.class.env_for "/queued/#@addr.txt" status, headers, body = @app.call(env.dup) assert headers["X-Last-Peak-At"], headers.inspect assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) } assert before != headers["X-Last-Peak-At"] queued_before = headers["X-Last-Peak-At"] sleep 2 env = @req.class.env_for "/queued/#@addr.txt" status, headers, body = @app.call(env) assert_equal "0", headers["X-Current"] assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) } assert_equal queued_before, headers["X-Last-Peak-At"], "should not change" assert_equal start, headers["X-First-Peak-At"] end end if RUBY_PLATFORM =~ /linux/ && defined?(Aggregate) raindrops-0.19.0/test/test_struct.rb0000644000004100000410000000214113145550250017517 0ustar www-datawww-datarequire 'test/unit' require 'raindrops' class TestRaindrops < Test::Unit::TestCase def test_struct_new @rw = Raindrops::Struct.new(:r, :w) assert @rw.kind_of?(Class) end TMP = Raindrops::Struct.new(:r, :w) def test_init_basic tmp = TMP.new assert_equal 0, tmp.r assert_equal 1, tmp.incr_r assert_equal 1, tmp.r assert_equal({ :r => 1, :w => 0 }, tmp.to_hash) assert_equal 1, tmp[0] assert_equal 0, tmp[1] assert_equal [ :r, :w ], TMP::MEMBERS end def test_init tmp = TMP.new(5, 6) assert_equal({ :r => 5, :w => 6 }, tmp.to_hash) end def test_dup a = TMP.new(5, 6) b = a.dup assert_equal({ :r => 5, :w => 6 }, b.to_hash) assert_nothing_raised { 4.times { b.decr_r } } assert_equal({ :r => 1, :w => 6 }, b.to_hash) assert_equal({ :r => 5, :w => 6 }, a.to_hash) end class Foo < Raindrops::Struct.new(:a, :b, :c, :d) def to_ary @raindrops.to_ary end def hello "world" end end def test_subclass assert_equal [0, 0, 0, 0], Foo.new.to_ary assert_equal "world", Foo.new.hello end end raindrops-0.19.0/test/test_linux.rb0000644000004100000410000002033713145550250017341 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'tempfile' require 'raindrops' require 'socket' require 'pp' $stderr.sync = $stdout.sync = true class TestLinux < Test::Unit::TestCase include Raindrops::Linux TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' def setup @to_close = [] end def teardown @to_close.each { |io| io.close unless io.closed? } end def test_unix tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :) File.unlink(tmp.path) us = UNIXServer.new(tmp.path) stats = unix_listener_stats([tmp.path]) assert_equal 1, stats.size assert_equal 0, stats[tmp.path].active assert_equal 0, stats[tmp.path].queued @to_close << UNIXSocket.new(tmp.path) stats = unix_listener_stats([tmp.path]) assert_equal 1, stats.size assert_equal 0, stats[tmp.path].active assert_equal 1, stats[tmp.path].queued @to_close << UNIXSocket.new(tmp.path) stats = unix_listener_stats([tmp.path]) assert_equal 1, stats.size assert_equal 0, stats[tmp.path].active assert_equal 2, stats[tmp.path].queued @to_close << us.accept stats = unix_listener_stats([tmp.path]) assert_equal 1, stats.size assert_equal 1, stats[tmp.path].active assert_equal 1, stats[tmp.path].queued end def test_unix_all tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :) File.unlink(tmp.path) us = UNIXServer.new(tmp.path) @to_close << UNIXSocket.new(tmp.path) stats = unix_listener_stats assert_equal 0, stats[tmp.path].active assert_equal 1, stats[tmp.path].queued @to_close << UNIXSocket.new(tmp.path) stats = unix_listener_stats assert_equal 0, stats[tmp.path].active assert_equal 2, stats[tmp.path].queued @to_close << us.accept stats = unix_listener_stats assert_equal 1, stats[tmp.path].active assert_equal 1, stats[tmp.path].queued end def test_unix_all_unused tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :) File.unlink(tmp.path) us = UNIXServer.new(tmp.path) stats = unix_listener_stats assert stats.keys.include?(tmp.path), stats.inspect assert_equal 0, stats[tmp.path].active assert_equal 0, stats[tmp.path].queued end def test_unix_resolves_symlinks tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :) File.unlink(tmp.path) us = UNIXServer.new(tmp.path) # Create a symlink link = Tempfile.new("somethingelse") File.unlink(link.path) # We need an available name, not an actual file File.symlink(tmp.path, link.path) @to_close << UNIXSocket.new(tmp.path) stats = unix_listener_stats assert_equal 0, stats[tmp.path].active assert_equal 1, stats[tmp.path].queued @to_close << UNIXSocket.new(link.path) stats = unix_listener_stats([link.path]) assert_equal 0, stats[link.path].active assert_equal 2, stats[link.path].queued assert_equal stats[link.path].object_id, stats[tmp.path].object_id @to_close << us.accept stats = unix_listener_stats assert_equal 1, stats[tmp.path].active assert_equal 1, stats[tmp.path].queued end def test_tcp s = TCPServer.new(TEST_ADDR, 0) port = s.addr[1] addr = "#{TEST_ADDR}:#{port}" addrs = [ addr ] stats = tcp_listener_stats(addrs) assert_equal 1, stats.size assert_equal 0, stats[addr].queued assert_equal 0, stats[addr].active @to_close << TCPSocket.new(TEST_ADDR, port) stats = tcp_listener_stats(addrs) assert_equal 1, stats.size assert_equal 1, stats[addr].queued assert_equal 0, stats[addr].active @to_close << s.accept stats = tcp_listener_stats(addrs) assert_equal 1, stats.size assert_equal 0, stats[addr].queued assert_equal 1, stats[addr].active end def test_tcp_reuse_sock nlsock = Raindrops::InetDiagSocket.new s = TCPServer.new(TEST_ADDR, 0) port = s.addr[1] addr = "#{TEST_ADDR}:#{port}" addrs = [ addr ] stats = tcp_listener_stats(addrs, nlsock) assert_equal 1, stats.size assert_equal 0, stats[addr].queued assert_equal 0, stats[addr].active @to_close << TCPSocket.new(TEST_ADDR, port) stats = tcp_listener_stats(addrs, nlsock) assert_equal 1, stats.size assert_equal 1, stats[addr].queued assert_equal 0, stats[addr].active @to_close << s.accept stats = tcp_listener_stats(addrs, nlsock) assert_equal 1, stats.size assert_equal 0, stats[addr].queued assert_equal 1, stats[addr].active ensure nlsock.close end def test_tcp_multi s1 = TCPServer.new(TEST_ADDR, 0) s2 = TCPServer.new(TEST_ADDR, 0) port1, port2 = s1.addr[1], s2.addr[1] addr1, addr2 = "#{TEST_ADDR}:#{port1}", "#{TEST_ADDR}:#{port2}" addrs = [ addr1, addr2 ] stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 0, stats[addr1].active assert_equal 0, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << TCPSocket.new(TEST_ADDR, port1) stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 1, stats[addr1].queued assert_equal 0, stats[addr1].active assert_equal 0, stats[addr2].queued assert_equal 0, stats[addr2].active sc1 = s1.accept stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 0, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << TCPSocket.new(TEST_ADDR, port2) stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 1, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << TCPSocket.new(TEST_ADDR, port2) stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 2, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << s2.accept stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 1, stats[addr2].queued assert_equal 1, stats[addr2].active sc1.close stats = tcp_listener_stats(addrs) assert_equal 0, stats[addr1].queued assert_equal 0, stats[addr1].active assert_equal 1, stats[addr2].queued assert_equal 1, stats[addr2].active # make sure we don't leave "true" placeholders in results if a # listener becomes invalid (even momentarily). s2.close stats = tcp_listener_stats(addrs) assert stats.values.all? { |x| x.instance_of?(Raindrops::ListenStats) }, "placeholders left: #{stats.inspect}" end # tries to overflow buffers def test_tcp_stress_test nr_proc = 32 nr_sock = 500 s = TCPServer.new(TEST_ADDR, 0) port = s.addr[1] addr = "#{TEST_ADDR}:#{port}" addrs = [ addr ] rda, wra = IO.pipe rdb, wrb = IO.pipe nr_proc.times do fork do rda.close wrb.close @to_close.concat((1..nr_sock).map { s.accept }) wra.syswrite('.') wra.close rdb.sysread(1) # wait for parent to nuke us end end nr_proc.times do fork do rda.close wrb.close @to_close.concat((1..nr_sock).map { TCPSocket.new(TEST_ADDR, port) }) wra.syswrite('.') wra.close rdb.sysread(1) # wait for parent to nuke us end end assert_equal('.' * (nr_proc * 2), rda.read(nr_proc * 2)) rda.close stats = tcp_listener_stats(addrs) expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 0] } assert_equal expect, stats @to_close << TCPSocket.new(TEST_ADDR, port) stats = tcp_listener_stats(addrs) expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 1] } assert_equal expect, stats if ENV["BENCHMARK"].to_i != 0 require 'benchmark' puts(Benchmark.measure{1000.times { tcp_listener_stats(addrs) }}) end wrb.syswrite('.' * (nr_proc * 2)) # broadcast a wakeup statuses = Process.waitall statuses.each { |(_,status)| assert status.success?, status.inspect } end if ENV["STRESS"].to_i != 0 end if RUBY_PLATFORM =~ /linux/ raindrops-0.19.0/test/test_middleware_unicorn.rb0000644000004100000410000000205713145550250022053 0ustar www-datawww-data# -*- encoding: binary -*- require "./test/rack_unicorn" $stderr.sync = $stdout.sync = true class TestMiddlewareUnicorn < Test::Unit::TestCase def setup @host = ENV["UNICORN_TEST_ADDR"] || "127.0.0.1" @sock = TCPServer.new @host, 0 @port = @sock.addr[1] ENV["UNICORN_FD"] = @sock.fileno.to_s @host_with_port = "#@host:#@port" @opts = { :listeners => [ @host_with_port ] } @addr_regexp = Regexp.escape @host_with_port end def test_auto_listener @app = Rack::Builder.new do use Raindrops::Middleware run Rack::Lobster.new end @srv = fork { Unicorn::HttpServer.new(@app, @opts).start.join } s = TCPSocket.new @host, @port s.write "GET /_raindrops HTTP/1.0\r\n\r\n" resp = s.read _, body = resp.split(/\r\n\r\n/, 2) assert_match %r{^#@addr_regexp active: 1$}, body assert_match %r{^#@addr_regexp queued: 0$}, body end def teardown Process.kill :QUIT, @srv _, status = Process.waitpid2 @srv assert status.success? end end if defined?(Unicorn) && RUBY_PLATFORM =~ /linux/ raindrops-0.19.0/test/test_raindrops_gc.rb0000644000004100000410000000167613145550250020661 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'raindrops' class TestRaindropsGc < Test::Unit::TestCase # we may need to create more garbage as GC may be less aggressive # about expiring things. This is completely unrealistic code, # though... def test_gc assert_nothing_raised do 1000000.times { |i| Raindrops.new(24); [] } end end def test_gc_postfork tmp = Raindrops.new 2 pid = fork do 1000000.times do tmp = Raindrops.new 2 tmp.to_ary end end _, status = Process.waitpid2(pid) assert status.success? assert_equal [ 0, 0 ], tmp.to_ary tmp.incr 1 assert_equal [ 0, 1 ], tmp.to_ary pid = fork do tmp.incr 1 exit([ 0, 2 ] == tmp.to_ary) end _, status = Process.waitpid2(pid) assert status.success? assert_equal [ 0, 2 ], tmp.to_ary end end if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" && ENV["STRESS"].to_i != 0 raindrops-0.19.0/test/test_aggregate_pmq.rb0000644000004100000410000000317013145550250021001 0ustar www-datawww-datarequire "test/unit" require "raindrops" pmq = begin Raindrops::Aggregate::PMQ rescue LoadError => e warn "W: #{e} skipping #{__FILE__}" false end if RUBY_VERSION.to_f < 1.9 pmq = false warn "W: skipping #{__FILE__}, only Ruby 1.9 supported for now" end Thread.abort_on_exception = true class TestAggregatePMQ < Test::Unit::TestCase def setup @queue = "/test.#{rand}" end def teardown POSIX_MQ.unlink @queue end def test_run pmq = Raindrops::Aggregate::PMQ.new :queue => @queue thr = Thread.new { pmq.master_loop } agg = Aggregate.new (1..10).each { |i| pmq << i; agg << i } pmq.stop_master_loop assert thr.join assert_equal agg.count, pmq.count assert_equal agg.mean, pmq.mean assert_equal agg.std_dev, pmq.std_dev assert_equal agg.min, pmq.min assert_equal agg.max, pmq.max assert_equal agg.to_s, pmq.to_s end def test_multi_process nr_workers = 4 nr = 100 pmq = Raindrops::Aggregate::PMQ.new :queue => @queue pid = fork { pmq.master_loop } workers = (1..nr_workers).map { fork { (1..nr).each { |i| pmq << i } pmq.flush } } workers.each { |wpid| assert Process.waitpid2(wpid).last.success? } pmq.stop_master_loop assert Process.waitpid2(pid).last.success? assert_equal 400, pmq.count agg = Aggregate.new (1..nr_workers).map { (1..nr).each { |i| agg << i } } assert_equal agg.to_s, pmq.to_s assert_equal agg.mean, pmq.mean assert_equal agg.std_dev, pmq.std_dev assert_equal agg.min, pmq.min assert_equal agg.max, pmq.max assert_equal agg.to_s, pmq.to_s end end if pmq raindrops-0.19.0/test/test_linux_all_tcp_listen_stats.rb0000644000004100000410000000256213145550250023633 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'socket' require 'raindrops' require 'pp' $stderr.sync = $stdout.sync = true class TestLinuxAllTcpListenStats < Test::Unit::TestCase include Raindrops::Linux TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' def test_print_all puts "EVERYTHING" pp Raindrops::Linux.tcp_listener_stats puts("-" * 72) end if $stdout.tty? def setup @socks = [] end def teardown @socks.each { |io| io.closed? or io.close } end def new_server s = TCPServer.new TEST_ADDR, 0 @socks << s [ s, s.addr[1] ] end def new_client(port) s = TCPSocket.new("127.0.0.1", port) @socks << s s end def new_accept(srv) c = srv.accept @socks << c c end def test_all_ports srv, port = new_server addr = "#{TEST_ADDR}:#{port}" all = Raindrops::Linux.tcp_listener_stats assert_equal [0,0], all[addr].to_a new_client(port) all = Raindrops::Linux.tcp_listener_stats assert_equal [0,1], all[addr].to_a new_client(port) all = Raindrops::Linux.tcp_listener_stats assert_equal [0,2], all[addr].to_a new_accept(srv) all = Raindrops::Linux.tcp_listener_stats assert_equal [1,1], all[addr].to_a new_accept(srv) all = Raindrops::Linux.tcp_listener_stats assert_equal [2,0], all[addr].to_a end end if RUBY_PLATFORM =~ /linux/ raindrops-0.19.0/archive/0000755000004100000410000000000013145550250015253 5ustar www-datawww-dataraindrops-0.19.0/archive/slrnpull.conf0000644000004100000410000000027613145550250020002 0ustar www-datawww-data# group_name max expire headers_only gmane.comp.lang.ruby.raindrops.general 1000000000 1000000000 0 # usage: slrnpull -d $PWD -h news.gmane.org --no-post raindrops-0.19.0/archive/.gitignore0000644000004100000410000000002613145550250017241 0ustar www-datawww-data/data /news /requests raindrops-0.19.0/.gitignore0000644000004100000410000000017113145550250015621 0ustar www-datawww-data*.o *.so *.log *.rbc Makefile /GIT-VERSION-FILE /local.mk /NEWS* /ChangeLog /.manifest /man /pkg /doc /LATEST /tmp /.rbx raindrops-0.19.0/raindrops.gemspec0000644000004100000410000000201213145550250017173 0ustar www-datawww-data# -*- encoding: binary -*- manifest = File.exist?('.manifest') ? IO.readlines('.manifest').map!(&:chomp!) : `git ls-files`.split("\n") test_files = manifest.grep(%r{\Atest/test_.*\.rb\z}) Gem::Specification.new do |s| s.name = %q{raindrops} s.version = (ENV["VERSION"] ||= '0.18.0').dup s.authors = ["raindrops hackers"] s.description = File.read('README').split("\n\n")[1] s.email = %q{raindrops-public@bogomips.org} s.extensions = %w(ext/raindrops/extconf.rb) s.extra_rdoc_files = IO.readlines('.document').map!(&:chomp!).keep_if do |f| File.exist?(f) end s.files = manifest s.homepage = 'https://bogomips.org/raindrops/' s.summary = 'real-time stats for preforking Rack servers' s.required_ruby_version = '>= 1.9.3' s.test_files = test_files s.add_development_dependency('aggregate', '~> 0.2') s.add_development_dependency('test-unit', '~> 3.0') s.add_development_dependency('posix_mq', '~> 2.0') s.add_development_dependency('rack', [ '>= 1.2', '< 3.0' ]) s.licenses = %w(LGPL-2.1+) end raindrops-0.19.0/pkg.mk0000644000004100000410000000773713145550250014762 0ustar www-datawww-dataRUBY = ruby RAKE = rake RSYNC = rsync OLDDOC = olddoc RDOC = rdoc GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @./GIT-VERSION-GEN -include GIT-VERSION-FILE -include local.mk DLEXT := $(shell $(RUBY) -rrbconfig -e 'puts RbConfig::CONFIG["DLEXT"]') RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION') RUBY_ENGINE := $(shell $(RUBY) -e 'puts((RUBY_ENGINE rescue "ruby"))') lib := lib ext := $(firstword $(wildcard ext/*)) ifneq ($(ext),) ext_pfx := tmp/ext/$(RUBY_ENGINE)-$(RUBY_VERSION) ext_h := $(wildcard $(ext)/*/*.h $(ext)/*.h) ext_src := $(wildcard $(ext)/*.c $(ext_h)) ext_pfx_src := $(addprefix $(ext_pfx)/,$(ext_src)) ext_d := $(ext_pfx)/$(ext)/.d $(ext)/extconf.rb: $(wildcard $(ext)/*.h) @>> $@ $(ext_d): @mkdir -p $(@D) @> $@ $(ext_pfx)/$(ext)/%: $(ext)/% $(ext_d) install -m 644 $< $@ $(ext_pfx)/$(ext)/Makefile: $(ext)/extconf.rb $(ext_d) $(ext_h) $(RM) -f $(@D)/*.o cd $(@D) && $(RUBY) $(CURDIR)/$(ext)/extconf.rb $(EXTCONF_ARGS) ext_sfx := _ext.$(DLEXT) ext_dl := $(ext_pfx)/$(ext)/$(notdir $(ext)_ext.$(DLEXT)) $(ext_dl): $(ext_src) $(ext_pfx_src) $(ext_pfx)/$(ext)/Makefile @echo $^ == $@ $(MAKE) -C $(@D) lib := $(lib):$(ext_pfx)/$(ext) build: $(ext_dl) else build: endif pkg_extra += GIT-VERSION-FILE NEWS LATEST NEWS: GIT-VERSION-FILE .olddoc.yml $(OLDDOC) prepare LATEST: NEWS manifest: $(RM) .manifest $(MAKE) .manifest .manifest: $(pkg_extra) (git ls-files && for i in $@ $(pkg_extra); do echo $$i; done) | \ LC_ALL=C sort > $@+ cmp $@+ $@ || mv $@+ $@ $(RM) $@+ doc:: .document .olddoc.yml $(pkg_extra) $(PLACEHOLDERS) -find lib -type f -name '*.rbc' -exec rm -f '{}' ';' -find ext -type f -name '*.rbc' -exec rm -f '{}' ';' $(RM) -r doc $(RDOC) -f oldweb $(OLDDOC) merge install -m644 COPYING doc/COPYING install -m644 NEWS doc/NEWS install -m644 NEWS.atom.xml doc/NEWS.atom.xml install -m644 $(shell LC_ALL=C grep '^[A-Z]' .document) doc/ ifneq ($(VERSION),) pkggem := pkg/$(rfpackage)-$(VERSION).gem pkgtgz := pkg/$(rfpackage)-$(VERSION).tgz # ensures we're actually on the tagged $(VERSION), only used for release verify: test x"$(shell umask)" = x0022 git rev-parse --verify refs/tags/v$(VERSION)^{} git diff-index --quiet HEAD^0 test $$(git rev-parse --verify HEAD^0) = \ $$(git rev-parse --verify refs/tags/v$(VERSION)^{}) fix-perms: -git ls-tree -r HEAD | awk '/^100644 / {print $$NF}' | xargs chmod 644 -git ls-tree -r HEAD | awk '/^100755 / {print $$NF}' | xargs chmod 755 gem: $(pkggem) install-gem: $(pkggem) gem install --local $(CURDIR)/$< $(pkggem): manifest fix-perms gem build $(rfpackage).gemspec mkdir -p pkg mv $(@F) $@ $(pkgtgz): distdir = $(basename $@) $(pkgtgz): HEAD = v$(VERSION) $(pkgtgz): manifest fix-perms @test -n "$(distdir)" $(RM) -r $(distdir) mkdir -p $(distdir) tar cf - $$(cat .manifest) | (cd $(distdir) && tar xf -) cd pkg && tar cf - $(basename $(@F)) | gzip -9 > $(@F)+ mv $@+ $@ package: $(pkgtgz) $(pkggem) release:: verify package # push gem to RubyGems.org gem push $(pkggem) else gem install-gem: GIT-VERSION-FILE $(MAKE) $@ VERSION=$(GIT_VERSION) endif all:: check test_units := $(wildcard test/test_*.rb) test: check check: test-unit test-unit: $(test_units) $(test_units): build $(RUBY) -I $(lib) $@ $(RUBY_TEST_OPTS) # this requires GNU coreutils variants ifneq ($(RSYNC_DEST),) publish_doc: -git set-file-times $(MAKE) doc $(MAKE) doc_gz $(RSYNC) -av doc/ $(RSYNC_DEST)/ git ls-files | xargs touch endif # Create gzip variants of the same timestamp as the original so nginx # "gzip_static on" can serve the gzipped versions directly. doc_gz: docs = $(shell find doc -type f ! -regex '^.*\.gz$$') doc_gz: for i in $(docs); do \ gzip --rsyncable -9 < $$i > $$i.gz; touch -r $$i $$i.gz; done check-warnings: @(for i in $$(git ls-files '*.rb'| grep -v '^setup\.rb$$'); \ do $(RUBY) -d -W2 -c $$i; done) | grep -v '^Syntax OK$$' || : ifneq ($(PLACEHOLDERS),) $(PLACEHOLDERS): echo olddoc_placeholder > $@ endif .PHONY: all .FORCE-GIT-VERSION-FILE doc check test $(test_units) manifest .PHONY: check-warnings raindrops-0.19.0/GIT-VERSION-FILE0000644000004100000410000000002513145550250016035 0ustar www-datawww-dataGIT_VERSION = 0.19.0 raindrops-0.19.0/LICENSE0000644000004100000410000000150513145550250014640 0ustar www-datawww-dataraindrops is copyrighted Free Software by all contributors, see logs in revision control for names and email addresses of all of them. You can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation, version {2.1}[https://www.gnu.org/licenses/lgpl-2.1.txt] or later. Currently version {3}[https://www.gnu.org/licenses/lgpl-3.0.txt], is preferred (see link:COPYING). raindrops is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the raindrops; if not, see raindrops-0.19.0/setup.rb0000644000004100000410000010652613145550250015331 0ustar www-datawww-data# -*- encoding: binary -*- # # setup.rb # # Copyright (c) 2000-2005 Minero Aoki # # This program is free software. # You can distribute/modify this program under the terms of # the GNU LGPL, Lesser General Public License version 2.1. # unless Enumerable.method_defined?(:map) # Ruby 1.4.6 module Enumerable alias map collect end end unless File.respond_to?(:read) # Ruby 1.6 def File.read(fname) open(fname) {|f| return f.read } end end unless Errno.const_defined?(:ENOTEMPTY) # Windows? module Errno class ENOTEMPTY # We do not raise this exception, implementation is not needed. end end end def File.binread(fname) open(fname, 'rb') {|f| return f.read } end # for corrupted Windows' stat(2) def File.dir?(path) File.directory?((path[-1,1] == '/') ? path : path + '/') end class ConfigTable include Enumerable def initialize(rbconfig) @rbconfig = rbconfig @items = [] @table = {} # options @install_prefix = nil @config_opt = nil @verbose = true @no_harm = false end attr_accessor :install_prefix attr_accessor :config_opt attr_writer :verbose def verbose? @verbose end attr_writer :no_harm def no_harm? @no_harm end def [](key) lookup(key).resolve(self) end def []=(key, val) lookup(key).set val end def names @items.map {|i| i.name } end def each(&block) @items.each(&block) end def key?(name) @table.key?(name) end def lookup(name) @table[name] or setup_rb_error "no such config item: #{name}" end def add(item) @items.push item @table[item.name] = item end def remove(name) item = lookup(name) @items.delete_if {|i| i.name == name } @table.delete_if {|name, i| i.name == name } item end def load_script(path, inst = nil) if File.file?(path) MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path end end def savefile '.config' end def load_savefile begin File.foreach(savefile()) do |line| k, v = *line.split(/=/, 2) self[k] = v.strip end rescue Errno::ENOENT setup_rb_error $!.message + "\n#{File.basename($0)} config first" end end def save @items.each {|i| i.value } File.open(savefile(), 'w') {|f| @items.each do |i| f.printf "%s=%s\n", i.name, i.value if i.value? and i.value end } end def load_standard_entries standard_entries(@rbconfig).each do |ent| add ent end end def standard_entries(rbconfig) c = rbconfig rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) major = c['MAJOR'].to_i minor = c['MINOR'].to_i teeny = c['TEENY'].to_i version = "#{major}.#{minor}" # ruby ver. >= 1.4.4? newpath_p = ((major >= 2) or ((major == 1) and ((minor >= 5) or ((minor == 4) and (teeny >= 4))))) if c['rubylibdir'] # V > 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = c['rubylibdir'] librubyverarch = c['archdir'] siteruby = c['sitedir'] siterubyver = c['sitelibdir'] siterubyverarch = c['sitearchdir'] elsif newpath_p # 1.4.4 <= V <= 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = c['sitedir'] siterubyver = "$siteruby/#{version}" siterubyverarch = "$siterubyver/#{c['arch']}" else # V < 1.4.4 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" siterubyver = siteruby siterubyverarch = "$siterubyver/#{c['arch']}" end parameterize = lambda {|path| path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') } if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } makeprog = arg.sub(/'/, '').split(/=/, 2)[1] else makeprog = 'make' end [ ExecItem.new('installdirs', 'std/site/home', 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ {|val, table| case val when 'std' table['rbdir'] = '$librubyver' table['sodir'] = '$librubyverarch' when 'site' table['rbdir'] = '$siterubyver' table['sodir'] = '$siterubyverarch' when 'home' setup_rb_error '$HOME was not set' unless ENV['HOME'] table['prefix'] = ENV['HOME'] table['rbdir'] = '$libdir/ruby' table['sodir'] = '$libdir/ruby' end }, PathItem.new('prefix', 'path', c['prefix'], 'path prefix of target environment'), PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 'the directory for commands'), PathItem.new('libdir', 'path', parameterize.call(c['libdir']), 'the directory for libraries'), PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 'the directory for shared data'), PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 'the directory for man pages'), PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 'the directory for system configuration files'), PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), 'the directory for local state data'), PathItem.new('libruby', 'path', libruby, 'the directory for ruby libraries'), PathItem.new('librubyver', 'path', librubyver, 'the directory for standard ruby libraries'), PathItem.new('librubyverarch', 'path', librubyverarch, 'the directory for standard ruby extensions'), PathItem.new('siteruby', 'path', siteruby, 'the directory for version-independent aux ruby libraries'), PathItem.new('siterubyver', 'path', siterubyver, 'the directory for aux ruby libraries'), PathItem.new('siterubyverarch', 'path', siterubyverarch, 'the directory for aux ruby binaries'), PathItem.new('rbdir', 'path', '$siterubyver', 'the directory for ruby scripts'), PathItem.new('sodir', 'path', '$siterubyverarch', 'the directory for ruby extentions'), PathItem.new('rubypath', 'path', rubypath, 'the path to set to #! line'), ProgramItem.new('rubyprog', 'name', rubypath, 'the ruby program using for installation'), ProgramItem.new('makeprog', 'name', makeprog, 'the make program to compile ruby extentions'), SelectItem.new('shebang', 'all/ruby/never', 'ruby', 'shebang line (#!) editing mode'), BoolItem.new('without-ext', 'yes/no', 'no', 'does not compile/install ruby extentions') ] end private :standard_entries def load_multipackage_entries multipackage_entries().each do |ent| add ent end end def multipackage_entries [ PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 'package names that you want to install'), PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 'package names that you do not want to install') ] end private :multipackage_entries ALIASES = { 'std-ruby' => 'librubyver', 'stdruby' => 'librubyver', 'rubylibdir' => 'librubyver', 'archdir' => 'librubyverarch', 'site-ruby-common' => 'siteruby', # For backward compatibility 'site-ruby' => 'siterubyver', # For backward compatibility 'bin-dir' => 'bindir', 'bin-dir' => 'bindir', 'rb-dir' => 'rbdir', 'so-dir' => 'sodir', 'data-dir' => 'datadir', 'ruby-path' => 'rubypath', 'ruby-prog' => 'rubyprog', 'ruby' => 'rubyprog', 'make-prog' => 'makeprog', 'make' => 'makeprog' } def fixup ALIASES.each do |ali, name| @table[ali] = @table[name] end @items.freeze @table.freeze @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ end def parse_opt(opt) m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" m.to_a[1,2] end def dllext @rbconfig['DLEXT'] end def value_config?(name) lookup(name).value? end class Item def initialize(name, template, default, desc) @name = name.freeze @template = template @value = default @default = default @description = desc end attr_reader :name attr_reader :description attr_accessor :default alias help_default default def help_opt "--#{@name}=#{@template}" end def value? true end def value @value end def resolve(table) @value.gsub(%r<\$([^/]+)>) { table[$1] } end def set(val) @value = check(val) end private def check(val) setup_rb_error "config: --#{name} requires argument" unless val val end end class BoolItem < Item def config_type 'bool' end def help_opt "--#{@name}" end private def check(val) return 'yes' unless val case val when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' else setup_rb_error "config: --#{@name} accepts only yes/no for argument" end end end class PathItem < Item def config_type 'path' end private def check(path) setup_rb_error "config: --#{@name} requires argument" unless path path[0,1] == '$' ? path : File.expand_path(path) end end class ProgramItem < Item def config_type 'program' end end class SelectItem < Item def initialize(name, selection, default, desc) super @ok = selection.split('/') end def config_type 'select' end private def check(val) unless @ok.include?(val.strip) setup_rb_error "config: use --#{@name}=#{@template} (#{val})" end val.strip end end class ExecItem < Item def initialize(name, selection, desc, &block) super name, selection, nil, desc @ok = selection.split('/') @action = block end def config_type 'exec' end def value? false end def resolve(table) setup_rb_error "$#{name()} wrongly used as option value" end undef set def evaluate(val, table) v = val.strip.downcase unless @ok.include?(v) setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" end @action.call v, table end end class PackageSelectionItem < Item def initialize(name, template, default, help_default, desc) super name, template, default, desc @help_default = help_default end attr_reader :help_default def config_type 'package' end private def check(val) unless File.dir?("packages/#{val}") setup_rb_error "config: no such package: #{val}" end val end end class MetaConfigEnvironment def initialize(config, installer) @config = config @installer = installer end def config_names @config.names end def config?(name) @config.key?(name) end def bool_config?(name) @config.lookup(name).config_type == 'bool' end def path_config?(name) @config.lookup(name).config_type == 'path' end def value_config?(name) @config.lookup(name).config_type != 'exec' end def add_config(item) @config.add item end def add_bool_config(name, default, desc) @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) end def add_path_config(name, default, desc) @config.add PathItem.new(name, 'path', default, desc) end def set_config_default(name, default) @config.lookup(name).default = default end def remove_config(name) @config.remove(name) end # For only multipackage def packages raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer @installer.packages end # For only multipackage def declare_packages(list) raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer @installer.packages = list end end end # class ConfigTable # This module requires: #verbose?, #no_harm? module FileOperations def mkdir_p(dirname, prefix = nil) dirname = prefix + File.expand_path(dirname) if prefix $stderr.puts "mkdir -p #{dirname}" if verbose? return if no_harm? # Does not check '/', it's too abnormal. dirs = File.expand_path(dirname).split(%r<(?=/)>) if /\A[a-z]:\z/i =~ dirs[0] disk = dirs.shift dirs[0] = disk + dirs[0] end dirs.each_index do |idx| path = dirs[0..idx].join('') Dir.mkdir path unless File.dir?(path) end end def rm_f(path) $stderr.puts "rm -f #{path}" if verbose? return if no_harm? force_remove_file path end def rm_rf(path) $stderr.puts "rm -rf #{path}" if verbose? return if no_harm? remove_tree path end def remove_tree(path) if File.symlink?(path) remove_file path elsif File.dir?(path) remove_tree0 path else force_remove_file path end end def remove_tree0(path) Dir.foreach(path) do |ent| next if ent == '.' next if ent == '..' entpath = "#{path}/#{ent}" if File.symlink?(entpath) remove_file entpath elsif File.dir?(entpath) remove_tree0 entpath else force_remove_file entpath end end begin Dir.rmdir path rescue Errno::ENOTEMPTY # directory may not be empty end end def move_file(src, dest) force_remove_file dest begin File.rename src, dest rescue File.open(dest, 'wb') {|f| f.write File.binread(src) } File.chmod File.stat(src).mode, dest File.unlink src end end def force_remove_file(path) begin remove_file path rescue end end def remove_file(path) File.chmod 0777, path File.unlink path end def install(from, dest, mode, prefix = nil) $stderr.puts "install #{from} #{dest}" if verbose? return if no_harm? realdest = prefix ? prefix + File.expand_path(dest) : dest realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) str = File.binread(from) if diff?(str, realdest) verbose_off { rm_f realdest if File.exist?(realdest) } File.open(realdest, 'wb') {|f| f.write str } File.chmod mode, realdest File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| if prefix f.puts realdest.sub(prefix, '') else f.puts realdest end } end end def diff?(new_content, path) return true unless File.exist?(path) new_content != File.binread(path) end def command(*args) $stderr.puts args.join(' ') if verbose? system(*args) or raise RuntimeError, "system(#{args.map{|a| a.inspect }.join(' ')}) failed" end def ruby(*args) command config('rubyprog'), *args end def make(task = nil) command(*[config('makeprog'), task].compact) end def extdir?(dir) File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") end def files_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.file?("#{dir}/#{ent}") } } end DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) def directories_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT } end end # This module requires: #srcdir_root, #objdir_root, #relpath module HookScriptAPI def get_config(key) @config[key] end alias config get_config # obsolete: use metaconfig to change configuration def set_config(key, val) @config[key] = val end # # srcdir/objdir (works only in the package directory) # def curr_srcdir "#{srcdir_root()}/#{relpath()}" end def curr_objdir "#{objdir_root()}/#{relpath()}" end def srcfile(path) "#{curr_srcdir()}/#{path}" end def srcexist?(path) File.exist?(srcfile(path)) end def srcdirectory?(path) File.dir?(srcfile(path)) end def srcfile?(path) File.file?(srcfile(path)) end def srcentries(path = '.') Dir.open("#{curr_srcdir()}/#{path}") {|d| return d.to_a - %w(. ..) } end def srcfiles(path = '.') srcentries(path).select {|fname| File.file?(File.join(curr_srcdir(), path, fname)) } end def srcdirectories(path = '.') srcentries(path).select {|fname| File.dir?(File.join(curr_srcdir(), path, fname)) } end end class ToplevelInstaller Version = '3.4.1' Copyright = 'Copyright (c) 2000-2005 Minero Aoki' TASKS = [ [ 'all', 'do config, setup, then install' ], [ 'config', 'saves your configurations' ], [ 'show', 'shows current configuration' ], [ 'setup', 'compiles ruby extentions and others' ], [ 'install', 'installs files' ], [ 'test', 'run all tests in test/' ], [ 'clean', "does `make clean' for each extention" ], [ 'distclean',"does `make distclean' for each extention" ] ] def ToplevelInstaller.invoke config = ConfigTable.new(load_rbconfig()) config.load_standard_entries config.load_multipackage_entries if multipackage? config.fixup klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) klass.new(File.dirname($0), config).invoke end def ToplevelInstaller.multipackage? File.dir?(File.dirname($0) + '/packages') end def ToplevelInstaller.load_rbconfig if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } ARGV.delete(arg) load File.expand_path(arg.split(/=/, 2)[1]) $".push 'rbconfig.rb' else require 'rbconfig' end ::Config::CONFIG end def initialize(ardir_root, config) @ardir = File.expand_path(ardir_root) @config = config # cache @valid_task_re = nil end def config(key) @config[key] end def inspect "#<#{self.class} #{__id__()}>" end def invoke run_metaconfigs case task = parsearg_global() when nil, 'all' parsearg_config init_installers exec_config exec_setup exec_install else case task when 'config', 'test' ; when 'clean', 'distclean' @config.load_savefile if File.exist?(@config.savefile) else @config.load_savefile end __send__ "parsearg_#{task}" init_installers __send__ "exec_#{task}" end end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig" end def init_installers @installer = Installer.new(@config, @ardir, File.expand_path('.')) end # # Hook Script API bases # def srcdir_root @ardir end def objdir_root '.' end def relpath '.' end # # Option Parsing # def parsearg_global while arg = ARGV.shift case arg when /\A\w+\z/ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) return arg when '-q', '--quiet' @config.verbose = false when '--verbose' @config.verbose = true when '--help' print_usage $stdout exit 0 when '--version' puts "#{File.basename($0)} version #{Version}" exit 0 when '--copyright' puts Copyright exit 0 else setup_rb_error "unknown global option '#{arg}'" end end nil end def valid_task?(t) valid_task_re() =~ t end def valid_task_re @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ end def parsearg_no_options unless ARGV.empty? task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" end end alias parsearg_show parsearg_no_options alias parsearg_setup parsearg_no_options alias parsearg_test parsearg_no_options alias parsearg_clean parsearg_no_options alias parsearg_distclean parsearg_no_options def parsearg_config evalopt = [] set = [] @config.config_opt = [] while i = ARGV.shift if /\A--?\z/ =~ i @config.config_opt = ARGV.dup break end name, value = *@config.parse_opt(i) if @config.value_config?(name) @config[name] = value else evalopt.push [name, value] end set.push name end evalopt.each do |name, value| @config.lookup(name).evaluate value, @config end # Check if configuration is valid set.each do |n| @config[n] if @config.value_config?(n) end end def parsearg_install @config.no_harm = false @config.install_prefix = '' while a = ARGV.shift case a when '--no-harm' @config.no_harm = true when /\A--prefix=/ path = a.split(/=/, 2)[1] path = File.expand_path(path) unless path[0,1] == '/' @config.install_prefix = path else setup_rb_error "install: unknown option #{a}" end end end def print_usage(out) out.puts 'Typical Installation Procedure:' out.puts " $ ruby #{File.basename $0} config" out.puts " $ ruby #{File.basename $0} setup" out.puts " # ruby #{File.basename $0} install (may require root privilege)" out.puts out.puts 'Detailed Usage:' out.puts " ruby #{File.basename $0} " out.puts " ruby #{File.basename $0} [] []" fmt = " %-24s %s\n" out.puts out.puts 'Global options:' out.printf fmt, '-q,--quiet', 'suppress message outputs' out.printf fmt, ' --verbose', 'output messages verbosely' out.printf fmt, ' --help', 'print this message' out.printf fmt, ' --version', 'print version and quit' out.printf fmt, ' --copyright', 'print copyright and quit' out.puts out.puts 'Tasks:' TASKS.each do |name, desc| out.printf fmt, name, desc end fmt = " %-24s %s [%s]\n" out.puts out.puts 'Options for CONFIG or ALL:' @config.each do |item| out.printf fmt, item.help_opt, item.description, item.help_default end out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" out.puts out.puts 'Options for INSTALL:' out.printf fmt, '--no-harm', 'only display what to do if given', 'off' out.printf fmt, '--prefix=path', 'install path prefix', '' out.puts end # # Task Handlers # def exec_config @installer.exec_config @config.save # must be final end def exec_setup @installer.exec_setup end def exec_install @installer.exec_install end def exec_test @installer.exec_test end def exec_show @config.each do |i| printf "%-20s %s\n", i.name, i.value if i.value? end end def exec_clean @installer.exec_clean end def exec_distclean @installer.exec_distclean end end # class ToplevelInstaller class ToplevelInstallerMulti < ToplevelInstaller include FileOperations def initialize(ardir_root, config) super @packages = directories_of("#{@ardir}/packages") raise 'no package exists' if @packages.empty? @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig", self @packages.each do |name| @config.load_script "#{@ardir}/packages/#{name}/metaconfig" end end attr_reader :packages def packages=(list) raise 'package list is empty' if list.empty? list.each do |name| raise "directory packages/#{name} does not exist"\ unless File.dir?("#{@ardir}/packages/#{name}") end @packages = list end def init_installers @installers = {} @packages.each do |pack| @installers[pack] = Installer.new(@config, "#{@ardir}/packages/#{pack}", "packages/#{pack}") end with = extract_selection(config('with')) without = extract_selection(config('without')) @selected = @installers.keys.select {|name| (with.empty? or with.include?(name)) \ and not without.include?(name) } end def extract_selection(list) a = list.split(/,/) a.each do |name| setup_rb_error "no such package: #{name}" unless @installers.key?(name) end a end def print_usage(f) super f.puts 'Inluded packages:' f.puts ' ' + @packages.sort.join(' ') f.puts end # # Task Handlers # def exec_config run_hook 'pre-config' each_selected_installers {|inst| inst.exec_config } run_hook 'post-config' @config.save # must be final end def exec_setup run_hook 'pre-setup' each_selected_installers {|inst| inst.exec_setup } run_hook 'post-setup' end def exec_install run_hook 'pre-install' each_selected_installers {|inst| inst.exec_install } run_hook 'post-install' end def exec_test run_hook 'pre-test' each_selected_installers {|inst| inst.exec_test } run_hook 'post-test' end def exec_clean rm_f @config.savefile run_hook 'pre-clean' each_selected_installers {|inst| inst.exec_clean } run_hook 'post-clean' end def exec_distclean rm_f @config.savefile run_hook 'pre-distclean' each_selected_installers {|inst| inst.exec_distclean } run_hook 'post-distclean' end # # lib # def each_selected_installers Dir.mkdir 'packages' unless File.dir?('packages') @selected.each do |pack| $stderr.puts "Processing the package `#{pack}' ..." if verbose? Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") Dir.chdir "packages/#{pack}" yield @installers[pack] Dir.chdir '../..' end end def run_hook(id) @root_installer.run_hook id end # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end end # class ToplevelInstallerMulti class Installer FILETYPES = %w( bin lib ext data conf man ) include FileOperations include HookScriptAPI def initialize(config, srcroot, objroot) @config = config @srcdir = File.expand_path(srcroot) @objdir = File.expand_path(objroot) @currdir = '.' end def inspect "#<#{self.class} #{File.basename(@srcdir)}>" end def noop(rel) end # # Hook Script API base methods # def srcdir_root @srcdir end def objdir_root @objdir end def relpath @currdir end # # Config Access # # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end def verbose_off begin save, @config.verbose = @config.verbose?, false yield ensure @config.verbose = save end end # # TASK config # def exec_config exec_task_traverse 'config' end alias config_dir_bin noop alias config_dir_lib noop def config_dir_ext(rel) extconf if extdir?(curr_srcdir()) end alias config_dir_data noop alias config_dir_conf noop alias config_dir_man noop def extconf ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt end # # TASK setup # def exec_setup exec_task_traverse 'setup' end def setup_dir_bin(rel) files_of(curr_srcdir()).each do |fname| update_shebang_line "#{curr_srcdir()}/#{fname}" end end alias setup_dir_lib noop def setup_dir_ext(rel) make if extdir?(curr_srcdir()) end alias setup_dir_data noop alias setup_dir_conf noop alias setup_dir_man noop def update_shebang_line(path) return if no_harm? return if config('shebang') == 'never' old = Shebang.load(path) if old $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 new = new_shebang(old) return if new.to_s == old.to_s else return unless config('shebang') == 'all' new = Shebang.new(config('rubypath')) end $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? open_atomic_writer(path) {|output| File.open(path, 'rb') {|f| f.gets if old # discard output.puts new.to_s output.print f.read } } end def new_shebang(old) if /\Aruby/ =~ File.basename(old.cmd) Shebang.new(config('rubypath'), old.args) elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' Shebang.new(config('rubypath'), old.args[1..-1]) else return old unless config('shebang') == 'all' Shebang.new(config('rubypath')) end end def open_atomic_writer(path, &block) tmpfile = File.basename(path) + '.tmp' begin File.open(tmpfile, 'wb', &block) File.rename tmpfile, File.basename(path) ensure File.unlink tmpfile if File.exist?(tmpfile) end end class Shebang def Shebang.load(path) line = nil File.open(path) {|f| line = f.gets } return nil unless /\A#!/ =~ line parse(line) end def Shebang.parse(line) cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') new(cmd, args) end def initialize(cmd, args = []) @cmd = cmd @args = args end attr_reader :cmd attr_reader :args def to_s "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") end end # # TASK install # def exec_install rm_f 'InstalledFiles' exec_task_traverse 'install' end def install_dir_bin(rel) install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 end def install_dir_lib(rel) install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 end def install_dir_ext(rel) return unless extdir?(curr_srcdir()) install_files rubyextentions('.'), "#{config('sodir')}/#{File.dirname(rel)}", 0555 end def install_dir_data(rel) install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 end def install_dir_conf(rel) # FIXME: should not remove current config files # (rename previous file to .old/.org) install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 end def install_dir_man(rel) install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 end def install_files(list, dest, mode) mkdir_p dest, @config.install_prefix list.each do |fname| install fname, dest, mode, @config.install_prefix end end def libfiles glob_reject(%w(*.y *.output), targetfiles()) end def rubyextentions(dir) ents = glob_select("*.#{@config.dllext}", targetfiles()) if ents.empty? setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" end ents end def targetfiles mapdir(existfiles() - hookfiles()) end def mapdir(ents) ents.map {|ent| if File.exist?(ent) then ent # objdir else "#{curr_srcdir()}/#{ent}" # srcdir end } end # picked up many entries from cvs-1.11.1/src/ignore.c JUNK_FILES = %w( core RCSLOG tags TAGS .make.state .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb *~ *.old *.bak *.BAK *.orig *.rej _$* *$ *.org *.in .* ) def existfiles glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) end def hookfiles %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| %w( config setup install clean ).map {|t| sprintf(fmt, t) } }.flatten end def glob_select(pat, ents) re = globs2re([pat]) ents.select {|ent| re =~ ent } end def glob_reject(pats, ents) re = globs2re(pats) ents.reject {|ent| re =~ ent } end GLOB2REGEX = { '.' => '\.', '$' => '\$', '#' => '\#', '*' => '.*' } def globs2re(pats) /\A(?:#{ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') })\z/ end # # TASK test # TESTDIR = 'test' def exec_test unless File.directory?('test') $stderr.puts 'no test in this package' if verbose? return end $stderr.puts 'Running tests...' if verbose? begin require 'test/unit' rescue LoadError setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' end runner = Test::Unit::AutoRunner.new(true) runner.to_run << TESTDIR runner.run end # # TASK clean # def exec_clean exec_task_traverse 'clean' rm_f @config.savefile rm_f 'InstalledFiles' end alias clean_dir_bin noop alias clean_dir_lib noop alias clean_dir_data noop alias clean_dir_conf noop alias clean_dir_man noop def clean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'clean' if File.file?('Makefile') end # # TASK distclean # def exec_distclean exec_task_traverse 'distclean' rm_f @config.savefile rm_f 'InstalledFiles' end alias distclean_dir_bin noop alias distclean_dir_lib noop def distclean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'distclean' if File.file?('Makefile') end alias distclean_dir_data noop alias distclean_dir_conf noop alias distclean_dir_man noop # # Traversing # def exec_task_traverse(task) run_hook "pre-#{task}" FILETYPES.each do |type| if type == 'ext' and config('without-ext') == 'yes' $stderr.puts 'skipping ext/* by user option' if verbose? next end traverse task, type, "#{task}_dir_#{type}" end run_hook "post-#{task}" end def traverse(task, rel, mid) dive_into(rel) { run_hook "pre-#{task}" __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') directories_of(curr_srcdir()).each do |d| traverse task, "#{rel}/#{d}", mid end run_hook "post-#{task}" } end def dive_into(rel) return unless File.dir?("#{@srcdir}/#{rel}") dir = File.basename(rel) Dir.mkdir dir unless File.dir?(dir) prevdir = Dir.pwd Dir.chdir dir $stderr.puts '---> ' + rel if verbose? @currdir = rel yield Dir.chdir prevdir $stderr.puts '<--- ' + rel if verbose? @currdir = File.dirname(rel) end def run_hook(id) path = [ "#{curr_srcdir()}/#{id}", "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } return unless path begin instance_eval File.read(path), path, 1 rescue raise if $DEBUG setup_rb_error "hook #{path} failed:\n" + $!.message end end end # class Installer class SetupError < StandardError; end def setup_rb_error(msg) raise SetupError, msg end if $0 == __FILE__ begin ToplevelInstaller.invoke rescue SetupError raise if $DEBUG $stderr.puts $!.message $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." exit 1 end end raindrops-0.19.0/TODO0000644000004100000410000000023113145550250014316 0ustar www-datawww-data* fix IPv6 inet_diag reporting for Raindrops::Middleware * pure Ruby version for non-forking servers (patches welcome!) * unix_diag and udp_diag support raindrops-0.19.0/ext/0000755000004100000410000000000013145550250014432 5ustar www-datawww-dataraindrops-0.19.0/ext/raindrops/0000755000004100000410000000000013145550250016433 5ustar www-datawww-dataraindrops-0.19.0/ext/raindrops/tcp_info.c0000644000004100000410000001535613145550250020412 0ustar www-datawww-data#include #include #include #if defined(HAVE_LINUX_TCP_H) # include #else # if defined(HAVE_NETINET_TCP_H) # include # endif # if defined(HAVE_NETINET_TCP_FSM_H) # include # endif #endif #ifdef HAVE_TYPE_STRUCT_TCP_INFO #include "my_fileno.h" CFUNC_tcp_info_tcpi_state CFUNC_tcp_info_tcpi_ca_state CFUNC_tcp_info_tcpi_retransmits CFUNC_tcp_info_tcpi_probes CFUNC_tcp_info_tcpi_backoff CFUNC_tcp_info_tcpi_options CFUNC_tcp_info_tcpi_snd_wscale CFUNC_tcp_info_tcpi_rcv_wscale CFUNC_tcp_info_tcpi_rto CFUNC_tcp_info_tcpi_ato CFUNC_tcp_info_tcpi_snd_mss CFUNC_tcp_info_tcpi_rcv_mss CFUNC_tcp_info_tcpi_unacked CFUNC_tcp_info_tcpi_sacked CFUNC_tcp_info_tcpi_lost CFUNC_tcp_info_tcpi_retrans CFUNC_tcp_info_tcpi_fackets CFUNC_tcp_info_tcpi_last_data_sent CFUNC_tcp_info_tcpi_last_ack_sent CFUNC_tcp_info_tcpi_last_data_recv CFUNC_tcp_info_tcpi_last_ack_recv CFUNC_tcp_info_tcpi_pmtu CFUNC_tcp_info_tcpi_rcv_ssthresh CFUNC_tcp_info_tcpi_rtt CFUNC_tcp_info_tcpi_rttvar CFUNC_tcp_info_tcpi_snd_ssthresh CFUNC_tcp_info_tcpi_snd_cwnd CFUNC_tcp_info_tcpi_advmss CFUNC_tcp_info_tcpi_reordering CFUNC_tcp_info_tcpi_rcv_rtt CFUNC_tcp_info_tcpi_rcv_space CFUNC_tcp_info_tcpi_total_retrans static size_t tcpi_memsize(const void *ptr) { return sizeof(struct tcp_info); } static const rb_data_type_t tcpi_type = { "tcp_info", { NULL, RUBY_TYPED_DEFAULT_FREE, tcpi_memsize, /* reserved */ }, /* parent, data, [ flags ] */ }; static VALUE alloc(VALUE klass) { struct tcp_info *info; return TypedData_Make_Struct(klass, struct tcp_info, &tcpi_type, info); } /* * call-seq: * * Raindrops::TCP_Info.new(tcp_socket) -> TCP_Info object * * Reads a TCP_Info object from any given +tcp_socket+. See the tcp(7) * manpage and /usr/include/linux/tcp.h for more details. */ static VALUE init(VALUE self, VALUE io) { int fd = my_fileno(io); struct tcp_info *info = DATA_PTR(self); socklen_t len = (socklen_t)sizeof(struct tcp_info); int rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, info, &len); if (rc != 0) rb_sys_fail("getsockopt"); return self; } void Init_raindrops_tcp_info(void) { VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject); VALUE cTCP_Info; /* * Document-class: Raindrops::TCP_Info * * This is used to wrap "struct tcp_info" as described in tcp(7) * and /usr/include/linux/tcp.h. The following readers methods * are defined corresponding to the "tcpi_" fields in the * tcp_info struct. * * As of raindrops 0.18.0+, this is supported on FreeBSD and OpenBSD * systems as well as Linux, although not all fields exist or * match the documentation, below. * * In particular, the +last_data_recv+ field is useful for measuring * the amount of time a client spent in the listen queue before * +accept()+, but only if +TCP_DEFER_ACCEPT+ is used with the * listen socket (it is on by default in Unicorn). * * - state * - ca_state * - retransmits * - probes * - backoff * - options * - snd_wscale * - rcv_wscale * - rto * - ato * - snd_mss * - rcv_mss * - unacked * - sacked * - lost * - retrans * - fackets * - last_data_sent * - last_ack_sent * - last_data_recv * - last_ack_recv * - pmtu * - rcv_ssthresh * - rtt * - rttvar * - snd_ssthresh * - snd_cwnd * - advmss * - reordering * - rcv_rtt * - rcv_space * - total_retrans * * https://kernel.org/doc/man-pages/online/pages/man7/tcp.7.html */ cTCP_Info = rb_define_class_under(cRaindrops, "TCP_Info", rb_cObject); rb_define_alloc_func(cTCP_Info, alloc); rb_define_private_method(cTCP_Info, "initialize", init, 1); /* * Document-method: Raindrops::TCP_Info#get! * * call-seq: * * info = Raindrops::TCP_Info.new(tcp_socket) * info.get!(tcp_socket) * * Update an existing TCP_Info objects with the latest stats * from the given socket. This even allows sharing TCP_Info * objects between different sockets to avoid garbage. */ rb_define_method(cTCP_Info, "get!", init, 1); DEFINE_METHOD_tcp_info_tcpi_state; DEFINE_METHOD_tcp_info_tcpi_ca_state; DEFINE_METHOD_tcp_info_tcpi_retransmits; DEFINE_METHOD_tcp_info_tcpi_probes; DEFINE_METHOD_tcp_info_tcpi_backoff; DEFINE_METHOD_tcp_info_tcpi_options; DEFINE_METHOD_tcp_info_tcpi_snd_wscale; DEFINE_METHOD_tcp_info_tcpi_rcv_wscale; DEFINE_METHOD_tcp_info_tcpi_rto; DEFINE_METHOD_tcp_info_tcpi_ato; DEFINE_METHOD_tcp_info_tcpi_snd_mss; DEFINE_METHOD_tcp_info_tcpi_rcv_mss; DEFINE_METHOD_tcp_info_tcpi_unacked; DEFINE_METHOD_tcp_info_tcpi_sacked; DEFINE_METHOD_tcp_info_tcpi_lost; DEFINE_METHOD_tcp_info_tcpi_retrans; DEFINE_METHOD_tcp_info_tcpi_fackets; DEFINE_METHOD_tcp_info_tcpi_last_data_sent; DEFINE_METHOD_tcp_info_tcpi_last_ack_sent; DEFINE_METHOD_tcp_info_tcpi_last_data_recv; DEFINE_METHOD_tcp_info_tcpi_last_ack_recv; DEFINE_METHOD_tcp_info_tcpi_pmtu; DEFINE_METHOD_tcp_info_tcpi_rcv_ssthresh; DEFINE_METHOD_tcp_info_tcpi_rtt; DEFINE_METHOD_tcp_info_tcpi_rttvar; DEFINE_METHOD_tcp_info_tcpi_snd_ssthresh; DEFINE_METHOD_tcp_info_tcpi_snd_cwnd; DEFINE_METHOD_tcp_info_tcpi_advmss; DEFINE_METHOD_tcp_info_tcpi_reordering; DEFINE_METHOD_tcp_info_tcpi_rcv_rtt; DEFINE_METHOD_tcp_info_tcpi_rcv_space; DEFINE_METHOD_tcp_info_tcpi_total_retrans; #ifdef RAINDROPS_TCP_STATES_ALL_KNOWN /* * Document-const: Raindrops::TCP * * This is a frozen hash storing the numeric values * maps platform-independent symbol keys to * platform-dependent numeric values. These states * are all valid values for the Raindrops::TCP_Info#state field. * * The platform-independent names of the keys in this hash are: * * - :ESTABLISHED * - :SYN_SENT * - :SYN_RECV * - :FIN_WAIT1 * - :FIN_WAIT2 * - :TIME_WAIT * - :CLOSE * - :CLOSE_WAIT * - :LAST_ACK * - :LISTEN * - :CLOSING * * This is only supported on platforms where TCP_Info is supported, * currently FreeBSD, OpenBSD, and Linux-based systems. */ { #define TCPSET(n,v) rb_hash_aset(tcp, ID2SYM(rb_intern(#n)), INT2NUM(v)) VALUE tcp = rb_hash_new(); TCPSET(ESTABLISHED, RAINDROPS_TCP_ESTABLISHED); TCPSET(SYN_SENT, RAINDROPS_TCP_SYN_SENT); TCPSET(SYN_RECV, RAINDROPS_TCP_SYN_RECV); TCPSET(FIN_WAIT1, RAINDROPS_TCP_FIN_WAIT1); TCPSET(FIN_WAIT2, RAINDROPS_TCP_FIN_WAIT2); TCPSET(TIME_WAIT, RAINDROPS_TCP_TIME_WAIT); TCPSET(CLOSE, RAINDROPS_TCP_CLOSE); TCPSET(CLOSE_WAIT, RAINDROPS_TCP_CLOSE_WAIT); TCPSET(LAST_ACK, RAINDROPS_TCP_LAST_ACK); TCPSET(LISTEN, RAINDROPS_TCP_LISTEN); TCPSET(CLOSING, RAINDROPS_TCP_CLOSING); #undef TCPSET OBJ_FREEZE(tcp); rb_define_const(cRaindrops, "TCP", tcp); } #endif } #endif /* HAVE_STRUCT_TCP_INFO */ raindrops-0.19.0/ext/raindrops/my_fileno.h0000644000004100000410000000126313145550250020567 0ustar www-datawww-data#include #ifdef HAVE_RUBY_IO_H # include #else # include # include #endif #if ! HAVE_RB_IO_T # define rb_io_t OpenFile #endif #ifdef GetReadFile # define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr))) #else # if !HAVE_RB_IO_T || (RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 8) # define FPTR_TO_FD(fptr) fileno(fptr->f) # else # define FPTR_TO_FD(fptr) fptr->fd # endif #endif static int my_fileno(VALUE io) { rb_io_t *fptr; int fd; if (TYPE(io) != T_FILE) io = rb_convert_type(io, T_FILE, "IO", "to_io"); GetOpenFile(io, fptr); fd = FPTR_TO_FD(fptr); if (fd < 0) rb_raise(rb_eIOError, "closed stream"); return fd; } raindrops-0.19.0/ext/raindrops/raindrops_atomic.h0000644000004100000410000000114213145550250022137 0ustar www-datawww-data/* * use wrappers around libatomic-ops for folks that don't have GCC * or a new enough version of GCC */ #ifndef HAVE_GCC_ATOMIC_BUILTINS #include static inline unsigned long __sync_add_and_fetch(unsigned long *dst, unsigned long incr) { AO_t tmp = AO_fetch_and_add((AO_t *)dst, (AO_t)incr); return (unsigned long)tmp + incr; } static inline unsigned long __sync_sub_and_fetch(unsigned long *dst, unsigned long incr) { AO_t tmp = AO_fetch_and_add((AO_t *)dst, (AO_t)(-(long)incr)); return (unsigned long)tmp - incr; } #endif /* HAVE_GCC_ATOMIC_BUILTINS */ raindrops-0.19.0/ext/raindrops/extconf.rb0000644000004100000410000001041113145550250020423 0ustar www-datawww-datarequire '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 = < #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@bogomips.org\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_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.0/ext/raindrops/raindrops.c0000644000004100000410000002524113145550250020604 0ustar www-datawww-data#include #include #include #include #include #include #include "raindrops_atomic.h" #ifndef SIZET2NUM # define SIZET2NUM(x) ULONG2NUM(x) #endif #ifndef NUM2SIZET # define NUM2SIZET(x) NUM2ULONG(x) #endif /* * most modern CPUs have a cache-line size of 64 or 128. * We choose a bigger one by default since our structure is not * heavily used */ static size_t raindrop_size = 128; static size_t rd_page_size; #define PAGE_MASK (~(rd_page_size - 1)) #define PAGE_ALIGN(addr) (((addr) + rd_page_size - 1) & PAGE_MASK) /* each raindrop is a counter */ struct raindrop { unsigned long counter; } __attribute__((packed)); /* allow mmap-ed regions to store more than one raindrop */ struct raindrops { size_t size; size_t capa; pid_t pid; struct raindrop *drops; }; /* called by GC */ static void rd_free(void *ptr) { struct raindrops *r = ptr; if (r->drops != MAP_FAILED) { int rv = munmap(r->drops, raindrop_size * r->capa); if (rv != 0) rb_bug("munmap failed in gc: %s", strerror(errno)); } xfree(ptr); } static size_t rd_memsize(const void *ptr) { const struct raindrops *r = ptr; return r->drops == MAP_FAILED ? 0 : raindrop_size * r->capa; } static const rb_data_type_t rd_type = { "raindrops", { NULL, rd_free, rd_memsize, /* reserved */ }, /* parent, data, [ flags ] */ }; /* automatically called at creation (before initialize) */ static VALUE alloc(VALUE klass) { struct raindrops *r; VALUE rv = TypedData_Make_Struct(klass, struct raindrops, &rd_type, r); r->drops = MAP_FAILED; return rv; } static struct raindrops *get(VALUE self) { struct raindrops *r; TypedData_Get_Struct(self, struct raindrops, &rd_type, r); if (r->drops == MAP_FAILED) rb_raise(rb_eStandardError, "invalid or freed Raindrops"); return r; } /* * call-seq: * Raindrops.new(size) -> raindrops object * * Initializes a Raindrops object to hold +size+ counters. +size+ is * only a hint and the actual number of counters the object has is * dependent on the CPU model, number of cores, and page size of * the machine. The actual size of the object will always be equal * or greater than the specified +size+. */ static VALUE init(VALUE self, VALUE size) { struct raindrops *r = DATA_PTR(self); int tries = 1; size_t tmp; if (r->drops != MAP_FAILED) rb_raise(rb_eRuntimeError, "already initialized"); r->size = NUM2SIZET(size); if (r->size < 1) rb_raise(rb_eArgError, "size must be >= 1"); tmp = PAGE_ALIGN(raindrop_size * r->size); r->capa = tmp / raindrop_size; assert(PAGE_ALIGN(raindrop_size * r->capa) == tmp && "not aligned"); retry: r->drops = mmap(NULL, tmp, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); if (r->drops == MAP_FAILED) { int err = errno; if ((err == EAGAIN || err == ENOMEM) && tries-- > 0) { rb_gc(); goto retry; } rb_sys_fail("mmap"); } r->pid = getpid(); return self; } /* * mremap() is currently broken with MAP_SHARED * https://bugzilla.kernel.org/show_bug.cgi?id=8691 */ #if defined(HAVE_MREMAP) && !defined(MREMAP_WORKS_WITH_MAP_SHARED) # undef HAVE_MREMAP #endif #ifdef HAVE_MREMAP #ifndef MREMAP_MAYMOVE # warn MREMAP_MAYMOVE undefined # define MREMAP_MAYMOVE 0 #endif static void resize(struct raindrops *r, size_t new_rd_size) { size_t old_size = raindrop_size * r->capa; size_t new_size = PAGE_ALIGN(raindrop_size * new_rd_size); void *old_address = r->drops; void *rv; if (r->pid != getpid()) rb_raise(rb_eRuntimeError, "cannot mremap() from child"); rv = mremap(old_address, old_size, new_size, MREMAP_MAYMOVE); if (rv == MAP_FAILED) { int err = errno; if (err == EAGAIN || err == ENOMEM) { rb_gc(); rv = mremap(old_address, old_size, new_size, 0); } if (rv == MAP_FAILED) rb_sys_fail("mremap"); } r->drops = rv; r->size = new_rd_size; r->capa = new_size / raindrop_size; assert(r->capa >= r->size && "bad sizing"); } #else /* ! HAVE_MREMAP */ /* * we cannot use munmap + mmap to reallocate the buffer since it may * already be shared by other processes, so we just fail */ static void resize(struct raindrops *r, size_t new_rd_size) { rb_raise(rb_eRangeError, "mremap(2) is not available"); } #endif /* ! HAVE_MREMAP */ /* * call-seq: * rd.size = new_size * * Increases or decreases the current capacity of our Raindrop. * Raises RangeError if +new_size+ is too big or small for the * current backing store */ static VALUE setsize(VALUE self, VALUE new_size) { size_t new_rd_size = NUM2SIZET(new_size); struct raindrops *r = get(self); if (new_rd_size <= r->capa) r->size = new_rd_size; else resize(r, new_rd_size); return new_size; } /* * call-seq: * rd.capa -> Integer * * Returns the number of slots allocated (but not necessarily used) by * the Raindrops object. */ static VALUE capa(VALUE self) { return SIZET2NUM(get(self)->capa); } /* * call-seq: * rd.dup -> rd_copy * * Duplicates and snapshots the current state of a Raindrops object. */ static VALUE init_copy(VALUE dest, VALUE source) { struct raindrops *dst = DATA_PTR(dest); struct raindrops *src = get(source); init(dest, SIZET2NUM(src->size)); memcpy(dst->drops, src->drops, raindrop_size * src->size); return dest; } static unsigned long *addr_of(VALUE self, VALUE index) { struct raindrops *r = get(self); unsigned long off = FIX2ULONG(index) * raindrop_size; if (off >= raindrop_size * r->size) rb_raise(rb_eArgError, "offset overrun"); return (unsigned long *)((unsigned long)r->drops + off); } static unsigned long incr_decr_arg(int argc, const VALUE *argv) { if (argc > 2 || argc < 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for 1+)", argc); return argc == 2 ? NUM2ULONG(argv[1]) : 1; } /* * call-seq: * rd.incr(index[, number]) -> result * * Increments the value referred to by the +index+ by +number+. * +number+ defaults to +1+ if unspecified. */ static VALUE incr(int argc, VALUE *argv, VALUE self) { unsigned long nr = incr_decr_arg(argc, argv); return ULONG2NUM(__sync_add_and_fetch(addr_of(self, argv[0]), nr)); } /* * call-seq: * rd.decr(index[, number]) -> result * * Decrements the value referred to by the +index+ by +number+. * +number+ defaults to +1+ if unspecified. */ static VALUE decr(int argc, VALUE *argv, VALUE self) { unsigned long nr = incr_decr_arg(argc, argv); return ULONG2NUM(__sync_sub_and_fetch(addr_of(self, argv[0]), nr)); } /* * call-seq: * rd.to_ary -> Array * * converts the Raindrops structure to an Array */ static VALUE to_ary(VALUE self) { struct raindrops *r = get(self); VALUE rv = rb_ary_new2(r->size); size_t i; unsigned long base = (unsigned long)r->drops; for (i = 0; i < r->size; i++) { rb_ary_push(rv, ULONG2NUM(*((unsigned long *)base))); base += raindrop_size; } return rv; } /* * call-seq: * rd.size -> Integer * * Returns the number of counters a Raindrops object can hold. Due to * page alignment, this is always equal or greater than the number of * requested slots passed to Raindrops.new */ static VALUE size(VALUE self) { return SIZET2NUM(get(self)->size); } /* * call-seq: * rd[index] = value * * Assigns +value+ to the slot designated by +index+ */ static VALUE aset(VALUE self, VALUE index, VALUE value) { unsigned long *addr = addr_of(self, index); *addr = NUM2ULONG(value); return value; } /* * call-seq: * rd[index] -> value * * Returns the value of the slot designated by +index+ */ static VALUE aref(VALUE self, VALUE index) { return ULONG2NUM(*addr_of(self, index)); } #ifdef __linux__ void Init_raindrops_linux_inet_diag(void); #endif #ifdef HAVE_TYPE_STRUCT_TCP_INFO void Init_raindrops_tcp_info(void); #endif #ifndef _SC_NPROCESSORS_CONF # if defined _SC_NPROCESSORS_ONLN # define _SC_NPROCESSORS_CONF _SC_NPROCESSORS_ONLN # elif defined _SC_NPROC_ONLN # define _SC_NPROCESSORS_CONF _SC_NPROC_ONLN # elif defined _SC_CRAY_NCPU # define _SC_NPROCESSORS_CONF _SC_CRAY_NCPU # endif #endif /* * call-seq: * rd.evaporate! -> nil * * Releases mmap()-ed memory allocated for the Raindrops object back * to the OS. The Ruby garbage collector will also release memory * automatically when it is not needed, but this forces release * under high memory pressure. */ static VALUE evaporate_bang(VALUE self) { struct raindrops *r = get(self); void *addr = r->drops; r->drops = MAP_FAILED; if (munmap(addr, raindrop_size * r->capa) != 0) rb_sys_fail("munmap"); return Qnil; } void Init_raindrops_ext(void) { VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject); long tmp = 2; #ifdef _SC_NPROCESSORS_CONF tmp = sysconf(_SC_NPROCESSORS_CONF); #endif /* no point in padding on single CPU machines */ if (tmp == 1) raindrop_size = sizeof(unsigned long); #ifdef _SC_LEVEL1_DCACHE_LINESIZE if (tmp != 1) { tmp = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); if (tmp > 0) raindrop_size = (size_t)tmp; } #endif #if defined(_SC_PAGE_SIZE) rd_page_size = (size_t)sysconf(_SC_PAGE_SIZE); #elif defined(_SC_PAGESIZE) rd_page_size = (size_t)sysconf(_SC_PAGESIZE); #elif defined(HAVE_GETPAGESIZE) rd_page_size = (size_t)getpagesize(); #elif defined(PAGE_SIZE) rd_page_size = (size_t)PAGE_SIZE; #elif defined(PAGESIZE) rd_page_size = (size_t)PAGESIZE; #else # error unable to detect page size for mmap() #endif if ((rd_page_size == (size_t)-1) || (rd_page_size < raindrop_size)) rb_raise(rb_eRuntimeError, "system page size invalid: %llu", (unsigned long long)rd_page_size); /* * The size of one page of memory for a mmap()-ed Raindrops region. * Typically 4096 bytes under Linux. */ rb_define_const(cRaindrops, "PAGE_SIZE", SIZET2NUM(rd_page_size)); /* * The size (in bytes) of a slot in a Raindrops object. * This is the size of a word on single CPU systems and * the size of the L1 cache line size if detectable. * * Defaults to 128 bytes if undetectable. */ rb_define_const(cRaindrops, "SIZE", SIZET2NUM(raindrop_size)); /* * The maximum value a raindrop counter can hold */ rb_define_const(cRaindrops, "MAX", ULONG2NUM((unsigned long)-1)); rb_define_alloc_func(cRaindrops, alloc); rb_define_method(cRaindrops, "initialize", init, 1); rb_define_method(cRaindrops, "incr", incr, -1); rb_define_method(cRaindrops, "decr", decr, -1); rb_define_method(cRaindrops, "to_ary", to_ary, 0); rb_define_method(cRaindrops, "[]", aref, 1); rb_define_method(cRaindrops, "[]=", aset, 2); rb_define_method(cRaindrops, "size", size, 0); rb_define_method(cRaindrops, "size=", setsize, 1); rb_define_method(cRaindrops, "capa", capa, 0); rb_define_method(cRaindrops, "initialize_copy", init_copy, 1); rb_define_method(cRaindrops, "evaporate!", evaporate_bang, 0); #ifdef __linux__ Init_raindrops_linux_inet_diag(); #endif #ifdef HAVE_TYPE_STRUCT_TCP_INFO Init_raindrops_tcp_info(); #endif } raindrops-0.19.0/GIT-VERSION-GEN0000755000004100000410000000133513145550250015737 0ustar www-datawww-data#!/bin/sh GVF=GIT-VERSION-FILE DEF_VER=v0.19.0 LF=' ' # First see if there is a version file (included in release tarballs), # then try git-describe, then default. if test -f version then VN=$(cat version) || VN="$DEF_VER" elif test -d .git -o -f .git && VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; v[0-9]*) git update-index -q --refresh test -z "$(git diff-index --name-only HEAD --)" || VN="$VN-dirty" ;; esac then VN=$(echo "$VN" | sed -e 's/-/./g'); else VN="$DEF_VER" fi VN=$(expr "$VN" : v*'\(.*\)') if test -r $GVF then VC=$(sed -e 's/^GIT_VERSION = //' <$GVF) else VC=unset fi test "$VN" = "$VC" || { echo >&2 "GIT_VERSION = $VN" echo "GIT_VERSION = $VN" >$GVF } raindrops-0.19.0/.document0000644000004100000410000000015313145550250015450 0ustar www-datawww-dataREADME LICENSE NEWS lib ext/raindrops/raindrops.c ext/raindrops/linux_inet_diag.c ext/raindrops/tcp_info.c raindrops-0.19.0/COPYING0000644000004100000410000001672513145550250014700 0ustar www-datawww-data GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.