From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id 84FFC6EC60; Mon, 29 Mar 2021 18:51:04 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 84FFC6EC60 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1617033064; bh=BgyAyoG/ZQtUmU8CaGEIQZ28uDhRHWfRQqePHBP3mto=; h=Date:In-Reply-To:To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=heJxtbPak/VLsnx83lDepXTHVJqAsj/Oo1LRIvAvMUWqUGFk5gCxm7TYAeqKPurQ7 w1WbcUVuAPGWPNNGK6FP6YKD7+JRdHhb2DTEIgByYbxiS+R6Gk44lLbKqoUy4ml+wZ yT8D/98sAWt6rrdGGKPDGnrd6CHcm4Q8gs6pjG30= Received: from smtp48.i.mail.ru (smtp48.i.mail.ru [94.100.177.108]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 120C26EC60 for ; Mon, 29 Mar 2021 18:51:01 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 120C26EC60 Received: by smtp48.i.mail.ru with esmtpa (envelope-from ) id 1lQuAN-0001Bh-Nq; Mon, 29 Mar 2021 18:51:00 +0300 Message-Id: <00C5AB40-D6C6-4386-BD59-4ABC66D05854@tarantool.org> Content-Type: multipart/alternative; boundary="Apple-Mail=_408D7518-1265-4F17-9365-A8D44A437FD9" Mime-Version: 1.0 (Mac OS X Mail 14.0 \(3654.60.0.2.21\)) Date: Mon, 29 Mar 2021 18:50:58 +0300 In-Reply-To: <20210317150323.GB28016@root> To: Sergey Kaplun References: <20210125193544.18050-1-skaplun@tarantool.org> <20210317150323.GB28016@root> X-Mailer: Apple Mail (2.3654.60.0.2.21) X-7564579A: 646B95376F6C166E X-77F55803: 4F1203BC0FB41BD9ED7173E37F4E32941B7C4A78AC10F96A7797F60C25BD4B06182A05F53808504029E61565046C93A6161668EF937E0633117573BBAC571ABA35B73AE5AA3D8426 X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE79A02CDD1178524C2EA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F79006377A079619B0C2EE308638F802B75D45FF914D58D5BE9E6BC131B5C99E7648C95C7428A34725AB662D193DC1173C7DD2A081308E4460D294BFA471835C12D1D9774AD6D5ED66289B5259CC434672EE6371117882F4460429724CE54428C33FAD30A8DF7F3B2552694AC26CFBAC0749D213D2E47CDBA5A9658359CC434672EE6371117882F4460429728AD0CFFFB425014E868A13BD56FB6657D81D268191BDAD3DC09775C1D3CA48CFB69F801DDE8C34C3BA3038C0950A5D36C8A9BA7A39EFB766EC990983EF5C0329BA3038C0950A5D36D5E8D9A59859A8B6A50BD5087FBFCDAA3AA81AA40904B5D9DBF02ECDB25306B2201CA6A4E26CD07C3BBE47FD9DD3FB595F5C1EE8F4F765FCA83251EDC214901ED5E8D9A59859A8B682BBBAF5DF00056E089D37D7C0E48F6C5571747095F342E88FB05168BE4CE3AF X-C1DE0DAB: 0D63561A33F958A5B2555EEF44C7FE7BD2FE3BA4F3EF4A5DDF7FBC2968D9CAE1D59269BC5F550898D99A6476B3ADF6B47008B74DF8BB9EF7333BD3B22AA88B938A852937E12ACA7502E6951B79FF9A3F410CA545F18667F91A7EA1CDA0B5A7A0 X-C8649E89: 4E36BF7865823D7055A7F0CF078B5EC49A30900B95165D348F343DA43F62289F86ACCD7C7ADD6CA0A786E9AB3277DC8E157629421C87FCE9516609D370EE96AE1D7E09C32AA3244CB0B020B45364398395BE1C3F714F061A435BF7150578642FFACE5A9C96DEB163 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2biojljIiQOC84rQK0VXCc5jsjw== X-Mailru-Sender: 455D65AE3A139168626D8C76E86D3AC0F665AD993217496A922A432E247A365BDDAC019A3742A8B676D79013C85012CDC77752E0C033A69E4BBE7EBD99111A499D0AB74157175C036C18EFA0BB12DBB0 X-Mras: Ok Subject: Re: [Tarantool-patches] [PATCH luajit v1] tools: introduce --leak-only memprof parser option X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Sergey Ostanevich via Tarantool-patches Reply-To: Sergey Ostanevich Cc: tarantool-patches@dev.tarantool.org Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" --Apple-Mail=_408D7518-1265-4F17-9365-A8D44A437FD9 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 Updates are LGTM. Although, I support Igor on his request to add some tests for alloc-free = pairs detection. Sergos =20 > On 17 Mar 2021, at 18:03, Sergey Kaplun wrote: >=20 > Hi! >=20 > Thanks for the review! >=20 > On 29.01.21, Sergey Ostanevich wrote: >> Hi!=20 >> Thanks for the patch, some comments below. >>=20 >> Regards, >> Sergos >>=20 >>=20 >>> On 25 Jan 2021, at 22:35, Sergey Kaplun = wrote: >>>=20 >>> This patch adds a new --leak-only memory profiler parser option. >>> When the parser runs with that option, it will report only lines >>> (or "INTERNAL") that allocate and do not free some amount of bytes. >>> The parser also reports the number of allocation and deallocation >>> events related to each line. >>> --- >>>=20 >>> Branch: = https://github.com/tarantool/luajit/tree/skaplun/gh-noticket-memprof-memle= aks-option >>> Testing branch: = https://github.com/tarantool/tarantool/tree/skaplun/gh-noticket-memprof-me= mleaks-option >=20 > I removed the old branches and create issue[1]-related: > Branch: = https://github.com/tarantool/luajit/tree/skaplun/gh-5812-memprof-memleaks-= option = > Testing branch: = https://github.com/tarantool/tarantool/tree/skaplun/gh-5812-memprof-memlea= ks-option = >=20 >>>=20 >>> Side note: I only update commit message thats why CI from the "old" >>> commit. >>> CI: https://gitlab.com/tarantool/tarantool/-/pipelines/246541599 = >>>=20 >>> This patch is a result of offline discussion of memory profiler >>> and its usage and feedback from Mons. >>>=20 >>> I don't know - is it reasonable to reference [1] issue here or = create a >>> new one? >>=20 >> I think it=E2=80=99s ok to mention [1] >=20 > Updated commit message to the following: >=20 > | tools: introduce --leak-only memprof parser option > | > | Memprof parser now adds postamble with the source lines of Lua = chunks > | (or "INTERNAL") that allocate and do not free some amount of bytes, = when > | profiler finishes. The parser also reports the number of allocation = and > | deallocation events related to each line. > | > | Also, this patch adds a new --leak-only memory profiler parser = option. > | When the parser runs with that option, it reports only leak > | information. > | > | Resolves tarantool/tarantool#5812 >=20 > Also, I've added the following patch to be able to run memprof from > Tarantool with leak-only option. >=20 > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > diff --git a/tools/memprof.lua b/tools/memprof.lua > index 85dadd4..d2231ee 100644 > --- a/tools/memprof.lua > +++ b/tools/memprof.lua > @@ -107,10 +107,14 @@ local function dump(inputfile) > os.exit(0) > end >=20 > +local function dump_wrapped(...) > + return dump(parseargs(...)) > +end > + > -- FIXME: this script should be application-independent. > local args =3D {...} > if #args =3D=3D 1 and args[1] =3D=3D "memprof" then > - return dump > + return dump_wrapped > else > - dump(parseargs(args)) > + dump_wrapped(args) > end > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D >=20 > Now memprof parser can be called from Tarantool with --leak-only = option > like the following: >=20 > | $ src/tarantool -e 'local memprof =3D require("memprof")(arg)' - = --leak-only /tmp/memprof.bin >=20 > Thoughts? >=20 >>=20 >>>=20 >>> The example of output. Assuming we have file : >>> | 1 jit.off() -- more verbose reports >>> | 2 >>> | 3 local function concat(a) >>> | 4 local nstr =3D a.."a" >>> | 5 return nstr >>> | 6 end >>> | 7 >>> | 8 local function format(a) >>> | 9 local nstr =3D string.format("%sa", a) >>> | 10 return nstr >>> | 11 end >>> | 12 >>> | 13 collectgarbage() -- cleanup >>> | 14 >>> | 15 local binfile =3D = "/tmp/memprof_"..(arg[0]):match("/([^/]*).lua")..".bin" >>> | 16 >>> | 17 local st, err =3D misc.memprof.start(binfile) >>> | 18 assert(st, err) >>> | 19 >>> | 20 for i =3D 1, 10000 do >>> | 21 local f =3D format(i) >>> | 22 local c =3D concat(i) >>> | 23 end >>> | 24 >>> | 25 local st, err =3D misc.memprof.stop() >>> | 26 assert(st, err) >>> | 27 >>> | 28 os.exit() >>>=20 >>> Parser's output without option: >>> | ALLOCATIONS >>> | @/home/burii/reports/demo_memprof/format_concat.lua:8, line 9: = 19998 624322 0 >>> |=20 >>> | REALLOCATIONS >>> |=20 >>> | DEALLOCATIONS >>> | INTERNAL: 283 0 5602 >>> | Overrides: >>> | @/home/burii/reports/demo_memprof/format_concat.lua:8, = line 9 >>> |=20 >>> | @/home/burii/reports/demo_memprof/format_concat.lua:8, line 9: 2 = 0 98304 >>> | Overrides: >>> | @/home/burii/reports/demo_memprof/format_concat.lua:8, = line 9 >>>=20 >>> And with: >>>=20 >>> | HEAP SUMMARY: >>> | @/home/burii/reports/demo_memprof/format_concat.lua:8, line 9 = holds 553214 bytes 19998 allocs, 283 frees >>>=20 >>> Side note: The attentive reader will notice that we have 283+2 frees = in >>> the first case and 283 in the second. >>> This is because --leak-only considers deallocation/reallocation = events >>> for which we know the source address. The default report aggregates >>> these all deallocations into one. See `link_to_previous()` in >>> for details. >>>=20 >>> tools/memprof.lua | 23 +++++----- >>> tools/memprof/humanize.lua | 91 = +++++++++++++++++++++++++++++++++++++- >>> tools/memprof/parse.lua | 20 +++++++-- >>> 3 files changed, 115 insertions(+), 19 deletions(-) >>>=20 >>> diff --git a/tools/memprof.lua b/tools/memprof.lua >>> index 9f96208..5d09fbd 100644 >>> --- a/tools/memprof.lua >>> +++ b/tools/memprof.lua >>> @@ -37,6 +37,11 @@ Supported options are: >>> os.exit(0) >>> end >>>=20 >>> +local leak_only =3D false >>> +opt_map["leak-only"] =3D function() >>=20 >> Should you add this into the SYNOPSIS part? >=20 > Yes, thanks, see the iterative patch below: >=20 > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > diff --git a/tools/memprof.lua b/tools/memprof.lua > index 5d09fbd..e1af99f 100644 > --- a/tools/memprof.lua > +++ b/tools/memprof.lua > @@ -33,6 +33,7 @@ luajit-parse-memprof [options] memprof.bin > Supported options are: >=20 > --help Show this help and exit > + --leak-only Report only leaks information > ]] > os.exit(0) > end > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D >=20 >>=20 >>> + leak_only =3D true >>> +end >>> + >>> -- Print error and exit with error status. >>> local function opterror(...) >>> stderr:write("luajit-parse-memprof.lua: ERROR: ", ...) >>> @@ -94,19 +99,11 @@ local function dump(inputfile) >>> local reader =3D bufread.new(inputfile) >>> local symbols =3D symtab.parse(reader) >>> local events =3D memprof.parse(reader, symbols) >>> - >>> - stdout:write("ALLOCATIONS", "\n") >>> - view.render(events.alloc, symbols) >>> - stdout:write("\n") >>> - >>> - stdout:write("REALLOCATIONS", "\n") >>> - view.render(events.realloc, symbols) >>> - stdout:write("\n") >>> - >>> - stdout:write("DEALLOCATIONS", "\n") >>> - view.render(events.free, symbols) >>> - stdout:write("\n") >>> - >>=20 >> Should the HEAP SUMMARY appear in the end of the =E2=80=98all=E2=80=99 = report? >> Otherwise the =E2=80=98_only=E2=80=99 in the option name is = mesleading: show part of=20 >> the report, while it shows completely different one. Same for the >> code below. >=20 > Agree, fixed. Also, I renamed `all()` function name to the more = verbose > one: >=20 > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > diff --git a/tools/memprof.lua b/tools/memprof.lua > index e1af99f..85dadd4 100644 > --- a/tools/memprof.lua > +++ b/tools/memprof.lua > @@ -100,11 +100,10 @@ local function dump(inputfile) > local reader =3D bufread.new(inputfile) > local symbols =3D symtab.parse(reader) > local events =3D memprof.parse(reader, symbols) > - if leak_only then > - view.leak_only(events, symbols) > - else > - view.all(events, symbols) > + if not leak_only then > + view.profile_info(events, symbols) > end > + view.leak_only(events, symbols) > os.exit(0) > end >=20 > diff --git a/tools/memprof/humanize.lua b/tools/memprof/humanize.lua > index bad0597..9e1bdf2 100644 > --- a/tools/memprof/humanize.lua > +++ b/tools/memprof/humanize.lua > @@ -42,7 +42,7 @@ function M.render(events, symbols) > end > end >=20 > -function M.all(events, symbols) > +function M.profile_info(events, symbols) > print("ALLOCATIONS") > M.render(events.alloc, symbols) > print("") > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D >=20 >>=20 >>> + if leak_only then >>> + view.leak_only(events, symbols) >>> + else >>> + view.all(events, symbols) >>> + end >>> os.exit(0) >>> end >>>=20 >>> diff --git a/tools/memprof/humanize.lua b/tools/memprof/humanize.lua >>> index 109a39d..bad0597 100644 >>> --- a/tools/memprof/humanize.lua >>> +++ b/tools/memprof/humanize.lua >>> @@ -28,8 +28,8 @@ function M.render(events, symbols) >>> )) >>>=20 >>> local prim_loc =3D {} >>> - for _, loc in pairs(event.primary) do >>> - table.insert(prim_loc, symtab.demangle(symbols, loc)) >>> + for _, heap_chunk in pairs(event.primary) do >>> + table.insert(prim_loc, symtab.demangle(symbols, = heap_chunk.loc)) >>=20 >> The order of allocations is changed, was it incorrect in some way? >=20 > The `event.primary` array is modified and contains now not only = location > line as a string, but a table with the line location in Lua chunk, > plus info about amount of allocated and freed bytes. >=20 >>=20 >>> end >>> if #prim_loc ~=3D 0 then >>> table.sort(prim_loc) >>> @@ -42,4 +42,91 @@ function M.render(events, symbols) >>> end >>> end >>>=20 >>> +function M.all(events, symbols) >>> + print("ALLOCATIONS") >>> + M.render(events.alloc, symbols) >>> + print("") >>> + >>> + print("REALLOCATIONS") >>> + M.render(events.realloc, symbols) >>> + print("") >>> + >>> + print("DEALLOCATIONS") >>> + M.render(events.free, symbols) >>> + print("") >>> +end >>> + >>> +function M.leak_only(events, symbols) >>> + -- Auto resurrects source event lines for counting/reporting. >>> + local heap =3D setmetatable({}, {__index =3D function(t, line) >>> + t[line] =3D { >>> + size =3D 0, >>> + cnt_alloc =3D 0, >>> + cnt_free =3D 0, >>> + } >>> + return t[line] >>> + end}) >>> + >>> + for _, event in pairs(events.alloc) do >>> + if event.loc then >>> + local ev_line =3D symtab.demangle(symbols, event.loc) >>> + >>> + heap[ev_line].size =3D heap[ev_line].size + event.alloc >>> + if (event.alloc > 0) then >>> + heap[ev_line].cnt_alloc =3D heap[ev_line].cnt_alloc + = event.num >>> + end >>> + end >>> + end >>> + >>> + -- Realloc and free events are pretty the same. >>> + -- We don't interesting in aggregated alloc/free sizes for >> aren=E2=80=99t interested >=20 > Fixed, thanks! See the iterative patch below. >=20 >>> + -- the event, but only for new and old size values inside >>> + -- alloc-realloc-free chain. Assuming that we have >>> + -- no collisions between different object addresses. >>=20 >> I believe the address is source of the chunk {_|de|re}allocation, is = there any >> different one? >=20 > I mean that, if some line allocated a few bytes in 0x4eadbeaf address, > freed and the other one line allocated data at the same address later, > it will be aggregated together. >=20 >>=20 >>> + local function process_non_alloc_events(events_by_type) >>> + for _, event in pairs(events_by_type) do >>> + -- Realloc and free events always have "primary" key >>> + -- that references table with rewrited memory >>> + -- (may be empty). >>> + for _, heap_chunk in pairs(event.primary) do >>> + local ev_line =3D symtab.demangle(symbols, heap_chunk.loc) >>> + >>> + heap[ev_line].size =3D heap[ev_line].size + = heap_chunk.alloced >>> + if (heap_chunk.alloced > 0) then >>=20 >> Can it be negative? If not - the above line can be put under this if = also. >=20 > No, it can't, see the iterative patch below: >=20 > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > diff --git a/tools/memprof/humanize.lua b/tools/memprof/humanize.lua > index 9e1bdf2..b8dbfbf 100644 > --- a/tools/memprof/humanize.lua > +++ b/tools/memprof/humanize.lua > @@ -71,15 +71,15 @@ function M.leak_only(events, symbols) > if event.loc then > local ev_line =3D symtab.demangle(symbols, event.loc) >=20 > - heap[ev_line].size =3D heap[ev_line].size + event.alloc > if (event.alloc > 0) then > + heap[ev_line].size =3D heap[ev_line].size + event.alloc > heap[ev_line].cnt_alloc =3D heap[ev_line].cnt_alloc + = event.num > end > end > end >=20 > -- Realloc and free events are pretty the same. > - -- We don't interesting in aggregated alloc/free sizes for > + -- We aren't interested in aggregated alloc/free sizes for > -- the event, but only for new and old size values inside > -- alloc-realloc-free chain. Assuming that we have > -- no collisions between different object addresses. > @@ -91,13 +91,13 @@ function M.leak_only(events, symbols) > for _, heap_chunk in pairs(event.primary) do > local ev_line =3D symtab.demangle(symbols, heap_chunk.loc) >=20 > - heap[ev_line].size =3D heap[ev_line].size + = heap_chunk.alloced > if (heap_chunk.alloced > 0) then > + heap[ev_line].size =3D heap[ev_line].size + = heap_chunk.alloced > heap[ev_line].cnt_alloc =3D heap[ev_line].cnt_alloc + = heap_chunk.cnt > end >=20 > - heap[ev_line].size =3D heap[ev_line].size - heap_chunk.freed > if (heap_chunk.freed > 0) then > + heap[ev_line].size =3D heap[ev_line].size - = heap_chunk.freed > heap[ev_line].cnt_free =3D heap[ev_line].cnt_free + = heap_chunk.cnt > end > end > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D >=20 >>=20 >>> + heap[ev_line].cnt_alloc =3D heap[ev_line].cnt_alloc + = heap_chunk.cnt >>> + end >>> + >>> + heap[ev_line].size =3D heap[ev_line].size - = heap_chunk.freed >>> + if (heap_chunk.freed > 0) then >>> + heap[ev_line].cnt_free =3D heap[ev_line].cnt_free + = heap_chunk.cnt >>> + end >>> + end >>> + end >>> + end >>> + process_non_alloc_events(events.realloc) >>> + process_non_alloc_events(events.free) >>> + >>> + local rest_heap =3D {} >>> + for line, info in pairs(heap) do >>> + -- Report "INTERNAL" events inconsistencies for profiling >>> + -- with enabled jit. >>> + if info.size > 0 then >>> + table.insert(rest_heap, {line =3D line, hold_bytes =3D = info.size}) >>> + end >>> + end >>> + >>> + table.sort(rest_heap, function(h1, h2) >>> + return h1.hold_bytes > h2.hold_bytes >>> + end) >>> + >>> + print("HEAP SUMMARY:") >>> + for _, h in pairs(rest_heap) do >>> + print(string.format( >>> + "%s holds %d bytes %d allocs, %d frees", >>> + h.line, h.hold_bytes, heap[h.line].cnt_alloc, = heap[h.line].cnt_free >>> + )) >>> + end >>> + print("") >>> +end >>> + >>> return M >>> diff --git a/tools/memprof/parse.lua b/tools/memprof/parse.lua >>> index 6dae22d..374686c 100644 >>> --- a/tools/memprof/parse.lua >>> +++ b/tools/memprof/parse.lua >>> @@ -39,11 +39,23 @@ local function new_event(loc) >>> } >>> end >>>=20 >>> -local function link_to_previous(heap_chunk, e) >>> +local function link_to_previous(heap_chunk, e, nsize) >>> -- Memory at this chunk was allocated before we start tracking. >>> if heap_chunk then >>> -- Save Lua code location (line) by address (id). >>> - e.primary[heap_chunk[2]] =3D heap_chunk[3] >>> + if e.primary[heap_chunk[2]] =3D=3D nil then >=20 > I don't like this line now, so I've updated it like the following: >=20 > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > diff --git a/tools/memprof/parse.lua b/tools/memprof/parse.lua > index 374686c..df10a45 100644 > --- a/tools/memprof/parse.lua > +++ b/tools/memprof/parse.lua > @@ -43,7 +43,7 @@ local function link_to_previous(heap_chunk, e, = nsize) > -- Memory at this chunk was allocated before we start tracking. > if heap_chunk then > -- Save Lua code location (line) by address (id). > - if e.primary[heap_chunk[2]] =3D=3D nil then > + if not e.primary[heap_chunk[2]] then > e.primary[heap_chunk[2]] =3D { > loc =3D heap_chunk[3], > alloced =3D 0, > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D >=20 >>> + e.primary[heap_chunk[2]] =3D { >>> + loc =3D heap_chunk[3], >>> + alloced =3D 0, >>> + freed =3D 0, >>> + cnt =3D 0, >>> + } >>> + end >>> + -- Save memory diff heap information. >>> + local location_data =3D e.primary[heap_chunk[2]] >>> + location_data.alloced =3D location_data.alloced + nsize >>> + location_data.freed =3D location_data.freed + heap_chunk[1] >>> + location_data.cnt =3D location_data.cnt + 1 >>> end >>> end >>>=20 >>> @@ -97,7 +109,7 @@ local function parse_realloc(reader, asource, = events, heap) >>> e.free =3D e.free + osize >>> e.alloc =3D e.alloc + nsize >>>=20 >>> - link_to_previous(heap[oaddr], e) >>> + link_to_previous(heap[oaddr], e, nsize) >>>=20 >>> heap[oaddr] =3D nil >>> heap[naddr] =3D {nsize, id, loc} >>> @@ -116,7 +128,7 @@ local function parse_free(reader, asource, = events, heap) >>> e.num =3D e.num + 1 >>> e.free =3D e.free + osize >>>=20 >>> - link_to_previous(heap[oaddr], e) >>> + link_to_previous(heap[oaddr], e, 0) >>>=20 >>> heap[oaddr] =3D nil >>> end >>> --=20 >>> 2.28.0 >>>=20 >>> [1]: https://github.com/tarantool/tarantool/issues/5657 >>=20 >=20 > [1]: https://github.com/tarantool/tarantool/issues/5812 = >=20 > --=20 > Best regards, > Sergey Kaplun --Apple-Mail=_408D7518-1265-4F17-9365-A8D44A437FD9 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8 Updates are LGTM.

Although, I support Igor on his request = to add some tests for alloc-free pairs detection.
Sergos
 

On 17 Mar 2021, at 18:03, Sergey Kaplun <skaplun@tarantool.org> wrote:

Hi!

Thanks for the review!

On 29.01.21, Sergey Ostanevich wrote:
Hi! 
Thanks for the patch, some comments below.

Regards,
Sergos


On 25 Jan 2021, at = 22:35, Sergey Kaplun <skaplun@tarantool.org> wrote:

This patch adds a new --leak-only memory profiler parser = option.
When the parser runs with that option, it will = report only lines
(or "INTERNAL") that allocate and do not = free some amount of bytes.
The parser also reports the = number of allocation and deallocation
events related to = each line.
---

Branch: https://github.com/tarantool/luajit/tree/skaplun/gh-noticket-me= mprof-memleaks-option
Testing branch: https://github.com/tarantool/tarantool/tree/skaplun/gh-noticket= -memprof-memleaks-option

I removed the = old branches and create issue[1]-related:
Branch: https://github.com/tarantool/luajit/tree/skaplun/gh-5812-mempro= f-memleaks-option
Testing branch: https://github.com/tarantool/tarantool/tree/skaplun/gh-5812-mem= prof-memleaks-option


Side note: I only update commit message thats = why CI from the "old"
commit.
CI: https://gitlab.com/tarantool/tarantool/-/pipelines/246541599

This patch is a result of offline = discussion of memory profiler
and its usage and feedback = from Mons.

I don't know - is it reasonable = to reference [1] issue here or create a
new one?

I think it=E2=80=99s ok to = mention [1]

Updated commit message to the following:

| tools: = introduce --leak-only memprof parser option
|
| Memprof = parser now adds postamble with the source lines of Lua chunks
| (or = "INTERNAL") that allocate and do not free some amount of bytes, = when
| profiler = finishes. The parser also reports the number of allocation and
| = deallocation events related to each line.
|
| Also, this = patch adds a new --leak-only memory profiler parser option.
| When the = parser runs with that option, it reports only leak
| = information.
|
| Resolves = tarantool/tarantool#5812

Also, I've added the following patch to be able to run = memprof from
Tarantool = with leak-only option.

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
diff --git = a/tools/memprof.lua b/tools/memprof.lua
index 85dadd4..d2231ee 100644
--- a/tools/memprof.lua
+++ b/tools/memprof.lua
@@ -107,10 +107,14 @@ local function = dump(inputfile)
  os.exit(0)
end

+local function dump_wrapped(...)
+ =  return dump(parseargs(...))
+end
+
-- FIXME: = this script should be application-independent.
local args =3D = {...}
if #args =3D=3D= 1 and args[1] =3D=3D "memprof" then
-  return dump
+  return dump_wrapped
else
-  dump(parseargs(args))
+  dump_wrapped(args)
end
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

Now memprof = parser can be called from Tarantool with --leak-only option
like the = following:

| $ = src/tarantool -e 'local memprof =3D require("memprof")(arg)' - = --leak-only /tmp/memprof.bin

Thoughts?



The example of output. Assuming = we have file <format_concat.lua>:
|  1 = jit.off() -- more verbose reports
|  2
| =  3 local function concat(a)
|  4 =   local nstr =3D a.."a"
|  5 =   return nstr
|  6 end
| =  7
|  8 local function format(a)
| =  9   local nstr =3D string.format("%sa", a)
|= 10   return nstr
| 11 end
| 12
| 13 collectgarbage() -- cleanup
| 14
| 15 local binfile =3D = "/tmp/memprof_"..(arg[0]):match("/([^/]*).lua")..".bin"
| = 16
| 17 local st, err =3D misc.memprof.start(binfile)
| 18 assert(st, err)
| 19
| 20 = for i =3D 1, 10000 do
| 21   local f =3D = format(i)
| 22   local c =3D concat(i)
| 23 end
| 24
| 25 local st, err = =3D misc.memprof.stop()
| 26 assert(st, err)
|= 27
| 28 os.exit()

Parser's = output without option:
| ALLOCATIONS
| = @/home/burii/reports/demo_memprof/format_concat.lua:8, line 9: = 19998 = 624322 = 0
| 
| = REALLOCATIONS
| 
| = DEALLOCATIONS
| INTERNAL: 283 0 5602
|  = Overrides:
|  = @/home/burii/reports/demo_memprof/format_concat.lua:8, line 9
| 
| @/home/burii/reports/demo_memprof/format_concat.lua:8, line = 9: 2 = 0 = 98304
|  = Overrides:
|  = @/home/burii/reports/demo_memprof/format_concat.lua:8, line 9

And with:

| HEAP = SUMMARY:
| = @/home/burii/reports/demo_memprof/format_concat.lua:8, line 9 holds = 553214 bytes 19998 allocs, 283 frees

Side = note: The attentive reader will notice that we have 283+2 frees in
the first case and 283 in the second.
This is = because --leak-only considers deallocation/reallocation events
for which we know the source address. The default report = aggregates
these all deallocations into one. See = `link_to_previous()` in
<tools/memprof/parse.lua> = for details.

tools/memprof.lua =          | 23 +++++-----
tools/memprof/humanize.lua | 91 = +++++++++++++++++++++++++++++++++++++-
tools/memprof/parse.lua    | 20 +++++++--
3 files changed, 115 insertions(+), 19 deletions(-)

diff --git a/tools/memprof.lua = b/tools/memprof.lua
index 9f96208..5d09fbd 100644
--- a/tools/memprof.lua
+++ = b/tools/memprof.lua
@@ -37,6 +37,11 @@ Supported options = are:
 os.exit(0)
end

+local leak_only =3D false
+opt_map["leak-only"] = =3D function()

Should you add = this into the SYNOPSIS part?

Yes, thanks, = see the iterative patch below:

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
diff --git = a/tools/memprof.lua b/tools/memprof.lua
index 5d09fbd..e1af99f 100644
--- a/tools/memprof.lua
+++ b/tools/memprof.lua
@@ -33,6 +33,7 @@ luajit-parse-memprof [options] = memprof.bin
Supported = options are:

  --help =             &n= bsp;           &nbs= p;  Show this help and exit
+  --leak-only =             &n= bsp;         Report only = leaks information
]]
  os.exit(0)
