module Raindrops::Linux
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
Constants
- PROC_NET_UNIX_ARGS
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
Public Class Methods
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;
}
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.
# File lib/raindrops/linux.rb, line 37 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
Private Instance Methods
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.
# File lib/raindrops/linux.rb, line 37 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