Skip to content

Commit 48d0617

Browse files
committed
player/lua/defaults: add LuaJIT profiling support
gives us a way to properly profile the lua scripts to get some verifiable way to test which "optimizations" work and which don't. currently only supports LuaJIT but the cli option is generically named in case support for profiling other scripts is added in the future.
1 parent 5921fe5 commit 48d0617

4 files changed

Lines changed: 125 additions & 0 deletions

File tree

DOCS/man/options.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,12 @@ Program Behavior
833833
and overwrites the internal list with it. The latter is a key/value list
834834
option. See `List Options`_ for details.
835835

836+
``--profile-script=<name[=path]>``, ``--profile-scripts=name1[=path1],name2[=path2],...``
837+
Profile the given script(s) and dump a report to ``path`` when the script
838+
exits. If no ``path`` is given, writes to standard out.
839+
840+
Currently only supports profiling lua scripts with LuaJIT.
841+
836842
``--merge-files``
837843
Pretend that all files passed to mpv are concatenated into a single, big
838844
file. This uses timeline/EDL support internally.

options/options.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,8 @@ static const m_option_t mp_opts[] = {
562562
{"js-memory-report", OPT_BOOL(js_memory_report)},
563563
#endif
564564
#if HAVE_LUA
565+
{"profile-scripts", OPT_STRINGLIST(profile_scripts)},
566+
{"profile-script", OPT_CLI_ALIAS("profile-scripts-append")},
565567
{"osc", OPT_BOOL(lua_load_osc), .flags = UPDATE_BUILTIN_SCRIPTS},
566568
{"ytdl", OPT_BOOL(lua_load_ytdl), .flags = UPDATE_BUILTIN_SCRIPTS},
567569
{"ytdl-format", OPT_STRING(lua_ytdl_format)},

options/options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ typedef struct MPOpts {
180180
char **reset_options;
181181
char **script_files;
182182
char **script_opts;
183+
char **profile_scripts;
183184
bool js_memory_report;
184185
bool lua_load_osc;
185186
bool lua_load_ytdl;

player/lua/defaults.lua

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,119 @@ function mp.get_opt(key, def)
2525
return val
2626
end
2727

28+
local script_profile = nil
29+
30+
local function get_profile_path()
31+
local scripts = mp.get_property_native("options/profile-scripts", {}) or {}
32+
for i = #scripts, 1, -1 do
33+
local entry = scripts[i]
34+
local name, path = entry:match("^([^=]+)=(.*)$")
35+
if not name then
36+
name = entry
37+
end
38+
if name == mp.script_name then
39+
return path or ""
40+
end
41+
end
42+
return nil
43+
end
44+
45+
local function format_script_profile(state)
46+
local entries = {}
47+
for stack, samples in pairs(state.entries) do
48+
entries[#entries + 1] = {stack = stack, samples = samples}
49+
end
50+
51+
table.sort(entries, function(a, b)
52+
if a.samples ~= b.samples then
53+
return a.samples > b.samples
54+
end
55+
return a.stack < b.stack
56+
end)
57+
58+
local lines = {
59+
"script: " .. mp.script_name,
60+
"samples: " .. state.total_samples,
61+
"",
62+
}
63+
64+
for _, entry in ipairs(entries) do
65+
local percent = entry.samples * 100 / (state.total_samples or 1)
66+
lines[#lines + 1] = string.format("%8d %6.2f%% %s",
67+
entry.samples, percent, entry.stack)
68+
end
69+
70+
return table.concat(lines, "\n")
71+
end
72+
73+
local function write_script_profile(outpath, data)
74+
local ok, err = true, ""
75+
local out
76+
local need_close = false
77+
78+
if outpath == "" then
79+
out = io.stdout
80+
outpath = "stdout"
81+
else
82+
out, err = io.open(outpath, "wb")
83+
ok = out ~= nil
84+
need_close = out ~= nil
85+
end
86+
if ok then
87+
ok, err = out:write(data, "\n")
88+
end
89+
if ok then
90+
ok, err = out:flush()
91+
end
92+
if need_close then
93+
out:close()
94+
end
95+
if ok then
96+
mp.log("info", "Profile data written to " .. outpath)
97+
else
98+
mp.log("error", "Could not write profile data to " ..
99+
outpath .. ": " .. tostring(err))
100+
end
101+
end
102+
103+
local function stop_script_profile()
104+
if not script_profile then
105+
return
106+
end
107+
108+
script_profile.profiler.stop()
109+
110+
local data = format_script_profile(script_profile)
111+
write_script_profile(script_profile.output_path, data)
112+
script_profile = nil
113+
end
114+
115+
local function setup_script_profile()
116+
local output_path = get_profile_path()
117+
if not output_path then
118+
return
119+
end
120+
121+
local ok, profiler = pcall(require, "jit.profile")
122+
if not ok then
123+
error("Lua profiling requires LuaJIT")
124+
end
125+
126+
script_profile = {
127+
profiler = profiler,
128+
total_samples = 0,
129+
entries = {},
130+
output_path = output_path,
131+
}
132+
133+
profiler.start("fi4", function(thread, samples)
134+
local stack = profiler.dumpstack(thread, "fZ;", -100)
135+
script_profile.entries[stack] =
136+
(script_profile.entries[stack] or 0) + samples
137+
script_profile.total_samples = script_profile.total_samples + samples
138+
end)
139+
end
140+
28141
function mp.input_define_section(section, contents, flags)
29142
if flags == nil or flags == "" then
30143
flags = "default"
@@ -502,8 +615,11 @@ _G.print = mp.msg.info
502615
package.loaded["mp"] = mp
503616
package.loaded["mp.msg"] = mp.msg
504617

618+
setup_script_profile()
619+
505620
_G.mp_event_loop = function()
506621
mp.dispatch_events(true)
622+
stop_script_profile()
507623
end
508624

509625
local function call_event_handlers(e)

0 commit comments

Comments
 (0)