end
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D


+  leak_only =3D = true
+end
+
-- Print error and = exit with error status.
local function opterror(...)
 stderr:write("luajit-parse-memprof.lua: ERROR: ", = ...)
@@ -94,19 +99,11 @@ local function dump(inputfile)
 local reader =3D bufread.new(inputfile)
 local symbols =3D symtab.parse(reader)
 local events =3D memprof.parse(reader, symbols)
-
-  stdout:write("ALLOCATIONS", "\n")
-  view.render(events.alloc, symbols)
- =  stdout:write("\n")
-
- =  stdout:write("REALLOCATIONS", "\n")
- =  view.render(events.realloc, symbols)
- =  stdout:write("\n")
-
- =  stdout:write("DEALLOCATIONS", "\n")
- =  view.render(events.free, symbols)
- =  stdout:write("\n")
-

Should the HEAP SUMMARY appear in the end of the =E2=80=98all=E2= =80=99 report?
Otherwise the =E2=80=98_only=E2=80=99 in = the option name is mesleading: show part of 
the report, = while it shows completely different one. Same for the
code = below.

Agree, fixed. Also, I renamed `all()` function name to the = more verbose
one:

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
diff --git = a/tools/memprof.lua b/tools/memprof.lua
index e1af99f..85dadd4 100644
--- a/tools/memprof.lua
+++ b/tools/memprof.lua
@@ -100,11 +100,10 @@ local function = dump(inputfile)
  local reader =3D bufread.new(inputfile)
  local symbols =3D symtab.parse(reader)
  local events =3D memprof.parse(reader, = symbols)
