Skip to content

Commit c9e7600

Browse files
committed
profilers: print user-friendly errors
Prior to this patch, there was no error-checking in profilers, which resulted in raw Lua errors being reported in cases of non-existing paths or corrupt file structures. This patch adds error handling, so all parsing errors are now reported in a user-friendly manner. Event parsing is now moved into a separate profiler-agnostic module. Tool CLI flag tests are adapted correspondingly to error message changes. Resolves tarantool/tarantool#9217 Part of tarantool/tarantool#5994
1 parent aac00cb commit c9e7600

File tree

9 files changed

+142
-15
lines changed

9 files changed

+142
-15
lines changed

Makefile.original

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ FILES_JITLIB= bc.lua bcsave.lua dump.lua p.lua v.lua zone.lua \
9797
dis_x86.lua dis_x64.lua dis_arm.lua dis_arm64.lua \
9898
dis_arm64be.lua dis_ppc.lua dis_mips.lua dis_mipsel.lua \
9999
dis_mips64.lua dis_mips64el.lua vmdef.lua
100-
FILES_UTILSLIB= avl.lua bufread.lua symtab.lua
100+
FILES_UTILSLIB= avl.lua bufread.lua evread.lua symtab.lua
101101
FILES_MEMPROFLIB= humanize.lua parse.lua process.lua
102102
FILES_SYSPROFLIB= parse.lua
103103
FILES_TOOLSLIB= memprof.lua sysprof.lua

