Module: Raindrops::Linux

Included in:
Watcher
Defined in:
lib/raindrops/linux.rb,
ext/raindrops/linux_inet_diag.c

Overview

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

Constant Summary collapse

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

[ '/proc/net/unix', { encoding: "binary" }]

Class Method Summary collapse

Class Method Details

.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.

Returns:

  • (Hash)


614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
# File 'ext/raindrops/linux_inet_diag.c', line 614

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;
}

.unix_listener_stats(paths = nil) ⇒ Object

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.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 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 File.symlink?(path)
        link = path
        path = File.readlink(link)
        path.force_encoding(Encoding::BINARY)
        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[0], encoding: 'binary').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