-  if = leak_only then
-    view.leak_only(events, symbols)
- =  else
- =    view.all(events, symbols)
+  if = not leak_only then
+    view.profile_info(events, = symbols)
  end
+  view.leak_only(events, symbols)
  os.exit(0)
end

diff --git a/tools/memprof/humanize.lua = b/tools/memprof/humanize.lua
index bad0597..9e1bdf2 100644
--- a/tools/memprof/humanize.lua
+++ = b/tools/memprof/humanize.lua
@@ -42,7 +42,7 @@ function M.render(events, = symbols)
  end
end

-function M.all(events, symbols)
+function = M.profile_info(events, symbols)
  print("ALLOCATIONS")
  M.render(events.alloc, symbols)
  print("")
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D


+  if leak_only = then
+    view.leak_only(events, = symbols)
+  else
+ =    view.all(events, symbols)
+  end
 os.exit(0)
end

diff --git a/tools/memprof/humanize.lua = b/tools/memprof/humanize.lua
index 109a39d..bad0597 = 100644
--- a/tools/memprof/humanize.lua
+++ = b/tools/memprof/humanize.lua
@@ -28,8 +28,8 @@ function = M.render(events, symbols)
   ))

   local prim_loc =3D {}
-    for _, loc in pairs(event.primary) do
-      table.insert(prim_loc, = symtab.demangle(symbols, loc))
+    for _, = heap_chunk in pairs(event.primary) do
+ =      table.insert(prim_loc, = symtab.demangle(symbols, heap_chunk.loc))

