raindrops.git  about / heads / tags
real-time stats for preforking Rack servers
   commit 17d57231c4d9d3dfb486b6d37b85d57e6065958e (patch)
   parent 8d9f163 test_watcher: disable test correctly when aggregate is missing
     tree 2ddee320f6d3b11b1cbdf9f3c65c3c52e96bdd15
   author Eric Wong <e@80x24.org>  2017-03-16 03:16:51 +0000
committer Eric Wong <e@80x24.org>  2017-03-17 22:39:11 +0000

tcp_info: support this struct under FreeBSD

Of course these fields are not portable between Linux and FreeBSD,
but they should remain ABI-compatible for future versions of each OS.

Tested on FreeBSD 10.3-RELEASE i386

TCP state names will be another problem...
---
 ext/raindrops/extconf.rb                          |  78 ++++++++++-
 ext/raindrops/raindrops.c                         |   8 +-
 ext/raindrops/{linux_tcp_info.c => tcp_info.c}    | 159 +++++++++++-----------
 test/{test_linux_tcp_info.rb => test_tcp_info.rb} |  37 +++--
 4 files changed, 189 insertions(+), 93 deletions(-)

diff --git a/ext/raindrops/extconf.rb b/ext/raindrops/extconf.rb
index 79d212c..5273b74 100644
--- a/ext/raindrops/extconf.rb
+++ b/ext/raindrops/extconf.rb
@@ -1,4 +1,5 @@
 require 'mkmf'
+require 'shellwords'
 
 dir_config('atomic_ops')
 have_func('mmap', 'sys/mman.h') or abort 'mmap() not found'
@@ -6,9 +7,83 @@
 
 $CPPFLAGS += " -D_GNU_SOURCE "
 have_func('mremap', 'sys/mman.h')
