diff --git a/.SpaceVim.d/autoload/SpaceVim/dev/profile.vim b/.SpaceVim.d/autoload/SpaceVim/dev/profile.vim new file mode 100644 index 000000000..b92528996 --- /dev/null +++ b/.SpaceVim.d/autoload/SpaceVim/dev/profile.vim @@ -0,0 +1,371 @@ +"============================================================================= +" profile.vim --- Profile startuptime +" Copyright (c) 2016-2019 Wang Shidong & Contributors +" Author: Wang Shidong < wsdjeg@outlook.com > +" URL: https://spacevim.org +" License: GPLv3 +"============================================================================= + +let s:CPT = SpaceVim#api#import('vim#compatible') + +let s:plugin_hints = [ + \ 'autoload', + \ 'colors', + \ 'compiler', + \ 'filetype.vim', + \ 'ftdetect', + \ 'ftplugin', + \ 'indent', + \ 'keymap', + \ 'plugin', + \ 'rplugin', + \ 'syntax', + \ ] + +let s:levels = [ + \ 'Flawless Victory', + \ 'Outstanding', + \ 'Fatality', + \ 'Toasty', + \ 'Impressive', + \ 'Well Done', + \ 'Test your might', + \ ] + + +function! s:plugin_sort(a, b) abort + return len(a:b[0]) - len(a:a[0]) +endfunction + + +function! s:result_sort(a, b) abort + if a:a[1] < a:b[1] + return 1 + elseif a:a[1] > a:b[1] + return -1 + endif + return 0 +endfunction + + +function! s:get_vimrc() abort + if exists('$MYVIMRC') && !empty($MYVIMRC) + return expand($MYVIMRC) + endif + + return expand(matchstr(s:CPT.execute('silent scriptnames'), '1: \zs\f\+')) +endfunction + + +function! s:init_plugins(vimrc) abort + let vimrc_path = substitute(fnamemodify(a:vimrc, ':p:h') . '/', + \ '//', '/', 'g') + let default_vimrc_path = fnamemodify('~/.vim', ':p') + let runtime_path = fnamemodify(expand('$VIMRUNTIME'), ':p') + let nvim_config = substitute((exists('$XDG_CONFIG_HOME') + \ ? expand('$XDG_CONFIG_HOME') : fnamemodify('~/.config', ':p')) + \ . '/nvim/', '//', '/', 'g') + let home = fnamemodify('~', ':p') + let seen = [vimrc_path, default_vimrc_path, runtime_path, nvim_config] + let s:plugins = [] + + if exists('g:plugs') + for [plugin, info] in items(g:plugs) + if !has_key(info, 'dir') || index(seen, info.dir) != -1 + continue + endif + call add(s:plugins, [info.dir, plugin]) + call add(seen, info.dir) + endfor + elseif exists('g:dein#_plugins') + for [plugin, info] in items(g:dein#_plugins) + if !has_key(info, 'rtp') || index(seen, info.rtp) != -1 + continue + endif + let dir = fnamemodify(info.rtp, ':p') + call add(s:plugins, [dir, plugin]) + call add(seen, dir) + endfor + endif + + for path in split(&runtimepath, ',') + let path = fnamemodify(path, ':p') + if path =~# '/$' + let path = path[:-2] + endif + + if path =~# '/after$' + let path = fnamemodify(path, ':h') + if path =~# '/$' + let path = path[:-2] + endif + endif + + if isdirectory(path) && index(seen, path . '/') == -1 + for hint in s:plugin_hints + let hint_path = path . '/' . hint + if isdirectory(hint_path) || filereadable(hint_path) + let name = fnamemodify(path, ':t') + call add(s:plugins, [path . '/', name]) + call add(seen, path . '/') + break + endif + endfor + endif + endfor + + call sort(s:plugins, function('s:plugin_sort')) + call add(s:plugins, [runtime_path, '[runtime]']) + + if vimrc_path != home + call add(s:plugins, [vimrc_path, '[vimrc]']) + endif + + if vimrc_path != default_vimrc_path && isdirectory(default_vimrc_path) + call add(s:plugins, [default_vimrc_path, '[vimrc]']) + endif + + if isdirectory(nvim_config) + call add(s:plugins, [nvim_config, '[vimrc]']) + endif +endfunction + + +function! s:get_plugin(fname) abort + for [path, name] in s:plugins + if len(path) < len(a:fname) && a:fname[:len(path)-1] == path + return name + endif + endfor + + return '[unknown]' +endfunction + + +function! s:get_samples(cmd, count, tmp) abort + let c = 0 + let phase_order = [] + let phases = {'startup': {'_files': {}, '_time': 0}} + let totals = {} + let total_time = 0 + let logs = [] + + while c < a:count + if getchar(0) == 27 + echomsg printf('Stopped after %d samples', c) + break + endif + + let c += 1 + redraw + echo printf('Sample %d/%d', c, a:count) + call system(a:cmd) + + if !filereadable(a:tmp) + echohl ErrorMsg + echo 'Profile log wasn''t created' + echohl None + break + endif + + let phase = 'startup' + let log = readfile(a:tmp) + call add(logs, log) + + for line in log + if line =~# '^\%(\d\+\.\d\+\s*\)\{2}:' + if c == 1 + call add(phase_order, phase) + endif + " call add(phases, {'phase': phase, 'times': cur_phase}) + " let cur_phase = {} + let phase = matchstr(line, '\d\+\.\d\+: \zs.*') + if !has_key(phases, phase) + let phases[phase] = {'_files': {}, '_time': 0} + endif + elseif line =~# '^\%(\d\+\.\d\+\s*\)\{3}: sourcing ' + let [time, fname] = split(matchstr(line, '\d\+\.\d\+: .*'), ':\s*sourcing\s*') + let plugin = s:get_plugin(fname) + + if !has_key(phases[phase], plugin) + let phases[phase][plugin] = 0 + let phases[phase]['_files'][plugin] = {} + endif + + if !has_key(phases[phase]['_files'][plugin], fname) + let phases[phase]['_files'][plugin][fname] = 0 + endif + + if !has_key(totals, plugin) + let totals[plugin] = 0 + endif + + let t = str2float(time) + let phases[phase][plugin] += t + let phases[phase]['_time'] += t + let phases[phase]['_files'][plugin][fname] += t + let totals[plugin] += t + let total_time += t + endif + endfor + + call delete(a:tmp) + endwhile + + for phase in keys(phases) + for plugin in keys(phases[phase]) + if plugin != '_files' + let phases[phase][plugin] = phases[phase][plugin] / c + else + for fplugin in keys(phases[phase][plugin]) + for fname in keys(phases[phase][plugin][fplugin]) + let phases[phase][plugin][fplugin][fname] = phases[phase][plugin][fplugin][fname] / c + endfor + endfor + endif + endfor + endfor + + for plugin in keys(totals) + let totals[plugin] = totals[plugin] / c + endfor + + let total_time = total_time / c + + return [total_time, totals, phase_order, phases, logs] +endfunction + + +function! SpaceVim#dev#profile#run(...) + let sample_count = 10 + let vimrc = s:get_vimrc() + let extra_args = [] + + for arg in a:000 + if arg == '--' + call add(extra_args, '') + elseif !empty(extra_args) + call add(extra_args, arg) + elseif arg =~# '\d\+' + let sample_count = str2nr(arg) + elseif arg =~# '\f\+' && filereadable(expand(arg)) + let vimrc = expand(arg) + endif + endfor + + if a:0 && type(a:1) == type(0) && a:1 > 0 + let sample_count = a:1 + endif + + call s:init_plugins(vimrc) + + if exists('v:progpath') && !empty(v:progpath) && executable(v:progpath) + let exe = v:progpath + else + let exe = has('nvim') ? 'nvim' : 'vim' + if has('win32') + let exe .= '.exe' + endif + endif + + let tmp = tempname() + let wintmp = '' + let quiet_arg = has('nvim') ? '--headless' : '--not-a-term' + let args = ' -i NONE --startuptime ' . tmp . ' +qa!' + if !empty(vimrc) + let args = ' -u ' . vimrc . args + endif + + if !empty(extra_args) + let args .= ' ' . join(extra_args, ' ') + endif + + call system(exe . ' ' . quiet_arg . ' +qa!') + + if v:shell_error + " Use `script` so Vim doesn't issue a delay warning + if has('macunix') + let cmd = 'script -q /dev/null ' . exe . args + elseif has('win32') + " Just hope for the best + let wintmp = tempname() + let cmd = exe . args . ' >' . wintmp .' 2>&1' + else + let cmd = 'script -q -c "' . exe . args . '" /dev/null' + endif + else + let cmd = exe . ' ' . quiet_arg . args + endif + + echomsg 'Sampling with command:' cmd + + let [total_time, totals, phase_order, + \ phases, logs] = s:get_samples(cmd, sample_count, tmp) + let total_samples = len(logs) + + if !empty(wintmp) && filereadable(wintmp) + call delete(wintmp) + endif + + let level_time = 1000 / (len(s:levels) - 1) + let l = float2nr(floor(min([float2nr(total_time), 1000]) / level_time)) + let level = s:levels[l] + + let lines = [printf('Total Time: %8.3f -- %s', total_time, level), ''] + + let slowest = sort(items(totals), function('s:result_sort'))[:9] + let width = max(map(copy(slowest), 'len(v:val[0])')) + let lines += ['', printf('Slowest %d plugins (out of %d)~', len(slowest), len(totals))] + + for [plugin, time] in slowest + call add(lines, printf("%*s\t%-8.3f", width, plugin, time)) + endfor + + let lines += ['', 'Phase Detail:~', ''] + + for phase in phase_order + let item = phases[phase] + let files = remove(item, '_files') + let phase_total = remove(item, '_time') + + if empty(item) + continue + endif + + let lines += [printf('%s (%0.3f)~', phase, phase_total)] + for [plugin, time] in sort(items(item), function('s:result_sort')) + let lines += [printf("%-8.3f %s >", time, plugin)] + for [fname, time] in sort(items(files[plugin]), function('s:result_sort')) + let lines += [printf("\t%-8.3f %s", time, fname)] + endfor + let lines += ['<'] + endfor + let lines += [''] + endfor + + let banner_line = repeat('=', 34) + let lines += ['', printf('%s FULL LOGS %s', banner_line, banner_line), ''] + + let i = 0 + for log in logs + let i += 1 + let lines += [printf('Log %d/%d >', i, total_samples)] + let l = 0 + for line in log + if line !~# '^\s*$' + let log = log[l :] + break + endif + let l += 1 + endfor + let lines += map(log, '" " . v:val') + ['<'] + endfor + + enew + silent %put=lines + call cursor(1, 1) + silent delete _ + set buftype=nofile syntax=help foldmethod=marker foldmarker=>,< nomodified + silent file startup-log.txt + normal! zM +endfunction diff --git a/.SpaceVim.d/init.vim b/.SpaceVim.d/init.vim index 047b8fe66..d50a4842d 100644 --- a/.SpaceVim.d/init.vim +++ b/.SpaceVim.d/init.vim @@ -33,4 +33,4 @@ command! -nargs=1 IssueEdit call SpaceVim#dev#issuemanager#edit() command! -nargs=1 PullCreate call SpaceVim#dev#pull#create() command! -nargs=1 PullMerge call SpaceVim#dev#pull#merge() command! ReleaseSpaceVim call SpaceVim#dev#releases#open() - +command! -nargs=* -complete=file Profile call SpaceVim#dev#profile#run()