raindrops-maintained 0.21.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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.document +7 -0
  3. data/.gitattributes +4 -0
  4. data/.gitignore +16 -0
  5. data/.manifest +62 -0
  6. data/.olddoc.yml +16 -0
  7. data/COPYING +165 -0
  8. data/GIT-VERSION-FILE +1 -0
  9. data/GIT-VERSION-GEN +40 -0
  10. data/GNUmakefile +4 -0
  11. data/LATEST +9 -0
  12. data/LICENSE +16 -0
  13. data/NEWS +384 -0
  14. data/README +101 -0
  15. data/TODO +3 -0
  16. data/archive/.gitignore +3 -0
  17. data/archive/slrnpull.conf +4 -0
  18. data/examples/linux-listener-stats.rb +122 -0
  19. data/examples/middleware.ru +5 -0
  20. data/examples/watcher.ru +4 -0
  21. data/examples/watcher_demo.ru +13 -0
  22. data/examples/yahns.conf.rb +30 -0
  23. data/examples/zbatery.conf.rb +16 -0
  24. data/ext/raindrops/extconf.rb +163 -0
  25. data/ext/raindrops/linux_inet_diag.c +713 -0
  26. data/ext/raindrops/my_fileno.h +16 -0
  27. data/ext/raindrops/raindrops.c +487 -0
  28. data/ext/raindrops/raindrops_atomic.h +23 -0
  29. data/ext/raindrops/tcp_info.c +245 -0
  30. data/lib/raindrops/aggregate/last_data_recv.rb +94 -0
  31. data/lib/raindrops/aggregate/pmq.rb +245 -0
  32. data/lib/raindrops/aggregate.rb +8 -0
  33. data/lib/raindrops/last_data_recv.rb +102 -0
  34. data/lib/raindrops/linux.rb +77 -0
  35. data/lib/raindrops/middleware/proxy.rb +40 -0
  36. data/lib/raindrops/middleware.rb +153 -0
  37. data/lib/raindrops/struct.rb +62 -0
  38. data/lib/raindrops/watcher.rb +428 -0
  39. data/lib/raindrops.rb +72 -0
  40. data/pkg.mk +151 -0
  41. data/raindrops-maintained.gemspec +1 -0
  42. data/raindrops.gemspec +26 -0
  43. data/setup.rb +1586 -0
  44. data/test/ipv6_enabled.rb +9 -0
  45. data/test/rack_unicorn.rb +11 -0
  46. data/test/test_aggregate_pmq.rb +65 -0
  47. data/test/test_inet_diag_socket.rb +16 -0
  48. data/test/test_last_data_recv.rb +57 -0
  49. data/test/test_last_data_recv_unicorn.rb +69 -0
  50. data/test/test_linux.rb +281 -0
  51. data/test/test_linux_all_tcp_listen_stats.rb +66 -0
  52. data/test/test_linux_all_tcp_listen_stats_leak.rb +43 -0
  53. data/test/test_linux_ipv6.rb +166 -0
  54. data/test/test_linux_middleware.rb +64 -0
  55. data/test/test_linux_reuseport_tcp_listen_stats.rb +51 -0
  56. data/test/test_middleware.rb +128 -0
  57. data/test/test_middleware_unicorn.rb +37 -0
  58. data/test/test_middleware_unicorn_ipv6.rb +37 -0
  59. data/test/test_raindrops.rb +207 -0
  60. data/test/test_raindrops_gc.rb +38 -0
  61. data/test/test_struct.rb +54 -0
  62. data/test/test_tcp_info.rb +88 -0
  63. data/test/test_watcher.rb +186 -0
  64. metadata +193 -0
