raindrops 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +7 -0
- data/.gitignore +13 -0
- data/COPYING +165 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +172 -0
- data/LICENSE +16 -0
- data/README +117 -0
- data/Rakefile +156 -0
- data/TODO +2 -0
- data/examples/linux-tcp-listener-stats.rb +28 -0
- data/ext/raindrops/extconf.rb +11 -0
- data/ext/raindrops/linux_inet_diag.c +342 -0
- data/ext/raindrops/raindrops.c +192 -0
- data/lib/raindrops.rb +35 -0
- data/lib/raindrops/linux.rb +55 -0
- data/lib/raindrops/middleware.rb +75 -0
- data/lib/raindrops/struct.rb +47 -0
- data/raindrops.gemspec +38 -0
- data/setup.rb +1586 -0
- data/test/test_linux.rb +228 -0
- data/test/test_linux_middleware.rb +59 -0
- data/test/test_middleware.rb +111 -0
- data/test/test_raindrops.rb +95 -0
- data/test/test_raindrops_gc.rb +13 -0
- data/test/test_struct.rb +54 -0
- metadata +103 -0
@@ -0,0 +1,192 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <sys/mman.h>
|
3
|
+
#include <assert.h>
|
4
|
+
#include <errno.h>
|
5
|
+
#include <stddef.h>
|
6
|
+
|
7
|
+
/*
|
8
|
+
* most modern CPUs have a cache-line size of 64 or 128.
|
9
|
+
* We choose a bigger one by default since our structure is not
|
10
|
+
* heavily used
|
11
|
+
*/
|
12
|
+
#ifndef CACHE_LINE_SIZE
|
13
|
+
# define CACHE_LINE_SIZE 128
|
14
|
+
#endif
|
15
|
+
|
16
|
+
/* each raindrop is a counter */
|
17
|
+
struct raindrop {
|
18
|
+
union {
|
19
|
+
unsigned long counter;
|
20
|
+
unsigned char padding[CACHE_LINE_SIZE];
|
21
|
+
} as;
|
22
|
+
} __attribute__((packed));
|
23
|
+
|
24
|
+
/* allow mmap-ed regions can store more than one raindrop */
|
25
|
+
struct raindrops {
|
26
|
+
long size;
|
27
|
+
struct raindrop *drops;
|
28
|
+
};
|
29
|
+
|
30
|
+
/* called by GC */
|
31
|
+
static void evaporate(void *ptr)
|
32
|
+
{
|
33
|
+
struct raindrops *r = ptr;
|
34
|
+
|
35
|
+
if (r->drops) {
|
36
|
+
int rv = munmap(r->drops, sizeof(struct raindrop) * r->size);
|
37
|
+
if (rv != 0)
|
38
|
+
rb_bug("munmap failed in gc: %s", strerror(errno));
|
39
|
+
}
|
40
|
+
|
41
|
+
xfree(ptr);
|
42
|
+
}
|
43
|
+
|
44
|
+
/* automatically called at creation (before initialize) */
|
45
|
+
static VALUE alloc(VALUE klass)
|
46
|
+
{
|
47
|
+
struct raindrops *r;
|
48
|
+
|
49
|
+
return Data_Make_Struct(klass, struct raindrops, NULL, evaporate, r);
|
50
|
+
}
|
51
|
+
|
52
|
+
static struct raindrops *get(VALUE self)
|
53
|
+
{
|
54
|
+
struct raindrops *r;
|
55
|
+
|
56
|
+
Data_Get_Struct(self, struct raindrops, r);
|
57
|
+
|
58
|
+
return r;
|
59
|
+
}
|
60
|
+
|
61
|
+
/* initializes a Raindrops object to hold +size+ elements */
|
62
|
+
static VALUE init(VALUE self, VALUE size)
|
63
|
+
{
|
64
|
+
struct raindrops *r = get(self);
|
65
|
+
int tries = 1;
|
66
|
+
|
67
|
+
if (r->drops)
|
68
|
+
rb_raise(rb_eRuntimeError, "already initialized");
|
69
|
+
|
70
|
+
r->size = NUM2LONG(size);
|
71
|
+
if (r->size < 1)
|
72
|
+
rb_raise(rb_eArgError, "size must be >= 1");
|
73
|
+
|
74
|
+
retry:
|
75
|
+
r->drops = mmap(NULL, sizeof(struct raindrop) * r->size,
|
76
|
+
PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0);
|
77
|
+
if (r->drops == MAP_FAILED) {
|
78
|
+
if ((errno == EAGAIN || errno == ENOMEM) && tries-- > 0) {
|
79
|
+
rb_gc();
|
80
|
+
goto retry;
|
81
|
+
}
|
82
|
+
rb_sys_fail("mmap");
|
83
|
+
}
|
84
|
+
|
85
|
+
return self;
|
86
|
+
}
|
87
|
+
|
88
|
+
/* :nodoc */
|
89
|
+
static VALUE init_copy(VALUE dest, VALUE source)
|
90
|
+
{
|
91
|
+
struct raindrops *dst = get(dest);
|
92
|
+
struct raindrops *src = get(source);
|
93
|
+
|
94
|
+
init(dest, LONG2NUM(src->size));
|
95
|
+
memcpy(dst->drops, src->drops, sizeof(struct raindrop) * src->size);
|
96
|
+
|
97
|
+
return dest;
|
98
|
+
}
|
99
|
+
|
100
|
+
static unsigned long *addr_of(VALUE self, VALUE index)
|
101
|
+
{
|
102
|
+
struct raindrops *r = get(self);
|
103
|
+
unsigned long off = FIX2ULONG(index) * sizeof(struct raindrop);
|
104
|
+
|
105
|
+
if (off >= sizeof(struct raindrop) * r->size)
|
106
|
+
rb_raise(rb_eArgError, "offset overrun");
|
107
|
+
|
108
|
+
return (unsigned long *)((unsigned long)r->drops + off);
|
109
|
+
}
|
110
|
+
|
111
|
+
static unsigned long incr_decr_arg(int argc, const VALUE *argv)
|
112
|
+
{
|
113
|
+
if (argc > 2 || argc < 1)
|
114
|
+
rb_raise(rb_eArgError,
|
115
|
+
"wrong number of arguments (%d for 1+)", argc);
|
116
|
+
|
117
|
+
return argc == 2 ? NUM2ULONG(argv[1]) : 1;
|
118
|
+
}
|
119
|
+
|
120
|
+
/* increments the value referred to by the +index+ constant by 1 */
|
121
|
+
static VALUE incr(int argc, VALUE *argv, VALUE self)
|
122
|
+
{
|
123
|
+
unsigned long nr = incr_decr_arg(argc, argv);
|
124
|
+
|
125
|
+
return ULONG2NUM(__sync_add_and_fetch(addr_of(self, argv[0]), nr));
|
126
|
+
}
|
127
|
+
|
128
|
+
/* decrements the value referred to by the +index+ constant by 1 */
|
129
|
+
static VALUE decr(int argc, VALUE *argv, VALUE self)
|
130
|
+
{
|
131
|
+
unsigned long nr = incr_decr_arg(argc, argv);
|
132
|
+
|
133
|
+
return ULONG2NUM(__sync_sub_and_fetch(addr_of(self, argv[0]), nr));
|
134
|
+
}
|
135
|
+
|
136
|
+
/* converts the raindrops structure to an Array */
|
137
|
+
static VALUE to_ary(VALUE self)
|
138
|
+
{
|
139
|
+
struct raindrops *r = get(self);
|
140
|
+
VALUE rv = rb_ary_new2(r->size);
|
141
|
+
long i;
|
142
|
+
unsigned long base = (unsigned long)r->drops;
|
143
|
+
|
144
|
+
for (i = 0; i < r->size; i++) {
|
145
|
+
rb_ary_push(rv, ULONG2NUM(*((unsigned long *)base)));
|
146
|
+
base += sizeof(struct raindrop);
|
147
|
+
}
|
148
|
+
|
149
|
+
return rv;
|
150
|
+
}
|
151
|
+
|
152
|
+
static VALUE size(VALUE self)
|
153
|
+
{
|
154
|
+
return LONG2NUM(get(self)->size);
|
155
|
+
}
|
156
|
+
|
157
|
+
static VALUE aset(VALUE self, VALUE index, VALUE value)
|
158
|
+
{
|
159
|
+
unsigned long *addr = addr_of(self, index);
|
160
|
+
|
161
|
+
*addr = NUM2ULONG(value);
|
162
|
+
|
163
|
+
return value;
|
164
|
+
}
|
165
|
+
|
166
|
+
static VALUE aref(VALUE self, VALUE index)
|
167
|
+
{
|
168
|
+
return ULONG2NUM(*addr_of(self, index));
|
169
|
+
}
|
170
|
+
|
171
|
+
#ifdef __linux__
|
172
|
+
void Init_raindrops_linux_inet_diag(void);
|
173
|
+
#endif
|
174
|
+
|
175
|
+
void Init_raindrops_ext(void)
|
176
|
+
{
|
177
|
+
VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject);
|
178
|
+
rb_define_alloc_func(cRaindrops, alloc);
|
179
|
+
|
180
|
+
rb_define_method(cRaindrops, "initialize", init, 1);
|
181
|
+
rb_define_method(cRaindrops, "incr", incr, -1);
|
182
|
+
rb_define_method(cRaindrops, "decr", decr, -1);
|
183
|
+
rb_define_method(cRaindrops, "to_ary", to_ary, 0);
|
184
|
+
rb_define_method(cRaindrops, "[]", aref, 1);
|
185
|
+
rb_define_method(cRaindrops, "[]=", aset, 2);
|
186
|
+
rb_define_method(cRaindrops, "size", size, 0);
|
187
|
+
rb_define_method(cRaindrops, "initialize_copy", init_copy, 1);
|
188
|
+
|
189
|
+
#ifdef __linux__
|
190
|
+
Init_raindrops_linux_inet_diag();
|
191
|
+
#endif
|
192
|
+
}
|
data/lib/raindrops.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
class Raindrops
|
3
|
+
|
4
|
+
# Raindrops is currently at version 0.1.0
|
5
|
+
VERSION = '0.1.0'
|
6
|
+
|
7
|
+
# Used to represent the number of +active+ and +queued+ sockets for
|
8
|
+
# a single listen socket across all threads and processes on a
|
9
|
+
# machine.
|
10
|
+
#
|
11
|
+
# For TCP listeners, only sockets in the TCP_ESTABLISHED state are
|
12
|
+
# accounted for. For Unix domain listeners, only CONNECTING and
|
13
|
+
# CONNECTED Unix domain sockets are accounted for.
|
14
|
+
#
|
15
|
+
# +active+ connections is the number of accept()-ed but not-yet-closed
|
16
|
+
# sockets in all threads/processes sharing the given listener.
|
17
|
+
#
|
18
|
+
# +queued+ connections is the number of un-accept()-ed sockets in the
|
19
|
+
# queue of a given listen socket.
|
20
|
+
#
|
21
|
+
# These stats are currently only available under Linux
|
22
|
+
class ListenStats < Struct.new(:active, :queued)
|
23
|
+
|
24
|
+
# the sum of +active+ and +queued+ sockets
|
25
|
+
def total
|
26
|
+
active + queued
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# TODO: pure Ruby version for single processes
|
31
|
+
require 'raindrops_ext'
|
32
|
+
|
33
|
+
autoload :Struct, 'raindrops/struct'
|
34
|
+
autoload :Middleware, 'raindrops/middleware'
|
35
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
class Raindrops
|
3
|
+
module Linux
|
4
|
+
|
5
|
+
# The standard proc path for active UNIX domain sockets, feel free to call
|
6
|
+
# String#replace on this if your /proc is mounted in a non-standard location
|
7
|
+
# for whatever reason
|
8
|
+
PROC_NET_UNIX = "/proc/net/unix"
|
9
|
+
|
10
|
+
# Get ListenStats from an array of +paths+
|
11
|
+
#
|
12
|
+
# Socket state mapping from integer => symbol, based on socket_state
|
13
|
+
# enum from include/linux/net.h in the Linux kernel:
|
14
|
+
# typedef enum {
|
15
|
+
# SS_FREE = 0, /* not allocated */
|
16
|
+
# SS_UNCONNECTED, /* unconnected to any socket */
|
17
|
+
# SS_CONNECTING, /* in process of connecting */
|
18
|
+
# SS_CONNECTED, /* connected to socket */
|
19
|
+
# SS_DISCONNECTING /* in process of disconnecting */
|
20
|
+
# } socket_state;
|
21
|
+
# * SS_CONNECTING maps to ListenStats#active
|
22
|
+
# * SS_CONNECTED maps to ListenStats#queued
|
23
|
+
#
|
24
|
+
# This method may be significantly slower than its tcp_listener_stats
|
25
|
+
# counterpart due to the latter being able to use inet_diag via netlink.
|
26
|
+
# This parses /proc/net/unix as there is no other (known) way
|
27
|
+
# to expose Unix domain socket statistics over netlink.
|
28
|
+
def unix_listener_stats(paths)
|
29
|
+
rv = Hash.new { |h,k| h[k.freeze] = ListenStats.new(0, 0) }
|
30
|
+
paths = paths.map do |path|
|
31
|
+
path = path.dup
|
32
|
+
path.force_encoding(Encoding::BINARY) if defined?(Encoding)
|
33
|
+
rv[path]
|
34
|
+
Regexp.escape(path)
|
35
|
+
end
|
36
|
+
paths = / 00000000 \d+ (\d+)\s+\d+ (#{paths.join('|')})$/n
|
37
|
+
|
38
|
+
# no point in pread since we can't stat for size on this file
|
39
|
+
File.open(PROC_NET_UNIX, "rb") do |fp|
|
40
|
+
fp.read.scan(paths).each do |s|
|
41
|
+
path = s.last
|
42
|
+
case s.first.to_i
|
43
|
+
when 2 then rv[path].queued += 1
|
44
|
+
when 3 then rv[path].active += 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
rv
|
50
|
+
end
|
51
|
+
|
52
|
+
module_function :unix_listener_stats
|
53
|
+
|
54
|
+
end # Linux
|
55
|
+
end # Raindrops
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'raindrops'
|
3
|
+
|
4
|
+
# Raindrops middleware should be loaded at the top of Rack
|
5
|
+
# middleware stack before other middlewares for maximum accuracy.
|
6
|
+
class Raindrops
|
7
|
+
class Middleware < ::Struct.new(:app, :stats, :path, :tcp, :unix)
|
8
|
+
|
9
|
+
# :stopdoc:
|
10
|
+
Stats = Raindrops::Struct.new(:calling, :writing)
|
11
|
+
PATH_INFO = "PATH_INFO"
|
12
|
+
# :startdoc:
|
13
|
+
|
14
|
+
def initialize(app, opts = {})
|
15
|
+
super(app, opts[:stats] || Stats.new, opts[:path] || "/_raindrops")
|
16
|
+
if tmp = opts[:listeners]
|
17
|
+
self.tcp = tmp.grep(/\A[^:]+:\d+\z/)
|
18
|
+
self.unix = tmp.grep(%r{\A/})
|
19
|
+
self.tcp = nil if tcp.empty?
|
20
|
+
self.unix = nil if unix.empty?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# standard Rack endpoing
|
25
|
+
def call(env)
|
26
|
+
env[PATH_INFO] == path ? stats_response : dup._call(env)
|
27
|
+
end
|
28
|
+
|
29
|
+
def _call(env)
|
30
|
+
stats.incr_calling
|
31
|
+
status, headers, self.app = app.call(env)
|
32
|
+
|
33
|
+
# the Rack server will start writing headers soon after this method
|
34
|
+
stats.incr_writing
|
35
|
+
[ status, headers, self ]
|
36
|
+
ensure
|
37
|
+
stats.decr_calling
|
38
|
+
end
|
39
|
+
|
40
|
+
# yield to the Rack server here for writing
|
41
|
+
def each(&block)
|
42
|
+
app.each(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# the Rack server should call this after #each (usually ensure-d)
|
46
|
+
def close
|
47
|
+
stats.decr_writing
|
48
|
+
ensure
|
49
|
+
app.close if app.respond_to?(:close)
|
50
|
+
end
|
51
|
+
|
52
|
+
def stats_response
|
53
|
+
body = "calling: #{stats.calling}\n" \
|
54
|
+
"writing: #{stats.writing}\n"
|
55
|
+
|
56
|
+
if defined?(Linux)
|
57
|
+
Linux.tcp_listener_stats(tcp).each do |addr,stats|
|
58
|
+
body << "#{addr} active: #{stats.active}\n" \
|
59
|
+
"#{addr} queued: #{stats.queued}\n"
|
60
|
+
end if tcp
|
61
|
+
Linux.unix_listener_stats(unix).each do |addr,stats|
|
62
|
+
body << "#{addr} active: #{stats.active}\n" \
|
63
|
+
"#{addr} queued: #{stats.queued}\n"
|
64
|
+
end if unix
|
65
|
+
end
|
66
|
+
|
67
|
+
headers = {
|
68
|
+
"Content-Type" => "text/plain",
|
69
|
+
"Content-Length" => body.size.to_s,
|
70
|
+
}
|
71
|
+
[ 200, headers, [ body ] ]
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
class Raindrops::Struct
|
4
|
+
|
5
|
+
def self.new(*members)
|
6
|
+
members = members.map { |x| x.to_sym }.freeze
|
7
|
+
str = <<EOS
|
8
|
+
def initialize(*values)
|
9
|
+
(MEMBERS.size >= values.size) or raise ArgumentError, "too many arguments"
|
10
|
+
@raindrops = Raindrops.new(MEMBERS.size)
|
11
|
+
values.each_with_index { |val,i| @raindrops[i] = values[i] }
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize_copy(src)
|
15
|
+
@raindrops = src.instance_variable_get(:@raindrops).dup
|
16
|
+
end
|
17
|
+
|
18
|
+
def []=(index, value)
|
19
|
+
@raindrops[index] = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](index)
|
23
|
+
@raindrops[index]
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_hash
|
27
|
+
ary = @raindrops.to_ary
|
28
|
+
rv = {}
|
29
|
+
MEMBERS.each_with_index { |member, i| rv[member] = ary[i] }
|
30
|
+
rv
|
31
|
+
end
|
32
|
+
EOS
|
33
|
+
|
34
|
+
members.each_with_index do |member, i|
|
35
|
+
str << "def incr_#{member}; @raindrops.incr(#{i}); end; " \
|
36
|
+
"def decr_#{member}; @raindrops.decr(#{i}); end; " \
|
37
|
+
"def #{member}; @raindrops[#{i}]; end; " \
|
38
|
+
"def #{member}=(val); @raindrops[#{i}] = val; end; "
|
39
|
+
end
|
40
|
+
|
41
|
+
klass = Class.new
|
42
|
+
klass.const_set(:MEMBERS, members)
|
43
|
+
klass.class_eval(str)
|
44
|
+
klass
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/raindrops.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
ENV["VERSION"] or abort "VERSION= must be specified"
|
4
|
+
manifest = File.readlines('.manifest').map! { |x| x.chomp! }
|
5
|
+
test_files = manifest.grep(%r{\Atest/test_.*\.rb\z})
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = %q{raindrops}
|
9
|
+
s.version = ENV["VERSION"]
|
10
|
+
|
11
|
+
s.authors = ["raindrops hackers"]
|
12
|
+
s.date = Time.now.utc.strftime('%Y-%m-%d')
|
13
|
+
s.description = File.read("README").split(/\n\n/)[1]
|
14
|
+
s.email = %q{raindrops@librelist.com}
|
15
|
+
s.extensions = %w(ext/raindrops/extconf.rb)
|
16
|
+
|
17
|
+
s.extra_rdoc_files = File.readlines('.document').map! do |x|
|
18
|
+
x.chomp!
|
19
|
+
if File.directory?(x)
|
20
|
+
manifest.grep(%r{\A#{x}/})
|
21
|
+
elsif File.file?(x)
|
22
|
+
x
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end.flatten.compact
|
27
|
+
|
28
|
+
s.files = manifest
|
29
|
+
s.homepage = %q{http://raindrops.bogomips.org/}
|
30
|
+
s.summary = %q{real-time stats for Rack servers}
|
31
|
+
s.rdoc_options = [ "-Na", "-t", "raindrops - #{s.summary}" ]
|
32
|
+
s.require_paths = %w(lib)
|
33
|
+
s.rubyforge_project = %q{raindrops}
|
34
|
+
|
35
|
+
s.test_files = test_files
|
36
|
+
|
37
|
+
# s.licenses = %w(LGPLv3) # accessor not compatible with older RubyGems
|
38
|
+
end
|