raindrops 0.13.0 → 0.19.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.document +1 -2
  3. data/.gitattributes +4 -0
  4. data/.gitignore +1 -1
  5. data/.olddoc.yml +13 -0
  6. data/GIT-VERSION-GEN +1 -1
  7. data/GNUmakefile +1 -2
  8. data/LICENSE +3 -3
  9. data/README +28 -34
  10. data/TODO +2 -0
  11. data/archive/.gitignore +3 -0
  12. data/archive/slrnpull.conf +4 -0
  13. data/examples/linux-listener-stats.rb +1 -2
  14. data/examples/watcher_demo.ru +1 -1
  15. data/examples/yahns.conf.rb +30 -0
  16. data/examples/zbatery.conf.rb +4 -1
  17. data/ext/raindrops/extconf.rb +107 -2
  18. data/ext/raindrops/linux_inet_diag.c +94 -101
  19. data/ext/raindrops/raindrops.c +28 -7
  20. data/ext/raindrops/tcp_info.c +245 -0
  21. data/lib/raindrops.rb +1 -1
  22. data/lib/raindrops/aggregate.rb +1 -1
  23. data/lib/raindrops/aggregate/last_data_recv.rb +1 -5
  24. data/lib/raindrops/aggregate/pmq.rb +23 -17
  25. data/lib/raindrops/linux.rb +5 -6
  26. data/lib/raindrops/middleware.rb +4 -6
  27. data/lib/raindrops/middleware/proxy.rb +2 -2
  28. data/lib/raindrops/watcher.rb +13 -13
  29. data/pkg.mk +26 -50
  30. data/raindrops.gemspec +14 -21
  31. data/test/ipv6_enabled.rb +4 -4
  32. data/test/test_aggregate_pmq.rb +1 -1
  33. data/test/test_inet_diag_socket.rb +1 -1
  34. data/test/test_last_data_recv_unicorn.rb +1 -1
  35. data/test/test_linux.rb +10 -2
  36. data/test/test_linux_all_tcp_listen_stats_leak.rb +2 -2
  37. data/test/test_linux_ipv6.rb +8 -0
  38. data/test/test_raindrops.rb +1 -1
  39. data/test/{test_linux_tcp_info.rb → test_tcp_info.rb} +34 -14
  40. data/test/test_watcher.rb +15 -10
  41. metadata +59 -171
  42. data/.wrongdoc.yml +0 -6
  43. data/Rakefile +0 -28
  44. data/ext/raindrops/linux_tcp_info.c +0 -173
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1e1444bc6e35e67993745b3627e848537490fd3b02a67e12be9721b031e22e70
4
+ data.tar.gz: 9ed2dff40418e89a71ecf18cfb74b72d28d9570ceef314ee1c81bdff19ed832a
5
+ SHA512:
6
+ metadata.gz: ccb15ce6a96c1619d53d6090851ef8152ebd673b2db8aed9c6d4bb42757de2d9d051888d3f105d6d3461683eb33575d343e7d31a1ddb91a8f9914bb4e6abcb81
7
+ data.tar.gz: ce265e39bf3217f3b62931ead0fff78e7e118785466bc0eecff1858f9d808982c12b14f2645ecfffd96c8cb8e0a1a72ac03c0d6ad220c84950dfd2fe05d7f258
data/.document CHANGED
@@ -1,8 +1,7 @@
1
1
  README
2
2
  LICENSE
3
3
  NEWS
4
- ChangeLog
5
4
  lib
6
5
  ext/raindrops/raindrops.c
7
6
  ext/raindrops/linux_inet_diag.c
8
- ext/raindrops/linux_tcp_info.c
7
+ ext/raindrops/tcp_info.c
data/.gitattributes ADDED
@@ -0,0 +1,4 @@
1
+ *.gemspec diff=ruby
2
+ *.rb diff=ruby
3
+ *.ru diff=ruby
4
+ Rakefile diff=ruby
data/.gitignore CHANGED
@@ -5,7 +5,7 @@
5
5
  Makefile
6
6
  /GIT-VERSION-FILE
7
7
  /local.mk
8
- /NEWS
8
+ /NEWS*
9
9
  /ChangeLog
10
10
  /.manifest
11
11
  /man
data/.olddoc.yml ADDED
@@ -0,0 +1,13 @@
1
+ ---
2
+ cgit_url: https://yhbt.net/raindrops.git/
3
+ rdoc_url: https://yhbt.net/raindrops/
4
+ public_email: raindrops-public@yhbt.net
5
+ ml_url:
6
+ - https://yhbt.net/raindrops-public/
7
+ - http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/raindrops-public
8
+ nntp_url:
9
+ - nntp://news.public-inbox.org/inbox.comp.lang.ruby.raindrops
10
+ - nntp://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/inbox.comp.lang.ruby.raindrops
11
+ source_code:
12
+ - git clone https://yhbt.net/raindrops.git
13
+ - torsocks git clone http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/raindrops.git
data/GIT-VERSION-GEN CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v0.13.0
4
+ DEF_VER=v0.19.2
5
5
 
