mall.git  about / heads / tags
access the mallinfo(3) and mallopt(3) functions from Ruby
blob fea06c3ae58fc8911443bd272c46166f3223e410 6268 bytes (raw)
$ git show HEAD:ext/mall/mall.c.erb	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
 
#include <ruby.h>
#include <malloc.h>
#include <stdio.h>
<%
mallinfo_keys = %w(
arena
ordblks
smblks
hblks
hblkhd
usmblks
fsmblks
uordblks
fordblks
keepcost
) %>

<% mallinfo_keys.each { |x| %>
static VALUE sym_<%= x %>;
<% } %>

/*
 * call-seq:
 *	Mall.info	-> hash
 *
 * Returns a hash with the following keys:
 *
 *  :arena      - bytes allocated via sbrk() (and not mmap())
 *  :ordblks    - number of free (unused) chunks
 *  :smblks     - number of fastbin blocks[1]
 *  :hblks      - number of allocated mmap()-ed regions
 *  :hblkhd     - bytes allocated in mmap()-ed regions
 *  :usmblks    - maximum total allocated space[1]
 *  :fsmblks    - space available in freed fastbin blocks[1]
 *  :uordblks   - total allocated space in use
 *  :fordblks   - total free space
 *  :keepcost   - top-most, releasable (via malloc_trim) space
 *
 * All values are limited to 32-bit integers.  This uses the limited
 * mallinfo(3) function, consider using Mall.xml and parsing its output
 * if you are using glibc (2.10+) with malloc_info(3)
 *
 * See also:
 * http:// gnu.org/software/libc/manual/html_node/Statistics-of-Malloc.html
 *
 * [1] - this key is unused by glibc (ptmalloc2)
 */
