mirror of
https://github.com/folke/snacks.nvim
synced 2025-07-07 13:15:08 +00:00
517 lines
18 KiB
Text
517 lines
18 KiB
Text
*snacks-profiler* snacks_profiler
|
||
|
||
==============================================================================
|
||
Table of Contents *snacks-profiler-table-of-contents*
|
||
|
||
1. Features |snacks-profiler-features|
|
||
2. Why? |snacks-profiler-why?|
|
||
3. Usage |snacks-profiler-usage|
|
||
- Caveats |snacks-profiler-usage-caveats|
|
||
- Recommended Setup |snacks-profiler-usage-recommended-setup|
|
||
- Profiling Neovim Startup |snacks-profiler-usage-profiling-neovim-startup|
|
||
- Filtering |snacks-profiler-usage-filtering|
|
||
4. Setup |snacks-profiler-setup|
|
||
5. Config |snacks-profiler-config|
|
||
6. Types |snacks-profiler-types|
|
||
- Traces |snacks-profiler-types-traces|
|
||
- Pick: grouping, filtering and sorting|snacks-profiler-types-pick:-grouping,-filtering-and-sorting|
|
||
- UI |snacks-profiler-types-ui|
|
||
- Other |snacks-profiler-types-other|
|
||
7. Module |snacks-profiler-module|
|
||
- Snacks.profiler.find() |snacks-profiler-module-snacks.profiler.find()|
|
||
- Snacks.profiler.highlight()|snacks-profiler-module-snacks.profiler.highlight()|
|
||
- Snacks.profiler.pick() |snacks-profiler-module-snacks.profiler.pick()|
|
||
- Snacks.profiler.running()|snacks-profiler-module-snacks.profiler.running()|
|
||
- Snacks.profiler.scratch()|snacks-profiler-module-snacks.profiler.scratch()|
|
||
- Snacks.profiler.start() |snacks-profiler-module-snacks.profiler.start()|
|
||
- Snacks.profiler.startup()|snacks-profiler-module-snacks.profiler.startup()|
|
||
- Snacks.profiler.status() |snacks-profiler-module-snacks.profiler.status()|
|
||
- Snacks.profiler.stop() |snacks-profiler-module-snacks.profiler.stop()|
|
||
- Snacks.profiler.toggle() |snacks-profiler-module-snacks.profiler.toggle()|
|
||
8. Links |snacks-profiler-links|
|
||
A low overhead Lua profiler for Neovim.
|
||
|
||
|
||
==============================================================================
|
||
1. Features *snacks-profiler-features*
|
||
|
||
- low overhead **instrumentation**
|
||
- captures a function’s **def**inition and **ref**erence (_caller_) locations
|
||
- profiling of **autocmds**
|
||
- profiling of **require**d modules
|
||
- buffer **highlighting** of functions and calls
|
||
- lots of different ways to **filter** and **group** traces
|
||
- show traces with:
|
||
- fzf-lua <https://github.com/ibhagwan/fzf-lua>
|
||
- telescope.nvim <https://github.com/nvim-telescope/telescope.nvim>
|
||
- trouble.nvim <https://github.com/folke/trouble.nvim>
|
||
|
||
|
||
==============================================================================
|
||
2. Why? *snacks-profiler-why?*
|
||
|
||
Before the snacks profiler, I used to use a combination of my own profiler(s),
|
||
**lazy.nvim**’s internal profiler, profile.nvim
|
||
<https://github.com/stevearc/profile.nvim> and perfanno.nvim
|
||
<https://github.com/t-troebst/perfanno.nvim>.
|
||
|
||
They all have their strengths and weaknesses:
|
||
|
||
- **lazy.nvim**’s profiler is great for structured traces, but needed a lot of
|
||
manual work to get the traces I wanted.
|
||
- **profile.nvim** does proper instrumentation, but was lacking in the UI department.
|
||
- **perfanno.nvim** has a great UI, but uses `jit.profile` which is not as
|
||
detailed as instrumentation.
|
||
|
||
The snacks profiler tries to combine the best of all worlds.
|
||
|
||
|
||
==============================================================================
|
||
3. Usage *snacks-profiler-usage*
|
||
|
||
The easiest way to use the profiler is to toggle it with the suggested
|
||
keybindings.
|
||
|
||
When the profiler stops, it will show a picker using the `on_stop` preset.
|
||
|
||
To quickly change picker options, you can use the `Snacks.profiler.scratch()`
|
||
scratch buffer.
|
||
|
||
|
||
CAVEATS *snacks-profiler-usage-caveats*
|
||
|
||
- your Neovim session might slow down when profiling
|
||
- due to the overhead of instrumentation, fast functions that are called
|
||
often, might skew the results. Best to add those to the `opts.filter_fn` config.
|
||
- by default, only captures functions defined on lua modules.
|
||
If you want to profile others, add them to `opts.globals`
|
||
- the profiler is not perfect and might not capture all calls
|
||
- the profiler might not work well with some plugins
|
||
- it can only profile `autocmds` created when the profiler is running.
|
||
- only `autocmds` with a lua function callback can be profiled
|
||
- functions that `resume` or `yield` won’t be captured correctly
|
||
- functions that do blocking calls like `vim.fn.getchar` will work,
|
||
but the time will include the time spent waiting for the blocking call
|
||
|
||
|
||
RECOMMENDED SETUP *snacks-profiler-usage-recommended-setup*
|
||
|
||
>lua
|
||
{
|
||
{
|
||
"folke/snacks.nvim",
|
||
opts = function()
|
||
-- Toggle the profiler
|
||
Snacks.toggle.profiler():map("<leader>pp")
|
||
-- Toggle the profiler highlights
|
||
Snacks.toggle.profiler_highlights():map("<leader>ph")
|
||
end,
|
||
keys = {
|
||
{ "<leader>ps", function() Snacks.profiler.scratch() end, desc = "Profiler Scratch Bufer" },
|
||
}
|
||
},
|
||
-- optional lualine component to show captured events
|
||
-- when the profiler is running
|
||
{
|
||
"nvim-lualine/lualine.nvim",
|
||
opts = function(_, opts)
|
||
table.insert(opts.sections.lualine_x, Snacks.profiler.status())
|
||
end,
|
||
},
|
||
}
|
||
<
|
||
|
||
|
||
PROFILING NEOVIM STARTUP *snacks-profiler-usage-profiling-neovim-startup*
|
||
|
||
In order to profile Neovim’s startup, you need to make sure `snacks.nvim` is
|
||
installed and loaded **before** doing anything else. So also before loading
|
||
your plugin manager.
|
||
|
||
You can add something like the below to the top of your `init.lua`.
|
||
|
||
Then you can profile your Neovim session, with `PROF=1 nvim`.
|
||
|
||
>lua
|
||
if vim.env.PROF then
|
||
-- example for lazy.nvim
|
||
-- change this to the correct path for your plugin manager
|
||
local snacks = vim.fn.stdpath("data") .. "/lazy/snacks.nvim"
|
||
vim.opt.rtp:append(snacks)
|
||
require("snacks.profiler").startup({
|
||
startup = {
|
||
event = "VimEnter", -- stop profiler on this event. Defaults to `VimEnter`
|
||
-- event = "UIEnter",
|
||
-- event = "VeryLazy",
|
||
},
|
||
})
|
||
end
|
||
<
|
||
|
||
|
||
FILTERING *snacks-profiler-usage-filtering*
|
||
|
||
For the full definition, see the `snacks.profiler.Filter` type.
|
||
|
||
Each field can be a string or a boolean.
|
||
|
||
When a field is a string, it will match the exact value, unless it starts with
|
||
`^` in which case it will match the pattern.
|
||
|
||
When any of the `def`/`ref` fields are `true`, the filter matches the current
|
||
location of the cursor.
|
||
|
||
For example, `{ref_file = true}` will match all traces calling something, in
|
||
the current file.
|
||
|
||
All other fields equal to `true` will match if the trace has a value for that
|
||
field.
|
||
|
||
|
||
==============================================================================
|
||
4. Setup *snacks-profiler-setup*
|
||
|
||
>lua
|
||
-- lazy.nvim
|
||
{
|
||
"folke/snacks.nvim",
|
||
---@type snacks.Config
|
||
opts = {
|
||
profiler = {
|
||
-- your profiler configuration comes here
|
||
-- or leave it empty to use the default settings
|
||
-- refer to the configuration section below
|
||
}
|
||
}
|
||
}
|
||
<
|
||
|
||
|
||
==============================================================================
|
||
5. Config *snacks-profiler-config*
|
||
|
||
>lua
|
||
---@class snacks.profiler.Config
|
||
{
|
||
autocmds = true,
|
||
runtime = vim.env.VIMRUNTIME, ---@type string
|
||
-- thresholds for buttons to be shown as info, warn or error
|
||
-- value is a tuple of [warn, error]
|
||
thresholds = {
|
||
time = { 2, 10 },
|
||
pct = { 10, 20 },
|
||
count = { 10, 100 },
|
||
},
|
||
on_stop = {
|
||
highlights = true, -- highlight entries after stopping the profiler
|
||
pick = true, -- show a picker after stopping the profiler (uses the `on_stop` preset)
|
||
},
|
||
---@type snacks.profiler.Highlights
|
||
highlights = {
|
||
min_time = 0, -- only highlight entries with time > min_time (in ms)
|
||
max_shade = 20, -- time in ms for the darkest shade
|
||
badges = { "time", "pct", "count", "trace" },
|
||
align = 80,
|
||
},
|
||
pick = {
|
||
picker = "snacks", ---@type snacks.profiler.Picker
|
||
---@type snacks.profiler.Badge.type[]
|
||
badges = { "time", "count", "name" },
|
||
---@type snacks.profiler.Highlights
|
||
preview = {
|
||
badges = { "time", "pct", "count" },
|
||
align = "right",
|
||
},
|
||
},
|
||
startup = {
|
||
event = "VimEnter", -- stop profiler on this event. Defaults to `VimEnter`
|
||
after = true, -- stop the profiler **after** the event. When false it stops **at** the event
|
||
pattern = nil, -- pattern to match for the autocmd
|
||
pick = true, -- show a picker after starting the profiler (uses the `startup` preset)
|
||
},
|
||
---@type table<string, snacks.profiler.Pick|fun():snacks.profiler.Pick?>
|
||
presets = {
|
||
startup = { min_time = 1, sort = false },
|
||
on_stop = {},
|
||
filter_by_plugin = function()
|
||
return { filter = { def_plugin = vim.fn.input("Filter by plugin: ") } }
|
||
end,
|
||
},
|
||
---@type string[]
|
||
globals = {
|
||
-- "vim",
|
||
-- "vim.api",
|
||
-- "vim.keymap",
|
||
-- "Snacks.dashboard.Dashboard",
|
||
},
|
||
-- filter modules by pattern.
|
||
-- longest patterns are matched first
|
||
filter_mod = {
|
||
default = true, -- default value for unmatched patterns
|
||
["^vim%."] = false,
|
||
["mason-core.functional"] = false,
|
||
["mason-core.functional.data"] = false,
|
||
["mason-core.optional"] = false,
|
||
["which-key.state"] = false,
|
||
},
|
||
filter_fn = {
|
||
default = true,
|
||
["^.*%._[^%.]*$"] = false,
|
||
["trouble.filter.is"] = false,
|
||
["trouble.item.__index"] = false,
|
||
["which-key.node.__index"] = false,
|
||
["smear_cursor.draw.wo"] = false,
|
||
["^ibl%.utils%."] = false,
|
||
},
|
||
icons = {
|
||
time = " ",
|
||
pct = " ",
|
||
count = " ",
|
||
require = " ",
|
||
modname = " ",
|
||
plugin = " ",
|
||
autocmd = "⚡",
|
||
file = " ",
|
||
fn = " ",
|
||
status = " ",
|
||
},
|
||
}
|
||
<
|
||
|
||
|
||
==============================================================================
|
||
6. Types *snacks-profiler-types*
|
||
|
||
|
||
TRACES *snacks-profiler-types-traces*
|
||
|
||
>lua
|
||
---@class snacks.profiler.Trace
|
||
---@field name string fully qualified name of the function
|
||
---@field time number time in nanoseconds
|
||
---@field depth number stack depth
|
||
---@field [number] snacks.profiler.Trace child traces
|
||
---@field fname string function name
|
||
---@field fn function function reference
|
||
---@field modname? string module name
|
||
---@field require? string special case for require
|
||
---@field autocmd? string special case for autocmd
|
||
---@field count? number number of calls
|
||
---@field def? snacks.profiler.Loc location of the definition
|
||
---@field ref? snacks.profiler.Loc location of the reference (caller)
|
||
---@field loc? snacks.profiler.Loc normalized location
|
||
<
|
||
|
||
>lua
|
||
---@class snacks.profiler.Loc
|
||
---@field file string path to the file
|
||
---@field line number line number
|
||
---@field loc? string normalized location
|
||
---@field modname? string module name
|
||
---@field plugin? string plugin name
|
||
<
|
||
|
||
|
||
PICK: GROUPING, FILTERING AND SORTING*snacks-profiler-types-pick:-grouping,-filtering-and-sorting*
|
||
|
||
>lua
|
||
---@class snacks.profiler.Find
|
||
---@field structure? boolean show traces as a tree or flat list
|
||
---@field sort? "time"|"count"|false sort by time or count, or keep original order
|
||
---@field loc? "def"|"ref" what location to show in the preview
|
||
---@field group? boolean|snacks.profiler.Field group traces by field
|
||
---@field filter? snacks.profiler.Filter filter traces by field(s)
|
||
---@field min_time? number only show grouped traces with `time >= min_time`
|
||
<
|
||
|
||
>lua
|
||
---@class snacks.profiler.Pick: snacks.profiler.Find
|
||
---@field picker? snacks.profiler.Picker
|
||
<
|
||
|
||
>lua
|
||
---@alias snacks.profiler.Picker "snacks"|"trouble"
|
||
---@alias snacks.profiler.Pick.spec snacks.profiler.Pick|{preset?:string}|fun():snacks.profiler.Pick
|
||
<
|
||
|
||
>lua
|
||
---@alias snacks.profiler.Field
|
||
---| "name" fully qualified name of the function
|
||
---| "def" definition
|
||
---| "ref" reference (caller)
|
||
---| "require" require
|
||
---| "autocmd" autocmd
|
||
---| "modname" module name of the called function
|
||
---| "def_file" file of the definition
|
||
---| "def_modname" module name of the definition
|
||
---| "def_plugin" plugin that defines the function
|
||
---| "ref_file" file of the reference
|
||
---| "ref_modname" module name of the reference
|
||
---| "ref_plugin" plugin that references the function
|
||
<
|
||
|
||
>lua
|
||
---@class snacks.profiler.Filter
|
||
---@field name? string|boolean fully qualified name of the function
|
||
---@field def? string|boolean location of the definition
|
||
---@field ref? string|boolean location of the reference (caller)
|
||
---@field require? string|boolean special case for require
|
||
---@field autocmd? string|boolean special case for autocmd
|
||
---@field modname? string|boolean module name
|
||
---@field def_file? string|boolean file of the definition
|
||
---@field def_modname? string|boolean module name of the definition
|
||
---@field def_plugin? string|boolean plugin that defines the function
|
||
---@field ref_file? string|boolean file of the reference
|
||
---@field ref_modname? string|boolean module name of the reference
|
||
---@field ref_plugin? string|boolean plugin that references the function
|
||
<
|
||
|
||
|
||
UI *snacks-profiler-types-ui*
|
||
|
||
>lua
|
||
---@alias snacks.profiler.Badge {icon:string, text:string, padding?:boolean, level?:string}
|
||
---@alias snacks.profiler.Badge.type "time"|"pct"|"count"|"name"|"trace"
|
||
<
|
||
|
||
>lua
|
||
---@class snacks.profiler.Highlights
|
||
---@field min_time? number only highlight entries with time >= min_time
|
||
---@field max_shade? number -- time in ms for the darkest shade
|
||
---@field badges? snacks.profiler.Badge.type[] badges to show
|
||
---@field align? "right"|"left"|number align the badges right, left or at a specific column
|
||
<
|
||
|
||
|
||
OTHER *snacks-profiler-types-other*
|
||
|
||
>lua
|
||
---@class snacks.profiler.Startup
|
||
---@field event? string
|
||
---@field pattern? string|string[] pattern to match for the autocmd
|
||
<
|
||
|
||
>lua
|
||
---@alias snacks.profiler.GroupFn fun(entry:snacks.profiler.Trace):{key:string, name?:string}?
|
||
<
|
||
|
||
|
||
==============================================================================
|
||
7. Module *snacks-profiler-module*
|
||
|
||
>lua
|
||
---@class snacks.profiler
|
||
---@field core snacks.profiler.core
|
||
---@field loc snacks.profiler.loc
|
||
---@field tracer snacks.profiler.tracer
|
||
---@field ui snacks.profiler.ui
|
||
---@field picker snacks.profiler.picker
|
||
Snacks.profiler = {}
|
||
<
|
||
|
||
|
||
`Snacks.profiler.find()` *Snacks.profiler.find()*
|
||
|
||
Group and filter traces
|
||
|
||
>lua
|
||
---@param opts snacks.profiler.Find
|
||
Snacks.profiler.find(opts)
|
||
<
|
||
|
||
|
||
`Snacks.profiler.highlight()` *Snacks.profiler.highlight()*
|
||
|
||
Toggle the profiler highlights
|
||
|
||
>lua
|
||
---@param enable? boolean
|
||
Snacks.profiler.highlight(enable)
|
||
<
|
||
|
||
|
||
`Snacks.profiler.pick()` *Snacks.profiler.pick()*
|
||
|
||
Group and filter traces and open a picker
|
||
|
||
>lua
|
||
---@param opts? snacks.profiler.Pick.spec
|
||
Snacks.profiler.pick(opts)
|
||
<
|
||
|
||
|
||
`Snacks.profiler.running()` *Snacks.profiler.running()*
|
||
|
||
Check if the profiler is running
|
||
|
||
>lua
|
||
Snacks.profiler.running()
|
||
<
|
||
|
||
|
||
`Snacks.profiler.scratch()` *Snacks.profiler.scratch()*
|
||
|
||
Open a scratch buffer with the profiler picker options
|
||
|
||
>lua
|
||
Snacks.profiler.scratch()
|
||
<
|
||
|
||
|
||
`Snacks.profiler.start()` *Snacks.profiler.start()*
|
||
|
||
Start the profiler
|
||
|
||
>lua
|
||
---@param opts? snacks.profiler.Config
|
||
Snacks.profiler.start(opts)
|
||
<
|
||
|
||
|
||
`Snacks.profiler.startup()` *Snacks.profiler.startup()*
|
||
|
||
Start the profiler on startup, and stop it after the event has been triggered.
|
||
|
||
>lua
|
||
---@param opts snacks.profiler.Config
|
||
Snacks.profiler.startup(opts)
|
||
<
|
||
|
||
|
||
`Snacks.profiler.status()` *Snacks.profiler.status()*
|
||
|
||
Statusline component
|
||
|
||
>lua
|
||
Snacks.profiler.status()
|
||
<
|
||
|
||
|
||
`Snacks.profiler.stop()` *Snacks.profiler.stop()*
|
||
|
||
Stop the profiler
|
||
|
||
>lua
|
||
---@param opts? {highlights?:boolean, pick?:snacks.profiler.Pick.spec}
|
||
Snacks.profiler.stop(opts)
|
||
<
|
||
|
||
|
||
`Snacks.profiler.toggle()` *Snacks.profiler.toggle()*
|
||
|
||
Toggle the profiler
|
||
|
||
>lua
|
||
Snacks.profiler.toggle()
|
||
<
|
||
|
||
==============================================================================
|
||
8. Links *snacks-profiler-links*
|
||
|
||
1. *image*: https://github.com/user-attachments/assets/cebb1308-077b-4f20-bee3-28644fb121b8
|
||
2. *image*: https://github.com/user-attachments/assets/4ee557c4-a290-4a52-b5c9-64e325bf1094
|
||
3. *image*: https://github.com/user-attachments/assets/ec03e440-6719-4463-a649-a8626dcfe2ec
|
||
|
||
Generated by panvimdoc <https://github.com/kdheepak/panvimdoc>
|
||
|
||
vim:tw=78:ts=8:noet:ft=help:norl:
|