summaryrefslogtreecommitdiff
path: root/lua/lvim/impatient/profile.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/lvim/impatient/profile.lua')
-rw-r--r--lua/lvim/impatient/profile.lua309
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