Module: Mwrap

Defined in:
ext/mwrap/mwrap.c,
ext/mwrap/mwrap.c

Overview

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

  • dump_heap: mask of heap_page_body statistics to dump

If both ‘dump_fd’ and ‘dump_path’ are specified, dump_path takes precedence.

dump_heap bitmask

  • 0x01 - summary stats (same info as HeapPageBody.stat)

  • 0x02 - all live heaps (similar to HeapPageBody.each)

  • 0x04 - skip non-heap_page_body-related output

Defined Under Namespace

Classes: HeapPageBody, SourceLocation

Class Method Summary collapse

Class Method Details

.[](loc) ⇒ Object

Mwrap -> Mwrap::SourceLocation

Returns the associated Mwrap::SourceLocation given the location String. location is either a Ruby source location path:line (e.g. “/path/to/foo.rb:5”) or a hexadecimal memory address with square-braces part yielded by Mwrap.dump (e.g. “[0xdeadbeef]”)



1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
# File 'ext/mwrap/mwrap.c', line 1026

static VALUE mwrap_aref(VALUE mod, VALUE loc)
{
	const char *str = StringValueCStr(loc);
	int len = RSTRING_LENINT(loc);
	struct src_loc *k = 0;
	uintptr_t p;
	struct cds_lfht_iter iter;
	struct cds_lfht_node *cur;
	struct cds_lfht *t;
	struct src_loc *l;
	VALUE val = Qnil;

	if (extract_addr(str, len, (void **)&p)) {
		k = (void *)kbuf;
		memcpy(k->k, &p, sizeof(p));
		k->capa = 0;
		k->hval = jhash(k->k, sizeof(p), 0xdeadbeef);
	} else {
		k = (void *)kbuf;
		memcpy(k->k, str, len + 1);
		k->capa = len + 1;
		k->hval = jhash(k->k, k->capa, 0xdeadbeef);
	}

	if (!k) return val;

	rcu_read_lock();
	t = rcu_dereference(totals);
	if (!t) goto out_unlock;

	cds_lfht_lookup(t, k->hval, loc_eq, k, &iter);
	cur = cds_lfht_iter_get_node(&iter);
	if (cur) {
		l = caa_container_of(cur, struct src_loc, hnode);
		val = TypedData_Wrap_Struct(cSrcLoc, &src_loc_type, l);
	}
out_unlock:
	rcu_read_unlock();
	return val;
}

.clearObject

:nodoc:



908
909
910
911
# File 'ext/mwrap/mwrap.c', line 908

static VALUE mwrap_clear(VALUE mod)
{
	return mwrap_reset(mod);
}

.dump(*args) ⇒ Object

Mwrap.dump([ [, 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



846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
# File 'ext/mwrap/mwrap.c', line 846

static VALUE mwrap_dump(int argc, VALUE * argv, VALUE mod)
{
	VALUE io, min;
	struct dump_arg a;
	rb_io_t *fptr;

	rb_scan_args(argc, argv, "02", &io, &min);

	if (NIL_P(io))
		/* library may be linked w/o Ruby */
		io = *((VALUE *)dlsym(RTLD_DEFAULT, "rb_stderr"));

	a.min = NIL_P(min) ? 0 : NUM2SIZET(min);
	io = rb_io_get_io(io);
	io = rb_io_get_write_io(io);
	GetOpenFile(io, fptr);
	a.fp = rb_io_stdio_file(fptr);

	rb_thread_call_without_gvl(dump_to_file, &a, 0, 0);
	RB_GC_GUARD(io);
	return Qnil;
}

.each(*args) ⇒ Object

Mwrap.each() do |location,total,allocations,frees,age_total,max_lifespan|

...

end

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.



977
978
979
980
981
982
983
984
985
986
987
988
989
# File 'ext/mwrap/mwrap.c', line 977

static VALUE mwrap_each(int argc, VALUE * argv, VALUE mod)
{
	VALUE min;
	struct dump_arg a;

	rb_scan_args(argc, argv, "01", &min);
	a.min = NIL_P(min) ? 0 : NUM2SIZET(min);

	++locating;
	rcu_read_lock();

	return rb_ensure(dump_each_rcu, (VALUE)&a, rcu_unlock_ensure, 0);
}

.quietObject

Mwrap.quiet do |depth|

# expensive sort/calculate/emitting results of Mwrap.each
# affecting statistics of the rest of the app

end

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+



1190
1191
1192
1193
1194
# File 'ext/mwrap/mwrap.c', line 1190

static VALUE mwrap_quiet(VALUE mod)
{
	size_t cur = ++locating;
	return rb_ensure(rb_yield, SIZET2NUM(cur), reset_locating, 0);
}

.resetObject

Mwrap.reset -> nil

Resets the the total tables by zero-ing all counters. This resets all statistics. This is not an atomic operation as other threads (outside of GVL) may increment counters.



901
902
903
904
905
# File 'ext/mwrap/mwrap.c', line 901

static VALUE mwrap_reset(VALUE mod)
{
	rb_thread_call_without_gvl(totals_reset, 0, 0, 0);
	return Qnil;
}

.total_bytes_allocatedObject

total bytes allocated as tracked by mwrap



1199
1200
1201
1202
# File 'ext/mwrap/mwrap.c', line 1199

static VALUE total_inc(VALUE mod)
{
	return SIZET2NUM(total_bytes_inc);
}

.total_bytes_freedObject

total bytes freed as tracked by mwrap



1207
1208
1209
1210
# File 'ext/mwrap/mwrap.c', line 1207

static VALUE total_dec(VALUE mod)
{
	return SIZET2NUM(total_bytes_dec);
}