The order of allocations is changed, was it incorrect in some = way?

The `event.primary` array is modified and contains now not = only location
line as a = string, but a table with the line location in Lua chunk,
plus info = about amount of allocated and freed bytes.


   end
   if #prim_loc ~=3D 0 then
     table.sort(prim_loc)
@@ -42,4 +42,91 @@ function M.render(events, symbols)
 end
end

+function M.all(events, symbols)
+ =  print("ALLOCATIONS")
+  M.render(events.alloc, = symbols)
+  print("")
+
+ =  print("REALLOCATIONS")
+ =  M.render(events.realloc, symbols)
+ =  print("")
+
+ =  print("DEALLOCATIONS")
+  M.render(events.free, = symbols)
+  print("")
+end
+
+function M.leak_only(events, symbols)
+  -- Auto resurrects source event lines for = counting/reporting.
+  local heap =3D = setmetatable({}, {__index =3D function(t, line)
+ =    t[line] =3D {
+ =      size =3D 0,
+ =      cnt_alloc =3D 0,
+ =      cnt_free =3D 0,
+ =    }
+    return t[line]
+  end})
+
+  for _, = event in pairs(events.alloc) do
+    if = event.loc then
+      local = ev_line =3D symtab.demangle(symbols, event.loc)
+
+      heap[ev_line].size =3D = heap[ev_line].size + event.alloc
+ =      if (event.alloc > 0) then
+=        heap[ev_line].cnt_alloc =3D = heap[ev_line].cnt_alloc + event.num
+ =      end
+ =    end
+  end
+
+  -- Realloc and free events are pretty the same.
+  -- We don't interesting in aggregated alloc/free = sizes for
        &n= bsp; aren=E2=80=99t interested