test/tarantool-tests/gh-5688-tool-cli-flag.test.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ local SMOKE_CMD_SET = {
4242
local MEMPROF_CMD_SET = {
4343
{
4444
cmd = MEMPROF_PARSER .. BAD_PATH,
45-
like = 'fopen, errno: 2',
45+
like = 'Failed to open.*fopen, errno: 2',
4646
},
4747
{
4848
cmd = MEMPROF_PARSER .. TMP_BINFILE_MEMPROF,
@@ -61,7 +61,7 @@ local MEMPROF_CMD_SET = {
6161
local SYSPROF_CMD_SET = {
6262
{
6363
cmd = SYSPROF_PARSER .. BAD_PATH,
64-
like = 'fopen, errno: 2',
64+
like = 'Failed to open.*fopen, errno: 2',
6565
},
6666
{
6767
cmd = SYSPROF_PARSER .. TMP_BINFILE_SYSPROF,
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
local tap = require('tap')
2+
local test = tap.test('gh-9217-profile-parsers-error-handling'):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+
jit.off()
12+
jit.flush()
13+
14+
local table_new = require('table.new')
15+
local utils = require('utils')
16+
17+
local BAD_PATH = utils.tools.profilename('bad-path-tmp.bin')
18+
local NON_PROFILE_DATA = utils.tools.profilename('not-profile-data.tmp.bin')
19+
local CORRUPT_PROFILE = utils.tools.profilename('profdata.tmp.bin')
20+
21+
local EXECUTABLE = utils.exec.luacmd(arg)
22+
local PARSERS = {
23+
memprof = EXECUTABLE .. ' -tm ',
24+
sysprof = EXECUTABLE .. ' -ts ',
25+
}
26+
local REDIRECT_OUTPUT = ' 2>&1'
27+
28+
local TABLE_SIZE = 20
29+
30+
local TEST_CASES = {
31+
{
32+
path = BAD_PATH,
33+
err_msg = 'Failed to open'
34+
},
35+
{
36+
path = NON_PROFILE_DATA,
37+
err_msg = 'Failed to parse symtab from'
38+
},
39+
{
40+
path = CORRUPT_PROFILE,
41+
err_msg = 'Failed to parse profile data from'
42+
},
43+
}
44+
45+
test:plan(2 * #TEST_CASES)
46+
47+
local function generate_non_profile_data(path)
48+
local file = io.open(path, 'w')
49+
file:write('data')
50+
file:close()
51+
end
52+
53+
local function generate_corrupt_profile(path)
54+
local res, err = misc.memprof.start(path)
55+
-- Should start successfully.
56+
assert(res, err)
57+
58+
local _ = table_new(TABLE_SIZE, 0)
59+
_ = nil
60+
collectgarbage()
61+
62+
res, err = misc.memprof.stop()
63+
-- Should stop successfully.
64+
assert(res, err)
65+
66+
local file = io.open(path, 'r')
67+
local content = file:read('*all')
68+
file:close()
69+
local index = string.find(content, 'ljm')
70+
71+
file = io.open(path, 'w')
72+
file:write(string.sub(content, 1, index - 1))
73+
file:close()
74+
end
75+
76+
generate_non_profile_data(NON_PROFILE_DATA)
77+
generate_corrupt_profile(CORRUPT_PROFILE)
78+
79+
for _, case in ipairs(TEST_CASES) do
80+
for profiler, parser in pairs(PARSERS) do
81+
local path = case.path
82+
local err_msg = case.err_msg
83+
local output = io.popen(parser .. path .. REDIRECT_OUTPUT):read('*all')
84+
test:like(output, err_msg, string.format('%s: %s', profiler, err_msg))
85+
end
86+
end
87+
88+
os.remove(NON_PROFILE_DATA)
89+
os.remove(CORRUPT_PROFILE)
90+
test:done(true)

tools/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ else()
1818
memprof.lua
1919
utils/avl.lua
2020
utils/bufread.lua
21+
utils/evread.lua
2122
utils/symtab.lua
2223
)
2324
list(APPEND LUAJIT_TOOLS_DEPS tools-parse-memprof)
@@ -36,6 +37,7 @@ else()
3637
install(FILES
3738
${CMAKE_CURRENT_SOURCE_DIR}/utils/avl.lua
3839
${CMAKE_CURRENT_SOURCE_DIR}/utils/bufread.lua
40+
${CMAKE_CURRENT_SOURCE_DIR}/utils/evread.lua
3941
${CMAKE_CURRENT_SOURCE_DIR}/utils/symtab.lua
4042
DESTINATION ${LUAJIT_DATAROOTDIR}/utils
4143
PERMISSIONS
@@ -65,6 +67,7 @@ else()
6567
sysprof.lua
6668
utils/avl.lua
6769
utils/bufread.lua
70+
utils/evread.lua
6871
utils/symtab.lua
6972
)
7073
list(APPEND LUAJIT_TOOLS_DEPS tools-parse-sysprof)
@@ -81,6 +84,7 @@ else()
8184
install(FILES
8285
${CMAKE_CURRENT_SOURCE_DIR}/utils/avl.lua
8386
${CMAKE_CURRENT_SOURCE_DIR}/utils/bufread.lua
87+
${CMAKE_CURRENT_SOURCE_DIR}/utils/evread.lua
8488
${CMAKE_CURRENT_SOURCE_DIR}/utils/symtab.lua
8589
DESTINATION ${LUAJIT_DATAROOTDIR}/utils
8690
PERMISSIONS

tools/memprof.lua

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@
1010
-- Major portions taken verbatim or adapted from the LuaVela.
1111
-- Copyright (C) 2015-2019 IPONWEB Ltd.
1212

13-
local bufread = require "utils.bufread"
1413
local memprof = require "memprof.parse"
1514
local process = require "memprof.process"
16-
local symtab = require "utils.symtab"
15+
local evread = require "utils.evread"
1716
local view = require "memprof.humanize"
1817

1918
local stdout, stderr = io.stdout, io.stderr
@@ -108,9 +107,11 @@ local function parseargs(args)
108107
end
109108

110109
local function dump(inputfile)
111-
local reader = bufread.new(inputfile)
112-
local symbols = symtab.parse(reader)
113-
local events = memprof.parse(reader, symbols)
110+
-- XXX: This function exits with a non-zero exit code and
111+
-- prints an error message if it encounters any failure during
112+
-- the process of parsing.
113+
local events, symbols = evread(memprof.parse, inputfile)
114+
114115
if not config.leak_only then
115116
view.profile_info(events, config)
116117
end

tools/sysprof.lua

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
local bufread = require "utils.bufread"
1+
local evread = require "utils.evread"
22
local sysprof = require "sysprof.parse"
3-
local symtab = require "utils.symtab"
43

54
local stdout, stderr = io.stdout, io.stderr
65
local match, gmatch = string.match, string.gmatch
@@ -78,9 +77,10 @@ local function parseargs(args)
7877
end
7978

8079
local function dump(inputfile)
81-
local reader = bufread.new(inputfile)
82-
local symbols = symtab.parse(reader)
83-
local events = sysprof.parse(reader, symbols)
80+
-- XXX: This function exits with a non-zero exit code and
81+
-- prints an error message if it encounters any failure during
82+
-- the process of parsing.
83+
local events = evread(sysprof.parse, inputfile)
8484

8585
for stack, count in pairs(events) do
8686
print(stack, count)

tools/sysprof/parse.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ function M.parse(reader, symbols)
237237
local _ = reader:read_octets(3)
238238

239239
if magic ~= LJP_MAGIC then
240-
error("Bad LJP format prologue: "..magic)
240+
error("Bad LJP format prologue: " .. tostring(magic))
241241
end
242242

243243
if string.byte(version) ~= LJP_CURRENT_VERSION then

tools/utils/evread.lua

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
local bufread = require('utils.bufread')
2+
local symtab = require('utils.symtab')
3+
4+
local function make_error_handler(fmt, inputfile)
5+
return function(err)
6+
io.stderr:write(string.format(fmt, inputfile))
7+
io.stderr:write(string.format('\n\t%s\n', err))
8+
os.exit(1, true)
9+
end
10+
end
11+
12+
return function(parser, inputfile)
13+
local _, reader = xpcall(
14+
bufread.new,
15+
make_error_handler('Failed to open %s.', inputfile),
16+
inputfile
17+
)
18+
19+
local _, symbols = xpcall(
20+
symtab.parse,
21+
make_error_handler('Failed to parse symtab from %s.', inputfile),
22+
reader
23+
)
24+
25+
local _, events = xpcall(
26+
parser,
27+
make_error_handler('Failed to parse profile data from %s.', inputfile),
28+
reader,
29+
symbols
30+
)
31+
return events, symbols
32+
end

tools/utils/symtab.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ function M.parse(reader)
9595
local _ = reader:read_octets(3)
9696

9797
if magic ~= LJS_MAGIC then
98-
error("Bad LJS format prologue: "..magic)
98+
error("Bad LJS format prologue: " .. tostring(magic))
9999
end
100100

101101
if string.byte(version) ~= LJS_CURRENT_VERSION then

0 commit comments

Comments
 (0)