From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp43.i.mail.ru (smtp43.i.mail.ru [94.100.177.103]) (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 7BBDC4765E3 for ; Mon, 28 Dec 2020 00:55:56 +0300 (MSK) From: Sergey Ostanevich Message-Id: <6CB52022-091A-441B-821E-1AF73FEEB400@tarantool.org> Content-Type: multipart/alternative; boundary="Apple-Mail=_D231FCDC-5053-49D1-A3A7-D64EA10964AC" Mime-Version: 1.0 (Mac OS X Mail 13.4 \(3608.120.23.2.4\)) Date: Mon, 28 Dec 2020 00:55:54 +0300 In-Reply-To: <20201227160213.GC14702@root> References: <03ac70e3bfb9bf7061ad71c1bac50ed3f8e853fc.1608907726.git.skaplun@tarantool.org> <20201227160213.GC14702@root> Subject: Re: [Tarantool-patches] [PATCH luajit v2 7/7] tools: introduce a memory profile parser List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: Sergey Kaplun Cc: tarantool-patches@dev.tarantool.org --Apple-Mail=_D231FCDC-5053-49D1-A3A7-D64EA10964AC Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 LGTM. Sergos > On 27 Dec 2020, at 19:02, Sergey Kaplun wrote: >=20 >=20 > Hi! >=20 > Thanks for the review. >=20 > On 27.12.20, Sergey Ostanevich wrote: >> Hi! >>=20 >> Thanks for the patch! >> See my 7 comments below. >>=20 >> Sergos >>=20 >>=20 >>> On 25 Dec 2020, at 18:26, Sergey Kaplun = wrote: >> >>> diff --git a/test/misclib-memprof-lapi.test.lua = b/test/misclib-memprof-lapi.test.lua >>> new file mode 100755 >>> index 0000000..e02c6fa >>> --- /dev/null >>> +++ b/test/misclib-memprof-lapi.test.lua >>> @@ -0,0 +1,135 @@ >>> +#!/usr/bin/env tarantool >>> + >>> +local tap =3D require('tap') >>> + >>> +local test =3D tap.test("misc-memprof-lapi") >>> +test:plan(9) >>> + >>> +jit.off() >>> +jit.flush() >>> + >>> +-- FIXME: Launch tests with LUA_PATH enviroment variable. >>> +local path =3D arg[0]:gsub('/[^/]+%.test%.lua', =E2=80=98=E2=80=99) >>=20 >> I believe it won=E2=80=99t work well for some cases, such as >>=20 >> tarantool> arg[0] >> --- >> - void.test.lua >> ... >>=20 >> tarantool> arg[0]:gsub('/[^/]+%.test%.lua', '') >> --- >> - void.test.lua >> - 0 >> ... >>=20 >> Alternative is: >>=20 >> tarantool> os.execute('dirname '..arg[0]) >> . >> --- >> - 0 >> ... >>=20 >>=20 >=20 > I suppose you want to use `popen` here to catch output? > General practice is using `gsub` for now (see ). > I fixed regexp to the following: >=20 > | local path =3D arg[0]:gsub("[^/]+%.test%.lua", "") > | local path_suffix =3D "../tools/?.lua;" > | package.path =3D ("%s%s;"):format(path, path_suffix)..package.path >=20 >>> +local path_suffix =3D '../tools/?.lua;' >>> +package.path =3D ('%s/%s;'):format(path, path_suffix)..package.path >>> + >>> +local table_new =3D require "table.new" >>> + >>> +local bufread =3D require "utils.bufread" >>> +local memprof =3D require "memprof.parse" >>> +local symtab =3D require "utils.symtab" >>> + >>> +local TMP_BINFILE =3D arg[0]:gsub('[^/]+%.test%.lua', = '%.%1.memprofdata.tmp.bin') >>> +local BAD_PATH =3D arg[0]:gsub('[^/]+%.test%.lua', = '%1/memprofdata.tmp.bin') >>> + >>> +local function payload() >>> + -- Preallocate table to avoid array part reallocations. >> ^parts? >=20 > No, I meant array part of table (not a hash part). > I rewrote it to the following: > | -- Preallocate table to avoid table array part reallocations. >=20 >>> + local _ =3D table_new(100, 0) >>> + >>> + -- Want too see 100 objects here. >>> + for i =3D 1, 100 do >>> + -- Try to avoid crossing with "test" module objects. >>> + _[i] =3D "memprof-str-"..i >>> + end >>> + >>> + _ =3D nil >>> + -- VMSTATE =3D=3D GC, reported as INTERNAL. >>> + collectgarbage() >>> +end >>> + >>> +local function generate_output(filename) >>> + -- Clean up all garbage to avoid polution of free. >> pollution >=20 > Fixed. >=20 >>> + collectgarbage() >>> + >>> + local res, err =3D misc.memprof.start(filename) >>> + -- Should start succesfully. >>> + assert(res, err) >>> + >>> + payload() >>> + >>> + res, err =3D misc.memprof.stop() >>> + -- Should stop succesfully. >>> + assert(res, err) >>> +end >>=20 >> >>=20 >=20 > I also foggot to return `jit.on()` back in the end of test. > Added. >=20 >>> diff --git a/tools/memprof/parse.lua b/tools/memprof/parse.lua >>> new file mode 100644 >>> index 0000000..f4996f4 >>> --- /dev/null >>> +++ b/tools/memprof/parse.lua >>=20 >> >>=20 >>> +local function link_to_previous(heap, e, oaddr) >>> + -- Memory at oaddr was allocated before we started tracking. >>> + local heap_chunk =3D heap[oaddr] >>=20 >> Do you need two args for this? Can you just pass the heap[oaddr] = instead? >=20 > Yes. It's looks better. Applied. Thank you! >=20 >>=20 >>> + if heap_chunk then >>> + -- Save Lua code location (line) by address (id). >>> + e.primary[heap_chunk[2]] =3D heap_chunk[3] >>> + end >>> +end >>> + >>=20 >> >>=20 >>> +local function ev_header_split(evh) >>> + return band(evh, 0x3), band(evh, lshift(0x3, 2)) >>=20 >> Should you intorduce masks along with AEVENT/ASOURCE to avoid these >> magic numbers? >=20 > My bad. Done. >=20 >>=20 >>> +end >>> + >>=20 >> >>=20 >>> diff --git a/tools/utils/bufread.lua b/tools/utils/bufread.lua >>=20 >> >>=20 >>> + >>> +local function _read_stream(reader, n) >>> + local tail_size =3D reader._end - reader._pos >>> + >>> + if tail_size >=3D n then >>> + -- Enough data to satisfy the request of n bytes. >>> + return true >>> + end >>> + >>> + -- Otherwise carry tail_size bytes from the end of the buffer >>> + -- to the start and fill up free_size bytes with fresh data. >>> + -- tail_size < n <=3D free_size (see assert below) ensures that >>> + -- we don't copy overlapping memory regions. >>> + -- reader._pos =3D=3D 0 means filling buffer for the first time. >>> + >>> + local free_size =3D reader._pos > 0 and reader._pos or n >>> + >>> + assert(n <=3D free_size, "Internal buffer is large enough") >>=20 >> Does it mean I will have a fail in case _pos is less that half of the >> buffer and n is more than the tail_size?=20 >> Which means I can use only half of the buffer? >=20 > Hat-trick of buffer's misuse. > Thanks you very much again! Fixed (see the iterative patch below). >=20 >>=20 >>> + >>> + if tail_size ~=3D 0 then >>> + ffi_C.memcpy(reader._buf, reader._buf + reader._pos, tail_size) >>> + end >>> + >>> + local bytes_read =3D ffi_C.fread( >>> + reader._buf + tail_size, 1, free_size, reader._file >>> + ) >>> + >>> + reader._pos =3D 0 >>> + reader._end =3D tail_size + bytes_read >>> + >>> + return reader._end - reader._pos >=3D n >>> +end >>> + >>=20 >> >>=20 >>> +function M.eof(reader) >>> + local sys_feof =3D ffi_C.feof(reader._file) >>> + if sys_feof =3D=3D 0 then >>> + return false >>> + end >>> + -- Otherwise return true only we have reached >> ^^ if we >=20 > Fixed. >=20 >>=20 >>> + -- the end of the buffer. >>> + return reader._pos =3D=3D reader._end >>> +end >> >>=20 >=20 > The iterative patch. Branch is force-pushed. > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=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/test/misclib-memprof-lapi.test.lua = b/test/misclib-memprof-lapi.test.lua > index 2366c00..dd484f4 100755 > --- a/test/misclib-memprof-lapi.test.lua > +++ b/test/misclib-memprof-lapi.test.lua > @@ -9,9 +9,9 @@ jit.off() > jit.flush() >=20 > -- FIXME: Launch tests with LUA_PATH enviroment variable. > -local path =3D arg[0]:gsub("/[^/]+%.test%.lua", "") > +local path =3D arg[0]:gsub("[^/]+%.test%.lua", "") > local path_suffix =3D "../tools/?.lua;" > -package.path =3D ("%s/%s;"):format(path, path_suffix)..package.path > +package.path =3D ("%s%s;"):format(path, path_suffix)..package.path >=20 > local table_new =3D require "table.new" >=20 > @@ -23,7 +23,7 @@ local TMP_BINFILE =3D = arg[0]:gsub("[^/]+%.test%.lua", "%.%1.memprofdata.tmp.bin") > local BAD_PATH =3D arg[0]:gsub("[^/]+%.test%.lua", = "%1/memprofdata.tmp.bin") >=20 > local function payload() > - -- Preallocate table to avoid array part reallocations. > + -- Preallocate table to avoid table array part reallocations. > local _ =3D table_new(100, 0) >=20 > -- Want too see 100 objects here. > @@ -38,7 +38,7 @@ local function payload() > end >=20 > local function generate_output(filename) > - -- Clean up all garbage to avoid polution of free. > + -- Clean up all garbage to avoid pollution of free. > collectgarbage() >=20 > local res, err =3D misc.memprof.start(filename) > @@ -132,4 +132,5 @@ test:ok(check_alloc_report(alloc, 32, 25, 100)) > -- Collect all previous allocated objects. > test:ok(free.INTERNAL.num =3D=3D 102) >=20 > +jit.on() > os.exit(test:check() and 0 or 1) > diff --git a/tools/memprof/parse.lua b/tools/memprof/parse.lua > index f4996f4..6dae22d 100644 > --- a/tools/memprof/parse.lua > +++ b/tools/memprof/parse.lua > @@ -19,10 +19,14 @@ local AEVENT_ALLOC =3D 1 > local AEVENT_FREE =3D 2 > local AEVENT_REALLOC =3D 3 >=20 > +local AEVENT_MASK =3D 0x3 > + > local ASOURCE_INT =3D lshift(1, 2) > local ASOURCE_LFUNC =3D lshift(2, 2) > local ASOURCE_CFUNC =3D lshift(3, 2) >=20 > +local ASOURCE_MASK =3D lshift(0x3, 2) > + > local M =3D {} >=20 > local function new_event(loc) > @@ -35,9 +39,8 @@ local function new_event(loc) > } > end >=20 > -local function link_to_previous(heap, e, oaddr) > - -- Memory at oaddr was allocated before we started tracking. > - local heap_chunk =3D heap[oaddr] > +local function link_to_previous(heap_chunk, e) > + -- 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] > @@ -94,7 +97,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, e, oaddr) > + link_to_previous(heap[oaddr], e) >=20 > heap[oaddr] =3D nil > heap[naddr] =3D {nsize, id, loc} > @@ -113,7 +116,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, e, oaddr) > + link_to_previous(heap[oaddr], e) >=20 > heap[oaddr] =3D nil > end > @@ -131,7 +134,7 @@ end > -- Splits event header into event type (aka aevent =3D allocation > -- event) and event source (aka asource =3D allocation source). > local function ev_header_split(evh) > - return band(evh, 0x3), band(evh, lshift(0x3, 2)) > + return band(evh, AEVENT_MASK), band(evh, ASOURCE_MASK) > end >=20 > local function parse_event(reader, events) > diff --git a/tools/utils/bufread.lua b/tools/utils/bufread.lua > index 873e06a..34bae9a 100644 > --- a/tools/utils/bufread.lua > +++ b/tools/utils/bufread.lua > @@ -22,7 +22,7 @@ local BUFFER_SIZE =3D 10 * 1024 * 1024 > local M =3D {} >=20 > ffi.cdef[[ > - void *memcpy(void *, const void *, size_t); > + void *memmove(void *, const void *, size_t); >=20 > typedef struct FILE_ FILE; > FILE *fopen(const char *, const char *); > @@ -34,6 +34,8 @@ ffi.cdef[[ > local function _read_stream(reader, n) > local tail_size =3D reader._end - reader._pos >=20 > + assert(n <=3D BUFFER_SIZE, "Internal buffer is large enough") > + > if tail_size >=3D n then > -- Enough data to satisfy the request of n bytes. > return true > @@ -41,16 +43,11 @@ local function _read_stream(reader, n) >=20 > -- Otherwise carry tail_size bytes from the end of the buffer > -- to the start and fill up free_size bytes with fresh data. > - -- tail_size < n <=3D free_size (see assert below) ensures that > - -- we don't copy overlapping memory regions. > - -- reader._pos =3D=3D 0 means filling buffer for the first time. > - > - local free_size =3D reader._pos > 0 and reader._pos or n >=20 > - assert(n <=3D free_size, "Internal buffer is large enough") > + local free_size =3D BUFFER_SIZE - tail_size >=20 > if tail_size ~=3D 0 then > - ffi_C.memcpy(reader._buf, reader._buf + reader._pos, tail_size) > + ffi_C.memmove(reader._buf, reader._buf + reader._pos, tail_size) > end >=20 > local bytes_read =3D ffi_C.fread( > @@ -114,7 +111,7 @@ function M.eof(reader) > if sys_feof =3D=3D 0 then > return false > end > - -- Otherwise return true only we have reached > + -- Otherwise return true only if we have reached > -- the end of the buffer. > return reader._pos =3D=3D reader._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 > Best regards, > Sergey Kaplun --Apple-Mail=_D231FCDC-5053-49D1-A3A7-D64EA10964AC Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8 LGTM.

Sergos

On 27 Dec 2020, at 19:02, = Sergey Kaplun <skaplun@tarantool.org> wrote:


Hi!

Thanks for = the review.

On 27.12.20, = Sergey Ostanevich wrote:
Hi!

Thanks= for the patch!
See my 7 comments below.

Sergos


On 25 Dec 2020, at 18:26, Sergey Kaplun <skaplun@tarantool.org> wrote:
<snipped>
diff --git a/test/misclib-memprof-lapi.test.lua = b/test/misclib-memprof-lapi.test.lua
new file mode = 100755
index 0000000..e02c6fa
--- = /dev/null
+++ b/test/misclib-memprof-lapi.test.lua
@@ -0,0 +1,135 @@
+#!/usr/bin/env tarantool
+
+local tap =3D require('tap')
+
+local test =3D = tap.test("misc-memprof-lapi")
+test:plan(9)
+
+jit.off()
+jit.flush()
+
+-- FIXME: Launch tests with LUA_PATH = enviroment variable.
+local path =3D = arg[0]:gsub('/[^/]+%.test%.lua', =E2=80=98=E2=80=99)

I believe it won=E2=80=99t work = well for some cases, such as

tarantool> = arg[0]
---
- void.test.lua
...

tarantool> = arg[0]:gsub('/[^/]+%.test%.lua', '')
---
- = void.test.lua
- 0
...

Alternative is:

tarantool> = os.execute('dirname '..arg[0])
.
---
- 0
...



I suppose you want to use `popen` here to catch = output?
General = practice is using `gsub` for now (see <test/utils.lua>).
I fixed = regexp to the following:

| local path =3D arg[0]:gsub("[^/]+%.test%.lua", = "")
| local = path_suffix =3D "../tools/?.lua;"
| package.path =3D ("%s%s;"):format(path, = path_suffix)..package.path

+local path_suffix =3D '../tools/?.lua;'
+package.path =3D ('%s/%s;'):format(path, = path_suffix)..package.path
+
+local = table_new =3D require "table.new"
+
+local = bufread =3D require "utils.bufread"
+local memprof =3D = require "memprof.parse"
+local symtab =3D require = "utils.symtab"
+
+local TMP_BINFILE =3D = arg[0]:gsub('[^/]+%.test%.lua', '%.%1.memprofdata.tmp.bin')
+local BAD_PATH =3D arg[0]:gsub('[^/]+%.test%.lua', = '%1/memprofdata.tmp.bin')
+
+local function = payload()
+  -- Preallocate table to avoid array part = reallocations.
        &n= bsp;           &nbs= p;            =        ^parts?

No, I meant array part of table (not a hash part).
I rewrote it = to the following:
| -- Preallocate table to avoid table array part = reallocations.

+ =  local _ =3D table_new(100, 0)
+
+ =  -- Want too see 100 objects here.
+  for i =3D = 1, 100 do
+    -- Try to avoid crossing = with "test" module objects.
+    _[i] =3D = "memprof-str-"..i
+  end
+
+  _ =3D nil
+  -- VMSTATE =3D=3D GC, = reported as INTERNAL.
+  collectgarbage()
+end
+
+local function = generate_output(filename)
+  -- Clean up all garbage = to avoid polution of free.
        &n= bsp;           &nbs= p;            =    pollution

Fixed.

+ =  collectgarbage()
+
+  local res, = err =3D misc.memprof.start(filename)
+  -- Should = start succesfully.
+  assert(res, err)
+
+  payload()
+
+  res, err =3D misc.memprof.stop()
+ =  -- Should stop succesfully.
+  assert(res, = err)
+end

<snipped>


I also foggot = to return `jit.on()` back in the end of test.
Added.

diff --git a/tools/memprof/parse.lua = b/tools/memprof/parse.lua
new file mode 100644
index 0000000..f4996f4
--- /dev/null
+++ b/tools/memprof/parse.lua

<snipped>

+local function link_to_previous(heap, e, = oaddr)
+  -- Memory at oaddr was allocated before we = started tracking.
+  local heap_chunk =3D = heap[oaddr]

Do you need two = args for this? Can you just pass the heap[oaddr] instead?

Yes. It's looks better. Applied. Thank you!


+  if heap_chunk = then
+    -- Save Lua code location (line) = by address (id).
+ =    e.primary[heap_chunk[2]] =3D heap_chunk[3]
+  end
+end
+

<snipped>

+local function = ev_header_split(evh)
+  return band(evh, 0x3), = band(evh, lshift(0x3, 2))

Should= you intorduce masks along with AEVENT/ASOURCE to avoid these
magic numbers?

My bad. = Done.


+end
+

<snipped>

diff --git = a/tools/utils/bufread.lua b/tools/utils/bufread.lua

<snipped>

+
+local = function _read_stream(reader, n)
+  local tail_size =3D= reader._end - reader._pos
+
+  if = tail_size >=3D n then
+    -- Enough = data to satisfy the request of n bytes.
+ =    return true
+  end
++  -- Otherwise carry tail_size bytes from the end of = the buffer
+  -- to the start and fill up free_size = bytes with fresh data.
+  -- tail_size < n <=3D = free_size (see assert below) ensures that
+  -- we = don't copy overlapping memory regions.
+  -- = reader._pos =3D=3D 0 means filling buffer for the first time.
+
+  local free_size =3D reader._pos > = 0 and reader._pos or n
+
+  assert(n = <=3D free_size, "Internal buffer is large enough")

Does it mean I will have a fail = in case _pos is less that half of the
buffer and n is more = than the tail_size? 
Which means I can use only half of the buffer?

Hat-trick of buffer's misuse.
Thanks you very much again! Fixed (see the iterative patch = below).


+
+ =  if tail_size ~=3D 0 then
+ =    ffi_C.memcpy(reader._buf, reader._buf + reader._pos, = tail_size)
+  end
+
+ =  local bytes_read =3D ffi_C.fread(
+ =    reader._buf + tail_size, 1, free_size, reader._file
+  )
+
+  reader._pos =3D= 0
+  reader._end =3D tail_size + bytes_read
+
+  return reader._end - reader._pos = >=3D n
+end
+

<snipped>

+function = M.eof(reader)
+  local sys_feof =3D = ffi_C.feof(reader._file)
+  if sys_feof =3D=3D 0 = then
+    return false
+ =  end
+  -- Otherwise return true only we have = reached
        &n= bsp;           &nbs= p;            = ^^ if we

Fixed.


+  -- the end of the buffer.
+=  return reader._pos =3D=3D reader._end
+end
<snipped>


The iterative patch. Branch is force-pushed.
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=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/test/misclib-memprof-lapi.test.lua = b/test/misclib-memprof-lapi.test.lua
index 2366c00..dd484f4 100755
--- a/test/misclib-memprof-lapi.test.lua
+++ = b/test/misclib-memprof-lapi.test.lua
@@ -9,9 +9,9 @@ jit.off()
jit.flush()

-- FIXME: Launch tests with LUA_PATH enviroment = variable.
-local path =3D= arg[0]:gsub("/[^/]+%.test%.lua", "")
+local path =3D arg[0]:gsub("[^/]+%.test%.lua", "")
local = path_suffix =3D "../tools/?.lua;"
-package.path =3D ("%s/%s;"):format(path, = path_suffix)..package.path
+package.path =3D ("%s%s;"):format(path, = path_suffix)..package.path

local table_new =3D require "table.new"

@@ -23,7 = +23,7 @@ local TMP_BINFILE =3D arg[0]:gsub("[^/]+%.test%.lua", = "%.%1.memprofdata.tmp.bin")
local BAD_PATH =3D arg[0]:gsub("[^/]+%.test%.lua", = "%1/memprofdata.tmp.bin")

local function payload()
-  -- Preallocate table to avoid array part = reallocations.
+  -- Preallocate table to avoid table array part = reallocations.
  local _ =3D table_new(100, 0)

  -- = Want too see 100 objects here.
@@ -38,7 +38,7 @@ local function payload()
end

local = function generate_output(filename)
-  -- Clean up all garbage to avoid polution of = free.
+  -- = Clean up all garbage to avoid pollution of free.
  collectgarbage()

  local res, err =3D = misc.memprof.start(filename)
@@ -132,4 +132,5 @@ test:ok(check_alloc_report(alloc, 32, 25, = 100))
-- Collect = all previous allocated objects.
test:ok(free.INTERNAL.num =3D=3D 102)

+jit.on()
os.exit(test:check() and 0 or 1)
diff --git = a/tools/memprof/parse.lua b/tools/memprof/parse.lua
index = f4996f4..6dae22d 100644
--- a/tools/memprof/parse.lua
+++ b/tools/memprof/parse.lua
@@ -19,10 +19,14 @@ local AEVENT_ALLOC =3D 1
local = AEVENT_FREE =3D 2
local AEVENT_REALLOC =3D 3

+local AEVENT_MASK =3D 0x3
+
local = ASOURCE_INT =3D lshift(1, 2)
local ASOURCE_LFUNC =3D lshift(2, 2)
local = ASOURCE_CFUNC =3D lshift(3, 2)

+local ASOURCE_MASK =3D lshift(0x3, 2)
+
local M =3D = {}

local = function new_event(loc)
@@ -35,9 +39,8 @@ local function new_event(loc)
  }