Fixed, = thanks! See the iterative patch below.

+ =  -- the event, but only for new and old size values inside
+  -- alloc-realloc-free chain. Assuming that we have
+  -- no collisions between different object = addresses.

I believe the = address is source of the chunk {_|de|re}allocation, is there any
different one?

I mean that, = if some line allocated a few bytes in 0x4eadbeaf address,
freed and the = other one line allocated data at the same address later,
it will be = aggregated together.


+  local function = process_non_alloc_events(events_by_type)
+ =    for _, event in pairs(events_by_type) do
+=      -- Realloc and free events always have = "primary" key
+      -- that = references table with rewrited memory
+ =      -- (may be empty).
+ =      for _, heap_chunk in pairs(event.primary) = do
+        local = ev_line =3D symtab.demangle(symbols, heap_chunk.loc)
+
+ =        heap[ev_line].size =3D = heap[ev_line].size + heap_chunk.alloced
+ =        if (heap_chunk.alloced > 0) = then

Can it be negative? If = not - the above line can be put under this if also.

No, it can't, see the iterative patch below:

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
diff --git = a/tools/memprof/humanize.lua b/tools/memprof/humanize.lua
index = 9e1bdf2..b8dbfbf 100644
--- a/tools/memprof/humanize.lua
+++ = b/tools/memprof/humanize.lua
@@ -71,15 +71,15 @@ function M.leak_only(events, = symbols)
    if event.loc then
      local ev_line =3D = symtab.demangle(symbols, event.loc)

