Perfanno.nvim
NeoVim lua plugin that annotates source code with profiling information from perf, LuaJIT, or other profilers.
Install / Use
/learn @t-troebst/Perfanno.nvimREADME
PerfAnno: Profiling Annotations and Call Graph Exploration in NeoVim!
PerfAnno is a simple lua plugin for NeoVim that allows you to annotate your code with output from perf or other call graph profilers that can generate stack traces in the flamegraph format. The plugin itself is language agnostic and has been tested with C, C++, Lua, and Python. PerfAno can be used to profile neovim itself easily.
Each line is annotated with the samples that occurred in that line including nested function calls. This requires that the perf.data file has been recorded with call graph information.
If the profiler provides multiple events such as, say, cpu cycles, branch mispredictions and cache misses, then you can switch between these.
In addition, PerfAnno provides Telescope, fzf-lua, or vim.ui.select finders that allow you to immediately jump to the hottest lines / functions in your code base or the hottest callers of a specific region of code (typically a function).
https://user-images.githubusercontent.com/15610942/153775719-ed236a8d-d012-448d-b3b1-8b38f57d1fbf.mp4
Installation
This plugin requires NeoVim 0.10 and was tested most recently with NeoVim 0.11 and perf 5.16. The call graph mode may require a relatively recent version of perf that supports folded output, though it should be easy to add support for older versions manually.
You should be able to install this plugin the same way you install other NeoVim lua plugins, e.g. via use "t-troebst/perfanno.nvim" in packer.
After installing, you need to initialize the plugin by calling:
require("perfanno").setup()
This will give you the default settings which are shown below.
However, you may want to set line_highlights and vt_highlight to appropriate highlights that do not clash with your color scheme and set some keybindings to make use of this plugin.
See the provided example config.
Dependencies:
If you want to use the commands that jump to the hottest lines of code, you will probably want to have either telescope.nvim or fzf-lua installed.
Otherwise (or if you explicitly disable both during setup), the plugin will fall back to vim.ui.select instead.
For :PerfAnnotateFunction and :PerfHottestCallersFunction you will need nvim-treesitter.
Example Config
The following config sets the highlights to a nice RGB color gradient between the background color and an orange red. It also sets convenient keybindings for most of the standard commands.
local perfanno = require("perfanno")
local util = require("perfanno.util")
perfanno.setup {
-- Creates a 10-step RGB color gradient beween background color and "#CC3300"
line_highlights = util.make_bg_highlights(nil, "#CC3300", 10),
vt_highlight = util.make_fg_highlight("#CC3300"),
}
local keymap = vim.api.nvim_set_keymap
local opts = {noremap = true, silent = true}
keymap("n", "<LEADER>plf", ":PerfLoadFlat<CR>", opts)
keymap("n", "<LEADER>plg", ":PerfLoadCallGraph<CR>", opts)
keymap("n", "<LEADER>plo", ":PerfLoadFlameGraph<CR>", opts)
keymap("n", "<LEADER>pe", ":PerfPickEvent<CR>", opts)
keymap("n", "<LEADER>pa", ":PerfAnnotate<CR>", opts)
keymap("n", "<LEADER>pf", ":PerfAnnotateFunction<CR>", opts)
keymap("v", "<LEADER>pa", ":PerfAnnotateSelection<CR>", opts)
keymap("n", "<LEADER>pt", ":PerfToggleAnnotations<CR>", opts)
keymap("n", "<LEADER>ph", ":PerfHottestLines<CR>", opts)
keymap("n", "<LEADER>ps", ":PerfHottestSymbols<CR>", opts)
keymap("n", "<LEADER>pc", ":PerfHottestCallersFunction<CR>", opts)
keymap("v", "<LEADER>pc", ":PerfHottestCallersSelection<CR>", opts)
Configuration
For the full list of potential configuration options, see the following setup call.
require("perfanno").setup {
-- List of highlights that will be used to highlight hot lines (or nil to disable).
line_highlights = require("perfanno.util").make_bg_highlights(nil, "#FF0000", 10),
-- Highlight used for virtual text annotations (or nil to disable virtual text).
vt_highlight = require("perfanno.util").make_fg_highlight("#FF0000"),
-- Annotation formats that can be cycled between via :PerfCycleFormat
-- "percent" controls whether percentages or absolute counts should be displayed
-- "format" is the format string that will be used to display counts / percentages
-- "minimum" is the minimum value below which lines will not be annotated
-- Note: this also controls what shows up in the telescope finders
formats = {
{percent = true, format = "%.2f%%", minimum = 0.5},
{percent = false, format = "%d", minimum = 1}
},
-- Automatically annotate files after :PerfLoadFlat and :PerfLoadCallGraph
annotate_after_load = true,
-- Automatically annotate newly opened buffers if information is available
annotate_on_open = true,
-- Options for telescope-based hottest line finders
telescope = {
-- Enable if possible, otherwise the plugin will fall back to fzf-lua or vim.ui.select
enabled = pcall(require, "telescope"),
-- Annotate inside of the preview window
annotate = true,
},
-- Options for fzf-lua hottest line finders
fzf_lua = {
-- Enable if possible, otherwise the plugin will fall back to vim.ui.select
enabled = pcall(require, "fzf-lua"),
-- Annotate inside of the preview window
annotate = true,
},
-- Node type patterns used to find the function that surrounds the cursor
ts_function_patterns = {
-- These should work for most languages (at least those used with perf)
default = {
"function",
"method",
},
-- Otherwise you can add patterns for specific languages like:
-- weirdlang = {
-- "weirdfunc",
-- }
},
-- Overwrite the default behaviour of prompting for the path to the perf.data with a custom function that returns
-- the path to a perf file as string.
get_path_callback = nil,
-- Enable per-thread profiling support for multi-threaded applications
-- When enabled, you can select which thread(s) to profile when loading perf.data
thread_support = false,
}
local telescope = require("telescope")
local actions = telescope.extensions.perfanno.actions
telescope.setup {
extensions = {
perfanno = {
-- Special mappings in the telescope finders
mappings = {
["i"] = {
-- Find hottest callers of selected entry
["<C-h>"] = actions.hottest_callers,
-- Find hottest callees of selected entry
["<C-l>"] = actions.hottest_callees,
},
["n"] = {
["gu"] = actions.hottest_callers,
["gd"] = actions.hottest_callees,
}
}
}
}
}
These are the default settings, so this is equivalent to require("perfanno").setup().
Workflow
In order to use this plugin, you will need to generate accurate profiling information with perf, ideally with call graph information. You will want to compile your program with debug information and then run:
perf record --call-graph dwarf {program}
This will then generate a perf.data file that can be used by this plugin.
From there you can use the commands shown below.
If the dwarf option creates files that are too large or take too long to process, you may also want to try:
perf record --call-graph fp {program}
However, this requires that your program and all libraries have been compiled with -fno-omit-frame-pointer and you may find that the line numbers are slightly off.
For more information, see the documentation of perf.
Multi-threaded Applications
If you're profiling multi-threaded applications and want to analyze per-thread performance, enable thread support in your configuration:
require("perfanno").setup({
thread_support = true,
})
When loading perf data from a multi-threaded program, you'll be prompted to select which thread(s) to profile:
- All threads (aggregated) - Default behavior, combines samples from all threads
- Individual threads - Analyze a specific thread, e.g. "TID 12345 (worker-thread)"
This is useful for identifying which threads are consuming the most resources or analyzing thread-specific performance characteristics.
Using Other Profilers
If you are using another profiler, you will need to generate a perf.log file that stores data in the flamegraph format, i.e. as a list of ;-separated stack traces with a count at the end in each line.
For example:
/path/to/src_1.cpp:30;/path/to/src_2.cpp:27;/path/to/src_1.cpp:27 47
/path/to/src_1.cpp:30;/path/to/src_2.cpp:50 20
/path/to/src_1.cpp:10;/path/to/src_3.cpp:20;/path/to/src_2.cpp:15 7
/path/to/src_1.cpp:10;/path/to/src_3.cpp:20;/path/to/src_2.cpp:50 92
Commands
Load profiling data
:PerfLoadFlatloads flat perf data. Obviously you will not be able to find callers of functions in this mode.:PerfLoadCallGraphloads full call graph perf data. This may take a while.:PerfLoadFlameGraphloads data from aperf.logfile in flamegraph format.
If there is no perf.data or perf.log file respectively in your working directory, you will be asked to locate one. If annotate_after_load is set this will immediately annotate all buffers.
Lua Profiling
PerfAnno can be used to easily profile NeoVim via the native LuaJIT profiler. Simply use the following commands in order:
:PerfLuaProfileStartstarts profiling.:PerfLuaProfileStopstops the current profiling run and loads the stack traces into the call graph. Automatically annotates all bu
Related Skills
node-connect
349.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
109.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
349.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
349.0kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
