From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-4.0 required=3.0 tests=ALL_TRUSTED,AWL,BAYES_00 shortcircuit=no autolearn=ham autolearn_force=no version=3.4.1 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id 78E2C1F597 for ; Fri, 20 Jul 2018 09:11:49 +0000 (UTC) From: Eric Wong To: mwrap-public@80x24.org Subject: [PATCH] documentation updates for 2.0.0 release Date: Fri, 20 Jul 2018 09:11:49 +0000 Message-Id: <20180720091149.18487-1-e@80x24.org> List-Id: --- .document | 1 + README | 34 ++++++++++++++++++++++------------ ext/mwrap/mwrap.c | 40 +++++++++++++++++++++++++++++++--------- lib/mwrap_rack.rb | 26 +++++++++++++++++++++----- 4 files changed, 75 insertions(+), 26 deletions(-) diff --git a/.document b/.document index c35f075..4ca33e3 100644 --- a/.document +++ b/.document @@ -1 +1,2 @@ ext/mwrap/mwrap.c +lib/mwrap_rack.rb diff --git a/README b/README index e3bcceb..3a20258 100644 --- a/README +++ b/README @@ -4,20 +4,23 @@ mwrap is designed to answer the question: Which lines of Ruby are hitting malloc the most? -mwrap wraps all malloc, calloc, and realloc calls to trace the Ruby -source location of such calls and bytes allocated at each callsite. -This functionality may be expanded in the future. - -It does not track allocation lifetimes, or frees, however. It works -best for allocations under GVL, but tries to track numeric caller -addresses for allocations made without GVL so you can get an idea of how -much memory usage certain extensions and native libraries use. +mwrap wraps all malloc-family calls to trace the Ruby source +location of such calls and bytes allocated at each callsite. +As of mwrap 2.0.0, it can also function as a leak detector +and show live allocations at every call site. Depending on +your application and workload, the overhead is roughly a 50% +increase memory and runtime. + +It works best for allocations under GVL, but tries to track +numeric caller addresses for allocations made without GVL so you +can get an idea of how much memory usage certain extensions and +native libraries use. It requires the concurrent lock-free hash table from the Userspace RCU project: https://liburcu.org/ -It does not require recompiling or rebuilding Ruby, but only supports -Ruby trunk (2.6.0dev+) on a few platforms: +It does not require recompiling or rebuilding Ruby, but only +supports Ruby trunk (2.6.0dev+) on a few platforms: * GNU/Linux * FreeBSD (tested 11.1) @@ -45,10 +48,12 @@ variable to append the results to a log file: sort -k1,1rn frees)); } +/* The number of allocations made from this location */ static VALUE src_loc_allocations(VALUE self) { return SIZET2NUM(uatomic_read(&src_loc_get(self)->allocations)); } +/* The total number of bytes allocated from this location */ static VALUE src_loc_total(VALUE self) { return SIZET2NUM(uatomic_read(&src_loc_get(self)->total)); } +/* + * The maximum age (in GC generations) of an allocation before it was freed. + * This does not account for live allocations. + */ static VALUE src_loc_max_lifespan(VALUE self) { return SIZET2NUM(uatomic_read(&src_loc_get(self)->max_lifespan)); @@ -1011,6 +1024,8 @@ static VALUE reset_locating(VALUE ign) { --locating; return Qfalse; } * Stops allocation tracking inside the block. This is useful for * monitoring code which calls other Mwrap (or ObjectSpace/GC) * functions which unavoidably allocate memory. + * + * This feature was added in mwrap 2.0.0+ */ static VALUE mwrap_quiet(VALUE mod) { @@ -1052,9 +1067,16 @@ void Init_mwrap(void) VALUE mod; ++locating; - mod = rb_define_module("Mwrap"); + mod = rb_define_module("Mwrap"); id_uminus = rb_intern("-@"); + /* + * Represents a location in source code or library + * address which calls a memory allocation. It is + * updated automatically as allocations are made, so + * there is no need to reload or reread it from Mwrap#[]. + * This class is only available since mwrap 2.0.0+. + */ cSrcLoc = rb_define_class_under(mod, "SourceLocation", rb_cObject); rb_define_singleton_method(mod, "dump", mwrap_dump, -1); rb_define_singleton_method(mod, "reset", mwrap_reset, 0); @@ -1080,18 +1102,18 @@ void Init_mwrap(void) __attribute__ ((destructor)) static void mwrap_dump_destructor(void) { - const char *opt = getenv("MWRAP"); - const char *modes[] = { "a", "a+", "w", "w+", "r+" }; - struct dump_arg a; - size_t i; - int dump_fd; + const char *opt = getenv("MWRAP"); + const char *modes[] = { "a", "a+", "w", "w+", "r+" }; + struct dump_arg a; + size_t i; + int dump_fd; char *dump_path; if (!opt) return; - ++locating; - if ((dump_path = strstr(opt, "dump_path:")) && + ++locating; + if ((dump_path = strstr(opt, "dump_path:")) && (dump_path += sizeof("dump_path")) && *dump_path) { char *end = strchr(dump_path, ','); @@ -1136,5 +1158,5 @@ static void mwrap_dump_destructor(void) } dump_to_file(&a); out: - --locating; + --locating; } diff --git a/lib/mwrap_rack.rb b/lib/mwrap_rack.rb index ef3872b..a750f32 100644 --- a/lib/mwrap_rack.rb +++ b/lib/mwrap_rack.rb @@ -5,9 +5,24 @@ require 'mwrap' require 'rack' require 'cgi' -# Usage: mwrap rackup ... +# MwrapRack is a standalone Rack application which can be +# mounted to run within your application process. +# +# Using the Rack::Builder API in config.ru, you can map it to +# the "/MWRAP/" endpoint. As with the rest of the Mwrap API, +# your Rack server needs to be spawned with the mwrap(1) +# wrapper to enable the LD_PRELOAD. +# +# require 'mwrap_rack' +# map('/MWRAP') { run(MwrapRack.new) } +# map('/') { run(your_normal_app) } +# +# A live demo is available at https://80x24.org/MWRAP/ +# (warning the demo machine is 32-bit, so counters will overflow) +# +# This module is only available in mwrap 2.0.0+ class MwrapRack - module HtmlResponse + module HtmlResponse # :nodoc: def response [ 200, { 'Expires' => 'Fri, 01 Jan 1980 00:00:00 GMT', @@ -18,7 +33,7 @@ class MwrapRack end end - class Each < Struct.new(:script_name, :min, :sort) + class Each < Struct.new(:script_name, :min, :sort) # :nodoc: include HtmlResponse HEADER = '' + %w(total allocations frees mean_life max_life location).join('') + '' @@ -61,7 +76,7 @@ class MwrapRack end end - class EachAt < Struct.new(:loc) + class EachAt < Struct.new(:loc) # :nodoc: include HtmlResponse HEADER = 'sizegeneration' @@ -77,10 +92,11 @@ class MwrapRack end end - def r404 + def r404 # :nodoc: [404,{'Content-Type'=>'text/plain'},["Not found\n"]] end + # The standard Rack application endpoint for MwrapRack def call(env) case env['PATH_INFO'] when %r{\A/each/(\d+)\z} -- EW