-      heap[ev_line].size =3D = heap[ev_line].size + event.alloc
      if (event.alloc > 0) = then
+ =        heap[ev_line].size =3D = heap[ev_line].size + event.alloc
        heap[ev_line].c= nt_alloc =3D heap[ev_line].cnt_alloc + event.num
      end
    end
  end

  -- Realloc and free events are pretty the = same.
-  -- We = don't interesting in aggregated alloc/free sizes for
+  -- We = aren't interested in aggregated alloc/free sizes for
  -- = the event, but only for new and old size values inside
  -- = alloc-realloc-free chain. Assuming that we have
  -- = no collisions between different object addresses.
@@ -91,13 = +91,13 @@ function M.leak_only(events, symbols)
      for _, heap_chunk in = pairs(event.primary) do
        local ev_line = =3D symtab.demangle(symbols, heap_chunk.loc)

- =        heap[ev_line].size =3D = heap[ev_line].size + heap_chunk.alloced
        if = (heap_chunk.alloced > 0) then
+ =          heap[ev_line].size = =3D heap[ev_line].size + heap_chunk.alloced
          hea= p[ev_line].cnt_alloc =3D heap[ev_line].cnt_alloc + = heap_chunk.cnt
        end

- =        heap[ev_line].size =3D = heap[ev_line].size - heap_chunk.freed
        if = (heap_chunk.freed > 0) then
