[Tarantool-patches] [PATCH luajit v2 5/6] memprof: introduce the `--human-readable` option
Sergey Bronnikov
sergeyb at tarantool.org
Wed Dec 20 15:24:15 MSK 2023
Thanks for the patch!
LGTM
On 12/6/23 21:55, Maxim Kokryashkin wrote:
> 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
> ---
> .../gh-5994-memprof-human-readable.test.lua | 73 +++++++++++++++++++
> tools/memprof.lua | 20 +++--
> tools/memprof/humanize.lua | 46 +++++++++---
> 3 files changed, 123 insertions(+), 16 deletions(-)
> create mode 100644 test/tarantool-tests/gh-5994-memprof-human-readable.test.lua
>
> diff --git a/test/tarantool-tests/gh-5994-memprof-human-readable.test.lua b/test/tarantool-tests/gh-5994-memprof-human-readable.test.lua
> new file mode 100644
> index 00000000..e34291be
> --- /dev/null
> +++ b/test/tarantool-tests/gh-5994-memprof-human-readable.test.lua
> @@ -0,0 +1,73 @@
> +local tap = require('tap')
> +local test = tap.test('gh-5994-memprof-human-readable'):skipcond({
> + ['Profile tools are implemented for x86_64 only'] = jit.arch ~= 'x86' and
> + jit.arch ~= 'x64',
> + ['Profile tools are implemented for Linux only'] = jit.os ~= 'Linux',
> + -- XXX: Tarantool integration is required to run this test properly.
> + -- luacheck: no global
> + ['No profile tools CLI option integration'] = _TARANTOOL,
> +})
> +
> +local utils = require('utils')
> +local TMP_BINFILE_MEMPROF = utils.tools.profilename('memprofdata.tmp.bin')
> +local PARSE_CMD = utils.exec.luacmd(arg) .. ' -tm '
> +
> +local function generate_output(bytes)
> + local res, err = misc.memprof.start(TMP_BINFILE_MEMPROF)
> + -- Should start successfully.
> + assert(res, err)
> +
> + -- luacheck: no unused
> + local _ = string.rep('_', bytes)
> +
> + res, err = misc.memprof.stop()
> + -- Should stop successfully.
> + assert(res, err)
> +end
> +
> +local TEST_SET = {
> + {
> + bytes = 2049,
> + match = '%dB',
> + hr = false,
> + name = 'non-human-readable mode is correct'
> + },
> + {
> + bytes = 100,
> + match = '%dB',
> + hr = true,
> + name = 'human-readable mode: bytes'
> + },
> + {
> + bytes = 2560,
> + match = '%d+%.%d%dKiB',
> + hr = true,
> + name = 'human-readable mode: float'
> + },
> + {
> + bytes = 2048,
> + match = '%dKiB',
> + hr = true,
> + name = 'human-readable mode: KiB'
> + },
> + {
> + bytes = 1024 * 1024,
> + match = '%dMiB',
> + hr = true,
> + name = 'human-readable mode: MiB'
> + },
> + -- XXX: The test case for GiB is not implemented because it is
> + -- OOM-prone for non-GC64 builds.
> +}
> +
> +test:plan(#TEST_SET)
> +
> +for _, params in ipairs(TEST_SET) do
> + generate_output(params.bytes)
> + local cmd = PARSE_CMD .. (params.hr and ' --human-readable ' or '')
> + local output = io.popen(cmd .. TMP_BINFILE_MEMPROF):read('*all')
> + test:like(output, params.match, params.name)
> +end
> +
> +os.remove(TMP_BINFILE_MEMPROF)
> +test:done(true)
> diff --git a/tools/memprof.lua b/tools/memprof.lua
> index 955a1327..537cb869 100644
> --- a/tools/memprof.lua
> +++ b/tools/memprof.lua
> @@ -22,6 +22,12 @@ local match, gmatch = string.match, string.gmatch
> -- Program options.
> local opt_map = {}
>
> +-- Default config for the memprof parser.
> +local config = {
> + leak_only = false,
> + human_readable = false,
> +}
> +
> function opt_map.help()
> stdout:write [[
> luajit-parse-memprof - parser of the memory usage profile collected
> @@ -34,14 +40,18 @@ luajit-parse-memprof [options] memprof.bin
> Supported options are:
>
> --help Show this help and exit
> + --human-readable Use KiB/MiB/GiB notation instead of bytes
> --leak-only Report only leaks information
> ]]
> os.exit(0)
> end
>
> -local leak_only = false
> opt_map["leak-only"] = function()
> - leak_only = true
> + config.leak_only = true
> +end
> +
> +opt_map["human-readable"] = function()
> + config.human_readable = true
> end
>
> -- Print error and exit with error status.
> @@ -101,11 +111,11 @@ local function dump(inputfile)
> local reader = bufread.new(inputfile)
> local symbols = symtab.parse(reader)
> local events = memprof.parse(reader, symbols)
> - if not leak_only then
> - view.profile_info(events)
> + if not config.leak_only then
> + view.profile_info(events, config)
> end
> local dheap = process.form_heap_delta(events)
> - view.leak_info(dheap)
> + view.leak_info(dheap, config)
> view.aliases(symbols)
> -- XXX: The second argument is required to properly close Lua
> -- universe (i.e. invoke <lua_close> before exiting).
> diff --git a/tools/memprof/humanize.lua b/tools/memprof/humanize.lua
> index 5b289ce3..a0b1693a 100644
> --- a/tools/memprof/humanize.lua
> +++ b/tools/memprof/humanize.lua
> @@ -5,7 +5,29 @@
>
> local M = {}
>
> -function M.render(events)
> +local function human_readable_bytes(bytes)
> + local units = {"B", "KiB", "MiB", "GiB"}
> + local magnitude = 1
> +
> + while bytes >= 1024 and magnitude < #units do
> + bytes = bytes / 1024
> + magnitude = magnitude + 1
> + end
> + local is_int = math.floor(bytes) == bytes
> + local fmt = is_int and "%d%s" or "%.2f%s"
> +
> + return string.format(fmt, bytes, units[magnitude])
> +end
> +
> +local function format_bytes(bytes, config)
> + if config.human_readable then
> + return human_readable_bytes(bytes)
> + else
> + return string.format('%dB', bytes)
> + end
> +end
> +
> +function M.render(events, config)
> local ids = {}
>
> for id, _ in pairs(events) do
> @@ -18,11 +40,11 @@ function M.render(events)
>
> for i = 1, #ids do
> local event = events[ids[i]]
> - print(string.format("%s: %d events\t+%d bytes\t-%d bytes",
> + print(string.format("%s: %d events\t+%s\t-%s",
> event.loc,
> event.num,
> - event.alloc,
> - event.free
> + format_bytes(event.alloc, config),
> + format_bytes(event.free, config)
> ))
>
> local prim_loc = {}
> @@ -40,21 +62,21 @@ function M.render(events)
> end
> end
>
> -function M.profile_info(events)
> +function M.profile_info(events, config)
> print("ALLOCATIONS")
> - M.render(events.alloc)
> + M.render(events.alloc, config)
> print("")
>
> print("REALLOCATIONS")
> - M.render(events.realloc)
> + M.render(events.realloc, config)
> print("")
>
> print("DEALLOCATIONS")
> - M.render(events.free)
> + M.render(events.free, config)
> print("")
> end
>
> -function M.leak_info(dheap)
> +function M.leak_info(dheap, config)
> local leaks = {}
> for line, info in pairs(dheap) do
> -- Report "INTERNAL" events inconsistencies for profiling
> @@ -71,8 +93,10 @@ function M.leak_info(dheap)
> print("HEAP SUMMARY:")
> for _, l in pairs(leaks) do
> print(string.format(
> - "%s holds %d bytes: %d allocs, %d frees",
> - l.line, l.dbytes, dheap[l.line].nalloc,
> + "%s holds %s: %d allocs, %d frees",
> + l.line,
> + format_bytes(l.dbytes, config),
> + dheap[l.line].nalloc,
> dheap[l.line].nfree
> ))
> end
More information about the Tarantool-patches
mailing list