end

-local function link_to_previous(heap, e, oaddr)
-  -- = Memory at oaddr was allocated before we started tracking.
-  local = heap_chunk =3D heap[oaddr]
+local function link_to_previous(heap_chunk, e)
+  -- = 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]
@@ -94,7 = +97,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, e, oaddr)
+  link_to_previous(heap[oaddr], e)

  heap[oaddr] =3D nil
  heap[naddr] =3D {nsize, id, loc}
@@ -113,7 = +116,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, e, oaddr)
+  link_to_previous(heap[oaddr], e)

  heap[oaddr] =3D nil
end
@@ -131,7 +134,7 @@ end
-- Splits event header into event type (aka aevent =3D = allocation
-- event) and = event source (aka asource =3D allocation source).
local = function ev_header_split(evh)
-  return band(evh, 0x3), band(evh, lshift(0x3, = 2))
+ =  return band(evh, AEVENT_MASK), band(evh, ASOURCE_MASK)
end

local = function parse_event(reader, events)
diff --git a/tools/utils/bufread.lua = b/tools/utils/bufread.lua
index 873e06a..34bae9a 100644
--- a/tools/utils/bufread.lua
+++ b/tools/utils/bufread.lua
@@ -22,7 +22,7 @@ local BUFFER_SIZE =3D 10 * 1024 * = 1024
local M =3D = {}