+ =          heap[ev_line].size = =3D heap[ev_line].size - heap_chunk.freed
          hea= p[ev_line].cnt_free =3D heap[ev_line].cnt_free + = heap_chunk.cnt
        end
      end
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D


+ =          heap[ev_line].cnt_al= loc =3D heap[ev_line].cnt_alloc + heap_chunk.cnt
+ =        end
+
+ =        heap[ev_line].size =3D = heap[ev_line].size - heap_chunk.freed
+ =        if (heap_chunk.freed > 0) = then
+ =          heap[ev_line].cnt_fr= ee =3D heap[ev_line].cnt_free + heap_chunk.cnt
+ =        end
+ =      end
+ =    end
+  end
+ =  process_non_alloc_events(events.realloc)
+ =  process_non_alloc_events(events.free)
+
+  local rest_heap =3D {}
+  for = line, info in pairs(heap) do
+    -- Report = "INTERNAL" events inconsistencies for profiling
+ =    -- with enabled jit.
+ =    if info.size > 0 then
+ =      table.insert(rest_heap, {line =3D line, = hold_bytes =3D info.size})
+    end
+  end
+
+ =  table.sort(rest_heap, function(h1, h2)
+ =    return h1.hold_bytes > h2.hold_bytes
+ =  end)
+
+  print("HEAP = SUMMARY:")
+  for _, h in pairs(rest_heap) do
+    print(string.format(
+ =      "%s holds %d bytes %d allocs, %d = frees",
+      h.line, = h.hold_bytes, heap[h.line].cnt_alloc, heap[h.line].cnt_free
+    ))
+  end
+  print("")
+end
+
return M
diff --git a/tools/memprof/parse.lua = b/tools/memprof/parse.lua
index 6dae22d..374686c 100644
--- a/tools/memprof/parse.lua
+++ = b/tools/memprof/parse.lua
@@ -39,11 +39,23 @@ local = function new_event(loc)
 }
