raindrops-0.12.0/0000755000004100000410000000000012211676722013631 5ustar www-datawww-dataraindrops-0.12.0/README0000644000004100000410000000733112211676722014515 0ustar www-datawww-data= raindrops - real-time stats for preforking Rack servers Raindrops is a real-time stats toolkit to show statistics for Rack HTTP servers. It is designed for preforking servers such as Rainbows! and Unicorn, but should support any Rack HTTP server under Ruby 2.0, 1.9, 1.8 and Rubinius on platforms supporting POSIX shared memory. It may also be used as a generic scoreboard for sharing atomic counters across multiple processes. == Features * counters are shared across all forked children and lock-free * counters are kept on separate cache lines to reduce contention under SMP * may expose server statistics as a Rack Middleware endpoint (default: "/_raindrops") * middleware displays the number of actively processing and writing clients from a single request regardless of which worker process it hits. == Linux-only Extra Features! * Middleware response includes extra stats for bound TCP and Unix domain sockets (configurable, it can include stats from other TCP or UNIX domain socket servers). * TCP socket stats use efficient inet_diag facilities via netlink instead of parsing /proc/net/tcp to minimize overhead. This was fun to discover and write. * TCP_Info reporting may be used to check stat for every accepted client on TCP servers 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 == Install We recommend GCC 4+ (or compatible) to support the {atomic builtins}[http://gcc.gnu.org/onlinedocs/gcc/Atomic-Builtins.html] (__sync_{add,sub}_and_fetch()). For non-GCC 4+ users, we also support compilation with the {libatomic_ops}[http://www.hpl.hp.com/research/linux/atomic_ops/] package starting with Raindrops 0.4.0. If you're using a packaged Ruby distribution, make sure you have a C compiler and the matching Ruby development libraries and headers. If you use RubyGems: gem install raindrops Otherwise grab the latest tarball from: http://raindrops.bogomips.org/files/ Unpack it, and run "ruby setup.rb" == Usage See Raindrops::Middleware and Raindrops::LastDataRecv documentation for use Rack servers. The entire library is fully-documented and we are responsive on the mailing list (mailto:raindrops@librelist.org) if you have any questions or comments. == Development You can get the latest source via git from the following locations: git://bogomips.org/raindrops.git git://repo.or.cz/raindrops.git (mirror) You may browse the code from the web and download the latest snapshot tarballs here: * http://bogomips.org/raindrops.git (cgit) * http://repo.or.cz/w/raindrops.git (gitweb) Inline patches (from "git format-patch") to the mailing list are preferred because they allow code review and comments in the reply to the patch. We will adhere to mostly the same conventions for patch submissions as git itself. See the Documentation/SubmittingPatches document distributed with git on on patch submission guidelines to follow. Just don't email the git mailing list or maintainer with raindrops patches. raindrops is currently dual-licensed under the LGPLv2.1 and LGPLv3. To allow for a transition to future versions of the LGPL, contributors are required to sign-off changes allowing allowing the project leader to relicense raindrops under newer versions of the LGPL (which should be similar in spirit to the existing LGPL). == Contact All feedback (bug reports, user/development discussion, patches, pull requests) go to the mailing list: mailto:raindrops@librelist.org The mailing list is mirrored to Gmane, all information about the group is here: http://gmane.org/info.php?group=gmane.comp.lang.ruby.raindrops.general Mailing list archives in mbox format may be downloaded here: http://raindrops.bogomips.org/archives/ raindrops-0.12.0/ext/0000755000004100000410000000000012211676722014431 5ustar www-datawww-dataraindrops-0.12.0/ext/raindrops/0000755000004100000410000000000012211676722016432 5ustar www-datawww-dataraindrops-0.12.0/ext/raindrops/linux_tcp_info.c0000644000004100000410000001053512211676722021622 0ustar www-datawww-data#ifdef __linux__ #include #include #include #include #ifdef TCP_INFO #include "my_fileno.h" #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); \ } 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) static VALUE alloc(VALUE klass) { struct tcp_info *info = xmalloc(sizeof(struct tcp_info)); /* Data_Make_Struct has an extra memset 0 which is so wasteful */ return Data_Wrap_Struct(klass, NULL, -1, info); } /* * call-seq: * * Raindrops::TCP_Info.new(tcp_socket) -> TCP_Info object * * Reads a TCP_Info object from any given +tcp_socket+. See the tcp(7) * manpage and /usr/include/linux/tcp.h for more details. */ static VALUE init(VALUE self, VALUE io) { int fd = my_fileno(io); struct tcp_info *info = DATA_PTR(self); socklen_t len = (socklen_t)sizeof(struct tcp_info); int rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, info, &len); if (rc != 0) rb_sys_fail("getsockopt"); return self; } void Init_raindrops_linux_tcp_info(void) { VALUE cRaindrops = rb_const_get(rb_cObject, rb_intern("Raindrops")); VALUE cTCP_Info; /* * Document-class: Raindrops::TCP_Info * * This is used to wrap "struct tcp_info" as described in tcp(7) * and /usr/include/linux/tcp.h. The following readers methods * are defined corresponding to the "tcpi_" fields in the * tcp_info struct. * * In particular, the +last_data_recv+ field is useful for measuring * the amount of time a client spent in the listen queue before * +accept()+, but only if +TCP_DEFER_ACCEPT+ is used with the * listen socket (it is on by default in Unicorn). * * - state * - ca_state * - retransmits * - probes * - backoff * - options * - snd_wscale * - rcv_wscale * - rto * - ato * - snd_mss * - rcv_mss * - unacked * - sacked * - lost * - retrans * - fackets * - last_data_sent * - last_ack_sent * - last_data_recv * - last_ack_recv * - pmtu * - rcv_ssthresh * - rtt * - rttvar * - snd_ssthresh * - snd_cwnd * - advmss * - reordering * - rcv_rtt * - rcv_space * - total_retrans * * http://kernel.org/doc/man-pages/online/pages/man7/tcp.7.html */ cTCP_Info = rb_define_class_under(cRaindrops, "TCP_Info", rb_cObject); rb_define_alloc_func(cTCP_Info, alloc); rb_define_private_method(cTCP_Info, "initialize", init, 1); 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); } #endif /* TCP_INFO */ #endif /* __linux__ */ raindrops-0.12.0/ext/raindrops/extconf.rb0000644000004100000410000000277012211676722020433 0ustar www-datawww-datarequire 'mkmf' dir_config('atomic_ops') have_func('mmap', 'sys/mman.h') or abort 'mmap() not found' have_func('munmap', 'sys/mman.h') or abort 'munmap() not found' $CPPFLAGS += " -D_GNU_SOURCE " have_func('mremap', 'sys/mman.h') $CPPFLAGS += " -D_BSD_SOURCE " have_func("getpagesize", "unistd.h") have_func('rb_thread_blocking_region') have_func('rb_thread_io_blocking_region') checking_for "GCC 4+ atomic builtins" do # we test CMPXCHG anyways even though we don't need it to filter out # ancient i386-only targets without CMPXCHG src = < #ifdef HAVE_RUBY_ST_H # include #else # include #endif #include "my_fileno.h" #ifdef __linux__ /* Ruby 1.8.6+ macros (for compatibility with Ruby 1.9) */ #ifndef RSTRING_LEN # define RSTRING_LEN(s) (RSTRING(s)->len) #endif /* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */ #ifndef HAVE_RB_THREAD_BLOCKING_REGION # include # define RUBY_UBF_IO ((rb_unblock_function_t *)-1) typedef void rb_unblock_function_t(void *); typedef VALUE rb_blocking_function_t(void *); static VALUE rb_thread_blocking_region( rb_blocking_function_t *func, void *data1, rb_unblock_function_t *ubf, void *data2) { VALUE rv; TRAP_BEG; rv = func(data1); TRAP_END; return rv; } #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */ #ifdef HAVE_RB_THREAD_IO_BLOCKING_REGION VALUE rb_thread_io_blocking_region(rb_blocking_function_t *, void *, int); #else # define rb_thread_io_blocking_region(fn,data,fd) \ rb_thread_blocking_region((fn),(data),RUBY_UBF_IO,0) #endif /* HAVE_RB_THREAD_IO_BLOCKING_REGION */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include union any_addr { struct sockaddr_storage ss; struct sockaddr sa; struct sockaddr_in in; struct sockaddr_in6 in6; }; static size_t page_size; static unsigned g_seq; static VALUE cListenStats, cIDSock; static ID id_new; struct listen_stats { uint32_t active; uint32_t queued; uint32_t listener_p; }; #define OPLEN (sizeof(struct inet_diag_bc_op) + \ sizeof(struct inet_diag_hostcond) + \ sizeof(struct sockaddr_storage)) struct nogvl_args { st_table *table; struct iovec iov[3]; /* last iov holds inet_diag bytecode */ struct listen_stats stats; int fd; }; #ifdef SOCK_CLOEXEC # define my_SOCK_RAW (SOCK_RAW|SOCK_CLOEXEC) # define FORCE_CLOEXEC(v) (v) #else # define my_SOCK_RAW SOCK_RAW static VALUE FORCE_CLOEXEC(VALUE io) { int fd = my_fileno(io); int flags = fcntl(fd, F_SETFD, FD_CLOEXEC); if (flags == -1) rb_sys_fail("fcntl(F_SETFD, FD_CLOEXEC)"); return io; } #endif /* * call-seq: * Raindrops::InetDiagSocket.new -> Socket * * Creates a new Socket object for the netlink inet_diag facility */ static VALUE ids_s_new(VALUE klass) { VALUE argv[3]; argv[0] = INT2NUM(AF_NETLINK); argv[1] = INT2NUM(my_SOCK_RAW); argv[2] = INT2NUM(NETLINK_INET_DIAG); return FORCE_CLOEXEC(rb_call_super(3, argv)); } /* creates a Ruby ListenStats Struct based on our internal listen_stats */ static VALUE rb_listen_stats(struct listen_stats *stats) { VALUE active = UINT2NUM(stats->active); VALUE queued = UINT2NUM(stats->queued); return rb_struct_new(cListenStats, active, queued); } static int st_free_data(st_data_t key, st_data_t value, st_data_t ignored) { xfree((void *)key); xfree((void *)value); return ST_DELETE; } static int st_to_hash(st_data_t key, st_data_t value, VALUE hash) { struct listen_stats *stats = (struct listen_stats *)value; if (stats->listener_p) { VALUE k = rb_str_new2((const char *)key); VALUE v = rb_listen_stats(stats); OBJ_FREEZE(k); rb_hash_aset(hash, k, v); } return st_free_data(key, value, 0); } static int st_AND_hash(st_data_t key, st_data_t value, VALUE hash) { struct listen_stats *stats = (struct listen_stats *)value; if (stats->listener_p) { VALUE k = rb_str_new2((const char *)key); if (rb_hash_lookup(hash, k) == Qtrue) { VALUE v = rb_listen_stats(stats); OBJ_FREEZE(k); rb_hash_aset(hash, k, v); } } return st_free_data(key, value, 0); } static const char *addr_any(sa_family_t family) { static const char ipv4[] = "0.0.0.0"; static const char ipv6[] = "[::]"; if (family == AF_INET) return ipv4; assert(family == AF_INET6 && "unknown family"); return ipv6; } static void bug_warn(void) { fprintf(stderr, "Please report how you produced this at "\ "raindrops@librelist.org\n"); fflush(stderr); } static struct listen_stats *stats_for(st_table *table, struct inet_diag_msg *r) { char *key, *port, *old_key; size_t alloca_len; struct listen_stats *stats; size_t keylen; size_t portlen = sizeof("65535"); union any_addr sa; socklen_t len = sizeof(struct sockaddr_storage); int rc; int flags = NI_NUMERICHOST | NI_NUMERICSERV; switch ((sa.ss.ss_family = r->idiag_family)) { case AF_INET: { sa.in.sin_port = r->id.idiag_sport; sa.in.sin_addr.s_addr = r->id.idiag_src[0]; keylen = INET_ADDRSTRLEN; alloca_len = keylen + 1 + portlen; key = alloca(alloca_len); key[keylen] = 0; /* will be ':' later */ port = key + keylen + 1; rc = getnameinfo(&sa.sa, len, key, keylen, port, portlen, flags); break; } case AF_INET6: { sa.in6.sin6_port = r->id.idiag_sport; memcpy(&sa.in6.sin6_addr, &r->id.idiag_src, sizeof(__be32[4])); keylen = INET6_ADDRSTRLEN; /* [ ] */ alloca_len = 1 + keylen + 1 + 1 + portlen; key = alloca(alloca_len); *key = '['; key[1 + keylen + 1] = 0; /* will be ':' later */ port = 1 + key + keylen + 1 + 1; rc = getnameinfo(&sa.sa, len, key + 1, keylen, port, portlen, flags); break; } default: assert(0 && "unsupported address family, could that be IPv7?!"); } if (rc != 0) { fprintf(stderr, "BUG: getnameinfo: %s\n", gai_strerror(rc)); bug_warn(); *key = 0; } keylen = strlen(key); portlen = strlen(port); switch (sa.ss.ss_family) { case AF_INET: key[keylen] = ':'; memmove(key + keylen + 1, port, portlen + 1); break; case AF_INET6: key[keylen] = ']'; key[keylen + 1] = ':'; memmove(key + keylen + 2, port, portlen + 1); keylen++; break; default: assert(0 && "unsupported address family, could that be IPv7?!"); } if (st_lookup(table, (st_data_t)key, (st_data_t *)&stats)) return stats; old_key = key; if (r->idiag_state == TCP_ESTABLISHED) { int n = snprintf(key, alloca_len, "%s:%u", addr_any(sa.ss.ss_family), ntohs(r->id.idiag_sport)); if (n <= 0) { fprintf(stderr, "BUG: snprintf: %d\n", n); bug_warn(); } if (st_lookup(table, (st_data_t)key, (st_data_t *)&stats)) return stats; if (n <= 0) { key = xmalloc(1); *key = '\0'; } else { old_key = key; key = xmalloc(n + 1); memcpy(key, old_key, n + 1); } } else { key = xmalloc(keylen + 1 + portlen + 1); memcpy(key, old_key, keylen + 1 + portlen + 1); } stats = xcalloc(1, sizeof(struct listen_stats)); st_insert(table, (st_data_t)key, (st_data_t)stats); return stats; } static void table_incr_active(st_table *table, struct inet_diag_msg *r) { struct listen_stats *stats = stats_for(table, r); ++stats->active; } static void table_set_queued(st_table *table, struct inet_diag_msg *r) { struct listen_stats *stats = stats_for(table, r); stats->listener_p = 1; stats->queued = r->idiag_rqueue; } /* inner loop of inet_diag, called for every socket returned by netlink */ static inline void r_acc(struct nogvl_args *args, struct inet_diag_msg *r) { /* * inode == 0 means the connection is still in the listen queue * and has not yet been accept()-ed by the server. The * inet_diag bytecode cannot filter this for us. */ if (r->idiag_inode == 0) return; if (r->idiag_state == TCP_ESTABLISHED) { if (args->table) table_incr_active(args->table, r); else args->stats.active++; } else { /* if (r->idiag_state == TCP_LISTEN) */ if (args->table) table_set_queued(args->table, r); else args->stats.queued = r->idiag_rqueue; } /* * we wont get anything else because of the idiag_states filter */ } static const char err_sendmsg[] = "sendmsg"; static const char err_recvmsg[] = "recvmsg"; static const char err_nlmsg[] = "nlmsg"; struct diag_req { struct nlmsghdr nlh; struct inet_diag_req r; }; static void prep_msghdr( struct msghdr *msg, struct nogvl_args *args, struct sockaddr_nl *nladdr, size_t iovlen) { memset(msg, 0, sizeof(struct msghdr)); msg->msg_name = (void *)nladdr; msg->msg_namelen = sizeof(struct sockaddr_nl); msg->msg_iov = args->iov; msg->msg_iovlen = iovlen; } static void prep_diag_args( struct nogvl_args *args, struct sockaddr_nl *nladdr, struct rtattr *rta, struct diag_req *req, struct msghdr *msg) { memset(req, 0, sizeof(struct diag_req)); memset(nladdr, 0, sizeof(struct sockaddr_nl)); nladdr->nl_family = AF_NETLINK; req->nlh.nlmsg_len = sizeof(struct diag_req) + RTA_LENGTH(args->iov[2].iov_len); req->nlh.nlmsg_type = TCPDIAG_GETSOCK; req->nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; req->nlh.nlmsg_pid = getpid(); req->r.idiag_states = (1<rta_type = INET_DIAG_REQ_BYTECODE; rta->rta_len = RTA_LENGTH(args->iov[2].iov_len); args->iov[0].iov_base = req; args->iov[0].iov_len = sizeof(struct diag_req); args->iov[1].iov_base = rta; args->iov[1].iov_len = sizeof(struct rtattr); prep_msghdr(msg, args, nladdr, 3); } static void prep_recvmsg_buf(struct nogvl_args *args) { /* reuse buffer that was allocated for bytecode */ args->iov[0].iov_len = page_size; args->iov[0].iov_base = args->iov[2].iov_base; } /* does the inet_diag stuff with netlink(), this is called w/o GVL */ static VALUE diag(void *ptr) { struct nogvl_args *args = ptr; struct sockaddr_nl nladdr; struct rtattr rta; struct diag_req req; struct msghdr msg; const char *err = NULL; unsigned seq = ++g_seq; prep_diag_args(args, &nladdr, &rta, &req, &msg); req.nlh.nlmsg_seq = seq; if (sendmsg(args->fd, &msg, 0) < 0) { err = err_sendmsg; goto out; } prep_recvmsg_buf(args); while (1) { ssize_t readed; size_t r; struct nlmsghdr *h = (struct nlmsghdr *)args->iov[0].iov_base; prep_msghdr(&msg, args, &nladdr, 1); readed = recvmsg(args->fd, &msg, 0); if (readed < 0) { if (errno == EINTR) continue; err = err_recvmsg; goto out; } if (readed == 0) goto out; r = (size_t)readed; for ( ; NLMSG_OK(h, r); h = NLMSG_NEXT(h, r)) { if (h->nlmsg_seq != seq) continue; if (h->nlmsg_type == NLMSG_DONE) goto out; if (h->nlmsg_type == NLMSG_ERROR) { err = err_nlmsg; goto out; } r_acc(args, NLMSG_DATA(h)); } } out: { int save_errno = errno; if (err && args->table) { st_foreach(args->table, st_free_data, 0); st_free_table(args->table); } errno = save_errno; } return (VALUE)err; } /* populates sockaddr_storage struct by parsing +addr+ */ static void parse_addr(union any_addr *inet, VALUE addr) { char *host_ptr; char *check; char *colon = NULL; char *rbracket = NULL; void *dst; long host_len; int af, rc; uint16_t *portdst; unsigned long port; Check_Type(addr, T_STRING); host_ptr = StringValueCStr(addr); host_len = RSTRING_LEN(addr); if (*host_ptr == '[') { /* ipv6 address format (rfc2732) */ rbracket = memchr(host_ptr + 1, ']', host_len - 1); if (rbracket == NULL) rb_raise(rb_eArgError, "']' not found in IPv6 addr=%s", host_ptr); if (rbracket[1] != ':') rb_raise(rb_eArgError, "':' not found in IPv6 addr=%s", host_ptr); colon = rbracket + 1; host_ptr++; *rbracket = 0; inet->ss.ss_family = af = AF_INET6; dst = &inet->in6.sin6_addr; portdst = &inet->in6.sin6_port; } else { /* ipv4 */ colon = memchr(host_ptr, ':', host_len); inet->ss.ss_family = af = AF_INET; dst = &inet->in.sin_addr; portdst = &inet->in.sin_port; } if (!colon) rb_raise(rb_eArgError, "port not found in: `%s'", host_ptr); port = strtoul(colon + 1, &check, 10); *colon = 0; rc = inet_pton(af, host_ptr, dst); *colon = ':'; if (rbracket) *rbracket = ']'; if (*check || ((uint16_t)port != port)) rb_raise(rb_eArgError, "invalid port: %s", colon + 1); if (rc != 1) rb_raise(rb_eArgError, "inet_pton failed for: `%s' with %d", host_ptr, rc); *portdst = ntohs((uint16_t)port); } /* generates inet_diag bytecode to match all addrs */ static void gen_bytecode_all(struct iovec *iov) { struct inet_diag_bc_op *op; struct inet_diag_hostcond *cond; /* iov_len was already set and base allocated in a parent function */ assert(iov->iov_len == OPLEN && iov->iov_base && "iov invalid"); op = iov->iov_base; op->code = INET_DIAG_BC_S_COND; op->yes = OPLEN; op->no = sizeof(struct inet_diag_bc_op) + OPLEN; cond = (struct inet_diag_hostcond *)(op + 1); cond->family = AF_UNSPEC; cond->port = -1; cond->prefix_len = 0; } /* generates inet_diag bytecode to match a single addr */ static void gen_bytecode(struct iovec *iov, union any_addr *inet) { struct inet_diag_bc_op *op; struct inet_diag_hostcond *cond; /* iov_len was already set and base allocated in a parent function */ assert(iov->iov_len == OPLEN && iov->iov_base && "iov invalid"); op = iov->iov_base; op->code = INET_DIAG_BC_S_COND; op->yes = OPLEN; op->no = sizeof(struct inet_diag_bc_op) + OPLEN; cond = (struct inet_diag_hostcond *)(op + 1); cond->family = inet->ss.ss_family; switch (inet->ss.ss_family) { case AF_INET: { cond->port = ntohs(inet->in.sin_port); cond->prefix_len = inet->in.sin_addr.s_addr == 0 ? 0 : sizeof(inet->in.sin_addr.s_addr) * CHAR_BIT; *cond->addr = inet->in.sin_addr.s_addr; } break; case AF_INET6: { cond->port = ntohs(inet->in6.sin6_port); cond->prefix_len = memcmp(&in6addr_any, &inet->in6.sin6_addr, sizeof(struct in6_addr)) == 0 ? 0 : sizeof(inet->in6.sin6_addr) * CHAR_BIT; memcpy(&cond->addr, &inet->in6.sin6_addr, sizeof(struct in6_addr)); } break; default: assert(0 && "unsupported address family, could that be IPv7?!"); } } static void nl_errcheck(VALUE r) { const char *err = (const char *)r; if (err) { if (err == err_nlmsg) rb_raise(rb_eRuntimeError, "NLMSG_ERROR"); else rb_sys_fail(err); } } static VALUE tcp_stats(struct nogvl_args *args, VALUE addr) { union any_addr query_addr; parse_addr(&query_addr, addr); gen_bytecode(&args->iov[2], &query_addr); memset(&args->stats, 0, sizeof(struct listen_stats)); nl_errcheck(rb_thread_io_blocking_region(diag, args, args->fd)); return rb_listen_stats(&args->stats); } /* * call-seq: * 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. */ 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); VALUE cur; if (len == 1) { 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); } /* 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(rb_thread_io_blocking_region(diag, &args, args.fd)); st_foreach(args.table, NIL_P(addrs) ? st_to_hash : st_AND_hash, rv); st_free_table(args.table); /* let GC deal with corner cases */ if (argc < 2) rb_io_close(sock); return rv; } void Init_raindrops_linux_inet_diag(void) { VALUE cRaindrops = rb_const_get(rb_cObject, rb_intern("Raindrops")); VALUE mLinux = rb_define_module_under(cRaindrops, "Linux"); rb_require("socket"); cIDSock = rb_const_get(rb_cObject, rb_intern("Socket")); id_new = rb_intern("new"); /* * Document-class: Raindrops::InetDiagSocket * * This is a subclass of +Socket+ specifically for talking * to the inet_diag facility of Netlink. */ cIDSock = rb_define_class_under(cRaindrops, "InetDiagSocket", cIDSock); rb_define_singleton_method(cIDSock, "new", ids_s_new, 0); cListenStats = rb_const_get(cRaindrops, rb_intern("ListenStats")); rb_define_module_function(mLinux, "tcp_listener_stats", tcp_listener_stats, -1); page_size = getpagesize(); assert(OPLEN <= page_size && "bytecode OPLEN is not <= PAGE_SIZE"); } #endif /* __linux__ */ raindrops-0.12.0/ext/raindrops/raindrops_atomic.h0000644000004100000410000000114212211676722022136 0ustar www-datawww-data/* * use wrappers around libatomic-ops for folks that don't have GCC * or a new enough version of GCC */ #ifndef HAVE_GCC_ATOMIC_BUILTINS #include static inline unsigned long __sync_add_and_fetch(unsigned long *dst, unsigned long incr) { AO_t tmp = AO_fetch_and_add((AO_t *)dst, (AO_t)incr); return (unsigned long)tmp + incr; } static inline unsigned long __sync_sub_and_fetch(unsigned long *dst, unsigned long incr) { AO_t tmp = AO_fetch_and_add((AO_t *)dst, (AO_t)(-(long)incr)); return (unsigned long)tmp - incr; } #endif /* HAVE_GCC_ATOMIC_BUILTINS */ raindrops-0.12.0/ext/raindrops/raindrops.c0000644000004100000410000002442312211676722020604 0ustar www-datawww-data#include #include #include #include #include #include #include "raindrops_atomic.h" #ifndef SIZET2NUM # define SIZET2NUM(x) ULONG2NUM(x) #endif #ifndef NUM2SIZET # define NUM2SIZET(x) NUM2ULONG(x) #endif /* * most modern CPUs have a cache-line size of 64 or 128. * We choose a bigger one by default since our structure is not * heavily used */ static size_t raindrop_size = 128; static size_t rd_page_size; #define PAGE_MASK (~(rd_page_size - 1)) #define PAGE_ALIGN(addr) (((addr) + rd_page_size - 1) & PAGE_MASK) /* each raindrop is a counter */ struct raindrop { unsigned long counter; } __attribute__((packed)); /* allow mmap-ed regions to store more than one raindrop */ struct raindrops { size_t size; size_t capa; pid_t pid; struct raindrop *drops; }; /* called by GC */ static void gcfree(void *ptr) { struct raindrops *r = ptr; if (r->drops != MAP_FAILED) { int rv = munmap(r->drops, raindrop_size * r->capa); if (rv != 0) rb_bug("munmap failed in gc: %s", strerror(errno)); } xfree(ptr); } /* automatically called at creation (before initialize) */ static VALUE alloc(VALUE klass) { struct raindrops *r; VALUE rv = Data_Make_Struct(klass, struct raindrops, NULL, gcfree, r); r->drops = MAP_FAILED; return rv; } static struct raindrops *get(VALUE self) { struct raindrops *r; Data_Get_Struct(self, struct raindrops, r); if (r->drops == MAP_FAILED) rb_raise(rb_eStandardError, "invalid or freed Raindrops"); return r; } /* * call-seq: * Raindrops.new(size) -> raindrops object * * Initializes a Raindrops object to hold +size+ counters. +size+ is * only a hint and the actual number of counters the object has is * dependent on the CPU model, number of cores, and page size of * the machine. The actual size of the object will always be equal * or greater than the specified +size+. */ static VALUE init(VALUE self, VALUE size) { struct raindrops *r = DATA_PTR(self); int tries = 1; size_t tmp; if (r->drops != MAP_FAILED) rb_raise(rb_eRuntimeError, "already initialized"); r->size = NUM2SIZET(size); if (r->size < 1) rb_raise(rb_eArgError, "size must be >= 1"); tmp = PAGE_ALIGN(raindrop_size * r->size); r->capa = tmp / raindrop_size; assert(PAGE_ALIGN(raindrop_size * r->capa) == tmp && "not aligned"); retry: r->drops = mmap(NULL, tmp, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); if (r->drops == MAP_FAILED) { if ((errno == EAGAIN || errno == ENOMEM) && tries-- > 0) { rb_gc(); goto retry; } rb_sys_fail("mmap"); } r->pid = getpid(); return self; } /* * mremap() is currently broken with MAP_SHARED * https://bugzilla.kernel.org/show_bug.cgi?id=8691 */ #if defined(HAVE_MREMAP) && !defined(MREMAP_WORKS_WITH_MAP_SHARED) # undef HAVE_MREMAP #endif #ifdef HAVE_MREMAP #ifndef MREMAP_MAYMOVE # warn MREMAP_MAYMOVE undefined # define MREMAP_MAYMOVE 0 #endif static void resize(struct raindrops *r, size_t new_rd_size) { size_t old_size = raindrop_size * r->capa; size_t new_size = PAGE_ALIGN(raindrop_size * new_rd_size); void *old_address = r->drops; void *rv; if (r->pid != getpid()) rb_raise(rb_eRuntimeError, "cannot mremap() from child"); rv = mremap(old_address, old_size, new_size, MREMAP_MAYMOVE); if (rv == MAP_FAILED) { if (errno == EAGAIN || errno == ENOMEM) { rb_gc(); rv = mremap(old_address, old_size, new_size, 0); } if (rv == MAP_FAILED) rb_sys_fail("mremap"); } r->drops = rv; r->size = new_rd_size; r->capa = new_size / raindrop_size; assert(r->capa >= r->size && "bad sizing"); } #else /* ! HAVE_MREMAP */ /* * we cannot use munmap + mmap to reallocate the buffer since it may * already be shared by other processes, so we just fail */ static void resize(struct raindrops *r, size_t new_rd_size) { rb_raise(rb_eRangeError, "mremap(2) is not available"); } #endif /* ! HAVE_MREMAP */ /* * call-seq: * rd.size = new_size * * Increases or decreases the current capacity of our Raindrop. * Raises RangeError if +new_size+ is too big or small for the * current backing store */ static VALUE setsize(VALUE self, VALUE new_size) { size_t new_rd_size = NUM2SIZET(new_size); struct raindrops *r = get(self); if (new_rd_size <= r->capa) r->size = new_rd_size; else resize(r, new_rd_size); return new_size; } /* * call-seq: * rd.capa -> Integer * * Returns the number of slots allocated (but not necessarily used) by * the Raindrops object. */ static VALUE capa(VALUE self) { return SIZET2NUM(get(self)->capa); } /* * call-seq: * rd.dup -> rd_copy * * Duplicates and snapshots the current state of a Raindrops object. */ static VALUE init_copy(VALUE dest, VALUE source) { struct raindrops *dst = DATA_PTR(dest); struct raindrops *src = get(source); init(dest, SIZET2NUM(src->size)); memcpy(dst->drops, src->drops, raindrop_size * src->size); return dest; } static unsigned long *addr_of(VALUE self, VALUE index) { struct raindrops *r = get(self); unsigned long off = FIX2ULONG(index) * raindrop_size; if (off >= raindrop_size * r->size) rb_raise(rb_eArgError, "offset overrun"); return (unsigned long *)((unsigned long)r->drops + off); } static unsigned long incr_decr_arg(int argc, const VALUE *argv) { if (argc > 2 || argc < 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for 1+)", argc); return argc == 2 ? NUM2ULONG(argv[1]) : 1; } /* * call-seq: * rd.incr(index[, number]) -> result * * Increments the value referred to by the +index+ by +number+. * +number+ defaults to +1+ if unspecified. */ static VALUE incr(int argc, VALUE *argv, VALUE self) { unsigned long nr = incr_decr_arg(argc, argv); return ULONG2NUM(__sync_add_and_fetch(addr_of(self, argv[0]), nr)); } /* * call-seq: * rd.decr(index[, number]) -> result * * Decrements the value referred to by the +index+ by +number+. * +number+ defaults to +1+ if unspecified. */ static VALUE decr(int argc, VALUE *argv, VALUE self) { unsigned long nr = incr_decr_arg(argc, argv); return ULONG2NUM(__sync_sub_and_fetch(addr_of(self, argv[0]), nr)); } /* * call-seq: * rd.to_ary -> Array * * converts the Raindrops structure to an Array */ static VALUE to_ary(VALUE self) { struct raindrops *r = get(self); VALUE rv = rb_ary_new2(r->size); size_t i; unsigned long base = (unsigned long)r->drops; for (i = 0; i < r->size; i++) { rb_ary_push(rv, ULONG2NUM(*((unsigned long *)base))); base += raindrop_size; } return rv; } /* * call-seq: * rd.size -> Integer * * Returns the number of counters a Raindrops object can hold. Due to * page alignment, this is always equal or greater than the number of * requested slots passed to Raindrops.new */ static VALUE size(VALUE self) { return SIZET2NUM(get(self)->size); } /* * call-seq: * rd[index] = value * * Assigns +value+ to the slot designated by +index+ */ static VALUE aset(VALUE self, VALUE index, VALUE value) { unsigned long *addr = addr_of(self, index); *addr = NUM2ULONG(value); return value; } /* * call-seq: * rd[index] -> value * * Returns the value of the slot designated by +index+ */ static VALUE aref(VALUE self, VALUE index) { return ULONG2NUM(*addr_of(self, index)); } #ifdef __linux__ void Init_raindrops_linux_inet_diag(void); void Init_raindrops_linux_tcp_info(void); #endif #ifndef _SC_NPROCESSORS_CONF # if defined _SC_NPROCESSORS_ONLN # define _SC_NPROCESSORS_CONF _SC_NPROCESSORS_ONLN # elif defined _SC_NPROC_ONLN # define _SC_NPROCESSORS_CONF _SC_NPROC_ONLN # elif defined _SC_CRAY_NCPU # define _SC_NPROCESSORS_CONF _SC_CRAY_NCPU # endif #endif /* * call-seq: * rd.evaporate! -> nil * * Releases mmap()-ed memory allocated for the Raindrops object back * to the OS. The Ruby garbage collector will also release memory * automatically when it is not needed, but this forces release * under high memory pressure. */ static VALUE evaporate_bang(VALUE self) { struct raindrops *r = get(self); void *addr = r->drops; r->drops = MAP_FAILED; if (munmap(addr, raindrop_size * r->capa) != 0) rb_sys_fail("munmap"); return Qnil; } void Init_raindrops_ext(void) { VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject); long tmp = 2; #ifdef _SC_NPROCESSORS_CONF tmp = sysconf(_SC_NPROCESSORS_CONF); #endif /* no point in padding on single CPU machines */ if (tmp == 1) raindrop_size = sizeof(unsigned long); #ifdef _SC_LEVEL1_DCACHE_LINESIZE if (tmp != 1) { tmp = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); if (tmp > 0) raindrop_size = (size_t)tmp; } #endif #if defined(_SC_PAGE_SIZE) rd_page_size = (size_t)sysconf(_SC_PAGE_SIZE); #elif defined(_SC_PAGESIZE) rd_page_size = (size_t)sysconf(_SC_PAGESIZE); #elif defined(HAVE_GETPAGESIZE) rd_page_size = (size_t)getpagesize(); #elif defined(PAGE_SIZE) rd_page_size = (size_t)PAGE_SIZE; #elif defined(PAGESIZE) rd_page_size = (size_t)PAGESIZE; #else # error unable to detect page size for mmap() #endif if ((rd_page_size == (size_t)-1) || (rd_page_size < raindrop_size)) rb_raise(rb_eRuntimeError, "system page size invalid: %llu", (unsigned long long)rd_page_size); /* * The size of one page of memory for a mmap()-ed Raindrops region. * Typically 4096 bytes under Linux. */ rb_define_const(cRaindrops, "PAGE_SIZE", SIZET2NUM(rd_page_size)); /* * The size (in bytes) of a slot in a Raindrops object. * This is the size of a word on single CPU systems and * the size of the L1 cache line size if detectable. * * Defaults to 128 bytes if undetectable. */ rb_define_const(cRaindrops, "SIZE", SIZET2NUM(raindrop_size)); /* * The maximum value a raindrop counter can hold */ rb_define_const(cRaindrops, "MAX", ULONG2NUM((unsigned long)-1)); rb_define_alloc_func(cRaindrops, alloc); rb_define_method(cRaindrops, "initialize", init, 1); rb_define_method(cRaindrops, "incr", incr, -1); rb_define_method(cRaindrops, "decr", decr, -1); rb_define_method(cRaindrops, "to_ary", to_ary, 0); rb_define_method(cRaindrops, "[]", aref, 1); rb_define_method(cRaindrops, "[]=", aset, 2); rb_define_method(cRaindrops, "size", size, 0); rb_define_method(cRaindrops, "size=", setsize, 1); rb_define_method(cRaindrops, "capa", capa, 0); rb_define_method(cRaindrops, "initialize_copy", init_copy, 1); rb_define_method(cRaindrops, "evaporate!", evaporate_bang, 0); #ifdef __linux__ Init_raindrops_linux_inet_diag(); Init_raindrops_linux_tcp_info(); #endif } raindrops-0.12.0/ext/raindrops/my_fileno.h0000644000004100000410000000126312211676722020566 0ustar www-datawww-data#include #ifdef HAVE_RUBY_IO_H # include #else # include # include #endif #if ! HAVE_RB_IO_T # define rb_io_t OpenFile #endif #ifdef GetReadFile # define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr))) #else # if !HAVE_RB_IO_T || (RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 8) # define FPTR_TO_FD(fptr) fileno(fptr->f) # else # define FPTR_TO_FD(fptr) fptr->fd # endif #endif static int my_fileno(VALUE io) { rb_io_t *fptr; int fd; if (TYPE(io) != T_FILE) io = rb_convert_type(io, T_FILE, "IO", "to_io"); GetOpenFile(io, fptr); fd = FPTR_TO_FD(fptr); if (fd < 0) rb_raise(rb_eIOError, "closed stream"); return fd; } raindrops-0.12.0/test/0000755000004100000410000000000012211676722014610 5ustar www-datawww-dataraindrops-0.12.0/test/test_middleware_unicorn_ipv6.rb0000644000004100000410000000212312211676722023010 0ustar www-datawww-data# -*- encoding: binary -*- require "./test/rack_unicorn" require "./test/ipv6_enabled" $stderr.sync = $stdout.sync = true class TestMiddlewareUnicornIPv6 < Test::Unit::TestCase def setup @host = ENV["TEST_HOST6"] || "::1" sock = TCPServer.new @host, 0 @port = sock.addr[1] ENV["UNICORN_FD"] = sock.fileno.to_s @host_with_port = "[#@host]:#@port" @opts = { :listeners => [ @host_with_port ] } @addr_regexp = Regexp.escape @host_with_port end def test_auto_listener @app = Rack::Builder.new do use Raindrops::Middleware run Rack::Lobster.new end @srv = fork { Unicorn::HttpServer.new(@app, @opts).start.join } s = TCPSocket.new @host, @port s.write "GET /_raindrops HTTP/1.0\r\n\r\n" resp = s.read _, body = resp.split(/\r\n\r\n/, 2) assert_match %r{^#@addr_regexp active: 1$}, body assert_match %r{^#@addr_regexp queued: 0$}, body end def teardown Process.kill :QUIT, @srv _, status = Process.waitpid2 @srv assert status.success? end end if defined?(Unicorn) && RUBY_PLATFORM =~ /linux/ && ipv6_enabled? raindrops-0.12.0/test/test_linux.rb0000644000004100000410000001767412211676722017352 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'tempfile' require 'raindrops' require 'socket' require 'pp' $stderr.sync = $stdout.sync = true class TestLinux < Test::Unit::TestCase include Raindrops::Linux TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' def setup @to_close = [] end def teardown @to_close.each { |io| io.close unless io.closed? } end def test_unix tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :) File.unlink(tmp.path) us = UNIXServer.new(tmp.path) stats = unix_listener_stats([tmp.path]) assert_equal 1, stats.size assert_equal 0, stats[tmp.path].active assert_equal 0, stats[tmp.path].queued @to_close << UNIXSocket.new(tmp.path) stats = unix_listener_stats([tmp.path]) assert_equal 1, stats.size assert_equal 0, stats[tmp.path].active assert_equal 1, stats[tmp.path].queued @to_close << UNIXSocket.new(tmp.path) stats = unix_listener_stats([tmp.path]) assert_equal 1, stats.size assert_equal 0, stats[tmp.path].active assert_equal 2, stats[tmp.path].queued @to_close << us.accept stats = unix_listener_stats([tmp.path]) assert_equal 1, stats.size assert_equal 1, stats[tmp.path].active assert_equal 1, stats[tmp.path].queued end def test_unix_all tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :) File.unlink(tmp.path) us = UNIXServer.new(tmp.path) @to_close << UNIXSocket.new(tmp.path) stats = unix_listener_stats assert_equal 0, stats[tmp.path].active assert_equal 1, stats[tmp.path].queued @to_close << UNIXSocket.new(tmp.path) stats = unix_listener_stats assert_equal 0, stats[tmp.path].active assert_equal 2, stats[tmp.path].queued @to_close << us.accept stats = unix_listener_stats assert_equal 1, stats[tmp.path].active assert_equal 1, stats[tmp.path].queued end def test_unix_all_unused tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :) File.unlink(tmp.path) us = UNIXServer.new(tmp.path) stats = unix_listener_stats assert stats.keys.include?(tmp.path), stats.inspect assert_equal 0, stats[tmp.path].active assert_equal 0, stats[tmp.path].queued end def test_unix_resolves_symlinks tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :) File.unlink(tmp.path) us = UNIXServer.new(tmp.path) # Create a symlink link = Tempfile.new("somethingelse") File.unlink(link.path) # We need an available name, not an actual file File.symlink(tmp.path, link.path) @to_close << UNIXSocket.new(tmp.path) stats = unix_listener_stats assert_equal 0, stats[tmp.path].active assert_equal 1, stats[tmp.path].queued @to_close << UNIXSocket.new(link.path) stats = unix_listener_stats([link.path]) assert_equal 0, stats[link.path].active assert_equal 2, stats[link.path].queued assert_equal stats[link.path].object_id, stats[tmp.path].object_id @to_close << us.accept stats = unix_listener_stats assert_equal 1, stats[tmp.path].active assert_equal 1, stats[tmp.path].queued end def test_tcp s = TCPServer.new(TEST_ADDR, 0) port = s.addr[1] addr = "#{TEST_ADDR}:#{port}" addrs = [ addr ] stats = tcp_listener_stats(addrs) assert_equal 1, stats.size assert_equal 0, stats[addr].queued assert_equal 0, stats[addr].active @to_close << TCPSocket.new(TEST_ADDR, port) stats = tcp_listener_stats(addrs) assert_equal 1, stats.size assert_equal 1, stats[addr].queued assert_equal 0, stats[addr].active @to_close << s.accept stats = tcp_listener_stats(addrs) assert_equal 1, stats.size assert_equal 0, stats[addr].queued assert_equal 1, stats[addr].active end def test_tcp_reuse_sock nlsock = Raindrops::InetDiagSocket.new s = TCPServer.new(TEST_ADDR, 0) port = s.addr[1] addr = "#{TEST_ADDR}:#{port}" addrs = [ addr ] stats = tcp_listener_stats(addrs, nlsock) assert_equal 1, stats.size assert_equal 0, stats[addr].queued assert_equal 0, stats[addr].active @to_close << TCPSocket.new(TEST_ADDR, port) stats = tcp_listener_stats(addrs, nlsock) assert_equal 1, stats.size assert_equal 1, stats[addr].queued assert_equal 0, stats[addr].active @to_close << s.accept stats = tcp_listener_stats(addrs, nlsock) assert_equal 1, stats.size assert_equal 0, stats[addr].queued assert_equal 1, stats[addr].active ensure nlsock.close end def test_tcp_multi s1 = TCPServer.new(TEST_ADDR, 0) s2 = TCPServer.new(TEST_ADDR, 0) port1, port2 = s1.addr[1], s2.addr[1] addr1, addr2 = "#{TEST_ADDR}:#{port1}", "#{TEST_ADDR}:#{port2}" addrs = [ addr1, addr2 ] stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 0, stats[addr1].active assert_equal 0, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << TCPSocket.new(TEST_ADDR, port1) stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 1, stats[addr1].queued assert_equal 0, stats[addr1].active assert_equal 0, stats[addr2].queued assert_equal 0, stats[addr2].active sc1 = s1.accept stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 0, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << TCPSocket.new(TEST_ADDR, port2) stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 1, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << TCPSocket.new(TEST_ADDR, port2) stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 2, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << s2.accept stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 1, stats[addr2].queued assert_equal 1, stats[addr2].active sc1.close stats = tcp_listener_stats(addrs) assert_equal 0, stats[addr1].queued assert_equal 0, stats[addr1].active assert_equal 1, stats[addr2].queued assert_equal 1, stats[addr2].active end # tries to overflow buffers def test_tcp_stress_test nr_proc = 32 nr_sock = 500 s = TCPServer.new(TEST_ADDR, 0) port = s.addr[1] addr = "#{TEST_ADDR}:#{port}" addrs = [ addr ] rda, wra = IO.pipe rdb, wrb = IO.pipe nr_proc.times do fork do rda.close wrb.close @to_close.concat((1..nr_sock).map { s.accept }) wra.syswrite('.') wra.close rdb.sysread(1) # wait for parent to nuke us end end nr_proc.times do fork do rda.close wrb.close @to_close.concat((1..nr_sock).map { TCPSocket.new(TEST_ADDR, port) }) wra.syswrite('.') wra.close rdb.sysread(1) # wait for parent to nuke us end end assert_equal('.' * (nr_proc * 2), rda.read(nr_proc * 2)) rda.close stats = tcp_listener_stats(addrs) expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 0] } assert_equal expect, stats @to_close << TCPSocket.new(TEST_ADDR, port) stats = tcp_listener_stats(addrs) expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 1] } assert_equal expect, stats if ENV["BENCHMARK"].to_i != 0 require 'benchmark' puts(Benchmark.measure{1000.times { tcp_listener_stats(addrs) }}) end wrb.syswrite('.' * (nr_proc * 2)) # broadcast a wakeup statuses = Process.waitall statuses.each { |(_,status)| assert status.success?, status.inspect } end if ENV["STRESS"].to_i != 0 end if RUBY_PLATFORM =~ /linux/ raindrops-0.12.0/test/test_linux_tcp_info.rb0000644000004100000410000000323212211676722021214 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'tempfile' require 'raindrops' require 'socket' require 'pp' $stderr.sync = $stdout.sync = true class TestLinuxTCP_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 s = TCPServer.new(TEST_ADDR, 0) rv = Raindrops::TCP_Info.new s c = TCPSocket.new TEST_ADDR, s.addr[1] tmp = Raindrops::TCP_Info.new s TCP_INFO_useful_listenq and assert_equal 1, tmp.unacked assert_equal 0, rv.unacked a = s.accept tmp = Raindrops::TCP_Info.new s assert_equal 0, tmp.unacked before = tmp.object_id tmp.get!(s) assert_equal before, tmp.object_id ensure c.close if c a.close if a s.close end def test_accessors s = TCPServer.new TEST_ADDR, 0 tmp = Raindrops::TCP_Info.new s tcp_info_methods = tmp.methods - Object.new.methods assert tcp_info_methods.size >= 32 tcp_info_methods.each do |m| next if m.to_sym == :get! val = tmp.__send__ m assert_kind_of Integer, val assert val >= 0 end ensure s.close end def test_tcp_server_delayed delay = 0.010 delay_ms = (delay * 1000).to_i s = TCPServer.new(TEST_ADDR, 0) c = TCPSocket.new TEST_ADDR, s.addr[1] c.syswrite "." sleep(delay * 1.2) a = s.accept i = Raindrops::TCP_Info.new(a) assert i.last_data_recv >= delay_ms, "#{i.last_data_recv} < #{delay_ms}" ensure c.close if c a.close if a s.close end end if RUBY_PLATFORM =~ /linux/ raindrops-0.12.0/test/rack_unicorn.rb0000644000004100000410000000041412211676722017611 0ustar www-datawww-data# -*- encoding: binary -*- require "test/unit" require "raindrops" require "rack" require "rack/lobster" require "open-uri" begin require "unicorn" require "rack/lobster" rescue LoadError => e warn "W: #{e} skipping test since Rack or Unicorn was not found" end raindrops-0.12.0/test/test_middleware.rb0000644000004100000410000000670612211676722020322 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'raindrops' class TestMiddleware < Test::Unit::TestCase def setup @resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' } @response = [ 200, @resp_headers, [] ] @app = lambda { |env| @response } end def test_setup app = Raindrops::Middleware.new(@app) response = app.call({}) assert_equal @response[0,2], response[0,2] assert response.last.kind_of?(Raindrops::Middleware::Proxy) assert response.last.object_id != app.object_id tmp = [] response.last.each { |y| tmp << y } assert tmp.empty? end def test_alt_stats stats = Raindrops::Middleware::Stats.new app = lambda { |env| if (stats.writing == 0 && stats.calling == 1) @app.call(env) else [ 500, @resp_headers, [] ] end } app = Raindrops::Middleware.new(app, :stats => stats) response = app.call({}) assert_equal 0, stats.calling assert_equal 1, stats.writing assert_equal 200, response[0] assert response.last.kind_of?(Raindrops::Middleware::Proxy) tmp = [] response.last.each do |y| assert_equal 1, stats.writing tmp << y end assert tmp.empty? end def test_default_endpoint app = Raindrops::Middleware.new(@app) response = app.call("PATH_INFO" => "/_raindrops") expect = [ 200, { "Content-Type" => "text/plain", "Content-Length" => "22" }, [ "calling: 0\nwriting: 0\n" ] ] assert_equal expect, response end def test_alt_endpoint app = Raindrops::Middleware.new(@app, :path => "/foo") response = app.call("PATH_INFO" => "/foo") expect = [ 200, { "Content-Type" => "text/plain", "Content-Length" => "22" }, [ "calling: 0\nwriting: 0\n" ] ] assert_equal expect, response end def test_concurrent rda, wra = IO.pipe rdb, wrb = IO.pipe app = lambda do |env| wrb.close wra.syswrite('.') wra.close # wait until parent has run app.call for stats endpoint rdb.read @app.call(env) end app = Raindrops::Middleware.new(app) pid = fork { app.call({}) } rdb.close # wait til child is running in app.call assert_equal '.', rda.sysread(1) rda.close response = app.call("PATH_INFO" => "/_raindrops") expect = [ 200, { "Content-Type" => "text/plain", "Content-Length" => "22" }, [ "calling: 1\nwriting: 0\n" ] ] assert_equal expect, response wrb.close # unblock child process assert Process.waitpid2(pid).last.success? # we didn't call close the body in the forked child, so it'll always be # marked as writing, a real server would close the body response = app.call("PATH_INFO" => "/_raindrops") expect = [ 200, { "Content-Type" => "text/plain", "Content-Length" => "22" }, [ "calling: 0\nwriting: 1\n" ] ] assert_equal expect, response end def test_middleware_proxy_to_path_missing app = Raindrops::Middleware.new(@app) response = app.call({}) body = response[2] assert_kind_of Raindrops::Middleware::Proxy, body assert ! body.respond_to?(:to_path) assert body.respond_to?(:close) orig_body = @response[2] def orig_body.to_path; "/dev/null"; end assert body.respond_to?(:to_path) assert_equal "/dev/null", body.to_path def orig_body.body; "this is a body"; end assert body.respond_to?(:body) assert_equal "this is a body", body.body end end raindrops-0.12.0/test/test_aggregate_pmq.rb0000644000004100000410000000316112211676722021000 0ustar www-datawww-datarequire "test/unit" require "raindrops" pmq = begin Raindrops::Aggregate::PMQ rescue LoadError => e warn "W: #{e} skipping test" false end if RUBY_VERSION.to_f < 1.9 pmq = false warn "W: skipping #{__FILE__}, only Ruby 1.9 supported for now" end Thread.abort_on_exception = true class TestAggregatePMQ < Test::Unit::TestCase def setup @queue = "/test.#{rand}" end def teardown POSIX_MQ.unlink @queue end def test_run pmq = Raindrops::Aggregate::PMQ.new :queue => @queue thr = Thread.new { pmq.master_loop } agg = Aggregate.new (1..10).each { |i| pmq << i; agg << i } pmq.stop_master_loop assert thr.join assert_equal agg.count, pmq.count assert_equal agg.mean, pmq.mean assert_equal agg.std_dev, pmq.std_dev assert_equal agg.min, pmq.min assert_equal agg.max, pmq.max assert_equal agg.to_s, pmq.to_s end def test_multi_process nr_workers = 4 nr = 100 pmq = Raindrops::Aggregate::PMQ.new :queue => @queue pid = fork { pmq.master_loop } workers = (1..nr_workers).map { fork { (1..nr).each { |i| pmq << i } pmq.flush } } workers.each { |wpid| assert Process.waitpid2(wpid).last.success? } pmq.stop_master_loop assert Process.waitpid2(pid).last.success? assert_equal 400, pmq.count agg = Aggregate.new (1..nr_workers).map { (1..nr).each { |i| agg << i } } assert_equal agg.to_s, pmq.to_s assert_equal agg.mean, pmq.mean assert_equal agg.std_dev, pmq.std_dev assert_equal agg.min, pmq.min assert_equal agg.max, pmq.max assert_equal agg.to_s, pmq.to_s end end if pmq raindrops-0.12.0/test/test_inet_diag_socket.rb0000644000004100000410000000071312211676722021470 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'raindrops' require 'fcntl' $stderr.sync = $stdout.sync = true class TestInetDiagSocket < Test::Unit::TestCase def test_new sock = Raindrops::InetDiagSocket.new assert_kind_of Socket, sock assert_kind_of Fixnum, sock.fileno flags = sock.fcntl(Fcntl::F_GETFD) assert_equal Fcntl::FD_CLOEXEC, flags & Fcntl::FD_CLOEXEC assert_nil sock.close end end if RUBY_PLATFORM =~ /linux/ raindrops-0.12.0/test/test_raindrops_gc.rb0000644000004100000410000000167612211676722020660 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'raindrops' class TestRaindropsGc < Test::Unit::TestCase # we may need to create more garbage as GC may be less aggressive # about expiring things. This is completely unrealistic code, # though... def test_gc assert_nothing_raised do 1000000.times { |i| Raindrops.new(24); [] } end end def test_gc_postfork tmp = Raindrops.new 2 pid = fork do 1000000.times do tmp = Raindrops.new 2 tmp.to_ary end end _, status = Process.waitpid2(pid) assert status.success? assert_equal [ 0, 0 ], tmp.to_ary tmp.incr 1 assert_equal [ 0, 1 ], tmp.to_ary pid = fork do tmp.incr 1 exit([ 0, 2 ] == tmp.to_ary) end _, status = Process.waitpid2(pid) assert status.success? assert_equal [ 0, 2 ], tmp.to_ary end end if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" && ENV["STRESS"].to_i != 0 raindrops-0.12.0/test/test_linux_all_tcp_listen_stats.rb0000644000004100000410000000256212211676722023632 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'socket' require 'raindrops' require 'pp' $stderr.sync = $stdout.sync = true class TestLinuxAllTcpListenStats < Test::Unit::TestCase include Raindrops::Linux TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' def test_print_all puts "EVERYTHING" pp Raindrops::Linux.tcp_listener_stats puts("-" * 72) end if $stdout.tty? def setup @socks = [] end def teardown @socks.each { |io| io.closed? or io.close } end def new_server s = TCPServer.new TEST_ADDR, 0 @socks << s [ s, s.addr[1] ] end def new_client(port) s = TCPSocket.new("127.0.0.1", port) @socks << s s end def new_accept(srv) c = srv.accept @socks << c c end def test_all_ports srv, port = new_server addr = "#{TEST_ADDR}:#{port}" all = Raindrops::Linux.tcp_listener_stats assert_equal [0,0], all[addr].to_a new_client(port) all = Raindrops::Linux.tcp_listener_stats assert_equal [0,1], all[addr].to_a new_client(port) all = Raindrops::Linux.tcp_listener_stats assert_equal [0,2], all[addr].to_a new_accept(srv) all = Raindrops::Linux.tcp_listener_stats assert_equal [1,1], all[addr].to_a new_accept(srv) all = Raindrops::Linux.tcp_listener_stats assert_equal [2,0], all[addr].to_a end end if RUBY_PLATFORM =~ /linux/ raindrops-0.12.0/test/test_linux_ipv6.rb0000644000004100000410000001113612211676722020301 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'tempfile' require 'raindrops' require 'socket' require 'pp' require "./test/ipv6_enabled" $stderr.sync = $stdout.sync = true class TestLinuxIPv6 < Test::Unit::TestCase include Raindrops::Linux TEST_ADDR = ENV["TEST_HOST6"] || "::1" def test_tcp s = TCPServer.new(TEST_ADDR, 0) port = s.addr[1] addr = "[#{TEST_ADDR}]:#{port}" addrs = [ addr ] stats = tcp_listener_stats(addrs) assert_equal 1, stats.size assert_equal 0, stats[addr].queued assert_equal 0, stats[addr].active @to_close << TCPSocket.new(TEST_ADDR, port) stats = tcp_listener_stats(addrs) assert_equal 1, stats.size assert_equal 1, stats[addr].queued assert_equal 0, stats[addr].active @to_close << s.accept stats = tcp_listener_stats(addrs) assert_equal 1, stats.size assert_equal 0, stats[addr].queued assert_equal 1, stats[addr].active end def test_tcp_multi s1 = TCPServer.new(TEST_ADDR, 0) s2 = TCPServer.new(TEST_ADDR, 0) port1, port2 = s1.addr[1], s2.addr[1] addr1, addr2 = "[#{TEST_ADDR}]:#{port1}", "[#{TEST_ADDR}]:#{port2}" addrs = [ addr1, addr2 ] stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 0, stats[addr1].active assert_equal 0, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << TCPSocket.new(TEST_ADDR, port1) stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 1, stats[addr1].queued assert_equal 0, stats[addr1].active assert_equal 0, stats[addr2].queued assert_equal 0, stats[addr2].active sc1 = s1.accept stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 0, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << TCPSocket.new(TEST_ADDR, port2) stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 1, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << TCPSocket.new(TEST_ADDR, port2) stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 2, stats[addr2].queued assert_equal 0, stats[addr2].active @to_close << s2.accept stats = tcp_listener_stats(addrs) assert_equal 2, stats.size assert_equal 0, stats[addr1].queued assert_equal 1, stats[addr1].active assert_equal 1, stats[addr2].queued assert_equal 1, stats[addr2].active sc1.close stats = tcp_listener_stats(addrs) assert_equal 0, stats[addr1].queued assert_equal 0, stats[addr1].active assert_equal 1, stats[addr2].queued assert_equal 1, stats[addr2].active end def test_invalid_addresses assert_raises(ArgumentError) { tcp_listener_stats(%w([1:::5)) } assert_raises(ArgumentError) { tcp_listener_stats(%w([1:::]5)) } end # tries to overflow buffers def test_tcp_stress_test nr_proc = 32 nr_sock = 500 s = TCPServer.new(TEST_ADDR, 0) port = s.addr[1] addr = "[#{TEST_ADDR}]:#{port}" addrs = [ addr ] rda, wra = IO.pipe rdb, wrb = IO.pipe nr_proc.times do fork do rda.close wrb.close @to_close.concat((1..nr_sock).map { s.accept }) wra.syswrite('.') wra.close rdb.sysread(1) # wait for parent to nuke us end end nr_proc.times do fork do rda.close wrb.close @to_close.concat((1..nr_sock).map { TCPSocket.new(TEST_ADDR, port) }) wra.syswrite('.') wra.close rdb.sysread(1) # wait for parent to nuke us end end assert_equal('.' * (nr_proc * 2), rda.read(nr_proc * 2)) rda.close stats = tcp_listener_stats(addrs) expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 0] } assert_equal expect, stats @to_close << TCPSocket.new(TEST_ADDR, port) stats = tcp_listener_stats(addrs) expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 1] } assert_equal expect, stats if ENV["BENCHMARK"].to_i != 0 require 'benchmark' puts(Benchmark.measure{1000.times { tcp_listener_stats(addrs) }}) end wrb.syswrite('.' * (nr_proc * 2)) # broadcast a wakeup statuses = Process.waitall statuses.each { |(_,status)| assert status.success?, status.inspect } end if ENV["STRESS"].to_i != 0 end if RUBY_PLATFORM =~ /linux/ && ipv6_enabled? raindrops-0.12.0/test/test_linux_middleware.rb0000644000004100000410000000321212211676722021526 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'tempfile' require 'raindrops' require 'socket' $stderr.sync = $stdout.sync = true class TestLinuxMiddleware < Test::Unit::TestCase def setup @resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' } @response = [ 200, @resp_headers, [] ] @app = lambda { |env| @response } @to_close = [] end def teardown @to_close.each { |io| io.close unless io.closed? } end def test_unix_listener tmp = Tempfile.new("") File.unlink(tmp.path) @to_close << UNIXServer.new(tmp.path) app = Raindrops::Middleware.new(@app, :listeners => [tmp.path]) linux_extra = "#{tmp.path} active: 0\n#{tmp.path} queued: 0\n" response = app.call("PATH_INFO" => "/_raindrops") expect = [ 200, { "Content-Type" => "text/plain", "Content-Length" => (22 + linux_extra.size).to_s }, [ "calling: 0\nwriting: 0\n#{linux_extra}" \ ] ] assert_equal expect, response end def test_unix_listener_queued tmp = Tempfile.new("") File.unlink(tmp.path) @to_close << UNIXServer.new(tmp.path) @to_close << UNIXSocket.new(tmp.path) app = Raindrops::Middleware.new(@app, :listeners => [tmp.path]) linux_extra = "#{tmp.path} active: 0\n#{tmp.path} queued: 1\n" response = app.call("PATH_INFO" => "/_raindrops") expect = [ 200, { "Content-Type" => "text/plain", "Content-Length" => (22 + linux_extra.size).to_s }, [ "calling: 0\nwriting: 0\n#{linux_extra}" \ ] ] assert_equal expect, response end end if RUBY_PLATFORM =~ /linux/ raindrops-0.12.0/test/test_struct.rb0000644000004100000410000000214112211676722017516 0ustar www-datawww-datarequire 'test/unit' require 'raindrops' class TestRaindrops < Test::Unit::TestCase def test_struct_new @rw = Raindrops::Struct.new(:r, :w) assert @rw.kind_of?(Class) end TMP = Raindrops::Struct.new(:r, :w) def test_init_basic tmp = TMP.new assert_equal 0, tmp.r assert_equal 1, tmp.incr_r assert_equal 1, tmp.r assert_equal({ :r => 1, :w => 0 }, tmp.to_hash) assert_equal 1, tmp[0] assert_equal 0, tmp[1] assert_equal [ :r, :w ], TMP::MEMBERS end def test_init tmp = TMP.new(5, 6) assert_equal({ :r => 5, :w => 6 }, tmp.to_hash) end def test_dup a = TMP.new(5, 6) b = a.dup assert_equal({ :r => 5, :w => 6 }, b.to_hash) assert_nothing_raised { 4.times { b.decr_r } } assert_equal({ :r => 1, :w => 6 }, b.to_hash) assert_equal({ :r => 5, :w => 6 }, a.to_hash) end class Foo < Raindrops::Struct.new(:a, :b, :c, :d) def to_ary @raindrops.to_ary end def hello "world" end end def test_subclass assert_equal [0, 0, 0, 0], Foo.new.to_ary assert_equal "world", Foo.new.hello end end raindrops-0.12.0/test/test_middleware_unicorn.rb0000644000004100000410000000205712211676722022052 0ustar www-datawww-data# -*- encoding: binary -*- require "./test/rack_unicorn" $stderr.sync = $stdout.sync = true class TestMiddlewareUnicorn < Test::Unit::TestCase def setup @host = ENV["UNICORN_TEST_ADDR"] || "127.0.0.1" @sock = TCPServer.new @host, 0 @port = @sock.addr[1] ENV["UNICORN_FD"] = @sock.fileno.to_s @host_with_port = "#@host:#@port" @opts = { :listeners => [ @host_with_port ] } @addr_regexp = Regexp.escape @host_with_port end def test_auto_listener @app = Rack::Builder.new do use Raindrops::Middleware run Rack::Lobster.new end @srv = fork { Unicorn::HttpServer.new(@app, @opts).start.join } s = TCPSocket.new @host, @port s.write "GET /_raindrops HTTP/1.0\r\n\r\n" resp = s.read _, body = resp.split(/\r\n\r\n/, 2) assert_match %r{^#@addr_regexp active: 1$}, body assert_match %r{^#@addr_regexp queued: 0$}, body end def teardown Process.kill :QUIT, @srv _, status = Process.waitpid2 @srv assert status.success? end end if defined?(Unicorn) && RUBY_PLATFORM =~ /linux/ raindrops-0.12.0/test/ipv6_enabled.rb0000644000004100000410000000033312211676722017472 0ustar www-datawww-datadef ipv6_enabled? tmp = TCPServer.new(ENV["TEST_HOST6"] || '::1', 0) tmp.close true rescue => e warn "skipping IPv6 tests, host does not seem to be IPv6 enabled:" warn " #{e.class}: #{e}" false end raindrops-0.12.0/test/test_last_data_recv_unicorn.rb0000644000004100000410000000373712211676722022716 0ustar www-datawww-data# -*- encoding: binary -*- require "./test/rack_unicorn" require "tempfile" require "net/http" $stderr.sync = $stdout.sync = true pmq = begin Raindrops::Aggregate::PMQ rescue LoadError => e warn "W: #{e} skipping test" false end if RUBY_VERSION.to_f < 1.9 pmq = false warn "W: skipping test=#{__FILE__}, only Ruby 1.9 supported for now" end class TestLastDataRecvUnicorn < Test::Unit::TestCase def setup @queue = "/test.#{rand}" @host = ENV["UNICORN_TEST_ADDR"] || "127.0.0.1" @sock = TCPServer.new @host, 0 @port = @sock.addr[1] ENV["UNICORN_FD"] = @sock.fileno.to_s @host_with_port = "#@host:#@port" @cfg = Tempfile.new 'unicorn_config_file' @cfg.puts "require 'raindrops'" @cfg.puts "preload_app true" ENV['RAINDROPS_MQUEUE'] = @queue # @cfg.puts "worker_processes 4" @opts = { :listeners => [ @host_with_port ], :config_file => @cfg.path } end def test_auto_listener @srv = fork { Thread.abort_on_exception = true app = %q!Rack::Builder.new do map("/ldr") { run Raindrops::LastDataRecv.new } map("/") { run Rack::Lobster.new } end.to_app! def app.arity; 0; end def app.call; eval self; end Unicorn::HttpServer.new(app, @opts).start.join } 400.times { assert_kind_of Net::HTTPSuccess, get("/") } resp = get("/ldr") # # p(resp.methods - Object.methods) # resp.each_header { |k,v| p [k, "=" , v] } assert resp.header["x-count"] assert resp.header["x-min"] assert resp.header["x-max"] assert resp.header["x-mean"] assert resp.header["x-std-dev"] assert resp.header["x-outliers-low"] assert resp.header["x-outliers-high"] assert resp.body.size > 0 end def get(path) Net::HTTP.start(@host, @port) { |http| http.get path } end def teardown Process.kill :QUIT, @srv _, status = Process.waitpid2 @srv assert status.success? POSIX_MQ.unlink @queue end end if defined?(Unicorn) && RUBY_PLATFORM =~ /linux/ && pmq raindrops-0.12.0/test/test_linux_all_tcp_listen_stats_leak.rb0000644000004100000410000000176412211676722024631 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'raindrops' require 'socket' require 'benchmark' $stderr.sync = $stdout.sync = true class TestLinuxAllTcpListenStatsLeak < Test::Unit::TestCase TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' def rss_kb File.readlines("/proc/#$$/status").grep(/VmRSS:/)[0].split(/\s+/)[1].to_i end def test_leak s = TCPServer.new(TEST_ADDR, 0) start_kb = rss_kb p [ :start_kb, start_kb ] assert_nothing_raised do p(Benchmark.measure { 1000.times { Raindrops::Linux.all_tcp_listener_stats } }) end cur_kb = rss_kb p [ :cur_kb, cur_kb ] now = Time.now.to_i fin = now + 60 assert_nothing_raised do 1000000000.times { |i| if (i % 1024) == 0 now = Time.now.to_i break if now > fin end Raindrops::Linux.all_tcp_listener_stats } end cur_kb = rss_kb p [ :cur_kb, cur_kb ] ensure s.close end end if ENV["STRESS"].to_i != 0 raindrops-0.12.0/test/test_raindrops.rb0000644000004100000410000000765112211676722020206 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'raindrops' class TestRaindrops < Test::Unit::TestCase def test_raindrop_counter_max assert_kind_of Integer, Raindrops::MAX assert Raindrops::MAX > 0 printf "Raindrops::MAX = 0x%x\n", Raindrops::MAX end def test_raindrop_size assert_kind_of Integer, Raindrops::SIZE assert Raindrops::SIZE > 0 puts "Raindrops::SIZE = #{Raindrops::SIZE}" end def test_page_size assert_kind_of Integer, Raindrops::PAGE_SIZE assert Raindrops::PAGE_SIZE > Raindrops::SIZE end def test_size_and_capa rd = Raindrops.new(4) assert_equal 4, rd.size assert rd.capa >= rd.size end def test_ary rd = Raindrops.new(4) assert_equal [0, 0, 0, 0] , rd.to_ary end def test_incr_no_args rd = Raindrops.new(4) assert_equal 1, rd.incr(0) assert_equal [1, 0, 0, 0], rd.to_ary end def test_incr_args rd = Raindrops.new(4) assert_equal 6, rd.incr(3, 6) assert_equal [0, 0, 0, 6], rd.to_ary end def test_decr_args rd = Raindrops.new(4) rd[3] = 6 assert_equal 5, rd.decr(3, 1) assert_equal [0, 0, 0, 5], rd.to_ary end def test_incr_shared rd = Raindrops.new(2) 5.times do pid = fork { rd.incr(1) } _, status = Process.waitpid2(pid) assert status.success? end assert_equal [0, 5], rd.to_ary end def test_incr_decr rd = Raindrops.new(1) fork { 1000000.times { rd.incr(0) } } 1000.times { rd.decr(0) } statuses = Process.waitall statuses.each { |pid, status| assert status.success? } assert_equal [999000], rd.to_ary end def test_bad_incr rd = Raindrops.new(1) assert_raises(ArgumentError) { rd.incr(-1) } assert_raises(ArgumentError) { rd.incr(2) } assert_raises(ArgumentError) { rd.incr(0xffffffff) } end def test_dup @rd = Raindrops.new(1) rd = @rd.dup assert_equal 1, @rd.incr(0) assert_equal 1, rd.incr(0) assert_equal 2, rd.incr(0) assert_equal 2, rd[0] assert_equal 1, @rd[0] end def test_clone @rd = Raindrops.new(1) rd = @rd.clone assert_equal 1, @rd.incr(0) assert_equal 1, rd.incr(0) assert_equal 2, rd.incr(0) assert_equal 2, rd[0] assert_equal 1, @rd[0] end def test_big expect = (1..256).map { 0 } rd = Raindrops.new(256) assert_equal expect, rd.to_ary assert_nothing_raised { rd[255] = 5 } assert_equal 5, rd[255] assert_nothing_raised { rd[2] = 2 } expect[255] = 5 expect[2] = 2 assert_equal expect, rd.to_ary end def test_resize rd = Raindrops.new(4) assert_equal 4, rd.size assert_equal rd.capa, rd.size = rd.capa assert_equal rd.capa, rd.to_ary.size assert_equal 0, rd[rd.capa - 1] assert_equal 1, rd.incr(rd.capa - 1) assert_raises(ArgumentError) { rd[rd.capa] } end def test_resize_mremap rd = Raindrops.new(4) assert_equal 4, rd.size old_capa = rd.capa rd.size = rd.capa + 1 assert_equal old_capa * 2, rd.capa # mremap() is currently broken with MAP_SHARED # https://bugzilla.kernel.org/show_bug.cgi?id=8691 assert_equal 0, rd[old_capa] assert_equal rd.capa, rd.to_ary.size assert_equal 0, rd[rd.capa - 1] assert_equal 1, rd.incr(rd.capa - 1) assert_raises(ArgumentError) { rd[rd.capa] } rescue RangeError end # if RUBY_PLATFORM =~ /linux/ def test_evaporate rd = Raindrops.new 1 assert_nil rd.evaporate! assert_raises(StandardError) { rd.evaporate! } end def test_evaporate_with_fork tmp = Raindrops.new 2 pid = fork do tmp.incr 0 exit(tmp.evaporate! == nil) end _, status = Process.waitpid2(pid) assert status.success? assert_equal [ 1, 0 ], tmp.to_ary tmp.incr 1 assert_equal [ 1, 1 ], tmp.to_ary pid = fork do tmp.incr 1 exit([ 1, 2 ] == tmp.to_ary) end _, status = Process.waitpid2(pid) assert status.success? assert_equal [ 1, 2 ], tmp.to_ary end end raindrops-0.12.0/test/test_watcher.rb0000644000004100000410000001270412211676722017635 0ustar www-datawww-data# -*- encoding: binary -*- require "test/unit" require "rack" require "raindrops" class TestWatcher < Test::Unit::TestCase TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' def check_headers(headers) %w(X-Count X-Std-Dev X-Min X-Max X-Mean X-Outliers-Low X-Outliers-Low X-Last-Reset).each { |x| assert_kind_of String, headers[x], "#{x} missing" } end def teardown @app.shutdown @ios.each { |io| io.close unless io.closed? } end def setup @ios = [] @srv = TCPServer.new TEST_ADDR, 0 @ios << @srv @port = @srv.addr[1] @client = TCPSocket.new TEST_ADDR, @port @addr = "#{TEST_ADDR}:#{@port}" @ios << @client @app = Raindrops::Watcher.new :delay => 0.001 @req = Rack::MockRequest.new @app end def test_index resp = @req.get "/" assert_equal 200, resp.status.to_i t = Time.parse resp.headers["Last-Modified"] assert_in_delta Time.now.to_f, t.to_f, 2.0 end def test_active_txt resp = @req.get "/active/#@addr.txt" assert_equal 200, resp.status.to_i assert_equal "text/plain", resp.headers["Content-Type"] check_headers(resp.headers) end def test_invalid assert_nothing_raised do @req.get("/active/666.666.666.666%3A666.txt") @req.get("/queued/666.666.666.666%3A666.txt") @req.get("/active/666.666.666.666%3A666.html") @req.get("/queued/666.666.666.666%3A666.html") end addr = @app.instance_eval do @peak_active.keys + @peak_queued.keys + @resets.keys + @active.keys + @queued.keys end assert addr.grep(/666\.666\.666\.666/).empty?, addr.inspect end def test_active_html resp = @req.get "/active/#@addr.html" assert_equal 200, resp.status.to_i assert_equal "text/html", resp.headers["Content-Type"] check_headers(resp.headers) end def test_queued_txt resp = @req.get "/queued/#@addr.txt" assert_equal 200, resp.status.to_i assert_equal "text/plain", resp.headers["Content-Type"] check_headers(resp.headers) end def test_queued_html resp = @req.get "/queued/#@addr.html" assert_equal 200, resp.status.to_i assert_equal "text/html", resp.headers["Content-Type"] check_headers(resp.headers) end def test_reset resp = @req.post "/reset/#@addr" assert_equal 302, resp.status.to_i end def test_tail env = @req.class.env_for "/tail/#@addr.txt" status, headers, body = @app.call env assert_equal "text/plain", headers["Content-Type"] assert_equal 200, status.to_i tmp = [] body.each do |x| assert_kind_of String, x tmp << x break if tmp.size > 1 end end def test_tail_queued_min env = @req.class.env_for "/tail/#@addr.txt?queued_min=1" status, headers, body = @app.call env assert_equal "text/plain", headers["Content-Type"] assert_equal 200, status.to_i tmp = [] body.each do |x| tmp = TCPSocket.new TEST_ADDR, @port @ios << tmp assert_kind_of String, x assert_equal 1, x.strip.split(/\s+/).last.to_i break end end def test_x_current_header env = @req.class.env_for "/active/#@addr.txt" status, headers, body = @app.call(env) assert_equal "0", headers["X-Current"], headers.inspect env = @req.class.env_for "/queued/#@addr.txt" status, headers, body = @app.call(env) assert_equal "1", headers["X-Current"], headers.inspect @ios << @srv.accept sleep 0.1 env = @req.class.env_for "/queued/#@addr.txt" status, headers, body = @app.call(env) assert_equal "0", headers["X-Current"], headers.inspect env = @req.class.env_for "/active/#@addr.txt" status, headers, body = @app.call(env) assert_equal "1", headers["X-Current"], headers.inspect end def test_peaks env = @req.class.env_for "/active/#@addr.txt" status, headers, body = @app.call(env.dup) start = headers["X-First-Peak-At"] assert headers["X-First-Peak-At"], headers.inspect assert headers["X-Last-Peak-At"], headers.inspect assert_nothing_raised { Time.parse(headers["X-First-Peak-At"]) } assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) } before = headers["X-Last-Peak-At"] env = @req.class.env_for "/queued/#@addr.txt" status, headers, body = @app.call(env) assert_nothing_raised { Time.parse(headers["X-First-Peak-At"]) } assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) } assert_equal before, headers["X-Last-Peak-At"], "should not change" sleep 2 env = @req.class.env_for "/active/#@addr.txt" status, headers, body = @app.call(env.dup) assert_equal before, headers["X-Last-Peak-At"], headers.inspect @ios << @srv.accept begin @srv.accept_nonblock assert false, "we should not get here" rescue => e assert_kind_of Errno::EAGAIN, e end sleep 0.1 env = @req.class.env_for "/queued/#@addr.txt" status, headers, body = @app.call(env.dup) assert headers["X-Last-Peak-At"], headers.inspect assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) } assert before != headers["X-Last-Peak-At"] queued_before = headers["X-Last-Peak-At"] sleep 2 env = @req.class.env_for "/queued/#@addr.txt" status, headers, body = @app.call(env) assert_equal "0", headers["X-Current"] assert_nothing_raised { Time.parse(headers["X-Last-Peak-At"]) } assert_equal queued_before, headers["X-Last-Peak-At"], "should not change" assert_equal start, headers["X-First-Peak-At"] end end if RUBY_PLATFORM =~ /linux/ raindrops-0.12.0/.wrongdoc.yml0000644000004100000410000000031712211676722016255 0ustar www-datawww-data--- cgit_url: http://bogomips.org/raindrops.git git_url: git://bogomips.org/raindrops.git rdoc_url: http://raindrops.bogomips.org/ public_email: raindrops@librelist.org private_email: raindrops@bogomips.org raindrops-0.12.0/examples/0000755000004100000410000000000012211676722015447 5ustar www-datawww-dataraindrops-0.12.0/examples/watcher.ru0000644000004100000410000000021312211676722017450 0ustar www-datawww-data# Sample standalone Rack application, recommended use is with Zbatery # See zbatery.conf.rb require "raindrops" run Raindrops::Watcher.new raindrops-0.12.0/examples/linux-listener-stats.rb0000755000004100000410000000635512211676722022126 0ustar www-datawww-data#!/usr/bin/ruby # -*- encoding: binary -*- $stdout.sync = $stderr.sync = true # this is used to show or watch the number of active and queued # connections on any listener socket from the command line require 'raindrops' require 'optparse' require 'ipaddr' require 'time' begin require 'sleepy_penguin' rescue LoadError end usage = "Usage: #$0 [-d DELAY] [-t QUEUED_THRESHOLD] ADDR..." ARGV.size > 0 or abort usage delay = false all = false queued_thresh = -1 # "normal" exits when driven on the command-line trap(:INT) { exit 130 } trap(:PIPE) { exit 0 } OptionParser.new('', 24, ' ') do |opts| opts.banner = usage opts.on('-d', '--delay=DELAY', Float) { |n| delay = n } opts.on('-t', '--queued-threshold=INT', Integer) { |n| queued_thresh = n } opts.on('-a', '--all') { all = true } opts.parse! ARGV end begin require 'aggregate' rescue LoadError $stderr.puts "Aggregate missing, USR1 and USR2 handlers unavailable" end if delay if delay && defined?(SleepyPenguin::TimerFD) @tfd = SleepyPenguin::TimerFD.new @tfd.settime nil, delay, delay def delay_for(seconds) @tfd.expirations end else alias delay_for sleep end agg_active = agg_queued = nil if delay && defined?(Aggregate) agg_active = Aggregate.new agg_queued = Aggregate.new def dump_aggregate(label, agg) $stderr.write "--- #{label} ---\n" %w(count min max outliers_low outliers_high mean std_dev).each do |f| $stderr.write "#{f}=#{agg.__send__ f}\n" end $stderr.write "#{agg}\n\n" end trap(:USR1) do dump_aggregate "active", agg_active dump_aggregate "queued", agg_queued end trap(:USR2) do agg_active = Aggregate.new agg_queued = Aggregate.new end $stderr.puts "USR1(dump_aggregate) and USR2(reset) handlers ready for PID=#$$" end ARGV.each do |addr| addr =~ %r{\A(127\..+):(\d+)\z} or next host, port = $1, $2 hex_port = '%X' % port.to_i ip_addr = IPAddr.new(host) hex_host = ip_addr.hton.each_byte.inject('') { |s,o| s << '%02X' % o } socks = File.readlines('/proc/net/tcp') hex_addr = "#{hex_host}:#{hex_port}" if socks.grep(/^\s+\d+:\s+#{hex_addr}\s+/).empty? && ! socks.grep(/^\s+\d+:\s+00000000:#{hex_port}\s+/).empty? warn "W: #{host}:#{port} (#{hex_addr}) not found in /proc/net/tcp" warn "W: Did you mean 0.0.0.0:#{port}?" end end len = "address".size now = nil tcp, unix = [], [] ARGV.each do |addr| bs = addr.respond_to?(:bytesize) ? addr.bytesize : addr.size len = bs if bs > len (addr =~ %r{\A/} ? unix : tcp) << addr end combined = {} tcp_args = unix_args = nil unless tcp.empty? && unix.empty? tcp_args = tcp unix_args = unix end sock = Raindrops::InetDiagSocket.new if tcp len = 35 if len > 35 fmt = "%20s % #{len}s % 10u % 10u\n" $stderr.printf fmt.tr('u','s'), *%w(timestamp address active queued) begin if now combined.clear now = nil end combined.merge! Raindrops::Linux.tcp_listener_stats(tcp_args, sock) combined.merge! Raindrops::Linux.unix_listener_stats(unix_args) combined.each do |addr,stats| active, queued = stats.active, stats.queued if agg_active agg_active << active agg_queued << queued end next if queued < queued_thresh printf fmt, now ||= Time.now.utc.iso8601, addr, active, queued end end while delay && delay_for(delay) raindrops-0.12.0/examples/zbatery.conf.rb0000644000004100000410000000073312211676722020403 0ustar www-datawww-data# Used for running Raindrops::Watcher, which requires a multi-threaded # Rack server capable of streaming a response. Threads must be used, # so Zbatery is recommended: http://zbatery.bogomip.org/ Rainbows! do use :ThreadSpawn end log_dir = "/var/log/zbatery" if File.writable?(log_dir) && File.directory?(log_dir) stderr_path "#{log_dir}/raindrops-demo.stderr.log" stdout_path "#{log_dir}/raindrops-demo.stdout.log" listen "/tmp/.r" pid "/tmp/.raindrops.pid" end raindrops-0.12.0/examples/watcher_demo.ru0000644000004100000410000000053112211676722020457 0ustar www-datawww-data# This is a snippet of the config that powers # http://raindrops-demo.bogomips.org/ # This may be used with the packaged zbatery.conf.rb # # zbatery -c zbatery.conf.ru watcher_demo.ru -E none require "raindrops" use Raindrops::Middleware listeners = %w( 0.0.0.0:9418 0.0.0.0:80 /tmp/.r ) run Raindrops::Watcher.new :listeners => listeners raindrops-0.12.0/examples/middleware.ru0000644000004100000410000000023512211676722020134 0ustar www-datawww-data# sample stand-alone rackup application for Raindrops::Middleware require 'rack/lobster' require 'raindrops' use Raindrops::Middleware run Rack::Lobster.new raindrops-0.12.0/LICENSE0000644000004100000410000000172412211676722014642 0ustar www-datawww-dataraindrops is copyrighted Free Software by all contributors, see logs in revision control for names and email addresses of all of them. You can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation, version {2.1}[http://www.gnu.org/licenses/lgpl-2.1.txt] or or {3}[http://www.gnu.org/licenses/lgpl-3.0.txt] (see link:COPYING). The raindrops project leader (Eric Wong) reserves the right to relicense raindrops under future versions of the LGPL. raindrops is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the raindrops; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 raindrops-0.12.0/Rakefile0000644000004100000410000000346012211676722015301 0ustar www-datawww-datadesc "read news article from STDIN and post to rubyforge" task :publish_news do require 'rubyforge' spec = Gem::Specification.load('raindrops.gemspec') tmp = Tempfile.new('rf-news') _, subject, body = `git cat-file tag v#{spec.version}`.split(/\n\n/, 3) tmp.puts subject tmp.puts tmp.puts spec.description.strip tmp.puts "" tmp.puts "* #{spec.homepage}" tmp.puts "* #{spec.email}" tmp.puts "* #{git_url}" tmp.print "\nChanges:\n\n" tmp.puts body tmp.flush system(ENV["VISUAL"], tmp.path) or abort "#{ENV["VISUAL"]} failed: #$?" msg = File.readlines(tmp.path) subject = msg.shift blank = msg.shift blank == "\n" or abort "no newline after subject!" subject.strip! body = msg.join("").strip! rf = RubyForge.new.configure rf.login rf.post_news('rainbows', subject, body) end desc "post to RAA" task :raa_update do require 'net/http' require 'net/netrc' rc = Net::Netrc.locate('raindrops-raa') or abort "~/.netrc not found" password = rc.password s = Gem::Specification.load('raindrops.gemspec') desc = [ s.description.strip ] desc << "" desc << "* #{s.email}" desc << "* #{git_url}" desc << "* #{cgit_url}" desc = desc.join("\n") uri = URI.parse('http://raa.ruby-lang.org/regist.rhtml') form = { :name => s.name, :short_description => s.summary, :version => s.version.to_s, :status => 'experimental', :owner => s.authors.first, :email => s.email, :category_major => 'Library', :category_minor => 'Rack', :url => s.homepage, :download => 'http://rubyforge.org/frs/?group_id=8977', :license => 'LGPL', # LGPLv3, actually, but RAA is ancient... :description_style => 'Plain', :description => desc, :pass => password, :submit => 'Update', } res = Net::HTTP.post_form(uri, form) p res puts res.body end raindrops-0.12.0/ChangeLog0000600000004100000410000017361412211676722015407 0ustar www-datawww-dataChangeLog from http://bogomips.org/raindrops.git commit 05cde4657605caa2db2cb9bd3da611e4bc9d79f6 Author: Eric Wong Date: Mon Sep 2 10:30:39 2013 +0000 raindrops 0.12.0 - compatibility fixes This release fixes builds on systems where compilers target i386 (and not later x86 systems). There are also minor improvements for Ruby 2.1.0dev and Rubinius. Eric Wong (5): doc: add email address to generated doc/site README: update regarding Ruby support status extconf: try harder for gcc atomics in i386-configured systems linux_inet_diag: improve compatibility with newer GCs test_watcher: fix for Ruby trunk r40195 and later commit 8e5a4714fd90f87e2bc56c7b3f2525bb9055a227 Author: Eric Wong Date: Mon Sep 2 09:53:51 2013 +0000 test_watcher: fix for Ruby trunk r40195 and later assert_raises doesn't capture exceptions the same way rescue works. commit 07efdceff18dc481424adb8b509244b6c6a822d5 Author: Eric Wong Date: Sun Jun 16 20:54:55 2013 +0000 linux_inet_diag: improve compatibility with newer GCs RARRAY_PTR is expensive with GCs in Ruby 2.1.0dev and Rubinius, so use rb_ary_entry for non-performance critical paths. Eventually, RARRAY_AREF/RARRAY_ASET may be common, but for now, using rb_ary_entry should require the least cognitive overhead for a developer. commit fc690079c889ed5a97359515d75fe9260dd9ea91 Author: Eric Wong Date: Wed Aug 28 10:10:29 2013 +0000 extconf: try harder for gcc atomics in i386-configured systems __sync_lock_test_and_set might not be the correct check after all, but __sync_val_compare_and_swap probably is. At least this is what Ruby 2.0.0 uses to check and attempt -march=i486 on. commit fdda1473240485b7e996e425ed1a46ce0e50174e Author: Eric Wong Date: Sat Apr 20 23:16:38 2013 +0000 README: update regarding Ruby support status Now that Ruby 2.0.0 is out... commit a59312421d68ee75532692423b3dc3c533f7a765 Author: Eric Wong Date: Sat Apr 20 23:12:38 2013 +0000 doc: add email address to generated doc/site commit e4eaf78dc922db10af75861cad036d042fd9c49e Author: Eric Wong Date: Sat Apr 20 22:53:06 2013 +0000 raindrops 0.11.0 - minor fixes improvements Eric Wong (7): raindrops: favor configured processor count over online count watcher: set Content-Type via assignment Linux::TCP_Info: implement #get! instance method linux_inet_diag: avoid unnecessary sockaddr initialization .gitignore: add .rbx switch back to gemspec development dependencies linux_inet_diag: better align listener_stats struct Lawrence Pit (1): Watcher: Use relative paths in HTML links commit 241cfdd395c7d045f0048a1a0410ae28e6555c3b Author: Eric Wong Date: Sat Apr 20 22:59:32 2013 +0000 linux_inet_diag: better align listener_stats struct Using an extra 4 bytes for the listener_stats should not significantly increase space usage, and it has the side benefit of making our code slightly smaller. $ ~/linux/scripts/bloat-o-meter before.so after.so add/remove: 0/0 grow/shrink: 1/2 up/down: 14/-32 (-18) function old new delta tcp_stats 392 406 +14 st_to_hash 195 187 -8 diag 763 739 -24 commit 6601f1ab43e90391932ad133335c814980bf7428 Author: Eric Wong Date: Sat Apr 13 02:23:22 2013 +0000 switch back to gemspec development dependencies This is more standardized than bundler for gem development. commit e5884e7946a6b5bfb5cb677c3d435a4a8f881573 Author: Eric Wong Date: Sat Apr 13 00:31:41 2013 +0000 .gitignore: add .rbx Rubinius drops a .rbx directory on us nowadays commit 602df28c6616a0724317e29bfc428ba06d1886e0 Author: Eric Wong Date: Thu Apr 11 23:39:08 2013 +0000 linux_inet_diag: avoid unnecessary sockaddr initialization This initialization was unnecessary and avoids the following warning with -Wmissing-braces on gcc 4.7.2-5 on Debian testing: linux_inet_diag.c: In function ‘stats_for’: linux_inet_diag.c:192:8: warning: missing braces around initializer [-Wmissing-braces] linux_inet_diag.c:192:8: warning: (near initialization for ‘sa.ss’) [-Wmissing-braces] commit 65a248c523ddbfa41ae1e1d6da0a7f71383428e2 Author: Eric Wong Date: Thu Apr 11 23:13:13 2013 +0000 Linux::TCP_Info: implement #get! instance method This allows reusing existing Linux::TCP_Info objects to avoid generating garbage. commit c29fd663b45888aa8661c8599c88c339f4fbe8c1 Author: Eric Wong Date: Thu Apr 11 22:57:53 2013 +0000 watcher: set Content-Type via assignment Relying on String#replace to set Content-Type stopped working with rack commit 3623d04526b953a63bfb3e72de2d6920a042563f This fixes compatibility with the Rack 1.5.x series. commit d92a3a66adab040ca935c1b3aa0f5965b4cc1d67 Author: Eric Wong Date: Thu Apr 11 22:50:35 2013 +0000 raindrops: favor configured processor count over online count The runnable CPUs of a process may change over the lifetime of the process. So favor the count of configured processor count since that is more likely to be stable. We do not currently do not have special handling for hot-plugging/removal of CPUs on systems that may load raindrops in a single CPU state. commit a8be099aabc0661e2074e1bd4aa42fbb5139ae0d Author: Lawrence Pit Date: Fri Nov 30 11:37:08 2012 +1100 Watcher: Use relative paths in HTML links When I mount Raindrops::Watcher like so: map "/_raindrops" do run Raindrops::Watcher.new end Then in the HTML output links use an absolute path instead of relative to the path /_raindrops/ Cheers, Lawrence Signed-off-by: Eric Wong commit dd72d3f180483957dd5c5d7a9369e9ee72fe14fa Author: Eric Wong Date: Tue Jun 19 08:27:25 2012 +0000 raindrops 0.10.0 - minor feature updates Improvements to the Unix domain socket handling and small bugfixes throughout. Support for the "unix_diag" facility in Linux 3.3+ is planned but not yet implemented (patches to raindrops@librelist.org appreciated) Brian Corrigan (1): resolve symlinks to Unix domain sockets Eric Wong (6): unix_listener_stats follows and remembers symlinks middleware/proxy: favor __send__ for method dispatch unix: show zero-value stats for idle listeners test_watcher: fix incorrect request/date comparison watcher: sort index of listener listing watcher: do not require Rack::Head for HEAD response See "git log v0.9.0..v0.10.0" for full details commit cee9ca2bfd98b96cb8b956dd03fe03f313fee222 Author: Eric Wong Date: Mon Jun 18 13:50:06 2012 -0700 watcher: do not require Rack::Head for HEAD response Rack webservers are not guaranteed to include Rack::Head in the middleware stack. Watcher is a standalone app, so it cannot rely on a framework which automatically includes Rack::Head. commit 963a7a144a5231f07da118cfe4fef0fb210f40c6 Author: Eric Wong Date: Mon Jun 18 13:45:13 2012 -0700 watcher: sort index of listener listing For hosts with many listeners, it should be easier to read the index page if the results are shown in a consistent order. Requested privately via email to raindrops@bogomips.org commit e893686157a1abb9c6943d1c42c9cbdae1b76cd4 Author: Eric Wong Date: Mon Jun 18 13:18:28 2012 -0700 test_watcher: fix incorrect request/date comparison It makes no sense to compare peak times of different queues (active vs queued) against each other. commit 5c62376bc9a1b0d7c8e664784a1fb97747a7c392 Author: Eric Wong Date: Tue Jun 12 18:29:00 2012 -0700 unix: show zero-value stats for idle listeners When unix_listener_stats is called without arguments, it should still match the behavior of tcp_listener_stats and return ListenerStats object with zero values. This allows callers to iterate through the results to find the pathnames of all the Unix domain sockets in in listen mode. commit 506df98df1dae59281fbb3b3c2c6bea7549a2288 Author: Eric Wong Date: Thu Jun 7 14:36:35 2012 -0700 middleware/proxy: favor __send__ for method dispatch "send" is more likely to be overridden in subclasses whereas the Ruby runtime (at least 1.9.3) will warn loudly if any user code (re)defines the "__send__" method. For example, BasicSocket#send and UDPSocket#send in the Ruby stdlib are wrappers for the send(2)/sendto(2) system calls, and it's entirely possible an application could return a Socket-subclass as a Rack response body. commit bd7236fe23c4388d2fa42a4f836aca3c796dabab Author: Eric Wong Date: Tue Jun 5 17:49:43 2012 -0700 unix_listener_stats follows and remembers symlinks Teach unix_listener_stats to remember the symlink path it followed and have it point to the same object as the resolved (real) socket path. This allows the case where looking up stats by symlinks works if the symlink is given to unix_listener_stats: File.symlink("/real/path/of.sock", "/path/to/link.sock") stats = unix_listener_stats(["/path/to/link.sock"]) stats["/path/to/link.sock"] => # same as stats["/real/path/of.sock"] commit 15dea14dd4e1af97aef928430c3c8ef8c5d74451 Author: Brian Corrigan Date: Tue Jun 5 11:46:34 2012 -0400 resolve symlinks to Unix domain sockets Raindrops currently fails when provided a symlink to a socket. As this is a common practice for many deployment tools (Vlad, etc.) this patch adds support for finding the realpath prior to looking the socket up in /proc/net/unix [ew: commit message subject] [ew: fixed test to pass under 1.9.3 and 1.8.7: * Tempfile#unlink is unsafe to call if we want to reuse the path, use File.unlink(tmp.path) instead * The return value of File.symlink is zero (or it raises), so it's unusable. * File.symlink will not call #to_path under 1.8.7, so it's necessary to pass pathnames to it, not Tempfile objects. ] Signed-off-by: Eric Wong commit 3a7387205dd5791a443215ae6b365865e0eacfa3 Author: Eric Wong Date: Mon May 21 00:03:47 2012 +0000 raindrops 0.9.0 - minor middleware/proxy update Raindrops::Middleware::Proxy now forwards method_missing to the proxied body object. This allows compatibility with non-standard Rack extensions employed by some middlewares, applications, or servers. Thanks to Ben Somers for the feature! commit ea021e1fc14a8d7da23580767e6e6ac5db2aca9b Author: Eric Wong Date: Mon May 21 00:02:32 2012 +0000 middleware/proxy: fixup comment/whitespace Noted, but not fixed in the previous commit commit abc6dd47ede5b96ada1ff8f37dfba73cd5fd586a (Add method_missing to Raindrops::Middleware::Proxy) commit abc6dd47ede5b96ada1ff8f37dfba73cd5fd586a Author: Ben Somers Date: Thu May 17 18:46:58 2012 -0700 Add method_missing to Raindrops::Middleware::Proxy This enables it to behave more like a Rack BodyProxy would, delegating methods to its body object when it does not implement them itself. (Also includes a minor grammar fix to a comment.) [ew: minor comment/whitespace fix] Signed-off-by: Eric Wong commit 127ec6d6c733a717df79084861b0d7f36c327154 Author: Eric Wong Date: Sat May 12 05:44:12 2012 +0000 raindrops 0.8.1 - compatibility fixes This release fixes a build problem found under a current SmartOS. This release also runs successfully on FreeBSD 9.0 under both x86-64 and i386. There are also documentation updates from Aman Gupta and a test suite fix from Jeremy Evans for OpenBSD. raindrops fully supports unicorn on recent versions of FreeBSD, OpenBSD, SmartOS, and possibly other Free Software systems. Portability reports and fixes for Free Software systems is greatly appreciated at raindrops@librelist.org Non-Free systems will never be supported. raindrops requires the Linux 2.6.18 or later for full functionality (which unicorn does not require). Future releases will take advantage of the unix_diag functionality found in the Linux 3.3 (and later) kernels. commit ce0b50e0a6aed669a59ee31ab3667b4eb34529de Author: Eric Wong Date: Sat May 12 05:38:58 2012 +0000 avoid warning for rb_thread_io_blocking_region This is exported (visibly) under Ruby 1.9.3 but not in headers, so it was causing warnings. commit 71f80afdbcb45245a01ee2c278ebda692587e92a Author: Eric Wong Date: Thu May 10 14:49:39 2012 -0700 extconf: better check for GCC atomic builtins Attempting to test for CMPXCHG on x86 should allow this check to fail on i386 systems. We also won't need try_run as a result, enabling cross-compilation. The configure.in check in Ruby 1.9.3 does something similar and that's far more widely used than raindrops is. commit 1c18fd9c13f95fef6bcbdc0587d38886fa8e9064 Author: Aman Gupta Date: Fri Jan 6 23:48:41 2012 -0800 avoid calling tcp_listener_stats when it is not available Signed-off-by: Aman Gupta Acked-by: Eric Wong commit d166ef4dc5bd62d18c3abd4612d6b11a1e1f28c7 Author: Aman Gupta Date: Fri Jan 6 23:48:40 2012 -0800 fix ListenStats documentation error in Raindrops::Linux.unix_listener_stats Signed-off-by: Aman Gupta Acked-by: Eric Wong commit 8fff8ab721305c4df70904154c4b93a7af993749 Author: Eric Wong Date: Thu Nov 17 10:25:38 2011 +0000 examples/: updated to match current state of affairs Consolidating multiple Rack apps into fewer Rack apps on bogomips.org, might as well be transparent about it. commit 499e63e2666f88134f7d47f3bcfdfedd72396e70 Author: Jeremy Evans Date: Mon Nov 14 10:31:53 2011 -0800 Test suite fix for non-linux users Most other linux-specific test files have this, but test_linux_tcp_info.rb does not. With this patch, gmake test passes on OpenBSD. commit 68f5fdb76231aad5d9d2bcf33c33758394bb15a8 Author: Eric Wong Date: Fri Oct 14 14:59:52 2011 -0700 raindrops 0.8.0 - watcher updates There are various updates to the Raindrops::Watcher Rack app. Most notably, the timestamp where the a statistic first and last hit its peak value (active/queued connections) is captured. As usual, the latest Raindrops::Watcher is running at: http://raindrops-demo.bogomips.org/ commit 5d76462eb312df5343c1870fb68094459deca6ca Author: Eric Wong Date: Fri Oct 14 14:38:16 2011 -0700 1.8 compatibility workarounds Math.sqrt on 1.8.7 does not give NaN for certain errors. We'll also fix our Errno::EDOM retry loop to avoid resetting the "retried" flag. commit d5fc6f66bc17f27770b38126b6c4211fd938c5b5 Author: Eric Wong Date: Wed Oct 12 14:00:23 2011 -0700 avoid inadvertant object creation with invalid addresses Just in case somebody tries to scan all addresses, we won't run out of memory as easily. commit 360b8569cbb2b795019db278463245cce4270d09 Author: Eric Wong Date: Mon Oct 3 18:40:52 2011 -0700 watcher: document new headers for users These names are not finalized, yet. commit 7e8b1f6abe561e33644ca2bf30dc587e0bfac3c0 Author: Eric Wong Date: Tue Sep 27 19:32:38 2011 -0700 watcher: add peak times for statistics It could be useful to know when the first and last peak time of a maximum was. commit 26d775916fd3d967a98534eda44a983ea30d0811 Author: Eric Wong Date: Tue Sep 27 19:50:43 2011 -0700 test: updates for newer Unicorn API Unicorn.run no longer exists commit e974e26fcfb78cd579556495a0635d30ec5c6976 Author: Eric Wong Date: Tue Sep 27 17:46:20 2011 -0700 watcher: add X-Current header to headers It can be useful to some to see that info all at once without hitting the HTML index page. commit 9a42edb11efcd091623bb37a33a8ea97b39a1d85 Author: Eric Wong Date: Mon Aug 1 13:57:24 2011 -0700 TODO: patches welcome! I don't need this feature myself. commit 3966549e4ae209598ed56839dae9154f4615d2dc Author: Eric Wong Date: Mon Aug 1 13:49:43 2011 -0700 watcher: add POST /reset/$LISTENER endpoint rdoc This was always supported via the HTML (browser) interface but there was no documented way of hitting it without a browser, before. commit a5830080b3cf6011d737342462700f7c5819942a Author: Jeremy Evans Date: Mon Jun 27 10:36:21 2011 -0700 Fix bad rescue statements in a couple tests commit af8f564217f87020fbe34c9bdbeb2cb57e13ac54 Author: Eric Wong Date: Mon Jun 27 05:31:49 2011 +0000 raindrops 0.7.0 - FreeBSD fix, code cleanups This release fixes a build issue on FreeBSD. There are various documentation and code cleanups, too. commit 2cb1800e7afe0085b135ac693d1b5a9378fadebc Author: Eric Wong Date: Mon Jun 27 04:11:33 2011 +0000 doc: librelist.com => librelist.org A non-profit TLD makes more sense for a Free Software project. commit 18318887920773d43b72e9fb6490d3e74674bcf0 Author: Eric Wong Date: Mon Jun 27 03:30:39 2011 +0000 fix Ruby warnings Found in the check-warnings target in pkg.mk commit ad3bf417b45d50be7b0121b5dacca2bc95b7953b Author: Eric Wong Date: Mon Jun 27 03:28:33 2011 +0000 Gemfile: remove wrongdoc dependency for tests It's not needed. commit da7ee89b75b5c0cdd2f332d073104f10ea14e055 Author: Eric Wong Date: Mon Jun 27 01:44:39 2011 +0000 pkg.mk: update to latest version * check-warnings option added * locale-independent grep invocation commit 1e7dc89cc38c5dec0b63ac452b23141297701f88 Author: Eric Wong Date: Fri Jun 24 17:06:56 2011 -0700 remove _XOPEN_SOURCE #define for FreeBSD This appears to cause __BSD_VISIBLE to not be defined, which is required for MAP_ANON to be visible in sys/mman.h Thanks for Aleksandar Simic for the hint and Troex Nevelin for the bug report! commit 880dbec44c13e3d5a6b858fc2212714f122b71c6 Author: Eric Wong Date: Thu Jun 16 00:39:11 2011 -0700 linux_inet_diag: avoid pointer aliasing warnings No need to cast when C has handy unions. Maybe the compiler will be able to make better optimization choices here, but at least it'll be less noisy. commit 579f0ea63d1f6976a9a312423f45e8d38357ea68 Author: Eric Wong Date: Wed Jun 15 23:31:28 2011 -0700 test_linux_tcp_info: fix test for higher HZ systems The last_data_recv field is dependent on the CONFIG_HZ of the kernel, so the stock 250 HZ kernel can change the timing on us. commit 9f6e03fcced2ff695eaa355709312a8fe2d4c187 Author: Eric Wong Date: Fri Mar 25 09:45:55 2011 -0700 last_data_recv: fixup rdoc formatting is hard :< commit cb90a12dec7b1c55ebae5a83bafa15f415b576da Author: Eric Wong Date: Thu Mar 24 13:35:32 2011 -0700 watcher: fix documentation for X-* headers I copied and pasted off the Raindrops::LastDataRecv documentation. While the headers and mathematical meanings are identical, they measure different things (but for the same purpose) Noticed-by: Troex Nevelin commit ee08d063e47636e9b1100d4fe9cdb7b4bc080f69 Author: Eric Wong Date: Mon Mar 21 15:29:38 2011 -0700 raindrops 0.6.1 - fix build on non-Linux TCP_INFO support couldn't compile under non-Linux, this was broken since 0.5.0 when TCP_INFO support was introduced. Thanks to Ben Bleything for the report. commit dbfe01c60d0d231f42e18768cbb8b23612f281fc Author: Eric Wong Date: Mon Mar 21 15:17:21 2011 -0700 tcp_info: mark this as Linux-only We can't even pull in linux/*.h headers to check for TCP_INFO. commit f8fe72d588cf50fa0c52058d46980af8bfa012bf Author: Eric Wong Date: Mon Mar 21 14:42:32 2011 -0700 raindrops 0.6.0 - polishing up the last release Following up the huge 0.5.0 release, 0.6.0 makes some minor improvements: * minor UI/UX improvements for Watcher Rack app * set close-on-exec by default for inet_diag sockets * inet_diag build fixes for newer GNU libc6 * --with-atomic_ops-dir= build option added commit 693beb4015cf3bfd68f76432cb3ce779e9752cc1 Author: Eric Wong Date: Mon Mar 21 14:12:17 2011 -0700 extconf: use dir_config properly dir_config may be used to build for libatomic_ops. The previous use of dir_config was from back when I didn't understand mkmf. This means you can now use: gem install raindrops -- --with-atomic_ops-dir=/usr/local If you libatomic_ops library was installed in /usr/local instead of /usr commit 14423a47266bbb37fe0b2fe5f24cef9e26037e38 Author: Eric Wong Date: Mon Mar 21 11:12:59 2011 -0700 inet_diag: no need to rely on sin6_addr internals Seems to breaks under newer libc headers (on Debian sid). commit 4331e3f461af6d66bc8de48c336ece0938e3b59e Author: Eric Wong Date: Mon Mar 21 11:10:10 2011 -0700 extconf: remove check for rb_struct_alloc_noinit() We no longer use it. commit f6af45add8c900b02f2b88617a765fa610fbe9aa Author: Eric Wong Date: Mon Mar 21 11:01:57 2011 -0700 inet_diag: fix build under MRI 1.8.7 commit 877433494e4a848d6c6a10d0e6521061e6a10118 Author: Eric Wong Date: Mon Mar 21 10:57:30 2011 -0700 inet_diag: FD_CLOEXEC for inet_diag sockets by default Very few programs can take advantage of inheriting FDs across exec() boundaries, and inet_diag sockets have no reason to be used in this way. commit a5d552d90942fe3c3d1adfc809638fd78992da6e Author: Eric Wong Date: Mon Mar 21 10:46:58 2011 -0700 inet_diag: use rb_thread_io_blocking_region under 1.9.3dev It can detect cross-thread close() calls commit f86b02018a4195e199136eb0bd8d127d01cae154 Author: Eric Wong Date: Mon Mar 21 10:27:59 2011 -0700 watcher: remove redundant Rack::Response No need to duplicate code commit cfadf58e6bce04b750605fd71d0cce6bbb06d8b0 Author: Eric Wong Date: Fri Mar 18 09:22:28 2011 +0000 watcher: add title attributes to elements It can help navigation, we think... commit 681e8295fe87e32676873319fb445709d16212d8 Author: Eric Wong Date: Fri Mar 18 09:12:28 2011 +0000 watcher: RDoc examples point to the demo No need to waste bandwidth of example.com when we have a meaningful demo site :) commit b8d44ff60c5cd6db6b971e2206cf18b5420a95cb Author: Eric Wong Date: Fri Mar 18 09:05:47 2011 +0000 watcher: set Expires headers for cache invalidation We know exactly when to invalidate based on the delay :) commit 7c7e34dab18331082fe7b703b9bbdd40ea5bcbf6 Author: Eric Wong Date: Fri Mar 18 08:58:25 2011 +0000 zbatery.conf: disable user switching, add pid file Zbatery sucks at user switching + USR2+QUIT reloads commit 734133e15620584618df49e76ecabd7c28c72444 Author: Eric Wong Date: Fri Mar 18 08:20:11 2011 +0000 watcher: prevent Rack::Deflater from buffering Set "Cache-Control: no-transform" to prevent frontend proxies from flushing data. commit 9af99455c06abd69d18fed751ea16d914e62cf84 Author: Eric Wong Date: Thu Mar 17 16:43:46 2011 -0700 watcher: add link to the Watcher documentation Hopefully people can learn to use the REST API this way. commit 37718b5833f04870d14bfdca3dc84e4f885c71d9 Author: Eric Wong Date: Wed Mar 16 20:18:22 2011 -0700 watcher: fix rdoc commit 744c4c11c58f40c925c0d36eefb2287095a2f127 Author: Eric Wong Date: Wed Mar 16 19:53:59 2011 -0700 raindrops 0.5.0 - more Linux extras! Portable changes: * Raindrops are now resizable within the limits of system page size * Raindrops::Middleware proxies +to_path+ in response bodies * More documentation Linux-only changes: * Raindrops::LastDataRecv[1] Rack application * Raindrops::Watcher[2] Rack application * Raindrops::TCP_Info[3] class for capturing TCP connection stats * IPv6 support for inet_diag * faster inet_diag stats for multiple sockets There is also a demo server running the Watcher and Middleware components. It's capped to 30 concurrent users, so go easy on it: Raindrops::Watcher: http://raindrops-demo.bogomips.org/ Raindrops::Middleware: http://raindrops-demo.bogomips.org/_raindrops [1] http://raindrops.bogomips.org/Raindrops/LastDataRecv.html [2] http://raindrops.bogomips.org/Raindrops/Watcher.html [3] http://raindrops.bogomips.org/Raindrops/TCP_Info.html commit f1b45e2f44f45122313abc88e139d49c0e39c7b7 Author: Eric Wong Date: Wed Mar 16 20:12:22 2011 -0700 watcher: retry on empty stats Those will cause Aggregate to raise Errno::EDOM commit 3c6aee0e33c53d0f270b1ceb40efe6ed29fa9232 Author: Eric Wong Date: Wed Mar 16 20:10:12 2011 -0700 test_raindrops_gc: disable by default It takes too long commit 7e3b385eb433a5b23da5742ca69804f34837f947 Author: Eric Wong Date: Wed Mar 16 20:09:38 2011 -0700 test_linux_all_tcp_listen_stats_leak: fix for 1.8 Apparently 1.8 Test::Unit doesn't like empty test classes commit a3cf985b82f5b252434c89d7e0ec6277ac8fe596 Author: Eric Wong Date: Thu Mar 17 02:47:47 2011 +0000 rdoc: document the demo URLs commit 0ab20cd7e10a3c9d4ddcbe2bde28542c350b4b0e Author: Eric Wong Date: Thu Mar 17 02:33:29 2011 +0000 watcher_demo: add more listeners to the mix git is on 9418 and my cgit UNIX domain socket commit 601fdd86fdbf847e47cdbb981afc60dafa407bd5 Author: Eric Wong Date: Thu Mar 17 02:33:28 2011 +0000 inet_diag: properly deal with INADDR_ANY binds Oops :x This was totally broken with the all-listener filter. commit ca49963f55447794c3e2254fe9dc633c9268e9be Author: Eric Wong Date: Thu Mar 17 02:33:27 2011 +0000 linux-listener-stats: all means all (UNIX and TCP listeners) No more splitting all TCP or all UNIX listeners commit 39cab8b630d41d8d002f5d4f65280bc63706c74f Author: Eric Wong Date: Thu Mar 17 02:33:26 2011 +0000 update examples for http://raindrops-demo.bogomips.org/ Whee! commit 1f29269eabd806f1e0cb9ca7fd4231d8588d7669 Author: Eric Wong Date: Thu Mar 17 02:33:25 2011 +0000 watcher: properly stream responses for <= HTTP/1.0 nginx makes HTTP/1.0 requests and expects HTTP/1.1 responses commit 57c9f70fb3c14ed94f4fb2445a8a4168f8c5253b Author: Eric Wong Date: Wed Mar 16 16:38:46 2011 -0700 update examples for Rack/Zbatery commit 3656a61bfb24ac76bc47d314327c01f9fd10a717 Author: Eric Wong Date: Wed Mar 16 16:03:56 2011 -0700 doc: fix documentation for Raindrops::InetDiagSocket class commit db9162575b885add7c3b7ab06f9c03a2ebc44a1f Author: Eric Wong Date: Wed Mar 16 15:22:42 2011 -0700 add Watcher Rack application It does streaming! commit b0be66c37bb7080b0f06ab76b0e7d3404e2f9059 Author: Eric Wong Date: Mon Mar 14 14:58:40 2011 -0700 linux-listener-stats: use timerfd if available It is more accurate than sleep and doesn't require calculating times commit 65dc45b7a13c92b27bc3589e37a3b52c29b7fbee Author: Eric Wong Date: Mon Mar 14 16:52:19 2011 -0700 linux: avoid Array#first/Array#last More confusing for me, actually... commit a9bfa55f9a1d1c96d73367d3bb46f7b9e81fbbf9 Author: Eric Wong Date: Mon Mar 14 16:38:58 2011 -0700 linux: unix_listener_stats may scan all paths This matches behavior of the TCP version. commit 724f82bb5feaf1df40b02b8f353e94496f060647 Author: Eric Wong Date: Mon Mar 14 14:50:43 2011 -0700 linux-listener-stats: allow -a for all TCP sockets Sometimes we want more of them commit 65f46737ad5641841a5e5a89f4651d160dacffc0 Author: Eric Wong Date: Sat Mar 12 12:56:16 2011 -0800 inet_diag: switch to inet_pton() for translation getaddrinfo() needs to get a list of available interfaces from the kernel with every single call (since ipv6 could've been modprobed), so it's a waste of syscalls. commit c478549ea7490de2432ed31ffee37a2bfc1d24f3 Author: Eric Wong Date: Sat Mar 12 12:56:15 2011 -0800 allow reusing netlink socket for inet_diag No need to waste resources on creating/destroying a socket. commit a4b9c8334e2bc0698f0650d275be0935e86dc13e Author: Eric Wong Date: Sat Mar 12 10:17:00 2011 +0000 inet_diag: fix signedness warnings on 32-bit I thought my compiler would be smarter :< commit 44bb459d7af7efad26540c9844b86f59088d3e13 Author: Eric Wong Date: Sat Mar 12 10:13:27 2011 +0000 inet_diag: no need to specify family The way we dump, we dump it all, it seems. commit f9f10fb9815e11d5935edc9427eb1bc05dce751f Author: Eric Wong Date: Sat Mar 12 10:04:45 2011 +0000 inet_diag: fix up IPv6 address stringification This means we can read multiple addresses at once, even IPv6 ones. commit 54e0335c80a062c2fb4ec1351975500cf778f7eb Author: Eric Wong Date: Sat Mar 12 09:43:21 2011 +0000 tests: remove unused_port function It's not needed since we don't care to rebind sockets commit 79be320c3e4baf1af4b36fb269f08448c5400e47 Author: Eric Wong Date: Sat Mar 12 09:39:49 2011 +0000 inet_diag: fall back on IPv6 lookups It's slow, but at least it works. commit bf0a3c731ec3573160b9ee40cbfd7e2817d91080 Author: Eric Wong Date: Sat Mar 12 09:26:30 2011 +0000 inet_diag: fold all_tcp_listener_stats into tcp_listener_stats No reason to have an extra method. This also speeds up the multi-listener case for tcp_listener_stats since it avoids expensive sendmsg() syscalls. commit 562e2e243e69f128c47ceb59c5b1710fa35c7081 Author: Eric Wong Date: Sat Mar 12 08:58:35 2011 +0000 inet_diag: small reorganization commit 8f12e060ce2a47f92b47d197f8daf7c094277bf3 Author: Eric Wong Date: Sat Mar 12 08:51:11 2011 +0000 inet_diag: do not set unblocking function netlink is fast and predictable in response times, so permitting interrupts would just complicate things and lead to errors. commit 3a2fe1f1330899c42deb5e714db1d5799e50a292 Author: Eric Wong Date: Sat Mar 12 08:46:07 2011 +0000 move st_table cleanup Fewer places to check for errors, we think. commit e6b5e940fdb8e2ded70a80132af6c280db02e8d8 Author: Eric Wong Date: Sat Mar 12 08:30:40 2011 +0000 inet_diag: stricter IPv6 address parsing Be stricter about invalid inputs. commit d4faac5480f6416cf92301745a9a9572bc865061 Author: Eric Wong Date: Fri Mar 11 20:56:49 2011 -0800 linux: method for dumping all TCP listener stats This is a work-in-progress and will probably be modified before the next release. commit fe2615590d5e8ac8e735200696ec8396fb3cd219 Author: Eric Wong Date: Fri Mar 11 19:45:54 2011 -0800 inet_diag: force the use of 32-bit counters 64-bit counters are unnecessarily large for tracking active or queued connections until we have IP_ROFLSCALE support :> commit f6cdffc3f8ba6dc276cff38c11d9f92aea1313ea Author: Eric Wong Date: Fri Mar 11 19:44:24 2011 -0800 inet_diag: fixup braindamage from refactoring Oops :x commit af73ca855100a9aadad9bf60de45df62e8aa44e2 Author: Eric Wong Date: Fri Mar 11 16:12:17 2011 -0800 cleanup struct initialization to avoid ifdefs Too hard to maintain. commit fac0211221d70b94c0301f977e8a6ba429d79ef8 Author: Eric Wong Date: Fri Mar 11 16:05:12 2011 -0800 use unsigned values for all counters We can't have negative values commit da18a484d7e8ae15de68298557aa63cc431a7c63 Author: Eric Wong Date: Fri Mar 11 15:58:33 2011 -0800 inet_diag: cleanup unnecessarily large struct We don't care for this address. commit cf4acabf3e8993fe59840aaa6717d65f2ca95fc9 Author: Eric Wong Date: Fri Mar 11 15:04:39 2011 -0800 inet_diag: fixup broken assertion Oops, strings are always true :x commit 70041dfa82015119b33d34b46ecbb0a5c45587cf Author: Eric Wong Date: Fri Mar 11 14:56:19 2011 -0800 inet_diag: factor out prep code We're going to experiment with something... commit 55c9127ede4114f65b0bc01cdaa1152ef21ca95a Author: Eric Wong Date: Fri Mar 11 13:49:08 2011 -0800 doc: more rdoc updates for Linux users Yes we love Linux more than other systems :> commit 57fe99f294e0fe948593beeae7cf879efe4cdd52 Author: Eric Wong Date: Fri Mar 11 13:37:48 2011 -0800 the core Raindrops class is portable, yes it is OK, not to non-Unix but I'll never care :> commit 154842a48c0baba66daf744f3be06fd8701b9638 Author: Eric Wong Date: Fri Mar 11 13:35:47 2011 -0800 tcp_info: more documentation on last_data_recv It's useful, yes. commit 666cb9c38fd132ec8bee0b7860b6cf30db0f9b41 Author: Eric Wong Date: Thu Mar 10 19:18:23 2011 -0800 .document: remove non-existent reference raindrops_ext.c never existed :o commit 035bcb9fa5ab9c95bd2f5b0515f74d8d51b87805 Author: Eric Wong Date: Thu Mar 10 18:29:10 2011 -0800 tests: only do the GC test on MRI "Advanced" GCs are typically less aggressive and typically allow much more memory to be used. commit 42e8c96aa4e4d2cf3ba82751fcc0487e7e8a0225 Author: Eric Wong Date: Thu Mar 10 18:28:18 2011 -0800 fix stupid typo in Raindrops#size= Oops, it could give the GC problems. commit e87875ba261fa5b0cd328bcf03e666e2d9078114 Author: Eric Wong Date: Thu Mar 10 17:24:02 2011 -0800 tests: ensure munmap() in children don't muck up the parent Raindrops is designed to work with forking servers after all. commit e516e45640206fa3e7864938da74a7cb5ca31715 Author: Eric Wong Date: Fri Mar 11 02:12:37 2011 +0000 support for Raindrops#size= and Raindrops#evaporate! This allows limited resizing of the Raindrops memory area since we always over-allocate due to the required page aligment for mmap. It would be nice if mremap() worked with MAP_SHARED, but it does not and triggers a bus error when attempting to access the new area. ref: https://bugzilla.kernel.org/show_bug.cgi?id=8691 commit 61962b27a51031965cef70451d369b115868fb11 Author: Eric Wong Date: Thu Mar 10 10:51:38 2011 +0000 rdoc: 100% documentation coverage! Of course, RDoc doesn't know quantity vs quality :) commit 8392f8186cd21f9190474bd6b5ac6ec58c7af96a Author: Eric Wong Date: Thu Mar 10 06:11:10 2011 +0000 test_middleware_unicorn: GC is aggressive! Oops, don't let GC close our listener before Unicorn can inherit it. commit b353e42e6f12f26a926bbb3202c8bb8e7a916d1c Author: Eric Wong Date: Wed Mar 9 21:12:32 2011 -0800 experiment with Bundler for dev dependencies It seems nice commit 485d3fa598a119b409734e0a4193d78fe50f4ff7 Author: Eric Wong Date: Wed Mar 9 17:03:20 2011 -0800 disable aggregate/pmq stuff under 1.8 POSIX message queues needs native threads to function. commit fa64d77ac096612d6bc731407066df8aa1ff79a7 Author: Eric Wong Date: Wed Mar 9 16:54:46 2011 -0800 aggregate/pmq: we need a Mutex to protect fcntl() locks fcntl() locks are per-process, so we also need something to protect individual threads within a process from stepping over each other. commit b57881c86de4e7406ca537b5285f27d7130b0073 Author: Eric Wong Date: Tue Mar 8 16:52:43 2011 -0800 linux-listener-stats: do not load aggregate for single snapshot This is only useful when looped inside screen or something similar... commit d5cbd604badd58ebd99a5e19953d20d7672da4dc Author: Eric Wong Date: Tue Mar 8 16:45:01 2011 -0800 linux-listener-stats: add optional Aggregate gem support USR1 dumps histograms, and USR2 resets the counters commit 93bb8af761dbaa8e19c4660c520db1ee7ba1c70b Author: Eric Wong Date: Tue Mar 8 16:30:47 2011 -0800 linux-listener-stats: favor narrower display if possible No need to make eyes drift :) commit 90726e5187a9053c6eb7caf90ebec1d38d4372ea Author: Eric Wong Date: Tue Mar 8 14:18:11 2011 -0800 preliminary Rack app to track last_data_recv Seems to basically work commit 96c8be2ea8830e2eb3a9108f501df52c21b42546 Author: Eric Wong Date: Fri Mar 4 17:08:13 2011 -0800 test_linux_tcp_info: add test for last_data_recv commit 4372cf8ef8203c93632cdaf163a1c923075e7d0f Author: Eric Wong Date: Fri Mar 4 16:00:38 2011 -0800 Aggregate support via POSIX message queues commit 55efe20df7f2cf594786e16e890382208a114f65 Author: Eric Wong Date: Tue Mar 1 16:36:44 2011 -0800 tests: cleaner skipping for missing components commit 26558f816ecbe25955f549355aa57aef5959a22e Author: Eric Wong Date: Tue Mar 1 16:30:24 2011 -0800 gemspec: add Rack and Unicorn as dev dependencies They are useful commit 16201f3c16b66beb9694eff2bed2f4b8ccd1f010 Author: Eric Wong Date: Tue Mar 1 14:47:42 2011 -0800 test_linux_tcp_info: workaround for ancient kernels UGH... commit 943fbb44f7b2194311ac614c3a6c54420c8dda94 Author: Eric Wong Date: Tue Mar 1 14:47:41 2011 -0800 use struct tcp_info from linux/tcp.h It's more complete for people on ancient systems where "struct tcp_info" is defined in netinet/tcp and missing tcp_ircv_rtt, tcpi_rcv_space and tcpi_total_retrans. commit d93e6b2903f2d702b5dc85de86eb91eb7794cc0d Author: Eric Wong Date: Tue Mar 1 14:07:59 2011 -0800 linux-listener-stats: add timestamps and thresholds Matches my common usage patterns commit 03c75f547db2d40ea29b5753359bb0df210834f9 Author: Eric Wong Date: Mon Feb 28 17:14:04 2011 -0800 linux-tcp-listener-stats: fix fractional second delays Sometimes sleeping for one second between reads is too much... commit 1c3e0a2575c0644a7fca02bbe865e2ce421f5adb Author: Eric Wong Date: Tue Mar 1 00:57:35 2011 +0000 linux-listener-stats: not just for TCP... This can now be used to monitor UNIX domain socket queues, too. commit 6d12a518f69f950d814eb223fe5bf1ad448dcc34 Author: Eric Wong Date: Tue Mar 1 00:50:40 2011 +0000 linux-tcp-listener-stats: synchronize stdout/stderr We want any pipe readers to see this immediately commit b080ac88303f31332252aae4b430af5445114b99 Author: Eric Wong Date: Mon Feb 28 16:29:18 2011 -0800 doc: mention tcp_diag/inet_diag for old kernels People actually need to load modules manually on older kernels :< commit 012d2fe51afd46fdddbf62f7ebba5102c7f3361a Author: Eric Wong Date: Sat Feb 26 21:02:57 2011 +0000 middleware: split out proxy class It's easier to find this way. commit ac6b83ea1c277cd798e2f627fa5eee605efab4fe Author: Eric Wong Date: Sat Feb 26 20:59:00 2011 +0000 middleware: avoid double decrement when reading stats Oops! commit e4ab2cc40bdbd1698f4bcf138e83c4823d118f81 Author: Eric Wong Date: Fri Feb 25 19:43:06 2011 -0800 proxy +to_path+ calls in the response body We need to do this for apps that depend on things like the sendfile() optimizations in Rainbows! commit fcf99bd7e3323ea99c5bfc8a3a15fbbc18cc8285 Author: Eric Wong Date: Tue Feb 22 19:24:56 2011 -0800 LICENSE: fix wording, we bundle a copy of the LGPL No need for people to download glibc to get the LGPL :> commit df3d3a0b09f05249aff9f25f558f988bfcad8874 Author: Eric Wong Date: Tue Feb 22 19:24:55 2011 -0800 add Raindrops::MAX constant This is the highest number a counter may be incremented to before it overflows. commit db192979f096d0153ad14e530e1e2e193289c7e0 Author: Eric Wong Date: Tue Feb 22 19:24:54 2011 -0800 linux: add support for TCP_INFO reporting This returns a Raindrops::TCP_Info object that wraps a tcp_info struct. commit ebc2093847705c382b4d83ed5120e44b9afad3c0 Author: Eric Wong Date: Tue Feb 22 19:24:53 2011 -0800 split out RSTRUCT* compat macros We might reuse that for other code... commit a73fe2f033a766a05ecef01bef015e2d1993bddd Author: Eric Wong Date: Tue Feb 22 19:24:52 2011 -0800 use autoload for Linux module We'll be doing more Linux-only stuff commit e10e520d47fa96cf549c7d544d6575baa8ed748a Author: Eric Wong Date: Wed Feb 16 01:31:41 2011 +0000 middleware: add ipv6 address detection for Unicorn Since Unicorn and Rainbows! support IPv6 now, it makes sense to support the rfc2732-style addresses it returns. commit e5faa4da78af196ee5abbccf197671fd8e77adad Author: Eric Wong Date: Wed Feb 16 01:31:40 2011 +0000 linux: add ipv6 support for inet_diag inet_diag already supports AF_INET6. commit 5183832d1e70cd89aab1cb8bb2e2795f0ad247c7 Author: Eric Wong Date: Tue Feb 15 15:02:04 2011 -0800 README: shorten git URLs bogomips.org is on a URL diet commit 05458d4067c1642fe89f164ce3e6466af1458043 Author: Eric Wong Date: Tue Feb 15 14:57:18 2011 -0800 test_linux: fix STRESS=1 tests commit f9233c20d453475df22ce7e4d0d098837f0d859a Author: Eric Wong Date: Tue Feb 15 10:37:06 2011 -0800 middleware: switch to ivars These are slightly faster than regular method dispatch commit 7f186515f214347bf5e146ce8d16a38f32f9cdfb Author: Eric Wong Date: Tue Feb 15 10:14:22 2011 -0800 middleware: avoid capturing block variable It's a needless allocation commit 489d08233b2ec43353e25f857e3639751f4f529b Author: Eric Wong Date: Tue Feb 15 10:13:15 2011 -0800 unindent modules No need to clutter/confuse namespace lookups commit 0344ffd5fa6c4693f5a8351e451c6a0852de81ed Author: Eric Wong Date: Tue Feb 15 09:58:09 2011 -0800 update packaging to use wrongdoc + pkg.mk No more JavaScript! commit 29216b7c3337d545e49a5ce978659d47a5f3e8b0 Author: Bryan McLellan Date: Fri Feb 11 16:46:43 2011 -0800 addrs are always an Array of Strings Acked-by: Eric Wong commit 304287f2e12e351fb5bf18e28ab3ad62a2f913e5 Author: Eric Wong Date: Sun Sep 26 06:51:57 2010 +0000 README: update for Rubinius support It works! commit 9736c2e991bc695b53b995ee2ecb44b58f88d0af Author: Eric Wong Date: Sun Sep 26 06:44:45 2010 +0000 raindrops 0.4.1 - more portability! Rubinius 1.1.0 support is complete. Atomic operations are now available under FreeBSD 7.0 now. Full changelog below: commit 8a2a725a4ad074af493e5aa075155eda8b1d6be7 Author: Eric Wong Date: Sat Sep 25 00:14:48 2010 -0700 force -march=i486 where GCC is targeted for i386 Nobody uses i386 anymore (especially not with Ruby!), but some systems like FreeBSD 7.0 still target GCC at i386 by default, so we force GCC to use a slightly more modern instruction set and allow it to use atomic builtins. commit 256cc7c8ffb441dcf2d2a2da3bbbcc82546962d9 Author: Eric Wong Date: Sat Sep 25 00:01:46 2010 -0700 disable Linux-only code on non-Linux This allows us to build and link correctly on FreeBSD 7.0 commit 22a5a39d75faa890048d07ae4ea0d494acd414ce Author: Eric Wong Date: Sat Sep 25 06:25:42 2010 +0000 linux: workaround missing RSTRUCT* macros in rbx Rubinius does not include macros for accessing Struct members in the C API. ref: http://github.com/evanphx/rubinius/issues/494 commit 8a2a725a4ad074af493e5aa075155eda8b1d6be7 Author: Eric Wong Date: Sat Sep 25 00:14:48 2010 -0700 force -march=i486 where GCC is targeted for i386 Nobody uses i386 anymore (especially not with Ruby!), but some systems like FreeBSD 7.0 still target GCC at i386 by default, so we force GCC to use a slightly more modern instruction set and allow it to use atomic builtins. commit 256cc7c8ffb441dcf2d2a2da3bbbcc82546962d9 Author: Eric Wong Date: Sat Sep 25 00:01:46 2010 -0700 disable Linux-only code on non-Linux This allows us to build and link correctly on FreeBSD 7.0 commit 22a5a39d75faa890048d07ae4ea0d494acd414ce Author: Eric Wong Date: Sat Sep 25 06:25:42 2010 +0000 linux: workaround missing RSTRUCT* macros in rbx Rubinius does not include macros for accessing Struct members in the C API. ref: http://github.com/evanphx/rubinius/issues/494 commit 663111480d8222763e5f553a71166442ef9990cc Author: Eric Wong Date: Tue Sep 21 22:40:16 2010 +0000 release: publish rubyforge news post Otherwise we'd keep forgetting and users would not know about us. commit 1370d991d6bbe7b1916550996bca95b6b7a8f43d Author: Eric Wong Date: Mon Sep 20 13:51:00 2010 -0700 raindrops 0.4.0 - flowing into new systems! Non-GCC 4.x users may use the libatomic_ops[1] package to compile Raindrops. Memory efficiency is improved for modern glibc users with run-time cache line size detection, we no longer assume 128 byte cache lines. [1] - http://www.hpl.hp.com/research/linux/atomic_ops/ commit 7cbba13a1be4928705148de359c3cf14a6552b71 Author: Eric Wong Date: Mon Sep 20 13:50:39 2010 -0700 README: updates for upcoming 0.4.0 release No more GCC 4.x dependency! commit 2d02974c708ab9fe1e690e902c94ba0bff5a4dd0 Merge: daec89e 24d6944 Author: Eric Wong Date: Mon Sep 20 12:14:33 2010 -0700 Merge branch '0.3.0' (fixup minor 0.3.0 mistag) * 0.3.0: raindrops v0.3.0 - LGPL v2.1 and v3.0 commit 24d6944eae2b9af585b89bf0f7de64162848b0a7 Merge: e3679c4 eed4332 Author: Eric Wong Date: Mon Sep 20 12:14:23 2010 -0700 Merge commit 'v0.3.0' into 0.3.0 * commit 'v0.3.0': raindrops v0.3.0 - LGPL v2.1 and v3.0 commit daec89e0cae4b4f87cd263d385eca353c04f3f0d Author: Eric Wong Date: Sat Sep 18 09:08:00 2010 +0000 use runtime cache line size detection Modern glibc can easily return the L1 cache line size with sysconf(3), so we'll use that and avoid paying a size penalty on CPUs with smaller cache lines than 128 (every modern x86 except the idiotic P4). Additionally, if we detect a single CPU machine, avoid paying any padding penalty at all. On machines without the non-portable glibc sysconf(3) enhancements, we'll continue to operate on the assumption of an enormous 128 byte cache line size. commit c658a2be7355ceee72736cc17754022dc7abfa9f Author: Eric Wong Date: Sat Sep 18 06:43:19 2010 +0000 allow using libatomic_ops headers from HP This allows non-GCC 4.x users to experience Raindrops. commit 7a0bb1afb81da3c83f2cc59403826e1f855d3f0d Author: Eric Wong Date: Tue Aug 3 17:11:32 2010 -0700 test: make GC test (hopefully) more reliable It's not pretty... commit dd84e5742cb2fc6aaa96aa0214e0ed3a963d68da Author: Eric Wong Date: Tue Aug 3 17:11:31 2010 -0700 avoid munmap(MAP_FAILED,...) in GC phase This is only possible during an exit if our invocation of rb_gc() fails and causes the process to terminate. Otherwise the GC won't free something that's obviously on the stack. commit aeb4de9ccdbc0c896e71f5286ca767c8200278bf Author: Eric Wong Date: Mon Aug 2 21:04:07 2010 +0000 doc: fix web link in Atom feed commit 5768c4339c130975658f4d084fb29fdb781403a6 Author: Eric Wong Date: Sat Jul 10 22:31:03 2010 +0000 doc: fix ChangeLog generation There was never a 0.2.1 :x commit e3679c4d3ebd8b80aa5d9e4a3a2a945c9c9ff2b4 Author: Eric Wong Date: Sat Jul 10 22:19:40 2010 +0000 raindrops v0.3.0 - LGPL v2.1 and v3.0 Raindrops is now licensed under the LGPLv2.1 or LGPLv3 (from LGPLv3-only) to allow bundling in GPLv2-only applications. There are small documentation updates and updated examples at http://raindrops.bogomips.org/examples/ commit eed4332421a421a3455aa48ec688347502a9cb0a Author: Eric Wong Date: Sat Jul 10 22:19:40 2010 +0000 raindrops v0.3.0 - LGPL v2.1 and v3.0 Raindrops is now licensed under the LGPLv2.1 or LGPLv3 (from LGPLv3-only) to allow bundling in GPLv2-only applications. There are small documentation updates and updated examples at http://raindrops.bogomips.org/examples/ commit 4e42082413f64baec0b844bdf743d598af433aad Author: Eric Wong Date: Sat Jul 10 22:19:20 2010 +0000 doc: add examples directory to website commit 87dc142973f7d1546bdf739b2b1f0fa714313750 Author: Eric Wong Date: Sat Jul 10 22:13:02 2010 +0000 LICENSE: dual license under LGPLv2.1 and LGPLv3 This allows GPLv2-only applications to bundle us. commit 3f7f59ff522bf322383f1bebaa18a979608804f7 Author: Eric Wong Date: Thu Jul 1 19:25:57 2010 -0700 examples/linux-tcp-listener-stats: warn on possibly invalid addresses It is a likely mistake to specify a local address such as "127.0.0.1:#{port}" as an argument to linux-tcp-listener-stats when our listener is listening on all addresses ("0.0.0.0:#{port}"). So make an effort to detect this mistake as all of our known users on our mailing list seem to make it: http://mid.gmane.org/AANLkTinP5fBfcRa6Y3-MrMzDqjRRrgEOIunvn_h7q2WS@mail.gmail.com commit bb24816e1291bbbb26065af3fb5bd76a4cbb0c95 Author: Eric Wong Date: Thu Jul 1 17:37:08 2010 -0700 linux_inet_diag: rename "addrs" => "query_addr" "query_addr" is a more appropriate name since we only query one address at a time via netlink rather than relying on OR-ing in the bytecode. commit 590b3d9b66e8b345a6053cbfc8304f08ab58caf5 Author: Eric Wong Date: Tue Jun 29 00:51:23 2010 +0000 examples/linux-tcp-listener-stats: fix usage string commit 06c5595cb7b77f65648740d47f0b98a1a73eed8c Author: Eric Wong Date: Tue May 4 17:30:26 2010 -0700 raindrops 0.2.0 - raining penguins! For servers running Unicorn 0.98.0 (and derivative servers) under Linux, :listeners no longer needs to be passed explicitly when configuring the Rack middleware. Some small documentation updates and cleanups, too. commit ccf41c19164d9658f7fcbcae170ae11267b1abd0 Author: Eric Wong Date: Tue May 4 17:29:24 2010 -0700 examples: add sample standalone Rack application commit 0a5a13eaae957a3c7cacd26a0fcc0c8053f1d0f3 Author: Eric Wong Date: Tue May 4 17:29:01 2010 -0700 README: add note about Unicorn 0.98.0 listener_names commit ad1a354968d0ae2abf5b0450cd5b254d95077ba0 Author: Eric Wong Date: Tue May 4 17:23:32 2010 -0700 cleanup a few typos commit eadaec56aef29360f5b18abd3d2683c835d3725a Author: Eric Wong Date: Tue May 4 17:16:04 2010 -0700 middleware: no need to ensure in #close If stats.decr_writing never fails, really (or something is seriously wrong (like memory corruption) and the ensure block wouldn't fire anyways). commit 7664144957b4916c76ec52132eaaa552a348de28 Author: Eric Wong Date: Tue May 4 17:01:37 2010 -0700 use Unicorn.listener_names method if available This allows easier configuration when used with Unicorn and derived servers (Rainbows! and Zbatery). commit c48b2ce337bddd5f0e7f0a6a21dfd7fc925f1e42 Author: Eric Wong Date: Sun May 2 16:57:58 2010 -0700 GNUmakefile: add publish_doc target commit 0f316f004787c4a86c854cc251e2c3cbf8c2dce3 Author: Eric Wong Date: Sun May 2 16:55:54 2010 -0700 rdoc 2.5.x updates commit b7f2bbca3ada5ef2e8942690f658510a226394ce Author: Eric Wong Date: Sun May 2 16:55:03 2010 -0700 README: add information about mailing list archives commit 957ddbbdc88ff0d644692935f31794a9efb86598 Author: Eric Wong Date: Sun Apr 11 20:21:59 2010 -0700 tests: fix to run under MRI 1.8.6 commit da0d9cbbd9f6cf2e68274dd5c2204b105f8b030d Author: Eric Wong Date: Thu Apr 8 14:26:04 2010 -0700 linux: slightly simpler scan for /proc/net/unix File.read under 1.9 takes an :encoding argument to force binary encoding for its return value. commit b9eeebe9d448431a97ac4515cae6a53b6976ac4e Author: Eric Wong Date: Wed Apr 7 17:57:23 2010 -0700 gemspec: fix rubyforge project commit 07832552d8d1e3fe0e40b529acf723e15ecaf042 Author: Eric Wong Date: Wed Apr 7 17:54:55 2010 -0700 gemspec: fix summary commit 2eb54ed4024c544448a712ffe4acd1e575823c9f Author: Eric Wong Date: Wed Apr 7 17:44:50 2010 -0700 raindrops 0.1.0 initial release commit ae4faee4054d7c9d95ef95a47c794185a5c20bd8 Author: Eric Wong Date: Wed Apr 7 17:43:45 2010 -0700 Rakefile: fix web_url commit c3e9f5ba6fc10397f55941f36da29808a105d248 Author: Eric Wong Date: Wed Apr 7 17:07:42 2010 -0700 initial raindrops-0.12.0/TODO0000644000004100000410000000007712211676722014325 0ustar www-datawww-data* pure Ruby version for non-forking servers (patches welcome!) raindrops-0.12.0/pkg.mk0000644000004100000410000001215612211676722014750 0ustar www-datawww-dataRUBY = ruby RAKE = rake RSYNC = rsync WRONGDOC = wrongdoc GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @./GIT-VERSION-GEN -include GIT-VERSION-FILE -include local.mk DLEXT := $(shell $(RUBY) -rrbconfig -e 'puts RbConfig::CONFIG["DLEXT"]') RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION') RUBY_ENGINE := $(shell $(RUBY) -e 'puts((RUBY_ENGINE rescue "ruby"))') lib := lib ifeq ($(shell test -f script/isolate_for_tests && echo t),t) isolate_libs := tmp/isolate/$(RUBY_ENGINE)-$(RUBY_VERSION)/isolate.mk $(isolate_libs): script/isolate_for_tests @$(RUBY) script/isolate_for_tests -include $(isolate_libs) lib := $(lib):$(ISOLATE_LIBS) endif ext := $(firstword $(wildcard ext/*)) ifneq ($(ext),) ext_pfx := tmp/ext/$(RUBY_ENGINE)-$(RUBY_VERSION) ext_h := $(wildcard $(ext)/*/*.h $(ext)/*.h) ext_src := $(wildcard $(ext)/*.c $(ext_h)) ext_pfx_src := $(addprefix $(ext_pfx)/,$(ext_src)) ext_d := $(ext_pfx)/$(ext)/.d $(ext)/extconf.rb: $(wildcard $(ext)/*.h) @>> $@ $(ext_d): @mkdir -p $(@D) @> $@ $(ext_pfx)/$(ext)/%: $(ext)/% $(ext_d) install -m 644 $< $@ $(ext_pfx)/$(ext)/Makefile: $(ext)/extconf.rb $(ext_d) $(ext_h) $(RM) -f $(@D)/*.o cd $(@D) && $(RUBY) $(CURDIR)/$(ext)/extconf.rb ext_sfx := _ext.$(DLEXT) ext_dl := $(ext_pfx)/$(ext)/$(notdir $(ext)_ext.$(DLEXT)) $(ext_dl): $(ext_src) $(ext_pfx_src) $(ext_pfx)/$(ext)/Makefile @echo $^ == $@ $(MAKE) -C $(@D) lib := $(lib):$(ext_pfx)/$(ext) build: $(ext_dl) else build: endif pkg_extra += GIT-VERSION-FILE NEWS ChangeLog LATEST ChangeLog: GIT-VERSION-FILE .wrongdoc.yml $(WRONGDOC) prepare NEWS LATEST: ChangeLog manifest: $(RM) .manifest $(MAKE) .manifest .manifest: $(pkg_extra) (git ls-files && for i in $@ $(pkg_extra); do echo $$i; done) | \ LC_ALL=C sort > $@+ cmp $@+ $@ || mv $@+ $@ $(RM) $@+ doc:: .document .wrongdoc.yml $(pkg_extra) -find lib -type f -name '*.rbc' -exec rm -f '{}' ';' -find ext -type f -name '*.rbc' -exec rm -f '{}' ';' $(RM) -r doc $(WRONGDOC) all install -m644 COPYING doc/COPYING install -m644 $(shell LC_ALL=C grep '^[A-Z]' .document) doc/ ifneq ($(VERSION),) pkggem := pkg/$(rfpackage)-$(VERSION).gem pkgtgz := pkg/$(rfpackage)-$(VERSION).tgz release_notes := release_notes-$(VERSION) release_changes := release_changes-$(VERSION) release-notes: $(release_notes) release-changes: $(release_changes) $(release_changes): $(WRONGDOC) release_changes > $@+ $(VISUAL) $@+ && test -s $@+ && mv $@+ $@ $(release_notes): $(WRONGDOC) release_notes > $@+ $(VISUAL) $@+ && test -s $@+ && mv $@+ $@ # ensures we're actually on the tagged $(VERSION), only used for release verify: test x"$(shell umask)" = x0022 git rev-parse --verify refs/tags/v$(VERSION)^{} git diff-index --quiet HEAD^0 test $$(git rev-parse --verify HEAD^0) = \ $$(git rev-parse --verify refs/tags/v$(VERSION)^{}) fix-perms: -git ls-tree -r HEAD | awk '/^100644 / {print $$NF}' | xargs chmod 644 -git ls-tree -r HEAD | awk '/^100755 / {print $$NF}' | xargs chmod 755 gem: $(pkggem) install-gem: $(pkggem) gem install $(CURDIR)/$< $(pkggem): manifest fix-perms gem build $(rfpackage).gemspec mkdir -p pkg mv $(@F) $@ $(pkgtgz): distdir = $(basename $@) $(pkgtgz): HEAD = v$(VERSION) $(pkgtgz): manifest fix-perms @test -n "$(distdir)" $(RM) -r $(distdir) mkdir -p $(distdir) tar cf - $$(cat .manifest) | (cd $(distdir) && tar xf -) cd pkg && tar cf - $(basename $(@F)) | gzip -9 > $(@F)+ mv $@+ $@ package: $(pkgtgz) $(pkggem) test-release:: verify package $(release_notes) $(release_changes) # make tgz release on RubyForge @echo rubyforge add_release -f \ -n $(release_notes) -a $(release_changes) \ $(rfproject) $(rfpackage) $(VERSION) $(pkgtgz) @echo gem push $(pkggem) @echo rubyforge add_file \ $(rfproject) $(rfpackage) $(VERSION) $(pkggem) release:: verify package $(release_notes) $(release_changes) # make tgz release on RubyForge rubyforge add_release -f -n $(release_notes) -a $(release_changes) \ $(rfproject) $(rfpackage) $(VERSION) $(pkgtgz) # push gem to RubyGems.org gem push $(pkggem) # in case of gem downloads from RubyForge releases page rubyforge add_file \ $(rfproject) $(rfpackage) $(VERSION) $(pkggem) else gem install-gem: GIT-VERSION-FILE $(MAKE) $@ VERSION=$(GIT_VERSION) endif all:: test test_units := $(wildcard test/test_*.rb) test: test-unit test-unit: $(test_units) $(test_units): build $(RUBY) -I $(lib) $@ $(RUBY_TEST_OPTS) # this requires GNU coreutils variants ifneq ($(RSYNC_DEST),) publish_doc: -git set-file-times $(MAKE) doc find doc/images -type f | \ TZ=UTC xargs touch -d '1970-01-01 00:00:06' doc/rdoc.css $(MAKE) doc_gz $(RSYNC) -av doc/ $(RSYNC_DEST)/ git ls-files | xargs touch endif # Create gzip variants of the same timestamp as the original so nginx # "gzip_static on" can serve the gzipped versions directly. doc_gz: docs = $(shell find doc -type f ! -regex '^.*\.\(gif\|jpg\|png\|gz\)$$') doc_gz: for i in $(docs); do \ gzip --rsyncable -9 < $$i > $$i.gz; touch -r $$i $$i.gz; done check-warnings: @(for i in $$(git ls-files '*.rb'| grep -v '^setup\.rb$$'); \ do $(RUBY) -d -W2 -c $$i; done) | grep -v '^Syntax OK$$' || : .PHONY: all .FORCE-GIT-VERSION-FILE doc test $(test_units) manifest .PHONY: check-warnings raindrops-0.12.0/GNUmakefile0000644000004100000410000000015412211676722015703 0ustar www-datawww-dataall:: RSYNC_DEST := bogomips.org:/srv/raindrops rfproject := rainbows rfpackage := raindrops include pkg.mk raindrops-0.12.0/LATEST0000644000004100000410000000104612211676722014551 0ustar www-datawww-data=== raindrops 0.12.0 - compatibility fixes / 2013-09-02 10:33 UTC This release fixes builds on systems where compilers target i386 (and not later x86 systems). There are also minor improvements for Ruby 2.1.0dev and Rubinius. Eric Wong (5): doc: add email address to generated doc/site README: update regarding Ruby support status extconf: try harder for gcc atomics in i386-configured systems linux_inet_diag: improve compatibility with newer GCs test_watcher: fix for Ruby trunk r40195 and later raindrops-0.12.0/.document0000644000004100000410000000017312211676722015451 0ustar www-datawww-dataREADME LICENSE NEWS ChangeLog lib ext/raindrops/raindrops.c ext/raindrops/linux_inet_diag.c ext/raindrops/linux_tcp_info.c raindrops-0.12.0/metadata.yml0000644000004100000410000001466012211676722016143 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: !binary |- cmFpbmRyb3Bz version: !ruby/object:Gem::Version version: 0.12.0 prerelease: platform: ruby authors: - !binary |- cmFpbmRyb3BzIGhhY2tlcnM= autorequire: bindir: bin cert_chain: [] date: 2013-09-02 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: !binary |- YWdncmVnYXRl requirement: !ruby/object:Gem::Requirement none: false requirements: - - !binary |- fj4= - !ruby/object:Gem::Version version: !binary |- MC4y type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - !binary |- fj4= - !ruby/object:Gem::Version version: !binary |- MC4y - !ruby/object:Gem::Dependency name: !binary |- aW8tZXh0cmE= requirement: !ruby/object:Gem::Requirement none: false requirements: - - !binary |- fj4= - !ruby/object:Gem::Version version: !binary |- MS4y - - !binary |- Pj0= - !ruby/object:Gem::Version version: !binary |- MS4yLjM= type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - !binary |- fj4= - !ruby/object:Gem::Version version: !binary |- MS4y - - !binary |- Pj0= - !ruby/object:Gem::Version version: !binary |- MS4yLjM= - !ruby/object:Gem::Dependency name: !binary |- cG9zaXhfbXE= requirement: !ruby/object:Gem::Requirement none: false requirements: - - !binary |- fj4= - !ruby/object:Gem::Version version: !binary |- Mi4w type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - !binary |- fj4= - !ruby/object:Gem::Version version: !binary |- Mi4w - !ruby/object:Gem::Dependency name: !binary |- cmFjaw== requirement: !ruby/object:Gem::Requirement none: false requirements: - - !binary |- fj4= - !ruby/object:Gem::Version version: !binary |- MS4y type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - !binary |- fj4= - !ruby/object:Gem::Version version: !binary |- MS4y - !ruby/object:Gem::Dependency name: !binary |- dW5pY29ybg== requirement: !ruby/object:Gem::Requirement none: false requirements: - - !binary |- Pj0= - !ruby/object:Gem::Version version: !binary |- MC45OA== type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - !binary |- Pj0= - !ruby/object:Gem::Version version: !binary |- MC45OA== description: ! 'Raindrops is a real-time stats toolkit to show statistics for Rack HTTP servers. It is designed for preforking servers such as Rainbows! and Unicorn, but should support any Rack HTTP server under Ruby 2.0, 1.9, 1.8 and Rubinius on platforms supporting POSIX shared memory. It may also be used as a generic scoreboard for sharing atomic counters across multiple processes.' email: !binary |- cmFpbmRyb3BzQGxpYnJlbGlzdC5vcmc= executables: [] extensions: - !binary |- ZXh0L3JhaW5kcm9wcy9leHRjb25mLnJi extra_rdoc_files: - README - LICENSE - NEWS - ChangeLog - lib/raindrops.rb - lib/raindrops/aggregate.rb - lib/raindrops/aggregate/last_data_recv.rb - lib/raindrops/aggregate/pmq.rb - lib/raindrops/last_data_recv.rb - lib/raindrops/linux.rb - lib/raindrops/middleware.rb - lib/raindrops/middleware/proxy.rb - lib/raindrops/struct.rb - lib/raindrops/watcher.rb - ext/raindrops/raindrops.c - ext/raindrops/linux_inet_diag.c - ext/raindrops/linux_tcp_info.c files: - .document - .gitignore - .manifest - .wrongdoc.yml - COPYING - ChangeLog - GIT-VERSION-FILE - GIT-VERSION-GEN - GNUmakefile - LATEST - LICENSE - NEWS - README - Rakefile - TODO - examples/linux-listener-stats.rb - examples/middleware.ru - examples/watcher.ru - examples/watcher_demo.ru - examples/zbatery.conf.rb - ext/raindrops/extconf.rb - ext/raindrops/linux_inet_diag.c - ext/raindrops/linux_tcp_info.c - ext/raindrops/my_fileno.h - ext/raindrops/raindrops.c - ext/raindrops/raindrops_atomic.h - lib/raindrops.rb - lib/raindrops/aggregate.rb - lib/raindrops/aggregate/last_data_recv.rb - lib/raindrops/aggregate/pmq.rb - lib/raindrops/last_data_recv.rb - lib/raindrops/linux.rb - lib/raindrops/middleware.rb - lib/raindrops/middleware/proxy.rb - lib/raindrops/struct.rb - lib/raindrops/watcher.rb - pkg.mk - raindrops.gemspec - setup.rb - test/ipv6_enabled.rb - test/rack_unicorn.rb - test/test_aggregate_pmq.rb - test/test_inet_diag_socket.rb - test/test_last_data_recv_unicorn.rb - test/test_linux.rb - test/test_linux_all_tcp_listen_stats.rb - test/test_linux_all_tcp_listen_stats_leak.rb - test/test_linux_ipv6.rb - test/test_linux_middleware.rb - test/test_linux_tcp_info.rb - test/test_middleware.rb - test/test_middleware_unicorn.rb - test/test_middleware_unicorn_ipv6.rb - test/test_raindrops.rb - test/test_raindrops_gc.rb - test/test_struct.rb - test/test_watcher.rb homepage: http://raindrops.bogomips.org/ licenses: [] post_install_message: rdoc_options: - -t - raindrops - real-time stats for preforking Rack servers - -W - http://bogomips.org/raindrops.git/tree/%s require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: !binary |- cmFpbmJvd3M= rubygems_version: 1.8.23 signing_key: specification_version: 3 summary: real-time stats for preforking Rack servers test_files: - test/test_aggregate_pmq.rb - test/test_inet_diag_socket.rb - test/test_last_data_recv_unicorn.rb - test/test_linux.rb - test/test_linux_all_tcp_listen_stats.rb - test/test_linux_all_tcp_listen_stats_leak.rb - test/test_linux_ipv6.rb - test/test_linux_middleware.rb - test/test_linux_tcp_info.rb - test/test_middleware.rb - test/test_middleware_unicorn.rb - test/test_middleware_unicorn_ipv6.rb - test/test_raindrops.rb - test/test_raindrops_gc.rb - test/test_struct.rb - test/test_watcher.rb raindrops-0.12.0/.manifest0000644000004100000410000000236512211676722015446 0ustar www-datawww-data.document .gitignore .manifest .wrongdoc.yml COPYING ChangeLog GIT-VERSION-FILE GIT-VERSION-GEN GNUmakefile LATEST LICENSE NEWS README Rakefile TODO examples/linux-listener-stats.rb examples/middleware.ru examples/watcher.ru examples/watcher_demo.ru examples/zbatery.conf.rb ext/raindrops/extconf.rb ext/raindrops/linux_inet_diag.c ext/raindrops/linux_tcp_info.c ext/raindrops/my_fileno.h ext/raindrops/raindrops.c ext/raindrops/raindrops_atomic.h lib/raindrops.rb lib/raindrops/aggregate.rb lib/raindrops/aggregate/last_data_recv.rb lib/raindrops/aggregate/pmq.rb lib/raindrops/last_data_recv.rb lib/raindrops/linux.rb lib/raindrops/middleware.rb lib/raindrops/middleware/proxy.rb lib/raindrops/struct.rb lib/raindrops/watcher.rb pkg.mk raindrops.gemspec setup.rb test/ipv6_enabled.rb test/rack_unicorn.rb test/test_aggregate_pmq.rb test/test_inet_diag_socket.rb test/test_last_data_recv_unicorn.rb test/test_linux.rb test/test_linux_all_tcp_listen_stats.rb test/test_linux_all_tcp_listen_stats_leak.rb test/test_linux_ipv6.rb test/test_linux_middleware.rb test/test_linux_tcp_info.rb test/test_middleware.rb test/test_middleware_unicorn.rb test/test_middleware_unicorn_ipv6.rb test/test_raindrops.rb test/test_raindrops_gc.rb test/test_struct.rb test/test_watcher.rb raindrops-0.12.0/raindrops.gemspec0000644000004100000410000000220712211676722017200 0ustar www-datawww-data# -*- encoding: binary -*- ENV["VERSION"] or abort "VERSION= must be specified" manifest = File.readlines('.manifest').map! { |x| x.chomp! } test_files = manifest.grep(%r{\Atest/test_.*\.rb\z}) require 'wrongdoc' extend Wrongdoc::Gemspec name, summary, title = readme_metadata Gem::Specification.new do |s| s.name = %q{raindrops} s.version = ENV["VERSION"].dup s.authors = ["raindrops hackers"] s.date = Time.now.utc.strftime('%Y-%m-%d') s.description = readme_description s.email = %q{raindrops@librelist.org} s.extensions = %w(ext/raindrops/extconf.rb) s.extra_rdoc_files = extra_rdoc_files(manifest) s.files = manifest s.homepage = Wrongdoc.config[:rdoc_url] s.summary = summary s.rdoc_options = rdoc_options s.rubyforge_project = %q{rainbows} s.test_files = test_files s.add_development_dependency('aggregate', '~> 0.2') s.add_development_dependency('io-extra', [ '~> 1.2', '>= 1.2.3']) s.add_development_dependency('posix_mq', '~> 2.0') s.add_development_dependency('rack', '~> 1.2') s.add_development_dependency('unicorn', '>= 0.98') # s.licenses = %w(LGPLv3) # accessor not compatible with older RubyGems end raindrops-0.12.0/setup.rb0000644000004100000410000010652612211676722015330 0ustar www-datawww-data# -*- encoding: binary -*- # # setup.rb # # Copyright (c) 2000-2005 Minero Aoki # # This program is free software. # You can distribute/modify this program under the terms of # the GNU LGPL, Lesser General Public License version 2.1. # unless Enumerable.method_defined?(:map) # Ruby 1.4.6 module Enumerable alias map collect end end unless File.respond_to?(:read) # Ruby 1.6 def File.read(fname) open(fname) {|f| return f.read } end end unless Errno.const_defined?(:ENOTEMPTY) # Windows? module Errno class ENOTEMPTY # We do not raise this exception, implementation is not needed. end end end def File.binread(fname) open(fname, 'rb') {|f| return f.read } end # for corrupted Windows' stat(2) def File.dir?(path) File.directory?((path[-1,1] == '/') ? path : path + '/') end class ConfigTable include Enumerable def initialize(rbconfig) @rbconfig = rbconfig @items = [] @table = {} # options @install_prefix = nil @config_opt = nil @verbose = true @no_harm = false end attr_accessor :install_prefix attr_accessor :config_opt attr_writer :verbose def verbose? @verbose end attr_writer :no_harm def no_harm? @no_harm end def [](key) lookup(key).resolve(self) end def []=(key, val) lookup(key).set val end def names @items.map {|i| i.name } end def each(&block) @items.each(&block) end def key?(name) @table.key?(name) end def lookup(name) @table[name] or setup_rb_error "no such config item: #{name}" end def add(item) @items.push item @table[item.name] = item end def remove(name) item = lookup(name) @items.delete_if {|i| i.name == name } @table.delete_if {|name, i| i.name == name } item end def load_script(path, inst = nil) if File.file?(path) MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path end end def savefile '.config' end def load_savefile begin File.foreach(savefile()) do |line| k, v = *line.split(/=/, 2) self[k] = v.strip end rescue Errno::ENOENT setup_rb_error $!.message + "\n#{File.basename($0)} config first" end end def save @items.each {|i| i.value } File.open(savefile(), 'w') {|f| @items.each do |i| f.printf "%s=%s\n", i.name, i.value if i.value? and i.value end } end def load_standard_entries standard_entries(@rbconfig).each do |ent| add ent end end def standard_entries(rbconfig) c = rbconfig rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) major = c['MAJOR'].to_i minor = c['MINOR'].to_i teeny = c['TEENY'].to_i version = "#{major}.#{minor}" # ruby ver. >= 1.4.4? newpath_p = ((major >= 2) or ((major == 1) and ((minor >= 5) or ((minor == 4) and (teeny >= 4))))) if c['rubylibdir'] # V > 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = c['rubylibdir'] librubyverarch = c['archdir'] siteruby = c['sitedir'] siterubyver = c['sitelibdir'] siterubyverarch = c['sitearchdir'] elsif newpath_p # 1.4.4 <= V <= 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = c['sitedir'] siterubyver = "$siteruby/#{version}" siterubyverarch = "$siterubyver/#{c['arch']}" else # V < 1.4.4 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" siterubyver = siteruby siterubyverarch = "$siterubyver/#{c['arch']}" end parameterize = lambda {|path| path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') } if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } makeprog = arg.sub(/'/, '').split(/=/, 2)[1] else makeprog = 'make' end [ ExecItem.new('installdirs', 'std/site/home', 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ {|val, table| case val when 'std' table['rbdir'] = '$librubyver' table['sodir'] = '$librubyverarch' when 'site' table['rbdir'] = '$siterubyver' table['sodir'] = '$siterubyverarch' when 'home' setup_rb_error '$HOME was not set' unless ENV['HOME'] table['prefix'] = ENV['HOME'] table['rbdir'] = '$libdir/ruby' table['sodir'] = '$libdir/ruby' end }, PathItem.new('prefix', 'path', c['prefix'], 'path prefix of target environment'), PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 'the directory for commands'), PathItem.new('libdir', 'path', parameterize.call(c['libdir']), 'the directory for libraries'), PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 'the directory for shared data'), PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 'the directory for man pages'), PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 'the directory for system configuration files'), PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), 'the directory for local state data'), PathItem.new('libruby', 'path', libruby, 'the directory for ruby libraries'), PathItem.new('librubyver', 'path', librubyver, 'the directory for standard ruby libraries'), PathItem.new('librubyverarch', 'path', librubyverarch, 'the directory for standard ruby extensions'), PathItem.new('siteruby', 'path', siteruby, 'the directory for version-independent aux ruby libraries'), PathItem.new('siterubyver', 'path', siterubyver, 'the directory for aux ruby libraries'), PathItem.new('siterubyverarch', 'path', siterubyverarch, 'the directory for aux ruby binaries'), PathItem.new('rbdir', 'path', '$siterubyver', 'the directory for ruby scripts'), PathItem.new('sodir', 'path', '$siterubyverarch', 'the directory for ruby extentions'), PathItem.new('rubypath', 'path', rubypath, 'the path to set to #! line'), ProgramItem.new('rubyprog', 'name', rubypath, 'the ruby program using for installation'), ProgramItem.new('makeprog', 'name', makeprog, 'the make program to compile ruby extentions'), SelectItem.new('shebang', 'all/ruby/never', 'ruby', 'shebang line (#!) editing mode'), BoolItem.new('without-ext', 'yes/no', 'no', 'does not compile/install ruby extentions') ] end private :standard_entries def load_multipackage_entries multipackage_entries().each do |ent| add ent end end def multipackage_entries [ PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 'package names that you want to install'), PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 'package names that you do not want to install') ] end private :multipackage_entries ALIASES = { 'std-ruby' => 'librubyver', 'stdruby' => 'librubyver', 'rubylibdir' => 'librubyver', 'archdir' => 'librubyverarch', 'site-ruby-common' => 'siteruby', # For backward compatibility 'site-ruby' => 'siterubyver', # For backward compatibility 'bin-dir' => 'bindir', 'bin-dir' => 'bindir', 'rb-dir' => 'rbdir', 'so-dir' => 'sodir', 'data-dir' => 'datadir', 'ruby-path' => 'rubypath', 'ruby-prog' => 'rubyprog', 'ruby' => 'rubyprog', 'make-prog' => 'makeprog', 'make' => 'makeprog' } def fixup ALIASES.each do |ali, name| @table[ali] = @table[name] end @items.freeze @table.freeze @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ end def parse_opt(opt) m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" m.to_a[1,2] end def dllext @rbconfig['DLEXT'] end def value_config?(name) lookup(name).value? end class Item def initialize(name, template, default, desc) @name = name.freeze @template = template @value = default @default = default @description = desc end attr_reader :name attr_reader :description attr_accessor :default alias help_default default def help_opt "--#{@name}=#{@template}" end def value? true end def value @value end def resolve(table) @value.gsub(%r<\$([^/]+)>) { table[$1] } end def set(val) @value = check(val) end private def check(val) setup_rb_error "config: --#{name} requires argument" unless val val end end class BoolItem < Item def config_type 'bool' end def help_opt "--#{@name}" end private def check(val) return 'yes' unless val case val when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' else setup_rb_error "config: --#{@name} accepts only yes/no for argument" end end end class PathItem < Item def config_type 'path' end private def check(path) setup_rb_error "config: --#{@name} requires argument" unless path path[0,1] == '$' ? path : File.expand_path(path) end end class ProgramItem < Item def config_type 'program' end end class SelectItem < Item def initialize(name, selection, default, desc) super @ok = selection.split('/') end def config_type 'select' end private def check(val) unless @ok.include?(val.strip) setup_rb_error "config: use --#{@name}=#{@template} (#{val})" end val.strip end end class ExecItem < Item def initialize(name, selection, desc, &block) super name, selection, nil, desc @ok = selection.split('/') @action = block end def config_type 'exec' end def value? false end def resolve(table) setup_rb_error "$#{name()} wrongly used as option value" end undef set def evaluate(val, table) v = val.strip.downcase unless @ok.include?(v) setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" end @action.call v, table end end class PackageSelectionItem < Item def initialize(name, template, default, help_default, desc) super name, template, default, desc @help_default = help_default end attr_reader :help_default def config_type 'package' end private def check(val) unless File.dir?("packages/#{val}") setup_rb_error "config: no such package: #{val}" end val end end class MetaConfigEnvironment def initialize(config, installer) @config = config @installer = installer end def config_names @config.names end def config?(name) @config.key?(name) end def bool_config?(name) @config.lookup(name).config_type == 'bool' end def path_config?(name) @config.lookup(name).config_type == 'path' end def value_config?(name) @config.lookup(name).config_type != 'exec' end def add_config(item) @config.add item end def add_bool_config(name, default, desc) @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) end def add_path_config(name, default, desc) @config.add PathItem.new(name, 'path', default, desc) end def set_config_default(name, default) @config.lookup(name).default = default end def remove_config(name) @config.remove(name) end # For only multipackage def packages raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer @installer.packages end # For only multipackage def declare_packages(list) raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer @installer.packages = list end end end # class ConfigTable # This module requires: #verbose?, #no_harm? module FileOperations def mkdir_p(dirname, prefix = nil) dirname = prefix + File.expand_path(dirname) if prefix $stderr.puts "mkdir -p #{dirname}" if verbose? return if no_harm? # Does not check '/', it's too abnormal. dirs = File.expand_path(dirname).split(%r<(?=/)>) if /\A[a-z]:\z/i =~ dirs[0] disk = dirs.shift dirs[0] = disk + dirs[0] end dirs.each_index do |idx| path = dirs[0..idx].join('') Dir.mkdir path unless File.dir?(path) end end def rm_f(path) $stderr.puts "rm -f #{path}" if verbose? return if no_harm? force_remove_file path end def rm_rf(path) $stderr.puts "rm -rf #{path}" if verbose? return if no_harm? remove_tree path end def remove_tree(path) if File.symlink?(path) remove_file path elsif File.dir?(path) remove_tree0 path else force_remove_file path end end def remove_tree0(path) Dir.foreach(path) do |ent| next if ent == '.' next if ent == '..' entpath = "#{path}/#{ent}" if File.symlink?(entpath) remove_file entpath elsif File.dir?(entpath) remove_tree0 entpath else force_remove_file entpath end end begin Dir.rmdir path rescue Errno::ENOTEMPTY # directory may not be empty end end def move_file(src, dest) force_remove_file dest begin File.rename src, dest rescue File.open(dest, 'wb') {|f| f.write File.binread(src) } File.chmod File.stat(src).mode, dest File.unlink src end end def force_remove_file(path) begin remove_file path rescue end end def remove_file(path) File.chmod 0777, path File.unlink path end def install(from, dest, mode, prefix = nil) $stderr.puts "install #{from} #{dest}" if verbose? return if no_harm? realdest = prefix ? prefix + File.expand_path(dest) : dest realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) str = File.binread(from) if diff?(str, realdest) verbose_off { rm_f realdest if File.exist?(realdest) } File.open(realdest, 'wb') {|f| f.write str } File.chmod mode, realdest File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| if prefix f.puts realdest.sub(prefix, '') else f.puts realdest end } end end def diff?(new_content, path) return true unless File.exist?(path) new_content != File.binread(path) end def command(*args) $stderr.puts args.join(' ') if verbose? system(*args) or raise RuntimeError, "system(#{args.map{|a| a.inspect }.join(' ')}) failed" end def ruby(*args) command config('rubyprog'), *args end def make(task = nil) command(*[config('makeprog'), task].compact) end def extdir?(dir) File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") end def files_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.file?("#{dir}/#{ent}") } } end DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) def directories_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT } end end # This module requires: #srcdir_root, #objdir_root, #relpath module HookScriptAPI def get_config(key) @config[key] end alias config get_config # obsolete: use metaconfig to change configuration def set_config(key, val) @config[key] = val end # # srcdir/objdir (works only in the package directory) # def curr_srcdir "#{srcdir_root()}/#{relpath()}" end def curr_objdir "#{objdir_root()}/#{relpath()}" end def srcfile(path) "#{curr_srcdir()}/#{path}" end def srcexist?(path) File.exist?(srcfile(path)) end def srcdirectory?(path) File.dir?(srcfile(path)) end def srcfile?(path) File.file?(srcfile(path)) end def srcentries(path = '.') Dir.open("#{curr_srcdir()}/#{path}") {|d| return d.to_a - %w(. ..) } end def srcfiles(path = '.') srcentries(path).select {|fname| File.file?(File.join(curr_srcdir(), path, fname)) } end def srcdirectories(path = '.') srcentries(path).select {|fname| File.dir?(File.join(curr_srcdir(), path, fname)) } end end class ToplevelInstaller Version = '3.4.1' Copyright = 'Copyright (c) 2000-2005 Minero Aoki' TASKS = [ [ 'all', 'do config, setup, then install' ], [ 'config', 'saves your configurations' ], [ 'show', 'shows current configuration' ], [ 'setup', 'compiles ruby extentions and others' ], [ 'install', 'installs files' ], [ 'test', 'run all tests in test/' ], [ 'clean', "does `make clean' for each extention" ], [ 'distclean',"does `make distclean' for each extention" ] ] def ToplevelInstaller.invoke config = ConfigTable.new(load_rbconfig()) config.load_standard_entries config.load_multipackage_entries if multipackage? config.fixup klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) klass.new(File.dirname($0), config).invoke end def ToplevelInstaller.multipackage? File.dir?(File.dirname($0) + '/packages') end def ToplevelInstaller.load_rbconfig if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } ARGV.delete(arg) load File.expand_path(arg.split(/=/, 2)[1]) $".push 'rbconfig.rb' else require 'rbconfig' end ::Config::CONFIG end def initialize(ardir_root, config) @ardir = File.expand_path(ardir_root) @config = config # cache @valid_task_re = nil end def config(key) @config[key] end def inspect "#<#{self.class} #{__id__()}>" end def invoke run_metaconfigs case task = parsearg_global() when nil, 'all' parsearg_config init_installers exec_config exec_setup exec_install else case task when 'config', 'test' ; when 'clean', 'distclean' @config.load_savefile if File.exist?(@config.savefile) else @config.load_savefile end __send__ "parsearg_#{task}" init_installers __send__ "exec_#{task}" end end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig" end def init_installers @installer = Installer.new(@config, @ardir, File.expand_path('.')) end # # Hook Script API bases # def srcdir_root @ardir end def objdir_root '.' end def relpath '.' end # # Option Parsing # def parsearg_global while arg = ARGV.shift case arg when /\A\w+\z/ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) return arg when '-q', '--quiet' @config.verbose = false when '--verbose' @config.verbose = true when '--help' print_usage $stdout exit 0 when '--version' puts "#{File.basename($0)} version #{Version}" exit 0 when '--copyright' puts Copyright exit 0 else setup_rb_error "unknown global option '#{arg}'" end end nil end def valid_task?(t) valid_task_re() =~ t end def valid_task_re @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ end def parsearg_no_options unless ARGV.empty? task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" end end alias parsearg_show parsearg_no_options alias parsearg_setup parsearg_no_options alias parsearg_test parsearg_no_options alias parsearg_clean parsearg_no_options alias parsearg_distclean parsearg_no_options def parsearg_config evalopt = [] set = [] @config.config_opt = [] while i = ARGV.shift if /\A--?\z/ =~ i @config.config_opt = ARGV.dup break end name, value = *@config.parse_opt(i) if @config.value_config?(name) @config[name] = value else evalopt.push [name, value] end set.push name end evalopt.each do |name, value| @config.lookup(name).evaluate value, @config end # Check if configuration is valid set.each do |n| @config[n] if @config.value_config?(n) end end def parsearg_install @config.no_harm = false @config.install_prefix = '' while a = ARGV.shift case a when '--no-harm' @config.no_harm = true when /\A--prefix=/ path = a.split(/=/, 2)[1] path = File.expand_path(path) unless path[0,1] == '/' @config.install_prefix = path else setup_rb_error "install: unknown option #{a}" end end end def print_usage(out) out.puts 'Typical Installation Procedure:' out.puts " $ ruby #{File.basename $0} config" out.puts " $ ruby #{File.basename $0} setup" out.puts " # ruby #{File.basename $0} install (may require root privilege)" out.puts out.puts 'Detailed Usage:' out.puts " ruby #{File.basename $0} " out.puts " ruby #{File.basename $0} [] []" fmt = " %-24s %s\n" out.puts out.puts 'Global options:' out.printf fmt, '-q,--quiet', 'suppress message outputs' out.printf fmt, ' --verbose', 'output messages verbosely' out.printf fmt, ' --help', 'print this message' out.printf fmt, ' --version', 'print version and quit' out.printf fmt, ' --copyright', 'print copyright and quit' out.puts out.puts 'Tasks:' TASKS.each do |name, desc| out.printf fmt, name, desc end fmt = " %-24s %s [%s]\n" out.puts out.puts 'Options for CONFIG or ALL:' @config.each do |item| out.printf fmt, item.help_opt, item.description, item.help_default end out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" out.puts out.puts 'Options for INSTALL:' out.printf fmt, '--no-harm', 'only display what to do if given', 'off' out.printf fmt, '--prefix=path', 'install path prefix', '' out.puts end # # Task Handlers # def exec_config @installer.exec_config @config.save # must be final end def exec_setup @installer.exec_setup end def exec_install @installer.exec_install end def exec_test @installer.exec_test end def exec_show @config.each do |i| printf "%-20s %s\n", i.name, i.value if i.value? end end def exec_clean @installer.exec_clean end def exec_distclean @installer.exec_distclean end end # class ToplevelInstaller class ToplevelInstallerMulti < ToplevelInstaller include FileOperations def initialize(ardir_root, config) super @packages = directories_of("#{@ardir}/packages") raise 'no package exists' if @packages.empty? @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig", self @packages.each do |name| @config.load_script "#{@ardir}/packages/#{name}/metaconfig" end end attr_reader :packages def packages=(list) raise 'package list is empty' if list.empty? list.each do |name| raise "directory packages/#{name} does not exist"\ unless File.dir?("#{@ardir}/packages/#{name}") end @packages = list end def init_installers @installers = {} @packages.each do |pack| @installers[pack] = Installer.new(@config, "#{@ardir}/packages/#{pack}", "packages/#{pack}") end with = extract_selection(config('with')) without = extract_selection(config('without')) @selected = @installers.keys.select {|name| (with.empty? or with.include?(name)) \ and not without.include?(name) } end def extract_selection(list) a = list.split(/,/) a.each do |name| setup_rb_error "no such package: #{name}" unless @installers.key?(name) end a end def print_usage(f) super f.puts 'Inluded packages:' f.puts ' ' + @packages.sort.join(' ') f.puts end # # Task Handlers # def exec_config run_hook 'pre-config' each_selected_installers {|inst| inst.exec_config } run_hook 'post-config' @config.save # must be final end def exec_setup run_hook 'pre-setup' each_selected_installers {|inst| inst.exec_setup } run_hook 'post-setup' end def exec_install run_hook 'pre-install' each_selected_installers {|inst| inst.exec_install } run_hook 'post-install' end def exec_test run_hook 'pre-test' each_selected_installers {|inst| inst.exec_test } run_hook 'post-test' end def exec_clean rm_f @config.savefile run_hook 'pre-clean' each_selected_installers {|inst| inst.exec_clean } run_hook 'post-clean' end def exec_distclean rm_f @config.savefile run_hook 'pre-distclean' each_selected_installers {|inst| inst.exec_distclean } run_hook 'post-distclean' end # # lib # def each_selected_installers Dir.mkdir 'packages' unless File.dir?('packages') @selected.each do |pack| $stderr.puts "Processing the package `#{pack}' ..." if verbose? Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") Dir.chdir "packages/#{pack}" yield @installers[pack] Dir.chdir '../..' end end def run_hook(id) @root_installer.run_hook id end # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end end # class ToplevelInstallerMulti class Installer FILETYPES = %w( bin lib ext data conf man ) include FileOperations include HookScriptAPI def initialize(config, srcroot, objroot) @config = config @srcdir = File.expand_path(srcroot) @objdir = File.expand_path(objroot) @currdir = '.' end def inspect "#<#{self.class} #{File.basename(@srcdir)}>" end def noop(rel) end # # Hook Script API base methods # def srcdir_root @srcdir end def objdir_root @objdir end def relpath @currdir end # # Config Access # # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end def verbose_off begin save, @config.verbose = @config.verbose?, false yield ensure @config.verbose = save end end # # TASK config # def exec_config exec_task_traverse 'config' end alias config_dir_bin noop alias config_dir_lib noop def config_dir_ext(rel) extconf if extdir?(curr_srcdir()) end alias config_dir_data noop alias config_dir_conf noop alias config_dir_man noop def extconf ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt end # # TASK setup # def exec_setup exec_task_traverse 'setup' end def setup_dir_bin(rel) files_of(curr_srcdir()).each do |fname| update_shebang_line "#{curr_srcdir()}/#{fname}" end end alias setup_dir_lib noop def setup_dir_ext(rel) make if extdir?(curr_srcdir()) end alias setup_dir_data noop alias setup_dir_conf noop alias setup_dir_man noop def update_shebang_line(path) return if no_harm? return if config('shebang') == 'never' old = Shebang.load(path) if old $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 new = new_shebang(old) return if new.to_s == old.to_s else return unless config('shebang') == 'all' new = Shebang.new(config('rubypath')) end $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? open_atomic_writer(path) {|output| File.open(path, 'rb') {|f| f.gets if old # discard output.puts new.to_s output.print f.read } } end def new_shebang(old) if /\Aruby/ =~ File.basename(old.cmd) Shebang.new(config('rubypath'), old.args) elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' Shebang.new(config('rubypath'), old.args[1..-1]) else return old unless config('shebang') == 'all' Shebang.new(config('rubypath')) end end def open_atomic_writer(path, &block) tmpfile = File.basename(path) + '.tmp' begin File.open(tmpfile, 'wb', &block) File.rename tmpfile, File.basename(path) ensure File.unlink tmpfile if File.exist?(tmpfile) end end class Shebang def Shebang.load(path) line = nil File.open(path) {|f| line = f.gets } return nil unless /\A#!/ =~ line parse(line) end def Shebang.parse(line) cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') new(cmd, args) end def initialize(cmd, args = []) @cmd = cmd @args = args end attr_reader :cmd attr_reader :args def to_s "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") end end # # TASK install # def exec_install rm_f 'InstalledFiles' exec_task_traverse 'install' end def install_dir_bin(rel) install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 end def install_dir_lib(rel) install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 end def install_dir_ext(rel) return unless extdir?(curr_srcdir()) install_files rubyextentions('.'), "#{config('sodir')}/#{File.dirname(rel)}", 0555 end def install_dir_data(rel) install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 end def install_dir_conf(rel) # FIXME: should not remove current config files # (rename previous file to .old/.org) install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 end def install_dir_man(rel) install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 end def install_files(list, dest, mode) mkdir_p dest, @config.install_prefix list.each do |fname| install fname, dest, mode, @config.install_prefix end end def libfiles glob_reject(%w(*.y *.output), targetfiles()) end def rubyextentions(dir) ents = glob_select("*.#{@config.dllext}", targetfiles()) if ents.empty? setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" end ents end def targetfiles mapdir(existfiles() - hookfiles()) end def mapdir(ents) ents.map {|ent| if File.exist?(ent) then ent # objdir else "#{curr_srcdir()}/#{ent}" # srcdir end } end # picked up many entries from cvs-1.11.1/src/ignore.c JUNK_FILES = %w( core RCSLOG tags TAGS .make.state .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb *~ *.old *.bak *.BAK *.orig *.rej _$* *$ *.org *.in .* ) def existfiles glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) end def hookfiles %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| %w( config setup install clean ).map {|t| sprintf(fmt, t) } }.flatten end def glob_select(pat, ents) re = globs2re([pat]) ents.select {|ent| re =~ ent } end def glob_reject(pats, ents) re = globs2re(pats) ents.reject {|ent| re =~ ent } end GLOB2REGEX = { '.' => '\.', '$' => '\$', '#' => '\#', '*' => '.*' } def globs2re(pats) /\A(?:#{ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') })\z/ end # # TASK test # TESTDIR = 'test' def exec_test unless File.directory?('test') $stderr.puts 'no test in this package' if verbose? return end $stderr.puts 'Running tests...' if verbose? begin require 'test/unit' rescue LoadError setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' end runner = Test::Unit::AutoRunner.new(true) runner.to_run << TESTDIR runner.run end # # TASK clean # def exec_clean exec_task_traverse 'clean' rm_f @config.savefile rm_f 'InstalledFiles' end alias clean_dir_bin noop alias clean_dir_lib noop alias clean_dir_data noop alias clean_dir_conf noop alias clean_dir_man noop def clean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'clean' if File.file?('Makefile') end # # TASK distclean # def exec_distclean exec_task_traverse 'distclean' rm_f @config.savefile rm_f 'InstalledFiles' end alias distclean_dir_bin noop alias distclean_dir_lib noop def distclean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'distclean' if File.file?('Makefile') end alias distclean_dir_data noop alias distclean_dir_conf noop alias distclean_dir_man noop # # Traversing # def exec_task_traverse(task) run_hook "pre-#{task}" FILETYPES.each do |type| if type == 'ext' and config('without-ext') == 'yes' $stderr.puts 'skipping ext/* by user option' if verbose? next end traverse task, type, "#{task}_dir_#{type}" end run_hook "post-#{task}" end def traverse(task, rel, mid) dive_into(rel) { run_hook "pre-#{task}" __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') directories_of(curr_srcdir()).each do |d| traverse task, "#{rel}/#{d}", mid end run_hook "post-#{task}" } end def dive_into(rel) return unless File.dir?("#{@srcdir}/#{rel}") dir = File.basename(rel) Dir.mkdir dir unless File.dir?(dir) prevdir = Dir.pwd Dir.chdir dir $stderr.puts '---> ' + rel if verbose? @currdir = rel yield Dir.chdir prevdir $stderr.puts '<--- ' + rel if verbose? @currdir = File.dirname(rel) end def run_hook(id) path = [ "#{curr_srcdir()}/#{id}", "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } return unless path begin instance_eval File.read(path), path, 1 rescue raise if $DEBUG setup_rb_error "hook #{path} failed:\n" + $!.message end end end # class Installer class SetupError < StandardError; end def setup_rb_error(msg) raise SetupError, msg end if $0 == __FILE__ begin ToplevelInstaller.invoke rescue SetupError raise if $DEBUG $stderr.puts $!.message $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." exit 1 end end raindrops-0.12.0/COPYING0000644000004100000410000001672512211676722014677 0ustar www-datawww-data GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. raindrops-0.12.0/GIT-VERSION-FILE0000644000004100000410000000002512211676722016034 0ustar www-datawww-dataGIT_VERSION = 0.12.0 raindrops-0.12.0/GIT-VERSION-GEN0000755000004100000410000000133512211676722015736 0ustar www-datawww-data#!/bin/sh GVF=GIT-VERSION-FILE DEF_VER=v0.12.0 LF=' ' # First see if there is a version file (included in release tarballs), # then try git-describe, then default. if test -f version then VN=$(cat version) || VN="$DEF_VER" elif test -d .git -o -f .git && VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; v[0-9]*) git update-index -q --refresh test -z "$(git diff-index --name-only HEAD --)" || VN="$VN-dirty" ;; esac then VN=$(echo "$VN" | sed -e 's/-/./g'); else VN="$DEF_VER" fi VN=$(expr "$VN" : v*'\(.*\)') if test -r $GVF then VC=$(sed -e 's/^GIT_VERSION = //' <$GVF) else VC=unset fi test "$VN" = "$VC" || { echo >&2 "GIT_VERSION = $VN" echo "GIT_VERSION = $VN" >$GVF } raindrops-0.12.0/.gitignore0000644000004100000410000000017012211676722015617 0ustar www-datawww-data*.o *.so *.log *.rbc Makefile /GIT-VERSION-FILE /local.mk /NEWS /ChangeLog /.manifest /man /pkg /doc /LATEST /tmp /.rbx raindrops-0.12.0/lib/0000755000004100000410000000000012211676722014377 5ustar www-datawww-dataraindrops-0.12.0/lib/raindrops/0000755000004100000410000000000012211676722016400 5ustar www-datawww-dataraindrops-0.12.0/lib/raindrops/middleware.rb0000644000004100000410000001152112211676722021042 0ustar www-datawww-data# -*- encoding: binary -*- require 'raindrops' # Raindrops::Middleware is Rack middleware that allows snapshotting # current activity from an HTTP request. For all operating systems, # it returns at least the following fields: # # * calling - the number of application dispatchers on your machine # * writing - the number of clients being written to on your machine # # Additional fields are available for \Linux users. # # It should be loaded at the top of Rack middleware stack before other # middlewares for maximum accuracy. # # === Usage (Rainbows!/Unicorn preload_app=false) # # If you're using preload_app=false (the default) in your Rainbows!/Unicorn # config file, you'll need to create the global Stats object before # forking. # # require 'raindrops' # $stats ||= Raindrops::Middleware::Stats.new # # In your Rack config.ru: # # use Raindrops::Middleware, :stats => $stats # # === Usage (Rainbows!/Unicorn preload_app=true) # # If you're using preload_app=true in your Rainbows!/Unicorn # config file, just add the middleware to your stack: # # In your Rack config.ru: # # use Raindrops::Middleware # # === Linux-only extras! # # To get bound listener statistics under \Linux, you need to specify the # listener names for your server. You can even include listen sockets for # *other* servers on the same machine. This can be handy for monitoring # your nginx proxy as well. # # In your Rack config.ru, just pass the :listeners argument as an array of # strings (along with any other arguments). You can specify any # combination of TCP or Unix domain socket names: # # use Raindrops::Middleware, :listeners => %w(0.0.0.0:80 /tmp/.sock) # # If you're running Unicorn 0.98.0 or later, you don't have to pass in # the :listeners array, Raindrops will automatically detect the listeners # used by Unicorn master process. This does not detect listeners in # different processes, of course. # # The response body includes the following stats for each listener # (see also Raindrops::ListenStats): # # * active - total number of active clients on that listener # * queued - total number of queued (pre-accept()) clients on that listener # # = Demo Server # # There is a server running this middleware (and Watcher) at # http://raindrops-demo.bogomips.org/_raindrops # # Also check out the Watcher demo at http://raindrops-demo.bogomips.org/ # # The demo server is only limited to 30 users, so be sure not to abuse it # by using the /tail/ endpoint too much. # class Raindrops::Middleware attr_accessor :app, :stats, :path, :tcp, :unix # :nodoc: # A Raindrops::Struct used to count the number of :calling and :writing # clients. This struct is intended to be shared across multiple processes # and both counters are updated atomically. # # This is supported on all operating systems supported by Raindrops class Stats < Raindrops::Struct.new(:calling, :writing) end # :stopdoc: PATH_INFO = "PATH_INFO" require "raindrops/middleware/proxy" # :startdoc: # +app+ may be any Rack application, this middleware wraps it. # +opts+ is a hash that understands the following members: # # * :stats - Raindrops::Middleware::Stats struct (default: Stats.new) # * :path - HTTP endpoint used for reading the stats (default: "/_raindrops") # * :listeners - array of host:port or socket paths (default: from Unicorn) def initialize(app, opts = {}) @app = app @stats = opts[:stats] || Stats.new @path = opts[:path] || "/_raindrops" tmp = opts[:listeners] if tmp.nil? && defined?(Unicorn) && Unicorn.respond_to?(:listener_names) tmp = Unicorn.listener_names end @tcp = @unix = nil if tmp @tcp = tmp.grep(/\A.+:\d+\z/) @unix = tmp.grep(%r{\A/}) @tcp = nil if @tcp.empty? @unix = nil if @unix.empty? end end # standard Rack endpoint def call(env) # :nodoc: env[PATH_INFO] == @path and return stats_response begin @stats.incr_calling status, headers, body = @app.call(env) rv = [ status, headers, Proxy.new(body, @stats) ] # the Rack server will start writing headers soon after this method @stats.incr_writing rv ensure @stats.decr_calling end end def stats_response # :nodoc: body = "calling: #{@stats.calling}\n" \ "writing: #{@stats.writing}\n" if defined?(Raindrops::Linux.tcp_listener_stats) Raindrops::Linux.tcp_listener_stats(@tcp).each do |addr,stats| body << "#{addr} active: #{stats.active}\n" \ "#{addr} queued: #{stats.queued}\n" end if @tcp Raindrops::Linux.unix_listener_stats(@unix).each do |addr,stats| body << "#{addr} active: #{stats.active}\n" \ "#{addr} queued: #{stats.queued}\n" end if @unix end headers = { "Content-Type" => "text/plain", "Content-Length" => body.size.to_s, } [ 200, headers, [ body ] ] end end raindrops-0.12.0/lib/raindrops/linux.rb0000644000004100000410000000561512211676722020073 0ustar www-datawww-data# -*- encoding: binary -*- # 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 require 'pathname' module Raindrops::Linux # 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_ARGS = %w(/proc/net/unix) defined?(::Encoding) and PROC_NET_UNIX_ARGS.push({ :encoding => "binary" }) # 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. 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 defined?(Encoding) if File.symlink?(path) link = path path = Pathname.new(link).realpath.to_s 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).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 module_function :unix_listener_stats end # Raindrops::Linux raindrops-0.12.0/lib/raindrops/watcher.rb0000644000004100000410000003111012211676722020356 0ustar www-datawww-data# -*- encoding: binary -*- require "thread" require "time" require "socket" require "rack" require "aggregate" # Raindrops::Watcher is a stand-alone Rack application for watching # any number of TCP and UNIX listeners (all of them by default). # # It depends on the {Aggregate RubyGem}[http://rubygems.org/gems/aggregate] # # In your Rack config.ru: # # run Raindrops::Watcher(options = {}) # # It takes the following options hash: # # - :listeners - an array of listener names, (e.g. %w(0.0.0.0:80 /tmp/sock)) # - :delay - interval between stats updates in seconds (default: 1) # # Raindrops::Watcher is compatible any thread-safe/thread-aware Rack # middleware. It does not work well with multi-process web servers # but can be used to monitor them. It consumes minimal resources # with the default :delay. # # == HTTP endpoints # # === GET / # # Returns an HTML summary listing of all listen interfaces watched on # # === GET /active/$LISTENER.txt # # Returns a plain text summary + histogram with X-* HTTP headers for # active connections. # # e.g.: curl http://raindrops-demo.bogomips.org/active/0.0.0.0%3A80.txt # # === GET /active/$LISTENER.html # # Returns an HTML summary + histogram with X-* HTTP headers for # active connections. # # e.g.: curl http://raindrops-demo.bogomips.org/active/0.0.0.0%3A80.html # # === GET /queued/$LISTENER.txt # # Returns a plain text summary + histogram with X-* HTTP headers for # queued connections. # # e.g.: curl http://raindrops-demo.bogomips.org/queued/0.0.0.0%3A80.txt # # === GET /queued/$LISTENER.html # # Returns an HTML summary + histogram with X-* HTTP headers for # queued connections. # # e.g.: curl http://raindrops-demo.bogomips.org/queued/0.0.0.0%3A80.html # # === POST /reset/$LISTENER # # Resets the active and queued statistics for the given listener. # # === GET /tail/$LISTENER.txt?active_min=1&queued_min=1 # # Streams chunked a response to the client. # Interval is the preconfigured +:delay+ of the application (default 1 second) # # The response is plain text in the following format: # # ISO8601_TIMESTAMP LISTENER_NAME ACTIVE_COUNT QUEUED_COUNT LINEFEED # # Query parameters: # # - active_min - do not stream a line until this active count is reached # - queued_min - do not stream a line until this queued count is reached # # == Response headers (mostly the same names as Raindrops::LastDataRecv) # # - X-Count - number of samples polled # - X-Last-Reset - date since the last reset # # The following headers are only present if X-Count is greater than one. # # - X-Min - lowest number of connections recorded # - X-Max - highest number of connections recorded # - X-Mean - mean number of connections recorded # - X-Std-Dev - standard deviation of connection count # - X-Outliers-Low - number of low outliers (hopefully many for queued) # - X-Outliers-High - number of high outliers (hopefully zero for queued) # - X-Current - current number of connections # - X-First-Peak-At - date of when X-Max was first reached # - X-Last-Peak-At - date of when X-Max was last reached # # = Demo Server # # There is a server running this app at http://raindrops-demo.bogomips.org/ # The Raindrops::Middleware demo is also accessible at # http://raindrops-demo.bogomips.org/_raindrops # # The demo server is only limited to 30 users, so be sure not to abuse it # by using the /tail/ endpoint too much. class Raindrops::Watcher # :stopdoc: attr_reader :snapshot include Rack::Utils include Raindrops::Linux DOC_URL = "http://raindrops.bogomips.org/Raindrops/Watcher.html" Peak = Struct.new(:first, :last) def initialize(opts = {}) @tcp_listeners = @unix_listeners = nil if l = opts[:listeners] tcp, unix = [], [] Array(l).each { |addr| (addr =~ %r{\A/} ? unix : tcp) << addr } unless tcp.empty? && unix.empty? @tcp_listeners = tcp @unix_listeners = unix end end @agg_class = opts[:agg_class] || Aggregate @start_time = Time.now.utc @active = Hash.new { |h,k| h[k] = @agg_class.new } @queued = Hash.new { |h,k| h[k] = @agg_class.new } @resets = Hash.new { |h,k| h[k] = @start_time } @peak_active = Hash.new { |h,k| h[k] = Peak.new(@start_time, @start_time) } @peak_queued = Hash.new { |h,k| h[k] = Peak.new(@start_time, @start_time) } @snapshot = [ @start_time, {} ] @delay = opts[:delay] || 1 @lock = Mutex.new @start = Mutex.new @cond = ConditionVariable.new @thr = nil end def hostname Socket.gethostname end # rack endpoint def call(env) @start.synchronize { @thr ||= aggregator_thread(env["rack.logger"]) } case env["REQUEST_METHOD"] when "GET" get env when "HEAD" r = get(env) r[2] = [] r when "POST" post env else Rack::Response.new(["Method Not Allowed"], 405).finish end end def aggregate!(agg_hash, peak_hash, addr, number, now) agg = agg_hash[addr] if (max = agg.max) && number > 0 && number >= max peak = peak_hash[addr] peak.first = now if number > max peak.last = now end agg << number end def aggregator_thread(logger) # :nodoc: @socket = sock = Raindrops::InetDiagSocket.new thr = Thread.new do begin combined = tcp_listener_stats(@tcp_listeners, sock) combined.merge!(unix_listener_stats(@unix_listeners)) @lock.synchronize do now = Time.now.utc combined.each do |addr,stats| aggregate!(@active, @peak_active, addr, stats.active, now) aggregate!(@queued, @peak_queued, addr, stats.queued, now) end @snapshot = [ now, combined ] @cond.broadcast end rescue => e logger.error "#{e.class} #{e.inspect}" end while sleep(@delay) && @socket sock.close end wait_snapshot thr end def non_existent_stats(time) [ time, @start_time, @agg_class.new, 0, Peak.new(@start_time, @start_time) ] end def active_stats(addr) # :nodoc: @lock.synchronize do time, combined = @snapshot stats = combined[addr] or return non_existent_stats(time) tmp, peak = @active[addr], @peak_active[addr] [ time, @resets[addr], tmp.dup, stats.active, peak ] end end def queued_stats(addr) # :nodoc: @lock.synchronize do time, combined = @snapshot stats = combined[addr] or return non_existent_stats(time) tmp, peak = @queued[addr], @peak_queued[addr] [ time, @resets[addr], tmp.dup, stats.queued, peak ] end end def wait_snapshot @lock.synchronize do @cond.wait @lock @snapshot end end def std_dev(agg) agg.std_dev.to_s rescue Errno::EDOM "NaN" end def agg_to_hash(reset_at, agg, current, peak) { "X-Count" => agg.count.to_s, "X-Min" => agg.min.to_s, "X-Max" => agg.max.to_s, "X-Mean" => agg.mean.to_s, "X-Std-Dev" => std_dev(agg), "X-Outliers-Low" => agg.outliers_low.to_s, "X-Outliers-High" => agg.outliers_high.to_s, "X-Last-Reset" => reset_at.httpdate, "X-Current" => current.to_s, "X-First-Peak-At" => peak.first.httpdate, "X-Last-Peak-At" => peak.last.httpdate, } end def histogram_txt(agg) updated_at, reset_at, agg, current, peak = *agg headers = agg_to_hash(reset_at, agg, current, peak) body = agg.to_s headers["Content-Type"] = "text/plain" headers["Expires"] = (updated_at + @delay).httpdate headers["Content-Length"] = bytesize(body).to_s [ 200, headers, [ body ] ] end def histogram_html(agg, addr) updated_at, reset_at, agg, current, peak = *agg headers = agg_to_hash(reset_at, agg, current, peak) body = "" \ "#{hostname} - #{escape_html addr}" \ "" << headers.map { |k,v| "" }.join << "
#{k.gsub(/^X-/, '')}#{v}
#{escape_html agg}
" \ "
" \ "
" \ "" headers["Content-Type"] = "text/html" headers["Expires"] = (updated_at + @delay).httpdate headers["Content-Length"] = bytesize(body).to_s [ 200, headers, [ body ] ] end def get(env) retried = false begin case env["PATH_INFO"] when "/" index when %r{\A/active/(.+)\.txt\z} histogram_txt(active_stats(unescape($1))) when %r{\A/active/(.+)\.html\z} addr = unescape $1 histogram_html(active_stats(addr), addr) when %r{\A/queued/(.+)\.txt\z} histogram_txt(queued_stats(unescape($1))) when %r{\A/queued/(.+)\.html\z} addr = unescape $1 histogram_html(queued_stats(addr), addr) when %r{\A/tail/(.+)\.txt\z} tail(unescape($1), env) else not_found end rescue Errno::EDOM raise if retried retried = true wait_snapshot retry end end def not_found Rack::Response.new(["Not Found"], 404).finish end def post(env) case env["PATH_INFO"] when %r{\A/reset/(.+)\z} reset!(env, unescape($1)) else not_found end end def reset!(env, addr) @lock.synchronize do @active.include?(addr) or return not_found @active.delete addr @queued.delete addr @resets[addr] = Time.now.utc @cond.wait @lock end req = Rack::Request.new(env) res = Rack::Response.new url = req.referer || "#{req.host_with_port}/" res.redirect(url) res["Content-Type"] = "text/plain" res.write "Redirecting to #{url}" res.finish end def index updated_at, all = snapshot headers = { "Content-Type" => "text/html", "Last-Modified" => updated_at.httpdate, "Expires" => (updated_at + @delay).httpdate, } body = "" \ "#{hostname} - all interfaces" \ "

Updated at #{updated_at.iso8601}

" \ "" \ "" \ "" << all.sort do |a,b| a[0] <=> b[0] # sort by addr end.map do |addr,stats| e_addr = escape addr "" \ "" \ "" \ "" \ "" \ "" \ end.join << "
addressactivequeuedreset
#{escape_html addr}#{stats.active}#{stats.queued}
" \ "
" \ "

" \ "This is running the #{self.class} service, see " \ "#{DOC_URL} " \ "for more information and options." \ "

" \ "" headers["Content-Length"] = bytesize(body).to_s [ 200, headers, [ body ] ] end def tail(addr, env) Tailer.new(self, addr, env).finish end # This is the response body returned for "/tail/$ADDRESS.txt". This # must use a multi-threaded Rack server with streaming response support. # It is an internal class and not expected to be used directly class Tailer def initialize(rdmon, addr, env) # :nodoc: @rdmon = rdmon @addr = addr q = Rack::Utils.parse_query env["QUERY_STRING"] @active_min = q["active_min"].to_i @queued_min = q["queued_min"].to_i len = Rack::Utils.bytesize(addr) len = 35 if len > 35 @fmt = "%20s % #{len}s % 10u % 10u\n" case env["HTTP_VERSION"] when "HTTP/1.0", nil @chunk = false else @chunk = true end end def finish headers = { "Content-Type" => "text/plain", "Cache-Control" => "no-transform", "Expires" => Time.at(0).httpdate, } headers["Transfer-Encoding"] = "chunked" if @chunk [ 200, headers, self ] end # called by the Rack server def each # :nodoc: begin time, all = @rdmon.wait_snapshot stats = all[@addr] or next stats.queued >= @queued_min or next stats.active >= @active_min or next body = sprintf(@fmt, time.iso8601, @addr, stats.active, stats.queued) body = "#{body.size.to_s(16)}\r\n#{body}\r\n" if @chunk yield body end while true yield "0\r\n\r\n" if @chunk end end # shuts down the background thread, only for tests def shutdown @socket = nil @thr.join if @thr @thr = nil end # :startdoc: end raindrops-0.12.0/lib/raindrops/aggregate.rb0000644000004100000410000000045612211676722020660 0ustar www-datawww-data# -*- encoding: binary -*- # # raindrops may use the {aggregate}[http://github.com/josephruscio/aggregate] # RubyGem to aggregate statistics from TCP_Info lookups. module Raindrops::Aggregate autoload :PMQ, "raindrops/aggregate/pmq" autoload :LastDataRecv, "raindrops/aggregate/last_data_recv" end raindrops-0.12.0/lib/raindrops/aggregate/0000755000004100000410000000000012211676722020326 5ustar www-datawww-dataraindrops-0.12.0/lib/raindrops/aggregate/last_data_recv.rb0000644000004100000410000000425212211676722023631 0ustar www-datawww-data# -*- encoding: binary -*- require "socket" # # # This module is used to extend TCPServer and Kgio::TCPServer objects # and aggregate +last_data_recv+ times for all accepted clients. It # is designed to be used with Raindrops::LastDataRecv Rack application # but can be easily changed to work with other stats collection devices. # # Methods wrapped include: # - TCPServer#accept # - TCPServer#accept_nonblock # - Kgio::TCPServer#kgio_accept # - Kgio::TCPServer#kgio_tryaccept module Raindrops::Aggregate::LastDataRecv # :stopdoc: TCP_Info = Raindrops::TCP_Info # :startdoc: # The integer value of +last_data_recv+ is sent to this object. # This is usually a duck type compatible with the \Aggregate class, # but can be *anything* that accepts the *<<* method. attr_accessor :raindrops_aggregate @@default_aggregate = nil # By default, this is a Raindrops::Aggregate::PMQ object # It may be anything that responds to *<<* def self.default_aggregate @@default_aggregate ||= Raindrops::Aggregate::PMQ.new end # Assign any object that responds to *<<* def self.default_aggregate=(agg) @@default_aggregate = agg end # automatically extends any TCPServer objects used by Unicorn def self.cornify! Unicorn::HttpServer::LISTENERS.each do |sock| sock.extend(self) if TCPServer === sock end end # each extended object needs to have TCP_DEFER_ACCEPT enabled # for accuracy. def self.extended(obj) obj.raindrops_aggregate = default_aggregate # obj.setsockopt Socket::SOL_TCP, tcp_defer_accept = 9, seconds = 60 obj.setsockopt Socket::SOL_TCP, 9, 60 end # :stopdoc: def kgio_tryaccept(*args) count! super end def kgio_accept(*args) count! super end def accept count! super end def accept_nonblock count! super end # :startdoc: # The +last_data_recv+ member of Raindrops::TCP_Info can be used to # infer the time a client spent in the listen queue before it was # accepted. # # We require TCP_DEFER_ACCEPT on the listen socket for # +last_data_recv+ to be accurate def count!(io) if io x = TCP_Info.new(io) @raindrops_aggregate << x.last_data_recv end io end end raindrops-0.12.0/lib/raindrops/aggregate/pmq.rb0000644000004100000410000001532712211676722021460 0ustar www-datawww-data# -*- encoding: binary -*- require "tempfile" require "aggregate" require "posix_mq" require "fcntl" require "io/extra" require "thread" # \Aggregate + POSIX message queues support for Ruby 1.9 and \Linux # # This class is duck-type compatible with \Aggregate and allows us to # aggregate and share statistics from multiple processes/threads aided # POSIX message queues. This is designed to be used with the # Raindrops::LastDataRecv Rack application, but can be used independently # on compatible Runtimes. # # Unlike the core of raindrops, this is only supported on Ruby 1.9 and # Linux 2.6. Using this class requires the following additional RubyGems # or libraries: # # * aggregate (tested with 0.2.2) # * io-extra (tested with 1.2.3) # * posix_mq (tested with 1.0.0) # # == Design # # There is one master thread which aggregates statistics. Individual # worker processes or threads will write to a shared POSIX message # queue (default: "/raindrops") that the master reads from. At a # predefined interval, the master thread will write out to a shared, # anonymous temporary file that workers may read from # # Setting +:worker_interval+ and +:master_interval+ to +1+ will result # in perfect accuracy but at the cost of a high synchronization # overhead. Larger intervals mean less frequent messaging for higher # performance but lower accuracy. class Raindrops::Aggregate::PMQ # :stopdoc: # These constants are for Linux. This is designed for aggregating # TCP_INFO. RDLOCK = [ Fcntl::F_RDLCK ].pack("s @256") WRLOCK = [ Fcntl::F_WRLCK ].pack("s @256") UNLOCK = [ Fcntl::F_UNLCK ].pack("s @256") # :startdoc: # returns the number of dropped messages sent to a POSIX message # queue if non-blocking operation was desired with :lossy attr_reader :nr_dropped # # Creates a new Raindrops::Aggregate::PMQ object # # Raindrops::Aggregate::PMQ.new(options = {}) -> aggregate # # +options+ is a hash that accepts the following keys: # # * :queue - name of the POSIX message queue (default: "/raindrops") # * :worker_interval - interval to send to the master (default: 10) # * :master_interval - interval to for the master to write out (default: 5) # * :lossy - workers drop packets if master cannot keep up (default: false) # * :aggregate - \Aggregate object (default: \Aggregate.new) # * :mq_umask - umask for creatingthe POSIX message queue (default: 0666) # def initialize(params = {}) opts = { :queue => ENV["RAINDROPS_MQUEUE"] || "/raindrops", :worker_interval => 10, :master_interval => 5, :lossy => false, :mq_attr => nil, :mq_umask => 0666, :aggregate => Aggregate.new, }.merge! params @master_interval = opts[:master_interval] @worker_interval = opts[:worker_interval] @aggregate = opts[:aggregate] @worker_queue = @worker_interval ? [] : nil @mutex = Mutex.new @mq_name = opts[:queue] mq = POSIX_MQ.new @mq_name, :w, opts[:mq_umask], opts[:mq_attr] Tempfile.open("raindrops_pmq") do |t| @wr = File.open(t.path, "wb") @rd = File.open(t.path, "rb") end @cached_aggregate = @aggregate flush_master @mq_send = if opts[:lossy] @nr_dropped = 0 mq.nonblock = true mq.method :trysend else mq.method :send end end # adds a sample to the underlying \Aggregate object def << val if q = @worker_queue q << val if q.size >= @worker_interval mq_send(q) or @nr_dropped += 1 q.clear end else mq_send(val) or @nr_dropped += 1 end end def mq_send(val) # :nodoc: @cached_aggregate = nil @mq_send.call Marshal.dump(val) end # # Starts running a master loop, usually in a dedicated thread or process: # # Thread.new { agg.master_loop } # # Any worker can call +agg.stop_master_loop+ to stop the master loop # (possibly causing the thread or process to exit) def master_loop buf = "" a = @aggregate nr = 0 mq = POSIX_MQ.new @mq_name, :r # this one is always blocking begin if (nr -= 1) < 0 nr = @master_interval flush_master end mq.shift(buf) data = begin Marshal.load(buf) or return rescue ArgumentError, TypeError next end Array === data ? data.each { |x| a << x } : a << data rescue Errno::EINTR rescue => e warn "Unhandled exception in #{__FILE__}:#{__LINE__}: #{e}" break end while true ensure flush_master end # Loads the last shared \Aggregate from the master thread/process def aggregate @cached_aggregate ||= begin flush Marshal.load(synchronize(@rd, RDLOCK) do |rd| IO.pread rd.fileno, rd.stat.size, 0 end) end end # Flushes the currently aggregate statistics to a temporary file. # There is no need to call this explicitly as +:worker_interval+ defines # how frequently your data will be flushed for workers to read. def flush_master dump = Marshal.dump @aggregate synchronize(@wr, WRLOCK) do |wr| wr.truncate 0 IO.pwrite wr.fileno, dump, 0 end end # stops the currently running master loop, may be called from any # worker thread or process def stop_master_loop sleep 0.1 until mq_send(false) rescue Errno::EINTR retry end def lock! io, type # :nodoc: io.fcntl Fcntl::F_SETLKW, type rescue Errno::EINTR retry end # we use both a mutex for thread-safety and fcntl lock for process-safety def synchronize io, type # :nodoc: @mutex.synchronize do begin lock! io, type yield io ensure lock! io, UNLOCK end end end # flushes the local queue of the worker process, sending all pending # data to the master. There is no need to call this explicitly as # +:worker_interval+ defines how frequently your queue will be flushed def flush if q = @local_queue && ! q.empty? mq_send q q.clear end nil end # proxy for \Aggregate#count def count; aggregate.count; end # proxy for \Aggregate#max def max; aggregate.max; end # proxy for \Aggregate#min def min; aggregate.min; end # proxy for \Aggregate#sum def sum; aggregate.sum; end # proxy for \Aggregate#mean def mean; aggregate.mean; end # proxy for \Aggregate#std_dev def std_dev; aggregate.std_dev; end # proxy for \Aggregate#outliers_low def outliers_low; aggregate.outliers_low; end # proxy for \Aggregate#outliers_high def outliers_high; aggregate.outliers_high; end # proxy for \Aggregate#to_s def to_s(*args); aggregate.to_s(*args); end # proxy for \Aggregate#each def each; aggregate.each { |*args| yield(*args) }; end # proxy for \Aggregate#each_nonzero def each_nonzero; aggregate.each_nonzero { |*args| yield(*args) }; end end raindrops-0.12.0/lib/raindrops/last_data_recv.rb0000644000004100000410000000603012211676722021677 0ustar www-datawww-data# -*- encoding: binary -*- require "raindrops" # This is highly experimental! # # A self-contained Rack application for aggregating in the # +tcpi_last_data_recv+ field in +struct+ +tcp_info+ defined in # +/usr/include/linux/tcp.h+. This is only useful for \Linux 2.6 and later. # This primarily supports Unicorn and derived servers, but may also be # used with any Ruby web server using the core TCPServer class in Ruby. # # Hitting the Rack endpoint configured for this application will return # a an ASCII histogram response body with the following headers: # # - X-Count - number of requests received # # The following headers are only present if X-Count is greater than one. # # - X-Min - lowest last_data_recv time recorded (in milliseconds) # - X-Max - highest last_data_recv time recorded (in milliseconds) # - X-Mean - mean last_data_recv time recorded (rounded, in milliseconds) # - X-Std-Dev - standard deviation of last_data_recv times # - X-Outliers-Low - number of low outliers (hopefully many!) # - X-Outliers-High - number of high outliers (hopefully zero!) # # == To use with Unicorn and derived servers (preload_app=false): # # Put the following in our Unicorn config file (not config.ru): # # require "raindrops/last_data_recv" # # Then follow the instructions below for config.ru: # # == To use with any Rack server using TCPServer # # Setup a route for Raindrops::LastDataRecv in your Rackup config file # (typically config.ru): # # require "raindrops" # map "/raindrops/last_data_recv" do # run Raindrops::LastDataRecv.new # end # map "/" do # use SomeMiddleware # use MoreMiddleware # # ... # run YourAppHere.new # end # # == To use with any other Ruby web server that uses TCPServer # # Put the following in any piece of Ruby code loaded after the server has # bound its TCP listeners: # # ObjectSpace.each_object(TCPServer) do |s| # s.extend Raindrops::Aggregate::LastDataRecv # end # # Thread.new do # Raindrops::Aggregate::LastDataRecv.default_aggregate.master_loop # end # # Then follow the above instructions for config.ru # class Raindrops::LastDataRecv # :stopdoc: # trigger autoloads if defined?(Unicorn) agg = Raindrops::Aggregate::LastDataRecv.default_aggregate AGGREGATE_THREAD = Thread.new { agg.master_loop } end # :startdoc def initialize(opts = {}) Raindrops::Aggregate::LastDataRecv.cornify! if defined?(Unicorn) @aggregate = opts[:aggregate] || Raindrops::Aggregate::LastDataRecv.default_aggregate end def call(_) a = @aggregate count = a.count headers = { "Content-Type" => "text/plain", "X-Count" => count.to_s, } if count > 1 headers["X-Min"] = a.min.to_s headers["X-Max"] = a.max.to_s headers["X-Mean"] = a.mean.round.to_s headers["X-Std-Dev"] = a.std_dev.round.to_s headers["X-Outliers-Low"] = a.outliers_low.to_s headers["X-Outliers-High"] = a.outliers_high.to_s end body = a.to_s headers["Content-Length"] = body.size.to_s [ 200, headers, [ body ] ] end end raindrops-0.12.0/lib/raindrops/middleware/0000755000004100000410000000000012211676722020515 5ustar www-datawww-dataraindrops-0.12.0/lib/raindrops/middleware/proxy.rb0000644000004100000410000000212312211676722022221 0ustar www-datawww-data# -*- encoding: binary -*- # :stopdoc: # This class is used by Raindrops::Middleware to proxy application # response bodies. There should be no need to use it directly. class Raindrops::Middleware::Proxy def initialize(body, stats) @body, @stats = body, stats end # yield to the Rack server here for writing def each @body.each { |x| yield x } end # the Rack server should call this after #each (usually ensure-d) def close @stats.decr_writing @body.close if @body.respond_to?(:close) end # Some Rack servers can optimize response processing if it responds # to +to_path+ via the sendfile(2) system call, we proxy +to_path+ # to the underlying body if possible. def to_path @body.to_path end # Rack servers use +respond_to?+ to check for the presence of +close+ # and +to_path+ methods. def respond_to?(m) m = m.to_sym :close == m || @body.respond_to?(m) end # Avoid breaking users of non-standard extensions (e.g. #body) # Rack::BodyProxy does the same. def method_missing(*args, &block) @body.__send__(*args, &block) end end raindrops-0.12.0/lib/raindrops/struct.rb0000644000004100000410000000313312211676722020251 0ustar www-datawww-data# -*- encoding: binary -*- # This is a wrapper around Raindrops objects much like the core Ruby # \Struct can be seen as a wrapper around the core \Array class. # It's usage is similar to the core \Struct class, except its fields # may only be used to house unsigned long integers. # # class Foo < Raindrops::Struct.new(:readers, :writers) # end # # foo = Foo.new 0, 0 # # foo.incr_writers -> 1 # foo.incr_readers -> 1 # class Raindrops::Struct # returns a new class derived from Raindrops::Struct and supporting # the given +members+ as fields, just like \Struct.new in core Ruby. def self.new(*members) members = members.map { |x| x.to_sym }.freeze str = <= values.size) or raise ArgumentError, "too many arguments" @raindrops = Raindrops.new(MEMBERS.size) values.each_with_index { |val,i| @raindrops[i] = values[i] } end def initialize_copy(src) @raindrops = src.instance_variable_get(:@raindrops).dup end def []=(index, value) @raindrops[index] = value end def [](index) @raindrops[index] end def to_hash ary = @raindrops.to_ary rv = {} MEMBERS.each_with_index { |member, i| rv[member] = ary[i] } rv end EOS members.each_with_index do |member, i| str << "def incr_#{member}; @raindrops.incr(#{i}); end; " \ "def decr_#{member}; @raindrops.decr(#{i}); end; " \ "def #{member}; @raindrops[#{i}]; end; " \ "def #{member}=(val); @raindrops[#{i}] = val; end; " end klass = Class.new klass.const_set(:MEMBERS, members) klass.class_eval(str) klass end end raindrops-0.12.0/lib/raindrops.rb0000644000004100000410000000326012211676722016726 0ustar www-datawww-data# -*- encoding: binary -*- # # Each Raindrops object is a container that holds several counters. # It is internally a page-aligned, shared memory area that allows # atomic increments, decrements, assignments and reads without any # locking. # # rd = Raindrops.new 4 # rd.incr(0, 1) -> 1 # rd.to_ary -> [ 1, 0, 0, 0 ] # # Unlike many classes in this package, the core Raindrops class is # intended to be portable to all reasonably modern *nix systems # supporting mmap(). Please let us know if you have portability # issues, patches or pull requests at mailto:raindrops@librelist.org class Raindrops # Used to represent the number of +active+ and +queued+ sockets for # a single listen socket across all threads and processes on a # machine. # # For TCP listeners, only sockets in the TCP_ESTABLISHED state are # accounted for. For Unix domain listeners, only CONNECTING and # CONNECTED Unix domain sockets are accounted for. # # +active+ connections is the number of accept()-ed but not-yet-closed # sockets in all threads/processes sharing the given listener. # # +queued+ connections is the number of un-accept()-ed sockets in the # queue of a given listen socket. # # These stats are currently only available under \Linux class ListenStats < Struct.new(:active, :queued) # the sum of +active+ and +queued+ sockets def total active + queued end end autoload :Linux, 'raindrops/linux' autoload :Struct, 'raindrops/struct' autoload :Middleware, 'raindrops/middleware' autoload :Aggregate, 'raindrops/aggregate' autoload :LastDataRecv, 'raindrops/last_data_recv' autoload :Watcher, 'raindrops/watcher' end require 'raindrops_ext' raindrops-0.12.0/NEWS0000600000004100000410000001630512211676722014325 0ustar www-datawww-data=== raindrops 0.12.0 - compatibility fixes / 2013-09-02 10:33 UTC This release fixes builds on systems where compilers target i386 (and not later x86 systems). There are also minor improvements for Ruby 2.1.0dev and Rubinius. Eric Wong (5): doc: add email address to generated doc/site README: update regarding Ruby support status extconf: try harder for gcc atomics in i386-configured systems linux_inet_diag: improve compatibility with newer GCs test_watcher: fix for Ruby trunk r40195 and later === raindrops 0.11.0 - minor fixes improvements / 2013-04-20 23:10 UTC Eric Wong (7): raindrops: favor configured processor count over online count watcher: set Content-Type via assignment Linux::TCP_Info: implement #get! instance method linux_inet_diag: avoid unnecessary sockaddr initialization .gitignore: add .rbx switch back to gemspec development dependencies linux_inet_diag: better align listener_stats struct Lawrence Pit (1): Watcher: Use relative paths in HTML links === raindrops 0.10.0 - minor feature updates / 2012-06-19 08:30 UTC Improvements to the Unix domain socket handling and small bugfixes throughout. Support for the "unix_diag" facility in Linux 3.3+ is planned but not yet implemented (patches to raindrops@librelist.org appreciated) Brian Corrigan (1): resolve symlinks to Unix domain sockets Eric Wong (6): unix_listener_stats follows and remembers symlinks middleware/proxy: favor __send__ for method dispatch unix: show zero-value stats for idle listeners test_watcher: fix incorrect request/date comparison watcher: sort index of listener listing watcher: do not require Rack::Head for HEAD response See "git log v0.9.0..v0.10.0" for full details === raindrops 0.9.0 - minor middleware/proxy update / 2012-05-21 00:06 UTC Raindrops::Middleware::Proxy now forwards method_missing to the proxied body object. This allows compatibility with non-standard Rack extensions employed by some middlewares, applications, or servers. Thanks to Ben Somers for the feature! === raindrops 0.8.1 - compatibility fixes / 2012-05-12 05:58 UTC This release fixes a build problem found under a current SmartOS. This release also runs successfully on FreeBSD 9.0 under both x86-64 and i386. There are also documentation updates from Aman Gupta and a test suite fix from Jeremy Evans for OpenBSD. raindrops fully supports unicorn on recent versions of FreeBSD, OpenBSD, SmartOS, and possibly other Free Software systems. Portability reports and fixes for Free Software systems is greatly appreciated at raindrops@librelist.org Non-Free systems will never be supported. raindrops requires the Linux 2.6.18 or later for full functionality (which unicorn does not require). Future releases will take advantage of the unix_diag functionality found in the Linux 3.3 (and later) kernels. === raindrops 0.8.0 - watcher updates / 2011-10-14 22:00 UTC There are various updates to the Raindrops::Watcher Rack app. Most notably, the timestamp where the a statistic first and last hit its peak value (active/queued connections) is captured. As usual, the latest Raindrops::Watcher is running at: http://raindrops-demo.bogomips.org/ === raindrops 0.7.0 - FreeBSD fix, code cleanups / 2011-06-27 07:26 UTC This release fixes a build issue on FreeBSD. There are various documentation and code cleanups, too. === raindrops 0.6.1 - fix build on non-Linux / 2011-03-21 22:31 UTC TCP_INFO support couldn't compile under non-Linux, this was broken since 0.5.0 when TCP_INFO support was introduced. Thanks to Ben Bleything for the report. === raindrops 0.6.0 - polishing up the last release / 2011-03-21 21:46 UTC Following up the huge 0.5.0 release, 0.6.0 makes some minor improvements: * minor UI/UX improvements for Watcher Rack app * set close-on-exec by default for inet_diag sockets * inet_diag build fixes for newer GNU libc6 * --with-atomic_ops-dir= build option added === raindrops 0.5.0 - more Linux extras! / 2011-03-17 03:13 UTC Portable changes: * Raindrops are now resizable within the limits of system page size * Raindrops::Middleware proxies +to_path+ in response bodies * More documentation Linux-only changes: * Raindrops::LastDataRecv[1] Rack application * Raindrops::Watcher[2] Rack application * Raindrops::TCP_Info[3] class for capturing TCP connection stats * IPv6 support for inet_diag * faster inet_diag stats for multiple sockets There is also a demo server running the Watcher and Middleware components. It's capped to 30 concurrent users, so go easy on it: Raindrops::Watcher: http://raindrops-demo.bogomips.org/ Raindrops::Middleware: http://raindrops-demo.bogomips.org/_raindrops [1] http://raindrops.bogomips.org/Raindrops/LastDataRecv.html [2] http://raindrops.bogomips.org/Raindrops/Watcher.html [3] http://raindrops.bogomips.org/Raindrops/TCP_Info.html === raindrops 0.4.1 - more portability! / 2010-09-26 06:49 UTC Rubinius 1.1.0 support is complete. Atomic operations are now available under FreeBSD 7.0 now. Full changelog below: commit 8a2a725a4ad074af493e5aa075155eda8b1d6be7 Author: Eric Wong Date: Sat Sep 25 00:14:48 2010 -0700 force -march=i486 where GCC is targeted for i386 Nobody uses i386 anymore (especially not with Ruby!), but some systems like FreeBSD 7.0 still target GCC at i386 by default, so we force GCC to use a slightly more modern instruction set and allow it to use atomic builtins. commit 256cc7c8ffb441dcf2d2a2da3bbbcc82546962d9 Author: Eric Wong Date: Sat Sep 25 00:01:46 2010 -0700 disable Linux-only code on non-Linux This allows us to build and link correctly on FreeBSD 7.0 commit 22a5a39d75faa890048d07ae4ea0d494acd414ce Author: Eric Wong Date: Sat Sep 25 06:25:42 2010 +0000 linux: workaround missing RSTRUCT* macros in rbx Rubinius does not include macros for accessing Struct members in the C API. ref: http://github.com/evanphx/rubinius/issues/494 === raindrops 0.4.0 - flowing into new systems! / 2010-09-21 22:32 UTC Non-GCC 4.x users may use the libatomic_ops[1] package to compile Raindrops. Memory efficiency is improved for modern glibc users with run-time cache line size detection, we no longer assume 128 byte cache lines. [1] - http://www.hpl.hp.com/research/linux/atomic_ops/ === raindrops v0.3.0 - LGPL v2.1 and v3.0 / 2010-07-10 22:29 UTC Raindrops is now licensed under the LGPLv2.1 or LGPLv3 (from LGPLv3-only) to allow bundling in GPLv2-only applications. There are small documentation updates and updated examples at http://raindrops.bogomips.org/examples/ === raindrops 0.2.0 - raining penguins! / 2010-05-05 00:35 UTC For servers running Unicorn 0.98.0 (and derivative servers) under Linux, :listeners no longer needs to be passed explicitly when configuring the Rack middleware. Some small documentation updates and cleanups, too. === raindrops 0.1.0 / 2010-04-08 00:45 UTC initial release