linux-listener-stats: add optional Aggregate gem support
[raindrops.git] / examples / linux-listener-stats.rb
blob9493931849bbd5afbe6209f19984f53293cfcdc7
1 #!/usr/bin/ruby
2 # -*- encoding: binary -*-
3 $stdout.sync = $stderr.sync = true
4 # this is used to show or watch the number of active and queued
5 # connections on any listener socket from the command line
7 require 'raindrops'
8 require 'optparse'
9 require 'ipaddr'
10 require 'time'
11 begin
12   require 'aggregate'
13 rescue LoadError
14   $stderr.puts "Aggregate missing, USR1 and USR2 handlers unavailable"
15 end
17 usage = "Usage: #$0 [-d DELAY] [-t QUEUED_THRESHOLD] ADDR..."
18 ARGV.size > 0 or abort usage
19 delay = false
20 queued_thresh = -1
21 # "normal" exits when driven on the command-line
22 trap(:INT) { exit 130 }
23 trap(:PIPE) { exit 0 }
25 agg_active = agg_queued = nil
26 if defined?(Aggregate)
27   agg_active = Aggregate.new
28   agg_queued = Aggregate.new
30   def dump_aggregate(label, agg)
31     $stderr.write "--- #{label} ---\n"
32     %w(count min max outliers_low outliers_high mean std_dev).each do |f|
33       $stderr.write "#{f}=#{agg.__send__ f}\n"
34     end
35     $stderr.write "#{agg}\n\n"
36   end
38   trap(:USR1) do
39     dump_aggregate "active", agg_active
40     dump_aggregate "queued", agg_queued
41   end
42   trap(:USR2) do
43     agg_active = Aggregate.new
44     agg_queued = Aggregate.new
45   end
46   $stderr.puts "USR1(dump_aggregate) and USR2(reset) handlers ready for PID=#$$"
47 end
49 opts = OptionParser.new('', 24, '  ') do |opts|
50   opts.banner = usage
51   opts.on('-d', '--delay=DELAY', Float) { |n| delay = n }
52   opts.on('-t', '--queued-threshold=INT', Integer) { |n| queued_thresh = n }
53   opts.parse! ARGV
54 end
56 ARGV.each do |addr|
57   addr =~ %r{\A(127\..+):(\d+)\z} or next
58   host, port = $1, $2
59   hex_port = '%X' % port.to_i
60   ip_addr = IPAddr.new(host)
61   hex_host = ip_addr.hton.each_byte.inject('') { |s,o| s << '%02X' % o }
62   socks = File.readlines('/proc/net/tcp')
63   hex_addr = "#{hex_host}:#{hex_port}"
64   if socks.grep(/^\s+\d+:\s+#{hex_addr}\s+/).empty? &&
65      ! socks.grep(/^\s+\d+:\s+00000000:#{hex_port}\s+/).empty?
66     warn "W: #{host}:#{port} (#{hex_addr}) not found in /proc/net/tcp"
67     warn "W: Did you mean 0.0.0.0:#{port}?"
68   end
69 end
71 len = "address".size
72 now = nil
73 tcp, unix = [], []
74 ARGV.each do |addr|
75   bs = addr.respond_to?(:bytesize) ? addr.bytesize : addr.size
76   len = bs if bs > len
77   (addr =~ %r{\A/} ? unix : tcp) << addr
78 end
79 combined = {}
80 tcp = nil if tcp.empty?
81 unix = nil if unix.empty?
83 len = 35 if len > 35
84 fmt = "%20s % #{len}s % 10u % 10u\n"
85 $stderr.printf fmt.tr('u','s'), *%w(timestamp address active queued)
87 begin
88   if now
89     combined.clear
90     now = nil
91   end
92   tcp and combined.merge! Raindrops::Linux.tcp_listener_stats(tcp)
93   unix and combined.merge! Raindrops::Linux.unix_listener_stats(unix)
94   combined.each do |addr,stats|
95     active, queued = stats.active, stats.queued
96     if agg_active
97       agg_active << active
98       agg_queued << queued
99     end
100     next if queued < queued_thresh
101     printf fmt, now ||= Time.now.utc.iso8601, addr, active, queued
102   end
103 end while delay && sleep(delay)