Skip to content

Commit aac00cb

Browse files
committed
memprof: introduce the --human-readable option
Prior to this patch, memprof could report only the raw amount of bytes, which is hard to read. This patch adds the `--human-readable` CLI option to the memprof parser, so the memory is reported in KiB, MiB, or GiB, depending on what's the biggest reasonable unit. This patch also refactors the options mechanism for parser, so all of the options are passed into parser's modules as a single config table instead of being handled individually. Part of tarantool/tarantool#5994
1 parent 26f8841 commit aac00cb

File tree

3 files changed

+123
-16
lines changed

3 files changed

+123
-16
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
local tap = require('tap')
2+
local test = tap.test('gh-5994-memprof-human-readable'):skipcond({
3+
['Profile tools are implemented for x86_64 only'] = jit.arch ~= 'x86' and
4+
jit.arch ~= 'x64',
5+
['Profile tools are implemented for Linux only'] = jit.os ~= 'Linux',
6+
-- XXX: Tarantool integration is required to run this test properly.
7+
-- luacheck: no global
8+
['No profile tools CLI option integration'] = _TARANTOOL,
9+
})
10+
11+
local utils = require('utils')
12+
local TMP_BINFILE_MEMPROF = utils.tools.profilename('memprofdata.tmp.bin')
13+
local PARSE_CMD = utils.exec.luacmd(arg) .. ' -tm '
14+
15+
local function generate_output(bytes)
16+
local res, err = misc.memprof.start(TMP_BINFILE_MEMPROF)
17+
-- Should start successfully.
18+
assert(res, err)
19+
20+
-- luacheck: no unused
21+
local _ = string.rep('_', bytes)
22+
23+
res, err = misc.memprof.stop()
24+
-- Should stop successfully.
25+
assert(res, err)
26+
end
27+
28+
local TEST_SET = {
29+
{
30+
bytes = 2049,
31+
match = '%dB',
32+
hr = false,
33+
name = 'non-human-readable mode is correct'
34+
},
35+
{
36+
bytes = 100,
37+
match = '%dB',
38+
hr = true,
39+
name = 'human-readable mode: bytes'
40+
},
41+
{
42+
bytes = 2560,
43+
match = '%d+%.%d%dKiB',
44+
hr = true,
45+
name = 'human-readable mode: float'
46+
},
47+
{
48+
bytes = 2048,
49+
match = '%dKiB',
50+
hr = true,
51+
name = 'human-readable mode: KiB'
52+
},
53+
{
54+
bytes = 1024 * 1024,
55+
match = '%dMiB',
56+
hr = true,
57+
name = 'human-readable mode: MiB'
58+
},
59+
-- XXX: The test case for GiB is not implemented because it is
60+
-- OOM-prone for non-GC64 builds.
61+
}
62+
63+
test:plan(#TEST_SET)
64+
65+
for _, params in ipairs(TEST_SET) do
66+
generate_output(params.bytes)
67+
local cmd = PARSE_CMD .. (params.hr and ' --human-readable ' or '')
68+
local output = io.popen(cmd .. TMP_BINFILE_MEMPROF):read('*all')
69+
test:like(output, params.match, params.name)
70+
end
71+
72+
os.remove(TMP_BINFILE_MEMPROF)
73+
test:done(true)

tools/memprof.lua

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ local match, gmatch = string.match, string.gmatch
2222
-- Program options.
2323
local opt_map = {}
2424

25+
-- Default config for the memprof parser.
26+
local config = {
27+
leak_only = false,
28+
human_readable = false,
29+
}
30+
2531
function opt_map.help()
2632
stdout:write [[
2733
luajit-parse-memprof - parser of the memory usage profile collected
@@ -34,14 +40,18 @@ luajit-parse-memprof [options] memprof.bin
3440
Supported options are:
3541
3642
--help Show this help and exit
43+
--human-readable Use KiB/MiB/GiB notation instead of bytes
3744
--leak-only Report only leaks information
3845
]]
3946
os.exit(0)
4047
end
4148

42-
local leak_only = false
4349
opt_map["leak-only"] = function()
44-
leak_only = true
50+
config.leak_only = true
51+
end
52+
53+
opt_map["human-readable"] = function()
54+
config.human_readable = true
4555
end
4656

4757
-- Print error and exit with error status.
@@ -101,11 +111,11 @@ local function dump(inputfile)
101111
local reader = bufread.new(inputfile)
102112
local symbols = symtab.parse(reader)
103113
local events = memprof.parse(reader, symbols)
104-
if not leak_only then
105-
view.profile_info(events)
114+
if not config.leak_only then
115+
view.profile_info(events, config)
106116
end
107117
local dheap = process.form_heap_delta(events)
108-
view.leak_info(dheap)
118+
view.leak_info(dheap, config)
109119
view.aliases(symbols)
110120
-- XXX: The second argument is required to properly close Lua
111121
-- universe (i.e. invoke <lua_close> before exiting).

tools/memprof/humanize.lua

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,29 @@
55

66
local M = {}
77

8-
function M.render(events)
8+
local function human_readable_bytes(bytes)
9+
local units = {"B", "KiB", "MiB", "GiB"}
10+
local magnitude = 1
11+
12+
while bytes >= 1024 and magnitude < #units do
13+
bytes = bytes / 1024
14+
magnitude = magnitude + 1
15+
end
16+
local is_int = math.floor(bytes) == bytes
17+
local fmt = is_int and "%d%s" or "%.2f%s"
18+
19+
return string.format(fmt, bytes, units[magnitude])
20+
end
21+
22+
local function format_bytes(bytes, config)
23+
if config.human_readable then
24+
return human_readable_bytes(bytes)
25+
else
26+
return string.format('%dB', bytes)
27+
end
28+
end
29+
30+
function M.render(events, config)
931
local ids = {}
1032

1133
for id, _ in pairs(events) do
@@ -18,11 +40,11 @@ function M.render(events)
1840

1941
for i = 1, #ids do
2042
local event = events[ids[i]]
21-
print(string.format("%s: %d events\t+%d bytes\t-%d bytes",
43+
print(string.format("%s: %d events\t+%s\t-%s",
2244
event.loc,
2345
event.num,
24-
event.alloc,
25-
event.free
46+
format_bytes(event.alloc, config),
47+
format_bytes(event.free, config)
2648
))
2749

2850
local prim_loc = {}
@@ -40,21 +62,21 @@ function M.render(events)
4062
end
4163
end
4264

43-
function M.profile_info(events)
65+
function M.profile_info(events, config)
4466
print("ALLOCATIONS")
45-
M.render(events.alloc)
67+
M.render(events.alloc, config)
4668
print("")
4769

4870
print("REALLOCATIONS")
49-
M.render(events.realloc)
71+
M.render(events.realloc, config)
5072
print("")
5173

5274
print("DEALLOCATIONS")
53-
M.render(events.free)
75+
M.render(events.free, config)
5476
print("")
5577
end
5678

57-
function M.leak_info(dheap)
79+
function M.leak_info(dheap, config)
5880
local leaks = {}
5981
for line, info in pairs(dheap) do
6082
-- Report "INTERNAL" events inconsistencies for profiling
@@ -71,8 +93,10 @@ function M.leak_info(dheap)
7193
print("HEAP SUMMARY:")
7294
for _, l in pairs(leaks) do
7395
print(string.format(
74-
"%s holds %d bytes: %d allocs, %d frees",
75-
l.line, l.dbytes, dheap[l.line].nalloc,
96+
"%s holds %s: %d allocs, %d frees",
97+
l.line,
98+
format_bytes(l.dbytes, config),
99+
dheap[l.line].nalloc,
76100
dheap[l.line].nfree
77101
))
78102
end

0 commit comments

Comments
 (0)