diff options
Diffstat (limited to 'lua/lvim/impatient')
| -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 | 
