diff options
author | Eric Wong <e@80x24.org> | 2018-07-02 09:26:22 +0000 |
---|---|---|
committer | Eric Wong <e@80x24.org> | 2018-07-02 09:26:22 +0000 |
commit | 61ee1c46a06268cbcf5e7d916a05ca9091337f7a (patch) | |
tree | d794c923df8fb6cf661b8c1e98911846af6a4c20 | |
parent | a47e497fbc2322457ac18679f9046250d4ee9d8f (diff) | |
download | mwrap-61ee1c46a06268cbcf5e7d916a05ca9091337f7a.tar.gz |
-rw-r--r-- | .document | 1 | ||||
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | README | 42 | ||||
-rw-r--r-- | ext/mwrap/mwrap.c | 62 |
4 files changed, 91 insertions, 15 deletions
diff --git a/.document b/.document new file mode 100644 index 0000000..c35f075 --- /dev/null +++ b/.document @@ -0,0 +1 @@ +ext/mwrap/mwrap.c @@ -3,3 +3,4 @@ *.so /pkg /*.gem +/doc @@ -1,21 +1,28 @@ = mwrap - LD_PRELOAD malloc wrapper + line stats for Ruby -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 change incompatibly or be expanded in the future. +mwrap is designed to answer the question: -This is useful for finding malloc hotspots in Ruby code. 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. + Which lines of Ruby are hitting malloc the most? -It requires a concurrent lock-free hash table from the +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. + +It requires the concurrent lock-free hash table from the Userspace RCU project: https://liburcu.org/ -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 11 +* FreeBSD (tested 11.1) + +It may work on NetBSD, OpenBSD and DragonFly BSD. == Install @@ -40,10 +47,17 @@ You may also set dump_path to append to a log file: MWRAP=dump_path:/path/to/log mwrap RUBY_COMMAND You may also `require 'mwrap'' in your Ruby code and use -Mwrap.dump, Mwrap.clear, Mwrap.each, etc. +Mwrap.dump, Mwrap.clear, Mwrap.reset, Mwrap.each, etc. + +However, mwrap MUST be loaded via LD_PRELOAD to have any +effect in tracking malloc use. -However, mwrap MUST be loaded via LD_PRELOAD-ed to have any -effect in tracking mallocs. +The output of the mwrap dump is a text file with 3 columns: + + total_bytes call_count location + +Where location is a Ruby source location (if made under GVL) +or an address retrieved by backtrace_symbols(3) == Known problems @@ -70,5 +84,3 @@ systems. == License GPL-2.0+ <https://www.gnu.org/licenses/gpl-2.0.txt> - -Note: we may depend on 3rd-party LGPL/GPL libraries in future releases diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index fbba32b..e6449d7 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -323,6 +323,19 @@ out_unlock: return 0; } +/* + * call-seq: + * + * Mwrap.dump([[io] [, min]] -> nil + * + * Dumps the current totals to +io+ which must be an IO object + * (StringIO and similar are not supported). Total sizes smaller + * than or equal to +min+ are skipped. + * + * The output is space-delimited by 3 columns: + * + * total_size call_count location + */ static VALUE mwrap_dump(int argc, VALUE * argv, VALUE mod) { VALUE io, min; @@ -373,6 +386,15 @@ static void *totals_clear(void *ign) return 0; } +/* + * call-seq: + * + * Mwrap.clear -> nil + * + * Atomically replaces the totals table and destroys the old one. + * This resets all statistics. It is more expensive than `Mwrap.reset' + * as new allocations will need to be made to repopulate the new table. + */ static VALUE mwrap_clear(VALUE mod) { rb_thread_call_without_gvl(totals_clear, 0, 0, 0); @@ -395,6 +417,15 @@ static void *totals_reset(void *ign) return 0; } +/* + * call-seq: + * + * Mwrap.reset -> nil + * + * Resets the the total tables by zero-ing all counters. + * This resets all statistics and is less costly than `Mwrap.clear' + * but is not an atomic operation. + */ static VALUE mwrap_reset(VALUE mod) { rb_thread_call_without_gvl(totals_reset, 0, 0, 0); @@ -443,6 +474,15 @@ static VALUE dump_each_rcu(VALUE x) return Qnil; } +/* + * call-seq: + * + * Mwrap.each([min]) { |location,total_bytes,call_count| ... } + * + * Yields each entry of the of the table to a caller-supplied block. + * +min+ may be specified to filter out lines with +total_bytes+ + * equal-to-or-smaller-than the supplied minimum. + */ static VALUE mwrap_each(int argc, VALUE * argv, VALUE mod) { VALUE min; @@ -457,6 +497,28 @@ static VALUE mwrap_each(int argc, VALUE * argv, VALUE mod) return rb_ensure(dump_each_rcu, (VALUE)&a, dump_ensure, 0); } +/* + * Document-module: Mwrap + * + * require 'mwrap' + * + * Mwrap has a dual function as both a Ruby C extension and LD_PRELOAD + * wrapper. As a Ruby C extension, it exposes a limited Ruby API. + * To be effective at gathering status, mwrap must be loaded as a + * LD_PRELOAD (using the mwrap(1) executable makes it easy) + * + * ENVIRONMENT + * + * The "MWRAP" environment variable contains a comma-delimited list + * of key:value options for automatically dumping at program exit. + * + * * dump_fd: a writable FD to dump to + * * dump_path: a path to dump to, the file is opened in O_APPEND mode + * * dump_min: the minimum allocation size (total) to dump + * + * If both `dump_fd' and `dump_path' are specified, dump_path takes + * precedence. + */ void Init_mwrap(void) { VALUE mod = rb_define_module("Mwrap"); |