@@ -0,0 +1,487 @@
1
+ #include <ruby.h>
2
+ #include <unistd.h>
3
+ #include <sys/mman.h>
4
+ #include <assert.h>
5
+ #include <errno.h>
6
+ #include <stddef.h>
7
+ #include <string.h>
8
+ #include "raindrops_atomic.h"
9
+
10
+ #ifndef SIZET2NUM
11
+ # define SIZET2NUM(x) ULONG2NUM(x)
12
+ #endif
13
+ #ifndef NUM2SIZET
14
+ # define NUM2SIZET(x) NUM2ULONG(x)
15
+ #endif
16
+
17
+ /*
18
+ * most modern CPUs have a cache-line size of 64 or 128.
19
+ * We choose a bigger one by default since our structure is not
20
+ * heavily used
21
+ */
22
+ static size_t raindrop_size = 128;
23
+ static size_t rd_page_size;
24
+
25
+ #define PAGE_MASK (~(rd_page_size - 1))
26
+ #define PAGE_ALIGN(addr) (((addr) + rd_page_size - 1) & PAGE_MASK)
27
+
28
+ /* each raindrop is a counter */
29
+ struct raindrop {
30
+ unsigned long counter;
31
+ } __attribute__((packed));
32
+
33
+ /* allow mmap-ed regions to store more than one raindrop */
34
+ struct raindrops {
35
+ size_t size;
36
+ size_t capa;
37
+ pid_t pid;
38
+ VALUE io;
39
+ struct raindrop *drops;
40
+ };
41
+
42
+ /* called by GC */
43
+ static void rd_mark(void *ptr)
44
+ {
45
+ struct raindrops *r = ptr;
46
+ rb_gc_mark(r->io);
47
+ }
48
+
49
+ /* called by GC */
50
+ static void rd_free(void *ptr)
51
+ {
52
+ struct raindrops *r = ptr;
53
+
54
+ if (r->drops != MAP_FAILED) {
55
+ int rv = munmap(r->drops, raindrop_size * r->capa);
56
+ if (rv != 0)
57
+ rb_bug("munmap failed in gc: %s", strerror(errno));
58
+ }
59
+
60
+ xfree(ptr);
61
+ }
62
+
63
+ static size_t rd_memsize(const void *ptr)
64
+ {
65
+ const struct raindrops *r = ptr;
66
+
67
+ return r->drops == MAP_FAILED ? 0 : raindrop_size * r->capa;
68
+ }
69
+
70
+ static const rb_data_type_t rd_type = {
71
+ "raindrops",
72
+ { rd_mark, rd_free, rd_memsize, /* reserved */ },
73
+ /* parent, data, [ flags ] */
74
+ };
75
+
76
+ /* automatically called at creation (before initialize) */
77
+ static VALUE alloc(VALUE klass)
78
+ {
79
+ struct raindrops *r;
80
+ VALUE rv = TypedData_Make_Struct(klass, struct raindrops, &rd_type, r);
81
+
82
+ r->drops = MAP_FAILED;
83
+ return rv;
84
+ }
85
+
86
+ static struct raindrops *get(VALUE self)
87
+ {
88
+ struct raindrops *r;
89
+
90
+ TypedData_Get_Struct(self, struct raindrops, &rd_type, r);
91
+
92
+ if (r->drops == MAP_FAILED)
93
+ rb_raise(rb_eStandardError, "invalid or freed Raindrops");
94
+
95
+ return r;
96
+ }
97
+
98
+ /*
99
+ * This is the _actual_ implementation of #initialize - the Ruby wrapper
100
+ * handles keyword-argument handling then calls this method.
101
+ */
102
+ static VALUE init_cimpl(VALUE self, VALUE size, VALUE io, VALUE zero)
103
+ {
104
+ struct raindrops *r = DATA_PTR(self);
105
+ int tries = 1;
106
+ size_t tmp;
107
+
108
+ if (r->drops != MAP_FAILED)
109
+ rb_raise(rb_eRuntimeError, "already initialized");
110
+
111
+ r->size = NUM2SIZET(size);
112
+ if (r->size < 1)
113
+ rb_raise(rb_eArgError, "size must be >= 1");
114
+
115
+ tmp = PAGE_ALIGN(raindrop_size * r->size);
116
+ r->capa = tmp / raindrop_size;
117
+ assert(PAGE_ALIGN(raindrop_size * r->capa) == tmp && "not aligned");
118
+
119
+ r->io = io;
120
+
121
+ retry:
122
+ if (RTEST(r->io)) {
123
+ int fd = NUM2INT(rb_funcall(r->io, rb_intern("fileno"), 0));
124
+ rb_funcall(r->io, rb_intern("truncate"), 1, SIZET2NUM(tmp));
125
+ r->drops = mmap(NULL, tmp,
126
+ PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
127
+ } else {
128
+ r->drops = mmap(NULL, tmp,
129
+ PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED,
130
+ -1, 0);
131
+ }
132
+ if (r->drops == MAP_FAILED) {
133
+ int err = errno;
134
+
135
+ if ((err == EAGAIN || err == ENOMEM) && tries-- > 0) {
136
+ rb_gc();
137
+ goto retry;
138
+ }
139
+ rb_sys_fail("mmap");
140
+ }
141
+ r->pid = getpid();
142
+
143
+ if (RTEST(zero))
144
+ memset(r->drops, 0, tmp);
145
+
146
+ return self;
147
+ }
148
+
149
+ /*
150
+ * mremap() is currently broken with MAP_SHARED
151
+ * https://bugzilla.kernel.org/show_bug.cgi?id=8691
152
+ */
153
+ #if defined(HAVE_MREMAP) && !defined(MREMAP_WORKS_WITH_MAP_SHARED)
154
+ # undef HAVE_MREMAP
155
+ #endif
156
+
157
+ #ifdef HAVE_MREMAP
158
+ #ifndef MREMAP_MAYMOVE
159
+ # warn MREMAP_MAYMOVE undefined
160
+ # define MREMAP_MAYMOVE 0
161
+ #endif
162
+ static void resize(struct raindrops *r, size_t new_rd_size)
163
+ {
164
+ size_t old_size = raindrop_size * r->capa;
165
+ size_t new_size = PAGE_ALIGN(raindrop_size * new_rd_size);
166
+ void *old_address = r->drops;
167
+ void *rv;
168
+
169
+ if (r->pid != getpid())
170
+ rb_raise(rb_eRuntimeError, "cannot mremap() from child");
171
+
172
+ rv = mremap(old_address, old_size, new_size, MREMAP_MAYMOVE);
173
+ if (rv == MAP_FAILED) {
174
+ int err = errno;
175
+
176
+ if (err == EAGAIN || err == ENOMEM) {
177
+ rb_gc();
178
+ rv = mremap(old_address, old_size, new_size, 0);
179
+ }
180
+ if (rv == MAP_FAILED)
181
+ rb_sys_fail("mremap");
182
+ }
183
+ r->drops = rv;
184
+ r->size = new_rd_size;
185
+ r->capa = new_size / raindrop_size;
186
+ assert(r->capa >= r->size && "bad sizing");
187
+ }
188
+ #else /* ! HAVE_MREMAP */
189
+ /*
190
+ * we cannot use munmap + mmap to reallocate the buffer since it may
191
+ * already be shared by other processes, so we just fail
192
+ */
193
+ static void resize(struct raindrops *r, size_t new_rd_size)
194
+ {
195
+ rb_raise(rb_eRangeError, "mremap(2) is not available");
196
+ }
197
+ #endif /* ! HAVE_MREMAP */
198
+
199
+ /*
200
+ * call-seq:
201
+ * rd.size = new_size
202
+ *
203
+ * Increases or decreases the current capacity of our Raindrop.
204
+ * Raises RangeError if +new_size+ is too big or small for the
205
+ * current backing store
206
+ */
207
+ static VALUE setsize(VALUE self, VALUE new_size)
208
+ {
209
+ size_t new_rd_size = NUM2SIZET(new_size);
210
+ struct raindrops *r = get(self);
211
+
212
+ if (new_rd_size <= r->capa)
213
+ r->size = new_rd_size;
214
+ else
215
+ resize(r, new_rd_size);
216
+
217
+ return new_size;
218
+ }
219
+
220
+ /*
221
+ * call-seq:
222
+ * rd.capa -> Integer
223
+ *
224
+ * Returns the number of slots allocated (but not necessarily used) by
225
+ * the Raindrops object.
226
+ */
227
+ static VALUE capa(VALUE self)
228
+ {
229
+ return SIZET2NUM(get(self)->capa);
230
+ }
231
+
232
+ /*
233
+ * call-seq:
234
+ * rd.dup -> rd_copy
235
+ *
236
+ * Duplicates and snapshots the current state of a Raindrops object. Even
237
+ * if the given Raindrops object is backed by a file, the copy will be backed
238
+ * by independent, anonymously mapped memory.
239
+ */
240
+ static VALUE init_copy(VALUE dest, VALUE source)
241
+ {
242
+ struct raindrops *dst = DATA_PTR(dest);
243
+ struct raindrops *src = get(source);
244
+
245
+ init_cimpl(dest, SIZET2NUM(src->size), Qnil, Qfalse);
246
+ memcpy(dst->drops, src->drops, raindrop_size * src->size);
247
+
248
+ return dest;
249
+ }
250
+
251
+ static unsigned long *addr_of(VALUE self, VALUE index)
252
+ {
253
+ struct raindrops *r = get(self);
254
+ unsigned long off = FIX2ULONG(index) * raindrop_size;
255
+
256
+ if (off >= raindrop_size * r->size)
257
+ rb_raise(rb_eArgError, "offset overrun");
258
+
259
+ return (unsigned long *)((unsigned long)r->drops + off);
260
+ }
261
+
262
+ static unsigned long incr_decr_arg(int argc, const VALUE *argv)
263
+ {
264
+ if (argc > 2 || argc < 1)
265
+ rb_raise(rb_eArgError,
266
+ "wrong number of arguments (%d for 1+)", argc);
267
+
268
+ return argc == 2 ? NUM2ULONG(argv[1]) : 1;
269
+ }
270
+
271
+ /*
272
+ * call-seq:
273
+ * rd.incr(index[, number]) -> result
274
+ *
275
+ * Increments the value referred to by the +index+ by +number+.
276
+ * +number+ defaults to +1+ if unspecified.
277
+ */
278
+ static VALUE incr(int argc, VALUE *argv, VALUE self)
279
+ {
280
+ unsigned long nr = incr_decr_arg(argc, argv);
281
+
282
+ return ULONG2NUM(__sync_add_and_fetch(addr_of(self, argv[0]), nr));
283
+ }
284
+
285
+ /*
286
+ * call-seq:
287
+ * rd.decr(index[, number]) -> result
288
+ *
289
+ * Decrements the value referred to by the +index+ by +number+.
290
+ * +number+ defaults to +1+ if unspecified.
291
+ */
292
+ static VALUE decr(int argc, VALUE *argv, VALUE self)
293
+ {
294
+ unsigned long nr = incr_decr_arg(argc, argv);
295
+
296
+ return ULONG2NUM(__sync_sub_and_fetch(addr_of(self, argv[0]), nr));
297
+ }
298
+
299
+ /*
300
+ * call-seq:
301
+ * rd.to_ary -> Array
302
+ *
303
+ * converts the Raindrops structure to an Array
304
+ */
305
+ static VALUE to_ary(VALUE self)
306
+ {
307
+ struct raindrops *r = get(self);
308
+ VALUE rv = rb_ary_new2(r->size);
309
+ size_t i;
310
+ unsigned long base = (unsigned long)r->drops;
311
+
312
+ for (i = 0; i < r->size; i++) {
313
+ rb_ary_push(rv, ULONG2NUM(*((unsigned long *)base)));
314
+ base += raindrop_size;
315
+ }
316
+
317
+ return rv;
318
+ }
319
+
320
+ /*
321
+ * call-seq:
322
+ * rd.size -> Integer
323
+ *
324
+ * Returns the number of counters a Raindrops object can hold. Due to
325
+ * page alignment, this is always equal or greater than the number of
326
+ * requested slots passed to Raindrops.new
327
+ */
328
+ static VALUE size(VALUE self)
329
+ {
330
+ return SIZET2NUM(get(self)->size);
331
+ }
332
+
333
+ /*
334
+ * call-seq:
335
+ * rd[index] = value
336
+ *
337
+ * Assigns +value+ to the slot designated by +index+
338
+ */
339
+ static VALUE aset(VALUE self, VALUE index, VALUE value)
340
+ {
341
+ unsigned long *addr = addr_of(self, index);
342
+
343
+ *addr = NUM2ULONG(value);
344
+
345
+ return value;
346
+ }
347
+
348
+ /*
349
+ * call-seq:
350
+ * rd[index] -> value
351
+ *
352
+ * Returns the value of the slot designated by +index+
353
+ */
354
+ static VALUE aref(VALUE self, VALUE index)
355
+ {
356
+ return ULONG2NUM(*addr_of(self, index));
357
+ }
358
+
359
+ #ifdef __linux__
360
+ void Init_raindrops_linux_inet_diag(void);
361
+ #endif
362
+ #ifdef HAVE_TYPE_STRUCT_TCP_INFO
363
+ void Init_raindrops_tcp_info(void);
364
+ #endif
365
+
366
+ #ifndef _SC_NPROCESSORS_CONF
367
+ # if defined _SC_NPROCESSORS_ONLN
368
+ # define _SC_NPROCESSORS_CONF _SC_NPROCESSORS_ONLN
369
+ # elif defined _SC_NPROC_ONLN
370
+ # define _SC_NPROCESSORS_CONF _SC_NPROC_ONLN
371
+ # elif defined _SC_CRAY_NCPU
372
+ # define _SC_NPROCESSORS_CONF _SC_CRAY_NCPU
373
+ # endif
374
+ #endif
375
+
376
+ /*
377
+ * call-seq:
378
+ * rd.evaporate! -> nil
379
+ *
380
+ * Releases mmap()-ed memory allocated for the Raindrops object back
381
+ * to the OS. The Ruby garbage collector will also release memory
382
+ * automatically when it is not needed, but this forces release
383
+ * under high memory pressure.
384
+ */
385
+ static VALUE evaporate_bang(VALUE self)
386
+ {
387
+ struct raindrops *r = get(self);
388
+ void *addr = r->drops;
389
+
390
+ r->drops = MAP_FAILED;
391
+ if (munmap(addr, raindrop_size * r->capa) != 0)
392
+ rb_sys_fail("munmap");
393
+ return Qnil;
394
+ }
395
+
396
+ /*
397
+ * call-seq:
398
+ * to_io -> IO
399
+ *
400
+ * Returns the IO object backing the memory for this raindrop, if
401
+ * one was specified when constructing this Raindrop. If this
402
+ * Raindrop is backed by anonymous memory, this method returns nil.
403
+ */
404
+ static VALUE to_io(VALUE self)
405
+ {
406
+ struct raindrops *r = get(self);
407
+ return r->io;
408
+ }
409
+
410
+ void Init_raindrops_ext(void)
411
+ {
412
+ VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject);
413
+ long tmp = 2;
414
+
415
+ #ifdef _SC_NPROCESSORS_CONF
416
+ tmp = sysconf(_SC_NPROCESSORS_CONF);
417
+ #endif
418
+ /* no point in padding on single CPU machines */
419
+ if (tmp == 1)
420
+ raindrop_size = sizeof(unsigned long);
421
+ #ifdef _SC_LEVEL1_DCACHE_LINESIZE
422
+ if (tmp != 1) {
423
+ tmp = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
424
+ if (tmp > 0)
425
+ raindrop_size = (size_t)tmp;
426
+ }
427
+ #endif
428
+ #if defined(_SC_PAGE_SIZE)
429
+ rd_page_size = (size_t)sysconf(_SC_PAGE_SIZE);
430
+ #elif defined(_SC_PAGESIZE)
431
+ rd_page_size = (size_t)sysconf(_SC_PAGESIZE);
432
+ #elif defined(HAVE_GETPAGESIZE)
433
+ rd_page_size = (size_t)getpagesize();
434
+ #elif defined(PAGE_SIZE)
435
+ rd_page_size = (size_t)PAGE_SIZE;
436
+ #elif defined(PAGESIZE)
437
+ rd_page_size = (size_t)PAGESIZE;
438
+ #else
439
+ # error unable to detect page size for mmap()
440
+ #endif
441
+ if ((rd_page_size == (size_t)-1) || (rd_page_size < raindrop_size))
442
+ rb_raise(rb_eRuntimeError,
443
+ "system page size invalid: %llu",
444
+ (unsigned long long)rd_page_size);
445
+
446
+ /*
447
+ * The size of one page of memory for a mmap()-ed Raindrops region.
448
+ * Typically 4096 bytes under Linux.
449
+ */
450
+ rb_define_const(cRaindrops, "PAGE_SIZE", SIZET2NUM(rd_page_size));
451
+
452
+ /*
453
+ * The size (in bytes) of a slot in a Raindrops object.
454
+ * This is the size of a word on single CPU systems and
455
+ * the size of the L1 cache line size if detectable.
456
+ *
457
+ * Defaults to 128 bytes if undetectable.
458
+ */
459
+ rb_define_const(cRaindrops, "SIZE", SIZET2NUM(raindrop_size));
460
+
461
+ /*
462
+ * The maximum value a raindrop counter can hold
463
+ */
464
+ rb_define_const(cRaindrops, "MAX", ULONG2NUM((unsigned long)-1));
465
+
466
+ rb_define_alloc_func(cRaindrops, alloc);
467
+
468
+ rb_define_private_method(cRaindrops, "initialize_cimpl", init_cimpl, 3);
469
+ rb_define_method(cRaindrops, "incr", incr, -1);
470
+ rb_define_method(cRaindrops, "decr", decr, -1);
471
+ rb_define_method(cRaindrops, "to_ary", to_ary, 0);
472
+ rb_define_method(cRaindrops, "[]", aref, 1);
473
+ rb_define_method(cRaindrops, "[]=", aset, 2);
474
+ rb_define_method(cRaindrops, "size", size, 0);
475
+ rb_define_method(cRaindrops, "size=", setsize, 1);
476
+ rb_define_method(cRaindrops, "capa", capa, 0);
477
+ rb_define_method(cRaindrops, "initialize_copy", init_copy, 1);
478
+ rb_define_method(cRaindrops, "evaporate!", evaporate_bang, 0);
479
+ rb_define_method(cRaindrops, "to_io", to_io, 0);
480
+
481
+ #ifdef __linux__
482
+ Init_raindrops_linux_inet_diag();
483
+ #endif
484
+ #ifdef HAVE_TYPE_STRUCT_TCP_INFO
485
+ Init_raindrops_tcp_info();
486
+ #endif
487
+ }
@@ -0,0 +1,23 @@
1
+ /*
2
+ * use wrappers around libatomic-ops for folks that don't have GCC
3
+ * or a new enough version of GCC
4
+ */
5
+ #ifndef HAVE_GCC_ATOMIC_BUILTINS
6
+ #include <atomic_ops.h>
7
+
8
+ static inline unsigned long
9
+ __sync_add_and_fetch(unsigned long *dst, unsigned long incr)
10
+ {
11
+ AO_t tmp = AO_fetch_and_add((AO_t *)dst, (AO_t)incr);
12
+
13
+ return (unsigned long)tmp + incr;
14
+ }
15
+
16
+ static inline unsigned long
17
+ __sync_sub_and_fetch(unsigned long *dst, unsigned long incr)
18
+ {
19
+ AO_t tmp = AO_fetch_and_add((AO_t *)dst, (AO_t)(-(long)incr));
20
+
21
+ return (unsigned long)tmp - incr;
22
+ }
23
+ #endif /* HAVE_GCC_ATOMIC_BUILTINS */