6
6
  LF='
7
7
  '
data/GNUmakefile CHANGED
@@ -1,5 +1,4 @@
1
1
  all::
2
- RSYNC_DEST := bogomips.org:/srv/raindrops
3
- rfproject := rainbows
2
+ RSYNC_DEST := yhbt.net:/srv/yhbt/raindrops
4
3
  rfpackage := raindrops
5
4
  include pkg.mk
data/LICENSE CHANGED
@@ -3,8 +3,8 @@ revision control for names and email addresses of all of them.
3
3
 
4
4
  You can redistribute it and/or modify it under the terms of the GNU
5
5
  Lesser General Public License (LGPL) as published by the Free Software
6
- Foundation, version {2.1}[http://www.gnu.org/licenses/lgpl-2.1.txt] or
7
- later. Currently version {3}[http://www.gnu.org/licenses/lgpl-3.0.txt],
6
+ Foundation, version {2.1}[https://www.gnu.org/licenses/lgpl-2.1.txt] or
7
+ later. Currently version {3}[https://www.gnu.org/licenses/lgpl-3.0.txt],
8
8
  is preferred (see link:COPYING).
9
9
 
10
10
  raindrops is distributed in the hope that it will be useful, but WITHOUT
@@ -13,4 +13,4 @@ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
13
13
  License for more details.
14
14
 
15
15
  You should have received a copy of the GNU Lesser General Public License
16
- along with the raindrops; if not, see <http://www.gnu.org/licenses/>
16
+ along with the raindrops; if not, see <https://www.gnu.org/licenses/>
data/README CHANGED
@@ -1,11 +1,10 @@
1
1
  = raindrops - real-time stats for preforking Rack servers
2
2
 
3
- Raindrops is a real-time stats toolkit to show statistics for Rack HTTP
4
- servers. It is designed for preforking servers such as Rainbows! and
5
- Unicorn, but should support any Rack HTTP server under Ruby 2.0, 1.9,
6
- 1.8 and Rubinius on platforms supporting POSIX shared memory. It may
7
- also be used as a generic scoreboard for sharing atomic counters across
8
- multiple processes.
3
+ raindrops is a real-time stats toolkit to show statistics for Rack HTTP
4
+ servers. It is designed for preforking servers such as unicorn, but
5
+ should support any Rack HTTP server on platforms supporting POSIX shared
6
+ memory. It may also be used as a generic scoreboard for sharing atomic
7
+ counters across multiple processes.
9
8
 
10
9
  == Features
11
10
 
@@ -38,12 +37,15 @@ and "tcp_diag" kernel modules are loaded as they do not autoload correctly
38
37
 
39
38
  == Install
40
39
 
41
- We recommend GCC 4+ (or compatible) to support the
42
- {atomic builtins}[http://gcc.gnu.org/onlinedocs/gcc/Atomic-Builtins.html]
43
- (__sync_{add,sub}_and_fetch()). For non-GCC 4+ users, we also support
44
- compilation with the
45
- {libatomic_ops}[http://www.hpl.hp.com/research/linux/atomic_ops/]
46
- package starting with Raindrops 0.4.0.
40
+ We recommend GCC 4+ (or compatible) to support the __sync builtins
41
+ (__sync_{add,sub}_and_fetch()):
42
+
43
+ https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
44
+
45
+ For non-GCC 4+ users, we also support compilation with the libatomic_ops
46
+ package starting with Raindrops 0.4.0:
47
+
48
+ https://github.com/ivmai/libatomic_ops
47
49
 
48
50
  If you're using a packaged Ruby distribution, make sure you have a C
49
51
  compiler and the matching Ruby development libraries and headers.
@@ -52,30 +54,25 @@ If you use RubyGems:
52
54
 
53
55
  gem install raindrops
54
56
 
55
- Otherwise grab the latest tarball from:
56
-
57
- http://raindrops.bogomips.org/files/
58
-
59
- Unpack it, and run "ruby setup.rb"
60
-
61
57
  == Usage
62
58
 
63
59
  See Raindrops::Middleware and Raindrops::LastDataRecv documentation for
64
60
  use Rack servers. The entire library is fully-documented and we are
65
- responsive on the mailing list (mailto:raindrops@librelist.org) if you
66
- have any questions or comments.
61
+ responsive on the publically archived mailing list
62
+ (mailto:raindrops-public@yhbt.net) if
63
+ you have any questions or comments.
67
64
 
68
65
  == Development
69
66
 
70
67
  You can get the latest source via git from the following locations:
71
68
 
72
- git://bogomips.org/raindrops.git
69
+ git://yhbt.net/raindrops.git
73
70
  git://repo.or.cz/raindrops.git (mirror)
74
71
 
75
72
  You may browse the code from the web and download the latest snapshot
76
73
  tarballs here:
77
74
 
78
- * http://bogomips.org/raindrops.git (cgit)
75
+ * https://yhbt.net/raindrops.git
79
76
  * http://repo.or.cz/w/raindrops.git (gitweb)
80
77
 
81
78
  Inline patches (from "git format-patch") to the mailing list are
@@ -87,22 +84,19 @@ git itself. See the Documentation/SubmittingPatches document
87
84
  distributed with git on on patch submission guidelines to follow. Just
88
85
  don't email the git mailing list or maintainer with raindrops patches.
89
86
 
90
- raindrops is currently dual-licensed under the LGPLv2.1 and LGPLv3. To
91
- allow for a transition to future versions of the LGPL, contributors are
92
- required to sign-off changes allowing allowing the project leader to
93
- relicense raindrops under newer versions of the LGPL (which should be
94
- similar in spirit to the existing LGPL).
87
+ raindrops is licensed under the LGPL-2.1+
95
88
 
96
89
  == Contact
97
90
 
98
91
  All feedback (bug reports, user/development discussion, patches, pull
99
- requests) go to the mailing list: mailto:raindrops@librelist.org
100
-
101
- The mailing list is mirrored to Gmane, all information about the
102
- group is here:
92
+ requests) go to the publically archived mailing list:
93
+ mailto:raindrops-public@yhbt.net
103
94
 
104
- http://gmane.org/info.php?group=gmane.comp.lang.ruby.raindrops.general
95
+ Mailing list archives are available over HTTPS and NNTP:
105
96
 
106
- Mailing list archives in mbox format may be downloaded here:
97
+ * https://yhbt.net/raindrops-public/
98
+ * http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/raindrops-public/
99
+ * nntp://news.public-inbox.org/inbox.comp.lang.ruby.raindrops
107
100
 
108
- http://raindrops.bogomips.org/archives/
101
+ Since archives are public, scrub sensitive information and
102
+ use anonymity tools such as Tor or Mixmaster if you deem necessary.
data/TODO CHANGED
@@ -1 +1,3 @@
1
+ * fix IPv6 inet_diag reporting for Raindrops::Middleware
1
2
  * pure Ruby version for non-forking servers (patches welcome!)
3
+ * unix_diag and udp_diag support
@@ -0,0 +1,3 @@
1
+ /data
2
+ /news
3
+ /requests
@@ -0,0 +1,4 @@
1
+ # group_name max expire headers_only
2
+ gmane.comp.lang.ruby.raindrops.general 1000000000 1000000000 0
3
+
4
+ # usage: slrnpull -d $PWD -h news.gmane.org --no-post
@@ -15,7 +15,6 @@
15
15
  usage = "Usage: #$0 [-d DELAY] [-t QUEUED_THRESHOLD] ADDR..."
16
16
  ARGV.size > 0 or abort usage
17
17
  delay = false
18
- all = false
19
18
  queued_thresh = -1
20
19
  # "normal" exits when driven on the command-line
21
20
  trap(:INT) { exit 130 }
@@ -25,7 +24,7 @@
25
24
  opts.banner = usage
26
25
  opts.on('-d', '--delay=DELAY', Float) { |n| delay = n }
27
26
  opts.on('-t', '--queued-threshold=INT', Integer) { |n| queued_thresh = n }
28
- opts.on('-a', '--all') { all = true }
27
+ opts.on('-a', '--all') { } # noop
29
28
  opts.parse! ARGV
30
29
  end
31
30
 
@@ -1,5 +1,5 @@
1
1
  # This is a snippet of the config that powers
2
- # http://raindrops-demo.bogomips.org/
2
+ # https://yhbt.net/raindrops-demo/
3
3
  # This may be used with the packaged zbatery.conf.rb
4
4
  #
5
5
  # zbatery -c zbatery.conf.ru watcher_demo.ru -E none
@@ -0,0 +1,30 @@
1
+ # Inlined rack app using yahns server (git clone git://yhbt.net/yahns.git)
2
+ # Usage: yahns -c /path/to/this/file.conf.rb
3
+ # There is no separate config.ru file for this example,
4
+ # but rack_app may also be a string pointing to the path of a
5
+ # config.ru file
6
+
7
+ require 'rack'
8
+ rack_app = Rack::Builder.new do
9
+ use Rack::Head
10
+ addr = %w(0.0.0.0:9418 0.0.0.0:443 [::]:443 0.0.0.0:80 [::]:80
11
+ 127.0.0.1:6081 127.0.0.1:280 0.0.0.0:119 [::]:119)
12
+ use Raindrops::Middleware, listeners: addr
13
+ run Raindrops::Watcher.new(listeners: addr)
14
+ end.to_app
15
+ # rack_app = '/path/to/config.ru' # a more standard config
16
+
17
+ app(:rack, rack_app) do
18
+ # I keep IPv4 and IPv6 on separate sockets to avoid ugly
19
+ # IPv4-mapped-IPv6 addresses:
20
+ listen 8080
21
+ listen '[::]:8080', ipv6only: true
22
+ client_max_body_size 0 # no POST or any uploads
23
+ client_timeout 5
24
+ output_buffering false # needed for /tail/ endpoint to avoid ENOSPC
25
+ queue { worker_threads 30 }
26
+ end
27
+
28
+ # logging is optional, but recommended for diagnosing problems
29
+ # stderr_path '/var/log/yahns/stderr-raindrops.log'
30
+ # stdout_path '/var/log/yahns/stdout-raindrops.log'
@@ -1,6 +1,9 @@
1
1
  # Used for running Raindrops::Watcher, which requires a multi-threaded
2
2
  # Rack server capable of streaming a response. Threads must be used,
3
- # so Zbatery is recommended: http://zbatery.bogomip.org/
3
+ # so any multi-threaded Rack server may be used.
4
+ # zbatery was recommended in the past, but it is abandoned
5
+ # <http://zbatery.bogomip.org/>.
6
+ # yahns may work as an alternative (see yahns.conf.rb in this dir)
4
7
  Rainbows! do
5
8
  use :ThreadSpawn
6
9
  end
@@ -1,4 +1,5 @@
1
1
  require 'mkmf'
2
+ require 'shellwords'
2
3
 
3
4
  dir_config('atomic_ops')
4
5
  have_func('mmap', 'sys/mman.h') or abort 'mmap() not found'
@@ -6,9 +7,112 @@
6
7
 
7
8
  $CPPFLAGS += " -D_GNU_SOURCE "
8
9
  have_func('mremap', 'sys/mman.h')
10
+ headers = %w(sys/types.h netdb.h string.h sys/socket.h netinet/in.h)
11
+ if have_header('linux/tcp.h')
12
+ headers << 'linux/tcp.h'
13
+ else
14
+ %w(netinet/tcp.h netinet/tcp_fsm.h).each { |h|
15
+ have_header(h, headers) and headers << h
16
+ }
17
+ end
9
18
 
10
19
  $CPPFLAGS += " -D_BSD_SOURCE "
20
+
21
+ if have_type("struct tcp_info", headers)
22
+ %w(
23
+ tcpi_state
24
+ tcpi_ca_state
25
+ tcpi_retransmits
26
+ tcpi_probes
27
+ tcpi_backoff
28
+ tcpi_options
29
+ tcpi_snd_wscale
30
+ tcpi_rcv_wscale
31
+ tcpi_rto
32
+ tcpi_ato
33
+ tcpi_snd_mss
34
+ tcpi_rcv_mss
35
+ tcpi_unacked
36
+ tcpi_sacked
37
+ tcpi_lost
38
+ tcpi_retrans
39
+ tcpi_fackets
40
+ tcpi_last_data_sent
41
+ tcpi_last_ack_sent
42
+ tcpi_last_data_recv
43
+ tcpi_last_ack_recv
44
+ tcpi_pmtu
45
+ tcpi_rcv_ssthresh
46
+ tcpi_rtt
47
+ tcpi_rttvar
48
+ tcpi_snd_ssthresh
49
+ tcpi_snd_cwnd
50
+ tcpi_advmss
51
+ tcpi_reordering
52
+ tcpi_rcv_rtt
53
+ tcpi_rcv_space
54
+ tcpi_total_retrans
55
+ tcpi_snd_wnd
56
+ tcpi_snd_bwnd
57
+ tcpi_snd_nxt
58
+ tcpi_rcv_nxt
59
+ tcpi_toe_tid
60
+ tcpi_snd_rexmitpack
61
+ tcpi_rcv_ooopack
62
+ tcpi_snd_zerowin
63
+ ).each do |field|
64
+ cfunc = "tcp_info_#{field}"
65
+ if have_struct_member('struct tcp_info', field, headers)
66
+ func_body = <<EOF
67
+ static VALUE #{cfunc}(VALUE self)
68
+ {
69
+ struct tcp_info *info = DATA_PTR(self);
70
+ return UINT2NUM((uint32_t)info->#{field});
71
+ }
72
+ EOF
73
+ func_body.delete!("\n")
74
+ $defs << "-DCFUNC_#{cfunc}=#{Shellwords.shellescape(func_body)}"
75
+ else
76
+ func_body = "static inline void #{cfunc}(void) {}"
77
+ $defs << "-DCFUNC_#{cfunc}=#{Shellwords.shellescape(func_body)}"
78
+ cfunc = 'rb_f_notimplement'.freeze
79
+ end
80
+ rbmethod = %Q("#{field.sub(/\Atcpi_/, ''.freeze)}")
81
+ $defs << "-DDEFINE_METHOD_tcp_info_#{field}=" \
82
+ "#{Shellwords.shellescape(
83
+ %Q[rb_define_method(cTCP_Info,#{rbmethod},#{cfunc},0)])}"
84
+ end
85
+ tcp_state_map = {
86
+ ESTABLISHED: %w(TCP_ESTABLISHED TCPS_ESTABLISHED),
87
+ SYN_SENT: %w(TCP_SYN_SENT TCPS_SYN_SENT),
88
+ SYN_RECV: %w(TCP_SYN_RECV TCPS_SYN_RECEIVED),
89
+ FIN_WAIT1: %w(TCP_FIN_WAIT1 TCPS_FIN_WAIT_1),
90
+ FIN_WAIT2: %w(TCP_FIN_WAIT2 TCPS_FIN_WAIT_2),
91
+ TIME_WAIT: %w(TCP_TIME_WAIT TCPS_TIME_WAIT),
92
+ CLOSE: %w(TCP_CLOSE TCPS_CLOSED),
93
+ CLOSE_WAIT: %w(TCP_CLOSE_WAIT TCPS_CLOSE_WAIT),
94
+ LAST_ACK: %w(TCP_LAST_ACK TCPS_LAST_ACK),
95
+ LISTEN: %w(TCP_LISTEN TCPS_LISTEN),
96
+ CLOSING: %w(TCP_CLOSING TCPS_CLOSING),
97
+ }
98
+ nstate = 0
99
+ tcp_state_map.each do |state, try|
100
+ try.each do |os_name|
101
+ have_const(os_name, headers) or next
102
+ tcp_state_map[state] = os_name
103
+ nstate += 1
104
+ end
105
+ end
106
+ if nstate == tcp_state_map.size
107
+ $defs << '-DRAINDROPS_TCP_STATES_ALL_KNOWN=1'
108
+ tcp_state_map.each do |state, name|
109
+ $defs << "-DRAINDROPS_TCP_#{state}=#{name}"
110
+ end
111
+ end
112
+ end
113
+
11
114
  have_func("getpagesize", "unistd.h")
115
+ have_func('rb_thread_call_without_gvl')
12
116
  have_func('rb_thread_blocking_region')
13
117
  have_func('rb_thread_io_blocking_region')
14
118
 
@@ -39,17 +143,18 @@
39
143
  $defs.push(format("-DHAVE_GCC_ATOMIC_BUILTINS"))
40
144
  true
41
145
  else
42
- prev_cflags = $CFLAGS
146
+ $CFLAGS = prev_cflags
43
147
  false
44
148
  end
45
149
  end
46
150
  end or have_header('atomic_ops.h') or abort <<-SRC
47
151
 
48
152
  libatomic_ops is required if GCC 4+ is not used.
49
- See http://www.hpl.hp.com/research/linux/atomic_ops/
153
+ See https://github.com/ivmai/libatomic_ops
50
154
 
51
155
  Users of Debian-based distros may run:
52
156
 
53
157
  apt-get install libatomic-ops-dev
54
158
  SRC
159
+ create_header # generate extconf.h to avoid excessively long command-line
55
160
  create_makefile('raindrops_ext')
@@ -1,45 +1,24 @@
1
1
  #include <ruby.h>
2
- #ifdef HAVE_RUBY_ST_H
3
- # include <ruby/st.h>
4
- #else
5
- # include <st.h>
6
- #endif
2
+ #include <stdarg.h>
3
+ #include <ruby/st.h>
7
4
  #include "my_fileno.h"
8
5
  #ifdef __linux__
9
6
 
10
- /* Ruby 1.8.6+ macros (for compatibility with Ruby 1.9) */
11
- #ifndef RSTRING_LEN
12
- # define RSTRING_LEN(s) (RSTRING(s)->len)
13
- #endif
14
-
15
- /* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
16
- #if !defined(HAVE_RB_THREAD_BLOCKING_REGION) && \
17
- !defined(HAVE_RB_THREAD_IO_BLOCKING_REGION)
18
- # include <rubysig.h>
19
- # define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
20
- typedef void rb_unblock_function_t(void *);
21
- typedef VALUE rb_blocking_function_t(void *);
22
- static VALUE
23
- rb_thread_blocking_region(
24
- rb_blocking_function_t *func, void *data1,
25
- rb_unblock_function_t *ubf, void *data2)
26
- {
27
- VALUE rv;
28
-
29
- TRAP_BEG;
30
- rv = func(data1);
31
- TRAP_END;
32
-
33
- return rv;
34
- }
35
- #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
36
-
37
7
  #ifdef HAVE_RB_THREAD_IO_BLOCKING_REGION
8
+ /* Ruby 1.9.3 and 2.0.0 */
38
9
  VALUE rb_thread_io_blocking_region(rb_blocking_function_t *, void *, int);
10
+ # define rd_fd_region(fn,data,fd) \
11
+ rb_thread_io_blocking_region((fn),(data),(fd))
12
+ #elif defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) && \
13
+ defined(HAVE_RUBY_THREAD_H) && HAVE_RUBY_THREAD_H
14
+ /* in case Ruby 2.0+ ever drops rb_thread_io_blocking_region: */
15
+ # include <ruby/thread.h>
16
+ # define COMPAT_FN (void *(*)(void *))
17
+ # define rd_fd_region(fn,data,fd) \
18
+ rb_thread_call_without_gvl(COMPAT_FN(fn),(data),RUBY_UBF_IO,NULL)
39
19
  #else
40
- # define rb_thread_io_blocking_region(fn,data,fd) \
41
- rb_thread_blocking_region((fn),(data),RUBY_UBF_IO,0)
42
- #endif /* HAVE_RB_THREAD_IO_BLOCKING_REGION */
20
+ # error Ruby <= 1.8 not supported
21
+ #endif
43
22
 
44
23
  #include <assert.h>
45
24
  #include <errno.h>
@@ -213,91 +192,89 @@ static const char *addr_any(sa_family_t family)
213
192
  return ipv6;
214
193
  }
215
194
 
216
- static void bug_warn(void)
195
+ #ifdef __GNUC__
196
+ static void bug_warn_nogvl(const char *, ...)
197
+ __attribute__((format(printf,1,2)));
198
+ #endif
199
+ static void bug_warn_nogvl(const char *fmt, ...)
217
200
  {
201
+ va_list ap;
202
+
203
+ va_start(ap, fmt);
204
+ vfprintf(stderr, fmt, ap);
205
+ va_end(ap);
206
+
218
207
  fprintf(stderr, "Please report how you produced this at "\
219
- "raindrops@librelist.org\n");
208
+ "raindrops-public@yhbt.net\n");
220
209
  fflush(stderr);
221
210
  }
222
211
 
223
212
  static struct listen_stats *stats_for(st_table *table, struct inet_diag_msg *r)
224
213
  {
225
- char *key, *port, *old_key;
214
+ char *host, *key, *port, *old_key;
226
215
  size_t alloca_len;
227
216
  struct listen_stats *stats;
228
- size_t keylen;
229
- size_t portlen = sizeof("65535");
230
- union any_addr sa;
231
- socklen_t len = sizeof(struct sockaddr_storage);
232
- int rc;
233
- int flags = NI_NUMERICHOST | NI_NUMERICSERV;
234
-
235
- switch ((sa.ss.ss_family = r->idiag_family)) {
217
+ socklen_t hostlen;
218
+ socklen_t portlen = (socklen_t)sizeof("65535");
219
+ int n;
220
+ const void *src = r->id.idiag_src;
221
+
222
+ switch (r->idiag_family) {
236
223
  case AF_INET: {
237
- sa.in.sin_port = r->id.idiag_sport;
238
- sa.in.sin_addr.s_addr = r->id.idiag_src[0];
239
- keylen = INET_ADDRSTRLEN;
240
- alloca_len = keylen + 1 + portlen;
241
- key = alloca(alloca_len);
242
- key[keylen] = 0; /* will be ':' later */
243
- port = key + keylen + 1;
244
- rc = getnameinfo(&sa.sa, len,
245
- key, keylen, port, portlen, flags);
224
+ hostlen = INET_ADDRSTRLEN;
225
+ alloca_len = hostlen + portlen;
226
+ host = key = alloca(alloca_len);
246
227
  break;
247
228
  }
248
229
  case AF_INET6: {
249
- sa.in6.sin6_port = r->id.idiag_sport;
250
- memcpy(&sa.in6.sin6_addr, &r->id.idiag_src, sizeof(__be32[4]));
251
- keylen = INET6_ADDRSTRLEN;
252
- /* [ ] */
253
- alloca_len = 1 + keylen + 1 + 1 + portlen;
230
+ hostlen = INET6_ADDRSTRLEN;
231
+ alloca_len = 1 + hostlen + 1 + portlen;
254
232
  key = alloca(alloca_len);
255
- *key = '[';
256
- key[1 + keylen + 1] = 0; /* will be ':' later */
257
- port = 1 + key + keylen + 1 + 1;
258
- rc = getnameinfo(&sa.sa, len,
259
- key + 1, keylen, port, portlen, flags);
233
+ host = key + 1;
260
234
  break;
261
235
  }
262
236
  default:
263
237
  assert(0 && "unsupported address family, could that be IPv7?!");
264
238
  }
265
- if (rc != 0) {
266
- fprintf(stderr, "BUG: getnameinfo: %s\n", gai_strerror(rc));
267
- bug_warn();
268
- *key = 0;
239
+ if (!inet_ntop(r->idiag_family, src, host, hostlen)) {
240
+ bug_warn_nogvl("BUG: inet_ntop: %s\n", strerror(errno));
241
+ *key = '\0';
242
+ *host = '\0';
269
243
  }
270
-
271
- keylen = strlen(key);
272
- portlen = strlen(port);
273
-
274
- switch (sa.ss.ss_family) {
244
+ hostlen = (socklen_t)strlen(host);
245
+ switch (r->idiag_family) {
275
246
  case AF_INET:
276
- key[keylen] = ':';
277
- memmove(key + keylen + 1, port, portlen + 1);
247
+ host[hostlen] = ':';
248
+ port = host + hostlen + 1;
278
249
  break;
279
250
  case AF_INET6:
280
- key[keylen] = ']';
281
- key[keylen + 1] = ':';
282
- memmove(key + keylen + 2, port, portlen + 1);
283
- keylen++;
251
+ key[0] = '[';
252
+ host[hostlen] = ']';
253
+ host[hostlen + 1] = ':';
254
+ port = host + hostlen + 2;
284
255
  break;
285
256
  default:
286
257
  assert(0 && "unsupported address family, could that be IPv7?!");
287
258
  }
288
259
 
260
+ n = snprintf(port, portlen, "%u", ntohs(r->id.idiag_sport));
261
+ if (n <= 0) {
262
+ bug_warn_nogvl("BUG: snprintf port: %d\n", n);
263
+ *key = '\0';
264
+ }
265
+
289
266
  if (st_lookup(table, (st_data_t)key, (st_data_t *)&stats))
290
267
  return stats;
291
268
 
292
269
  old_key = key;
293
270
 
294
271
  if (r->idiag_state == TCP_ESTABLISHED) {
295
- int n = snprintf(key, alloca_len, "%s:%u",
296
- addr_any(sa.ss.ss_family),
272
+ n = snprintf(key, alloca_len, "%s:%u",
273
+ addr_any(r->idiag_family),
297
274
  ntohs(r->id.idiag_sport));
298
275
  if (n <= 0) {
299
- fprintf(stderr, "BUG: snprintf: %d\n", n);
300
- bug_warn();
276
+ bug_warn_nogvl("BUG: snprintf: %d\n", n);
277
+ *key = '\0';
301
278
  }
302
279
  if (st_lookup(table, (st_data_t)key, (st_data_t *)&stats))
303
280
  return stats;
@@ -310,8 +287,9 @@ static struct listen_stats *stats_for(st_table *table, struct inet_diag_msg *r)
310
287
  memcpy(key, old_key, n + 1);
311
288
  }
312
289
  } else {
313
- key = xmalloc(keylen + 1 + portlen + 1);
314
- memcpy(key, old_key, keylen + 1 + portlen + 1);
290
+ size_t old_len = strlen(old_key) + 1;
291
+ key = xmalloc(old_len);
292
+ memcpy(key, old_key, old_len);
315
293
  }
316
294
  stats = xcalloc(1, sizeof(struct listen_stats));
317
295
  st_insert(table, (st_data_t)key, (st_data_t)stats);
@@ -391,8 +369,8 @@ static void prep_diag_args(
391
369
 
392
370
  nladdr->nl_family = AF_NETLINK;
393
371
 
394
- req->nlh.nlmsg_len = sizeof(struct diag_req) +
395
- RTA_LENGTH(args->iov[2].iov_len);
372
+ req->nlh.nlmsg_len = (unsigned int)(sizeof(struct diag_req) +
373
+ RTA_LENGTH(args->iov[2].iov_len));
396
374
  req->nlh.nlmsg_type = TCPDIAG_GETSOCK;
397
375
  req->nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
398
376
  req->nlh.nlmsg_pid = getpid();
@@ -465,12 +443,12 @@ static VALUE diag(void *ptr)
465
443
  }
466
444
  }
467
445
  out:
468
- {
446
+ /* prepare to raise, free memory before reacquiring GVL */
447
+ if (err && args->table) {
469
448
  int save_errno = errno;
470
- if (err && args->table) {
471
- st_foreach(args->table, st_free_data, 0);
472
- st_free_table(args->table);
473
- }
449
+
450
+ st_foreach(args->table, st_free_data, 0);
451
+ st_free_table(args->table);
474
452
  errno = save_errno;
475
453
  }
476
454
  return (VALUE)err;
@@ -584,6 +562,10 @@ static void gen_bytecode(struct iovec *iov, union any_addr *inet)
584
562
  }
585
563
  }
586
564
 
565
+ /*
566
+ * n.b. we may safely raise here because an error will cause diag()
567
+ * to free args->table
568
+ */
587
569
  static void nl_errcheck(VALUE r)
588
570
  {
589
571
  const char *err = (const char *)r;
@@ -604,11 +586,18 @@ static VALUE tcp_stats(struct nogvl_args *args, VALUE addr)
604
586
  gen_bytecode(&args->iov[2], &query_addr);
605
587
 
606
588
  memset(&args->stats, 0, sizeof(struct listen_stats));
607
- nl_errcheck(rb_thread_io_blocking_region(diag, args, args->fd));
589
+ nl_errcheck(rd_fd_region(diag, args, args->fd));
608
590
 
609
591
  return rb_listen_stats(&args->stats);
610
592
  }
611
593
 
594
+ static int drop_placeholders(st_data_t k, st_data_t v, st_data_t ign)
595
+ {
596
+ if ((VALUE)v == Qtrue)
597
+ return ST_DELETE;
598
+ return ST_CONTINUE;
599
+ }
600
+
612
601
  /*
613
602
  * call-seq:
614
603
  * Raindrops::Linux.tcp_listener_stats([addrs[, sock]]) => hash
@@ -649,10 +638,9 @@ static VALUE tcp_listener_stats(int argc, VALUE *argv, VALUE self)
649
638
  case T_ARRAY: {
650
639
  long i;
651
640
  long len = RARRAY_LEN(addrs);
652
- VALUE cur;
653
641
 
654
642
  if (len == 1) {
655
- cur = rb_ary_entry(addrs, 0);
643
+ VALUE cur = rb_ary_entry(addrs, 0);
656
644
 
657
645
  rb_hash_aset(rv, cur, tcp_stats(&args, cur));
658
646
  return rv;
@@ -662,7 +650,7 @@ static VALUE tcp_listener_stats(int argc, VALUE *argv, VALUE self)
662
650
  VALUE cur = rb_ary_entry(addrs, i);
663
651
 
664
652
  parse_addr(&check, cur);
665
- rb_hash_aset(rv, cur, Qtrue);
653
+ rb_hash_aset(rv, cur, Qtrue /* placeholder */);
666
654
  }
667
655
  /* fall through */
668
656
  }
@@ -675,11 +663,14 @@ static VALUE tcp_listener_stats(int argc, VALUE *argv, VALUE self)
675
663
  "addr must be an array of strings, a string, or nil");
676
664
  }
677
665
 
678
- nl_errcheck(rb_thread_io_blocking_region(diag, &args, args.fd));
666
+ nl_errcheck(rd_fd_region(diag, &args, args.fd));
679
667
 
680
668
  st_foreach(args.table, NIL_P(addrs) ? st_to_hash : st_AND_hash, rv);
681
669
  st_free_table(args.table);
682
670
 
671
+ if (RHASH_SIZE(rv) > 1)
672
+ rb_hash_foreach(rv, drop_placeholders, Qfalse);
673
+
683
674
  /* let GC deal with corner cases */
684
675
  if (argc < 2) rb_io_close(sock);
685
676
  return rv;
@@ -687,11 +678,12 @@ static VALUE tcp_listener_stats(int argc, VALUE *argv, VALUE self)
687
678
 
688
679
  void Init_raindrops_linux_inet_diag(void)
689
680
  {
690
- VALUE cRaindrops = rb_const_get(rb_cObject, rb_intern("Raindrops"));
681
+ VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject);
691
682
  VALUE mLinux = rb_define_module_under(cRaindrops, "Linux");
683
+ VALUE Socket;
692
684
 
693
685
  rb_require("socket");
694
- cIDSock = rb_const_get(rb_cObject, rb_intern("Socket"));
686
+ Socket = rb_const_get(rb_cObject, rb_intern("Socket"));
695
687
  id_new = rb_intern("new");
696
688
 
697
689
  /*
@@ -700,10 +692,11 @@ void Init_raindrops_linux_inet_diag(void)
700
692
  * This is a subclass of +Socket+ specifically for talking
701
693
  * to the inet_diag facility of Netlink.
702
694
  */
703
- cIDSock = rb_define_class_under(cRaindrops, "InetDiagSocket", cIDSock);
695
+ cIDSock = rb_define_class_under(cRaindrops, "InetDiagSocket", Socket);
704
696
  rb_define_singleton_method(cIDSock, "new", ids_s_new, 0);
705
697
 
706
698
  cListenStats = rb_const_get(cRaindrops, rb_intern("ListenStats"));
699
+ rb_gc_register_mark_object(cListenStats); /* pin */
707
700
 
708
701
  rb_define_module_function(mLinux, "tcp_listener_stats",
709
702
  tcp_listener_stats, -1);