static VALUE info(VALUE klass)
{
	VALUE rv = rb_hash_new();
	struct mallinfo stats = mallinfo(); /* whee aggregate returns :( */

#define MALLINFO_SET(KEY) \
	rb_hash_aset(rv, sym_##KEY, INT2FIX(stats.KEY))

	MALLINFO_SET(arena);
	MALLINFO_SET(ordblks);
	MALLINFO_SET(smblks);
	MALLINFO_SET(hblks);
	MALLINFO_SET(hblkhd);
	MALLINFO_SET(usmblks);
	MALLINFO_SET(fsmblks);
	MALLINFO_SET(uordblks);
	MALLINFO_SET(fordblks);
	MALLINFO_SET(keepcost);
#undef MALLINFO_SET
	return rv;
}

/*
 * call-seq:
 *	Mall.opt(Mall::MMAP_THRESHOLD, 128 * 1024)
 *
 * some malloc implementations may not like mallopt() being called after
 * malloc has been initialized (first call to malloc()).  This is not
 * the case with glibc malloc.
 *
 * See also:
 * http://gnu.org/software/libc/manual/html_node/Malloc-Tunable-Parameters.html
 */
static VALUE opt(VALUE klass, VALUE param, VALUE value)
{
	int rv = mallopt(FIX2INT(param), FIX2INT(value));

	return rv == 0 ? Qfalse : Qtrue;
}

#ifdef HAVE_MALLOC_TRIM
/*
 *  call-seq:
 *	Mall.trim(pad) => true or false
 *
 *  Attempt to trim off the top of the heap and release it back to
 *  the OS. +pad+ represents the amount of free space (in bytes) to
 *  leave unreleased for future allocations.
 *
 *  Returns true if memory was released and false if not.
 *
 *  This method is glibc-specific.
 */
static VALUE trim(VALUE klass, VALUE pad)
{
	unsigned long tmp = NUM2ULONG(pad);
	int rv = malloc_trim((size_t)tmp);

	return rv == 1 ? Qtrue : Qfalse;
	return Qfalse;
}
#endif /* HAVE_MALLOC_TRIM */

#ifdef HAVE_MALLOC_STATS
/*
 * call-seq:
 *	Mall.dump_stats
 *
 * Dump malloc stats to STDERR
 *
 * This calls malloc_stats() internally, a function that is glibc-specific
 */
static VALUE dump_stats(VALUE klass)
{
	fflush(stderr);
	malloc_stats();
	fflush(stderr);
	return Qnil;
}
#endif /* HAVE_MALLOC_STATS */

#ifdef HAVE_MALLOC_INFO
static ID id_ltlt;
#include <errno.h>
static void xmlerr(FILE *fp, int err, const char *msg)
{
	fclose(fp);
	errno = err ? err : EIO; /* gotta have something */
	rb_sys_fail(msg);
}

/*
 * call-seq:
 *	Mall.xml -> XML string
 *	Mall.xml(options = 0, io = $stderr) -> io
 *
 * Called with no arguments, this returns an XML string suitable for
 * parsing with your favorite XML parser.
 *
 * If specified, +options+ must currently be +0+, but is reserved for
 * future expansion.
 *
 * The second optional argument may be any object that responds to "<<"
 * so it may be an IO, Array, StringIO, or String object among other
 * things.
 *
 * This relies on malloc_info(3) which is only in glibc 2.10+
 */
static VALUE xml(int argc, VALUE *argv, VALUE self)
{
	int err;
	long len;
	VALUE options, out, buf;
	int xoptions;
	FILE *fp;

	rb_scan_args(argc, argv, "02", &options, &out);
	xoptions = NIL_P(options) ? 0 : NUM2INT(options);

	fp = tmpfile();
	if (fp == NULL)
		rb_sys_fail("tmpfile");

	err = malloc_info(xoptions, fp);
	if (err != 0)
		xmlerr(fp, err, "malloc_info");

	len = ftell(fp);
	if (len < 0)
		xmlerr(fp, errno, "ftell");

	rewind(fp);
	buf = rb_str_new(0, len);
	if (fread(RSTRING_PTR(buf), 1, len, fp) != (size_t)len)
		xmlerr(fp, ferror(fp), "fread");
	fclose(fp);

	if (NIL_P(out))
		return buf;
	return rb_funcall(out, id_ltlt, 1, buf);
}
#endif /* HAVE_MALLOC_INFO */

/*
 * Mall is a single module with several singleton methods, most of which
 * are glibc-specific.  All constants may be used as the first argument
 * to Mall.opt.
 */
void Init_mall(void)
{
	VALUE mMall = rb_define_module("Mall");
	rb_define_singleton_method(mMall, "opt", opt, 2);
	rb_define_singleton_method(mMall, "info", info, 0);
#ifdef HAVE_MALLOC_TRIM
	rb_define_singleton_method(mMall, "trim", trim, 1);
#endif /* HAVE_MALLOC_TRIM */
#ifdef HAVE_MALLOC_STATS
	rb_define_singleton_method(mMall, "dump_stats", dump_stats, 0);
#endif /* HAVE_MALLOC_STATS */
#ifdef HAVE_MALLOC_INFO
	id_ltlt = rb_intern("<<");
	rb_define_singleton_method(mMall, "xml", xml, -1);
#endif /* HAVE_MALLOC_INFO*/

<% mallinfo_keys.each { |x| %>
	sym_<%= x %> = ID2SYM(rb_intern("<%= x %>"));
<% } %>

<%
{
  :mxfast => 'max request size for "fastbins"',
  :nlblks => 'unused in glibc',
  :grain => 'unused in glibc',
  :keep => 'unused in glibc',
  :trim_threshold =>
    'maximum amount of unused memory at the top of the heap' \
    'to keep before releasing it back to the kernel',
  :top_pad =>
    'amount of extra space to allocate when allocating from the heap',
  :mmap_threshold =>
    'the request size threshold for using mmap() (instead of sbrk())',
  :mmap_max =>
    'the maximum number of active mmap() requests in use at once',
  :check_action =>
    'bitmask value used for debug message output (glibc)',
  :perturb =>
    'perturb memory allocations with a given byte (for debugging) (glibc)',
  :arena_test => 'initial number of arenas to allocate (glibc 2.10+)',
  :arena_max => 'maximum number of arenas to allocate (glibc 2.10+)',
}.each { |opt, doc|
  opt = opt.to_s.upcase!
  m_opt = "M_#{opt}"
%>
#ifdef <%= m_opt %>
	/* <%= doc %> */
	rb_define_const(mMall, "<%= opt %>", INT2FIX(<%= m_opt %>));
#endif
<% } %>
}

git clone https://yhbt.net/mall.git