-have_header('linux/tcp.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 = <<EOF
+static VALUE #{cfunc}(VALUE self)
+{
+	struct tcp_info *info = DATA_PTR(self);
+	return UINT2NUM((uint32_t)info->#{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
+end
+
 have_func("getpagesize", "unistd.h")
 have_func('rb_thread_blocking_region')
 have_func('rb_thread_io_blocking_region')
@@ -53,4 +128,5 @@
 
   apt-get install libatomic-ops-dev
 SRC
+create_header # generate extconf.h to avoid excessively long command-line
 create_makefile('raindrops_ext')
diff --git a/ext/raindrops/raindrops.c b/ext/raindrops/raindrops.c
index 390b8b8..0ea3e32 100644
--- a/ext/raindrops/raindrops.c
+++ b/ext/raindrops/raindrops.c
@@ -336,7 +336,9 @@ static VALUE aref(VALUE self, VALUE index)
 
 #ifdef __linux__
 void Init_raindrops_linux_inet_diag(void);
-void Init_raindrops_linux_tcp_info(void);
+#endif
+#ifdef HAVE_TYPE_STRUCT_TCP_INFO
+void Init_raindrops_tcp_info(void);
 #endif
 
 #ifndef _SC_NPROCESSORS_CONF
@@ -441,6 +443,8 @@ void Init_raindrops_ext(void)
 
 #ifdef __linux__
 	Init_raindrops_linux_inet_diag();
-	Init_raindrops_linux_tcp_info();
+#endif
+#ifdef HAVE_TYPE_STRUCT_TCP_INFO
+	Init_raindrops_tcp_info();
 #endif
 }
diff --git a/ext/raindrops/linux_tcp_info.c b/ext/raindrops/tcp_info.c
similarity index 51%
rename from ext/raindrops/linux_tcp_info.c
rename to ext/raindrops/tcp_info.c
index 83001a5..dc615f7 100644
--- a/ext/raindrops/linux_tcp_info.c
+++ b/ext/raindrops/tcp_info.c
@@ -1,50 +1,53 @@
-#if defined(__linux__) && defined(HAVE_LINUX_TCP_H)
 #include <ruby.h>
+#include "extconf.h"
 #include <sys/socket.h>
 #include <netinet/in.h>
-#include <linux/tcp.h>
-#ifdef TCP_INFO
-#include "my_fileno.h"
+#if defined(HAVE_LINUX_TCP_H)
+#  include <linux/tcp.h>
+#else
+#  if defined(HAVE_NETINET_TCP_H)
+#    include <netinet/tcp.h>
+#  endif
+#  if defined(HAVE_NETINET_TCP_FSM_H)
+#    include <netinet/tcp_fsm.h>
+#  endif
+#endif
 
-#define TCPI_ATTR_READER(x) \
-static VALUE tcp_info_##x(VALUE self) \
-{ \
-	struct tcp_info *info = DATA_PTR(self); \
-	return UINT2NUM((uint32_t)info->tcpi_##x); \
-}
+#ifdef HAVE_TYPE_STRUCT_TCP_INFO
+#include "my_fileno.h"
 
-TCPI_ATTR_READER(state)
-TCPI_ATTR_READER(ca_state)
-TCPI_ATTR_READER(retransmits)
-TCPI_ATTR_READER(probes)
-TCPI_ATTR_READER(backoff)
-TCPI_ATTR_READER(options)
-TCPI_ATTR_READER(snd_wscale)
-TCPI_ATTR_READER(rcv_wscale)
-TCPI_ATTR_READER(rto)
-TCPI_ATTR_READER(ato)
-TCPI_ATTR_READER(snd_mss)
-TCPI_ATTR_READER(rcv_mss)
-TCPI_ATTR_READER(unacked)
-TCPI_ATTR_READER(sacked)
-TCPI_ATTR_READER(lost)
-TCPI_ATTR_READER(retrans)
-TCPI_ATTR_READER(fackets)
-TCPI_ATTR_READER(last_data_sent)
-TCPI_ATTR_READER(last_ack_sent)
-TCPI_ATTR_READER(last_data_recv)
-TCPI_ATTR_READER(last_ack_recv)
-TCPI_ATTR_READER(pmtu)
-TCPI_ATTR_READER(rcv_ssthresh)
-TCPI_ATTR_READER(rtt)
-TCPI_ATTR_READER(rttvar)
-TCPI_ATTR_READER(snd_ssthresh)
-TCPI_ATTR_READER(snd_cwnd)
-TCPI_ATTR_READER(advmss)
-TCPI_ATTR_READER(reordering)
-TCPI_ATTR_READER(rcv_rtt)
-TCPI_ATTR_READER(rcv_space)
-TCPI_ATTR_READER(total_retrans)
+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)
 {
@@ -85,7 +88,7 @@ static VALUE init(VALUE self, VALUE io)
 	return self;
 }
 
-void Init_raindrops_linux_tcp_info(void)
+void Init_raindrops_tcp_info(void)
 {
 	VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject);
 	VALUE cTCP_Info;
@@ -156,41 +159,37 @@ void Init_raindrops_linux_tcp_info(void)
 	 */
 	rb_define_method(cTCP_Info, "get!", init, 1);
 
-#define TCPI_DEFINE_METHOD(x) \
-	rb_define_method(cTCP_Info, #x, tcp_info_##x, 0)
-
-	TCPI_DEFINE_METHOD(state);
-	TCPI_DEFINE_METHOD(ca_state);
-	TCPI_DEFINE_METHOD(retransmits);
-	TCPI_DEFINE_METHOD(probes);
-	TCPI_DEFINE_METHOD(backoff);
-	TCPI_DEFINE_METHOD(options);
-	TCPI_DEFINE_METHOD(snd_wscale);
-	TCPI_DEFINE_METHOD(rcv_wscale);
-	TCPI_DEFINE_METHOD(rto);
-	TCPI_DEFINE_METHOD(ato);
-	TCPI_DEFINE_METHOD(snd_mss);
-	TCPI_DEFINE_METHOD(rcv_mss);
-	TCPI_DEFINE_METHOD(unacked);
-	TCPI_DEFINE_METHOD(sacked);
-	TCPI_DEFINE_METHOD(lost);
-	TCPI_DEFINE_METHOD(retrans);
-	TCPI_DEFINE_METHOD(fackets);
-	TCPI_DEFINE_METHOD(last_data_sent);
-	TCPI_DEFINE_METHOD(last_ack_sent);
-	TCPI_DEFINE_METHOD(last_data_recv);
-	TCPI_DEFINE_METHOD(last_ack_recv);
-	TCPI_DEFINE_METHOD(pmtu);
-	TCPI_DEFINE_METHOD(rcv_ssthresh);
-	TCPI_DEFINE_METHOD(rtt);
-	TCPI_DEFINE_METHOD(rttvar);
-	TCPI_DEFINE_METHOD(snd_ssthresh);
-	TCPI_DEFINE_METHOD(snd_cwnd);
-	TCPI_DEFINE_METHOD(advmss);
-	TCPI_DEFINE_METHOD(reordering);
-	TCPI_DEFINE_METHOD(rcv_rtt);
-	TCPI_DEFINE_METHOD(rcv_space);
-	TCPI_DEFINE_METHOD(total_retrans);
+	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;
 }
-#endif /* TCP_INFO */
-#endif /* defined(__linux__) && defined(HAVE_LINUX_TCP_H) */
+#endif /* HAVE_STRUCT_TCP_INFO */
diff --git a/test/test_linux_tcp_info.rb b/test/test_tcp_info.rb
similarity index 66%
rename from test/test_linux_tcp_info.rb
rename to test/test_tcp_info.rb
index c947211..15df087 100644
--- a/test/test_linux_tcp_info.rb
+++ b/test/test_tcp_info.rb
@@ -5,15 +5,15 @@
 require 'socket'
 require 'pp'
 $stderr.sync = $stdout.sync = true
-class TestLinuxTCP_Info < Test::Unit::TestCase
+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
+  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]
@@ -29,10 +29,8 @@ def test_tcp_server
     tmp.get!(s)
     assert_equal before, tmp.object_id
 
-    ensure
-      c.close if c
-      a.close if a
-      s.close
+  ensure
+    [ c, a, s ].compact.each(&:close)
   end
 
   def test_accessors
@@ -42,12 +40,14 @@ def test_accessors
     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
-    ensure
-      s.close
+    assert tmp.respond_to?(:state), 'every OS knows about TCP state, right?'
+  ensure
+    s.close
   end
 
   def test_tcp_server_delayed
@@ -65,4 +65,21 @@ def test_tcp_server_delayed
       a.close if a
       s.close
   end
-end if RUBY_PLATFORM =~ /linux/
+
+  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
+    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


glossary
--------
Commit objects reference one tree, and zero or more parents.

Single parent commits can typically generate a patch in
unified diff format via `git format-patch'.

Multiple parents means the commit is a merge.

Root commits have no ancestor.  Note that it is
possible to have multiple root commits when merging independent histories.

Every commit references one top-level tree object.

git clone https://yhbt.net/raindrops.git