ffi.cdef[[
-  void *memcpy(void *, const void *, size_t);
+  void = *memmove(void *, const void *, size_t);

  typedef struct FILE_ FILE;
  FILE *fopen(const char *, const char = *);
@@ -34,6 = +34,8 @@ ffi.cdef[[
local function _read_stream(reader, n)
  local tail_size =3D reader._end - = reader._pos

+ =  assert(n <=3D BUFFER_SIZE, "Internal buffer is large = enough")
+
  if = tail_size >=3D n then
    -- Enough data to satisfy the request = of n bytes.
    return true
@@ -41,16 = +43,11 @@ local function _read_stream(reader, n)

  -- = Otherwise carry tail_size bytes from the end of the buffer
  -- = to the start and fill up free_size bytes with fresh data.
-  -- = tail_size < n <=3D free_size (see assert below) ensures = that
-  -- we = don't copy overlapping memory regions.
-  -- reader._pos =3D=3D 0 means filling buffer for the = first time.
-
-  local = free_size =3D reader._pos > 0 and reader._pos or n

- =  assert(n <=3D free_size, "Internal buffer is large = enough")
+  local = free_size =3D BUFFER_SIZE - tail_size

  if tail_size ~=3D 0 then
- =    ffi_C.memcpy(reader._buf, reader._buf + reader._pos, = tail_size)
+ =    ffi_C.memmove(reader._buf, reader._buf + reader._pos, = tail_size)
  end

  local bytes_read =3D ffi_C.fread(
@@ -114,7 = +111,7 @@ function M.eof(reader)
  if sys_feof =3D=3D 0 then
    return false
  end
-  -- Otherwise return true only we have = reached
+  -- = Otherwise return true only if we have reached
  -- = the end of the buffer.
  return reader._pos =3D=3D reader._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

-- 
Best = regards,
Sergey = Kaplun

= --Apple-Mail=_D231FCDC-5053-49D1-A3A7-D64EA10964AC--