Skip to content

Commit

Permalink
Feature: autogenerate profiling code for packer_compiled (#221)
Browse files Browse the repository at this point in the history
* WIP adding optional timing code to compiled output

* Implement basic timed_chunk function

* feat: add packer profile output function

* feat: pass in profile flag to compile

This is so a user can set in their config if packer should profile by
default or per command

* feat: add packer profile command to open display

* feat: highlight times differently based on size

* fix: don't mutate the profile output directly

* refactor: separate highlights into self function

* feat: add configurable profile threshold

* fix: check global packer object exists

before adding to it

* docs: update README and help text

* chore: add warning if profile command used

when there is no profiling data

* docs: fix typos and add to main config example

* fix: change profile enabled key to be enable

* Fix: ensure _G._packer is initialized and not overwritten

* Slight style tweak in parse_args and run lua-format

Co-authored-by: Akin Sowemimo <[email protected]>
  • Loading branch information
wbthomason and akinsho authored Apr 19, 2021
1 parent 8115dd4 commit 93f440d
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 32 deletions.
38 changes: 35 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ Have a problem or idea? Make an [issue](https://github.com/wbthomason/packer.nvi
4. [Performing plugin management operations](#performing-plugin-management-operations)
5. [Extending packer](#extending-packer)
6. [Compiling Lazy-Loaders](#compiling-lazy-loaders)
7. [Debugging](#debugging)
8. [Status](#status)
9. [Contributors](#contributors)
7. [Profiling](#profiling)
8. [Debugging](#debugging)
9. [Status](#status)
10. [Contributors](#contributors)

## Notices
- **2021-02-18**: Having trouble with Luarocks on macOS? See [this issue](https://github.com/wbthomason/packer.nvim/issues/180).
Expand Down Expand Up @@ -283,6 +284,10 @@ default configuration values (and structure of the configuration table) are:
},
luarocks = {
python_cmd = 'python' -- Set the python command to use for running hererocks
},
profile = {
enable = false,
threshold = 1, -- integer in milliseconds, plugins which load faster than this won't be shown in profile output
}
}
```
Expand Down Expand Up @@ -484,6 +489,33 @@ require knowing when the operations are complete, you can use the following `Use

- `PackerComplete`: Fires after install, update, clean, and sync asynchronous operations finish.
- `PackerCompileDone`: Fires after compiling (see [the section on compilation](#compiling-lazy-loaders))

## Profiling
Packer has built in functionality that can allow you to profile the time taken loading your plugins.
In order to use this functionality you must either enable profiling in your config, or pass in an argument
when running packer compile.

#### Setup via config
```lua
config = {
profile = {
enable = true,
threshold = 1 -- the amount in ms that a plugins load time must be over for it to be included in the profile
}
}
```

#### Using the packer compile command
```vim
:PackerCompile profile=true
" or
:PackerCompile profile=false
```

#### Profiling usage
This will rebuild your `packer_compiled.vim` with profiling code included. In order to visualise the output of the profile
restart your neovim and run `PackerProfile`. This will open a window with the output of your profiling.

## Debugging
`packer.nvim` logs to `stdpath(cache)/packer.nvim.log`. Looking at this file is usually a good start
if something isn't working as expected.
Expand Down
30 changes: 30 additions & 0 deletions doc/packer.txt
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,36 @@ The option `compile_on_sync`, which defaults to `true`, will run
Note that otherwise, you **must** run `packer.compile` yourself to generate
the lazy-loader file!

PROFILING PLUGINS *packer-profiling*
You can measure how long it takes your plugins to load using packer's builtin
profiling functionality.
In order to use this functionality you must either enable profiling in your config, or pass in an argument
when running packer compile.

Setup via config >
config = {
profile = {
enable = true,
threshold = 1 -- the amount in ms that a plugins load time must be over for it to be included in the profile
}
}
<

Using the packer compile command
>
:PackerCompile profile=true
" or
:PackerCompile profile=false
<

NOTE you can also set a `threshold` in your profile config which is a number
in `ms` above which plugin load times will be show e.g. if you set a threshold
value of `3` then any plugin that loads slower than `3ms` will not be included in
the output window.

This will rebuild your `packer_compiled.vim` with profiling code included. In order to visualise the output of the profile
Restart your neovim and run `PackerProfile`. This will open a window with the output of your profiling.

EXTENDING PACKER *packer-extending*
You can add custom key handlers to `packer` by calling
`packer.set_handler(name, func)` where `name` is the key you wish to handle
Expand Down
57 changes: 51 additions & 6 deletions lua/packer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ local util = require('packer.util')
local async = a.sync
local await = a.wait

--- Instantiate global packer namespace for use for
--- callbacks and other data generated whilst packer
--- is running
_G._packer = _G._packer or {}

-- Config
local packer = {}
local config_defaults = {
Expand Down Expand Up @@ -67,7 +72,8 @@ local config_defaults = {
keybindings = {quit = 'q', toggle_info = '<CR>', diff = 'd', prompt_revert = 'r'}
},
luarocks = {python_cmd = 'python'},
log = {level = 'warn'}
log = {level = 'warn'},
profile = {enable = false}
}

local config = vim.tbl_extend('force', {}, config_defaults)
Expand Down Expand Up @@ -99,8 +105,9 @@ packer.init = function(user_config)
vim.cmd [[command! PackerUpdate lua require('packer').update()]]
vim.cmd [[command! PackerSync lua require('packer').sync()]]
vim.cmd [[command! PackerClean lua require('packer').clean()]]
vim.cmd [[command! PackerCompile lua require('packer').compile()]]
vim.cmd [[command! -nargs=* PackerCompile lua require('packer').compile(<q-args>)]]
vim.cmd [[command! PackerStatus lua require('packer').status()]]
vim.cmd [[command! PackerProfile lua require('packer').profile_output()]]
end
end

Expand Down Expand Up @@ -429,13 +436,40 @@ local function refresh_configs(plugs)
end
end

local function parse_value(value)
if value == "true" then return true end
if value == "false" then return false end
return value
end

local function parse_args(args)
local result = {}
if args then
local parts = vim.split(args, ' ')
for _, part in ipairs(parts) do
if part then
if part:find('=') then
local key, value = unpack(vim.split(part, '='))
result[key] = parse_value(value)
end
end
end
end
return result
end

--- Update the compiled lazy-loader code
-- Takes an optional argument of a path to which to output the resulting compiled code
packer.compile = function(output_path)
output_path = output_path or config.compile_path
--- Takes an optional argument of a path to which to output the resulting compiled code
packer.compile = function(raw_args)
local args = parse_args(raw_args)
local output_path = args.output_path or config.compile_path
local should_profile = args.profile
-- the user might explicitly choose for this value to be false in which case
-- an or operator will not work
if should_profile == nil then should_profile = config.profile.enable end
refresh_configs(plugins)
-- NOTE: we copy the plugins table so the in memory value is not mutated during compilation
local compiled_loader = compile(vim.deepcopy(plugins))
local compiled_loader = compile(vim.deepcopy(plugins), should_profile)
output_path = vim.fn.expand(output_path)
vim.fn.mkdir(vim.fn.fnamemodify(output_path, ":h"), 'p')
local output_file = io.open(output_path, 'w')
Expand All @@ -446,6 +480,17 @@ packer.compile = function(output_path)
packer.on_compile_done()
end

packer.profile_output = function()
if _G._packer.profile_output then
async(function()
local display_win = display.open(config.display.open_fn or config.display.open_cmd)
display_win:profile_output(_G._packer.profile_output)
end)()
else
log.warn('You must run PackerCompile with profiling enabled first e.g. PackerProfile profile=true')
end
end

packer.config = config

--- Convenience function for simple setup
Expand Down
Loading

0 comments on commit 93f440d

Please sign in to comment.