diff options
Diffstat (limited to 'lua/lvim/impatient/profile.lua')
-rw-r--r-- | lua/lvim/impatient/profile.lua | 309 |
1 files changed, 201 insertions, 108 deletions
diff --git a/lua/lvim/impatient/profile.lua b/lua/lvim/impatient/profile.lua index 0f4f8236..3676c298 100644 --- a/lua/lvim/impatient/profile.lua +++ b/lua/lvim/impatient/profile.lua @@ -1,145 +1,238 @@ local M = {} -local api = vim.api +local api, uv = vim.api, vim.loop -function M.print_profile(profile) - if not profile then +local std_data = vim.fn.stdpath "data" +local std_config = vim.fn.stdpath "config" +local vimruntime = os.getenv "VIMRUNTIME" + +local function load_buffer(title, lines) + local bufnr = api.nvim_create_buf(false, false) + api.nvim_buf_set_lines(bufnr, 0, 0, false, lines) + api.nvim_buf_set_option(bufnr, "bufhidden", "wipe") + api.nvim_buf_set_option(bufnr, "buftype", "nofile") + api.nvim_buf_set_option(bufnr, "swapfile", false) + api.nvim_buf_set_option(bufnr, "modifiable", false) + api.nvim_buf_set_name(bufnr, title) + api.nvim_set_current_buf(bufnr) +end + +local function mod_path(path) + if not path then + return "?" + end + path = path:gsub(std_data .. "/site/pack/packer/", "<PACKER>/") + path = path:gsub(std_data .. "/", "<STD_DATA>/") + path = path:gsub(std_config .. "/", "<STD_CONFIG>/") + path = path:gsub(vimruntime .. "/", "<VIMRUNTIME>/") + return path +end + +local function time_tostr(x) + if x == 0 then + return "?" + end + return string.format("%8.3fms", x) +end + +local function mem_tostr(x) + local unit = "" + for _, u in ipairs { "K", "M", "G" } do + if x < 1000 then + break + end + x = x / 1000 + unit = u + end + return string.format("%1.1f%s", x, unit) +end + +function M.print_profile(I) + local mod_profile = I.modpaths.profile + local chunk_profile = I.chunks.profile + + if not mod_profile and not chunk_profile then print "Error: profiling was not enabled" return end local total_resolve = 0 local total_load = 0 - local name_pad = 0 local modules = {} - local plugins = {} - - for module, p in pairs(profile) do - p.resolve = p.resolve / 1000000 - p.load = p.load / 1000000 - p.total = p.resolve + p.load - p.module = module:gsub("/", ".") - - local plugin = p.module:match "([^.]+)" - if plugin then - if not plugins[plugin] then - plugins[plugin] = { - module = plugin, - resolve = 0, - load = 0, - total = 0, - } - end - local r = plugins[plugin] - r.resolve = r.resolve + p.resolve - r.load = r.load + p.load - r.total = r.total + p.total + for path, m in pairs(chunk_profile) do + m.load = m.load_end - m.load_start + m.load = m.load / 1000000 + m.path = mod_path(path) + end - if not r.loader then - r.loader = p.loader - elseif r.loader ~= p.loader then - r.loader = "mixed" - end + local module_content_width = 0 + + for module, m in pairs(mod_profile) do + m.resolve = 0 + if m.resolve_end then + m.resolve = m.resolve_end - m.resolve_start + m.resolve = m.resolve / 1000000 end - total_resolve = total_resolve + p.resolve - total_load = total_load + p.load + m.module = module:gsub("/", ".") + m.loader = m.loader or m.loader_guess + + local path = I.modpaths.cache[module] + local path_prof = chunk_profile[path] + m.path = mod_path(path) - if #module > name_pad then - name_pad = #module + if path_prof then + chunk_profile[path] = nil + m.load = path_prof.load + m.ploader = path_prof.loader + else + m.load = 0 + m.ploader = "NA" end - modules[#modules + 1] = p + total_resolve = total_resolve + m.resolve + total_load = total_load + m.load + + if #module > module_content_width then + module_content_width = #module + end + + modules[#modules + 1] = m end table.sort(modules, function(a, b) - return a.module > b.module + return (a.resolve + a.load) > (b.resolve + b.load) end) - do - local plugins_a = {} - for _, v in pairs(plugins) do - plugins_a[#plugins_a + 1] = v - end - plugins = plugins_a + local paths = {} + + local total_paths_load = 0 + for _, m in pairs(chunk_profile) do + paths[#paths + 1] = m + total_paths_load = total_paths_load + m.load end - table.sort(plugins, function(a, b) - return a.total > b.total + table.sort(paths, function(a, b) + return a.load > b.load end) local lines = {} - local function add(...) - lines[#lines + 1] = string.format(...) - end + local function add(fmt, ...) + local args = { ... } + for i, a in ipairs(args) do + if type(a) == "number" then + args[i] = time_tostr(a) + end + end - local l = string.rep("ā", name_pad + 1) + lines[#lines + 1] = string.format(fmt, unpack(args)) + end - add( - "%sā¬āāāāāāāāāāāā¬āāāāāāāāāāāāā¬āāāāāāāāāāāāā¬āāāāāāāāāāāāā", - l - ) - add("%-" .. name_pad .. "s ā Loader ā Resolve ā Load ā Total ā", "") - add( - "%sā¼āāāāāāāāāāāā¼āāāāāāāāāāāāā¼āāāāāāāāāāāāā¼āāāāāāāāāāāāā¤", - l - ) - add( - "%-" .. name_pad .. "s ā ā %8.4fms ā %8.4fms ā %8.4fms ā", - "Total", - total_resolve, - total_load, - total_resolve + total_load - ) - add( - "%sā“āāāāāāāāāāāā“āāāāāāāāāāāāā“āāāāāāāāāāāāā“āāāāāāāāāāāāā¤", - l + local time_cell_width = 12 + local loader_cell_width = 11 + local time_content_width = time_cell_width - 2 + local loader_content_width = loader_cell_width - 2 + local module_cell_width = module_content_width + 2 + + local tcwl = string.rep("ā", time_cell_width) + local lcwl = string.rep("ā", loader_cell_width) + local mcwl = string.rep("ā", module_cell_width + 2) + + local n = string.rep("ā", 200) + + local module_cell_format = "%-" .. module_cell_width .. "s" + local loader_format = "%-" .. loader_content_width .. "s" + local line_format = "%s ā %s ā %s ā %s ā %s ā %s" + + local row_fmt = line_format:format( + " %" .. time_content_width .. "s", + loader_format, + "%" .. time_content_width .. "s", + loader_format, + module_cell_format, + "%s" ) - add("%-" .. name_pad .. "s ā", "By Plugin") - add( - "%sā¬āāāāāāāāāāāā¬āāāāāāāāāāāāā¬āāāāāāāāāāāāā¬āāāāāāāāāāāāā¤", - l + + local title_fmt = line_format:format( + " %-" .. time_content_width .. "s", + loader_format, + "%-" .. time_content_width .. "s", + loader_format, + module_cell_format, + "%s" ) - for _, p in ipairs(plugins) do - add( - "%-" .. name_pad .. "s ā %9s ā %8.4fms ā %8.4fms ā %8.4fms ā", - p.module, - p.loader, - p.resolve, - p.load, - p.total - ) + + local title1_width = time_cell_width + loader_cell_width - 1 + local title1_fmt = ("%s ā %s ā"):format(" %-" .. title1_width .. "s", "%-" .. title1_width .. "s") + + add "Note: this report is not a measure of startup time. Only use this for comparing" + add "between cached and uncached loads of Lua modules" + add "" + + add "Cache files:" + for _, f in ipairs { I.chunks.path, I.modpaths.path } do + local size = vim.loop.fs_stat(f).size + add(" %s %s", f, mem_tostr(size)) end - add( - "%sā“āāāāāāāāāāāā“āāāāāāāāāāāāā“āāāāāāāāāāāāā“āāāāāāāāāāāāā¤", - l - ) - add("%-" .. name_pad .. "s ā", "By Module") - add( - "%sā¬āāāāāāāāāāāā¬āāāāāāāāāāāāā¬āāāāāāāāāāāāā¬āāāāāāāāāāāāā¤", - l - ) - for _, p in pairs(modules) do - add( - "%-" .. name_pad .. "s ā %9s ā %8.4fms ā %8.4fms ā %8.4fms ā", - p.module, - p.loader, - p.resolve, - p.load, - p.total - ) + add "" + + add("%sā%sā¬%sā%sā", tcwl, lcwl, tcwl, lcwl) + add(title1_fmt, "Resolve", "Load") + add("%sā¬%sā¼%sā¬%sā¼%sā¬%s", tcwl, lcwl, tcwl, lcwl, mcwl, n) + add(title_fmt, "Time", "Method", "Time", "Method", "Module", "Path") + add("%sā¼%sā¼%sā¼%sā¼%sā¼%s", tcwl, lcwl, tcwl, lcwl, mcwl, n) + add(row_fmt, total_resolve, "", total_load, "", "Total", "") + add("%sā¼%sā¼%sā¼%sā¼%sā¼%s", tcwl, lcwl, tcwl, lcwl, mcwl, n) + for _, p in ipairs(modules) do + add(row_fmt, p.resolve, p.loader, p.load, p.ploader, p.module, p.path) + end + add("%sā“%sā“%sā“%sā“%sā“%s", tcwl, lcwl, tcwl, lcwl, mcwl, n) + + if #paths > 0 then + add "" + add(n) + local f3 = " %" .. time_content_width .. "s ā %" .. loader_content_width .. "s ā %s" + add "Files loaded with no associated module" + add("%sā¬%sā¬%s", tcwl, lcwl, n) + add(f3, "Time", "Loader", "Path") + add("%sā¼%sā¼%s", tcwl, lcwl, n) + add(f3, total_paths_load, "", "Total") + add("%sā¼%sā¼%s", tcwl, lcwl, n) + for _, p in ipairs(paths) do + add(f3, p.load, p.loader, p.path) + end + add("%sā“%sā“%s", tcwl, lcwl, n) + add "" end - add( - "%sā“āāāāāāāāāāāā“āāāāāāāāāāāāā“āāāāāāāāāāāāā“āāāāāāāāāāāāā", - l - ) - local bufnr = api.nvim_create_buf(false, false) - api.nvim_buf_set_lines(bufnr, 0, 0, false, lines) - api.nvim_buf_set_option(bufnr, "buftype", "nofile") - api.nvim_buf_set_name(bufnr, "Impatient Profile Report") - api.nvim_set_current_buf(bufnr) + load_buffer("Impatient Profile Report", lines) +end + +M.setup = function(profile) + local _require = require + + -- luacheck: ignore 121 + require = function(mod) + local basename = mod:gsub("%.", "/") + if not profile[basename] then + profile[basename] = {} + profile[basename].resolve_start = uv.hrtime() + profile[basename].loader_guess = "C" + end + return _require(mod) + end + + -- Add profiling around all the loaders + local pl = package.loaders + for i = 1, #pl do + local l = pl[i] + pl[i] = function(mod) + local basename = mod:gsub("%.", "/") + profile[basename].loader_guess = i == 1 and "preloader" or "loader #" .. i + return l(mod) + end + end end return M |