end

-local function link_to_previous(heap_chunk, = e)
+local function link_to_previous(heap_chunk, e, = nsize)
 -- Memory at this chunk was allocated before = we start tracking.
 if heap_chunk then
   -- Save Lua code location (line) by address = (id).
-    e.primary[heap_chunk[2]] =3D = heap_chunk[3]
+    if = e.primary[heap_chunk[2]] =3D=3D nil then

I don't like this line now, so I've updated it like the = following:

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
diff --git = a/tools/memprof/parse.lua b/tools/memprof/parse.lua
index = 374686c..df10a45 100644
--- a/tools/memprof/parse.lua
+++ b/tools/memprof/parse.lua
@@ -43,7 +43,7 @@ local function link_to_previous(heap_chunk, = e, nsize)
  -- = Memory at this chunk was allocated before we start tracking.
  if = heap_chunk then
    -- Save Lua code location (line) by = address (id).
- =    if e.primary[heap_chunk[2]] =3D=3D nil then
+ =    if not e.primary[heap_chunk[2]] then
      e.primary[heap_chunk[2]] = =3D {
        loc =3D = heap_chunk[3],
        alloced =3D = 0,
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

+ =      e.primary[heap_chunk[2]] =3D {
+        loc =3D = heap_chunk[3],
+ =        alloced =3D 0,
+ =        freed =3D 0,
+ =        cnt =3D 0,
+ =      }
+    end
+    -- Save memory diff heap information.
+    local location_data =3D = e.primary[heap_chunk[2]]
+ =    location_data.alloced =3D location_data.alloced + = nsize
+    location_data.freed =3D = location_data.freed + heap_chunk[1]
+ =    location_data.cnt =3D location_data.cnt + 1
 end
end

@@ = -97,7 +109,7 @@ local function parse_realloc(reader, asource, events, = heap)
 e.free =3D e.free + osize
 e.alloc =3D e.alloc + nsize

-  link_to_previous(heap[oaddr], e)
+ =  link_to_previous(heap[oaddr], e, nsize)

 heap[oaddr] =3D nil
 heap[naddr] =3D = {nsize, id, loc}
@@ -116,7 +128,7 @@ local function = parse_free(reader, asource, events, heap)
 e.num =3D = e.num + 1
 e.free =3D e.free + osize

-  link_to_previous(heap[oaddr], e)
+ =  link_to_previous(heap[oaddr], e, 0)

 heap[oaddr] =3D nil
end
-- 
2.28.0

[1]:
https://github.com/tarantool/tarantool/issues/5657


[1]: https://github.com/tarantool/tarantool/issues/5812

-- 
Best = regards,
Sergey = Kaplun

= --Apple-Mail=_408D7518-1265-4F17-9365-A8D44A437FD9--