runtime(indent-tests): Instrument timed "search*()" calls
The offered "tracer.vim" script can be used to measure and
record elapsed time for explicitly annotated "search*()"es,
set off with "VIM_INDENT_TEST_TRACE_(START|END)" comment
markers, in indent plugins.
related: #17116
Signed-off-by: Aliaksei Budavei <0x000c70@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/Filelist b/Filelist
index 4067df5..c5b3c4f 100644
--- a/Filelist
+++ b/Filelist
@@ -863,6 +863,7 @@
runtime/indent/testdir/*.vim \
runtime/indent/testdir/*.in \
runtime/indent/testdir/*.ok \
+ runtime/indent/testdir/tools/* \
runtime/ftplugin/*.vim \
runtime/ftplugin/logtalk.dict \
runtime/ftplugin/README.txt \
diff --git a/runtime/indent/testdir/tools/tracer.vim b/runtime/indent/testdir/tools/tracer.vim
new file mode 100644
index 0000000..d873154
--- /dev/null
+++ b/runtime/indent/testdir/tools/tracer.vim
@@ -0,0 +1,166 @@
+vim9script
+
+# Whenever indent plugins contain "search*()" lines explicitly annotated with
+# "VIM_INDENT_TEST_TRACE_(START|END)" comment markers; this script can then be
+# used as shown to measure and record elapsed time for such decorated calls.
+#
+# Usage:
+# cd runtime/indent
+# vim -u NONE -S testdir/tools/tracer.vim \
+# html.vim javascript.vim \
+# ../autoload/python.vim ../autoload/dist/vimindent.vim
+# git diff
+# make clean test
+# vim testdir/00-TRACE_LOG.fail
+
+def GenerateTempletForTracing(fname: string, vname: string): list<string>
+ #### ONLY INSTRUMENT "search*()"es FOR INDENT TESTS.
+
+ const templet: list<string> =<< trim eval END
+
+ if getcwd() =~# '\<runtime/indent$'
+
+ def! g:IndentTestTrace(id: string, start: list<number>, result: any): any
+ const end: list<number> = reltime(start)
+
+ if !has_key(g:indent_test_trace_times, id)
+ g:indent_test_trace_times[id] = []
+ endif
+
+ g:indent_test_trace_times[id]
+ ->add(reltimefloat(end))
+ return result
+ enddef
+
+ def! g:IndentTestInitTracing()
+ # Possibly use a later "{fname}", cf. ":runtime indent/foo.vim".
+ autocmd_add([{{
+ replace: true,
+ group: 'tracing',
+ event: 'QuitPre',
+ bufnr: bufnr(),
+ cmd: 'g:IndentTestWriteTraceTimes()',
+ }}])
+ g:indent_test_trace_times = {{}}
+ enddef
+
+ def! g:IndentTestWriteTraceTimes()
+ # Anticipate usage by multiple languages.
+ const token: string = printf('%02x', (rand() % 26))
+ writefile(['" {fname}:',
+ "let {vname}_" .. token .. " = " .. string(g:indent_test_trace_times),
+ "let {vname}_" .. token .. "_summary = " .. string(g:indent_test_trace_times
+ ->items()
+ ->reduce((outer: dict<dict<any>>, times: list<any>) =>
+ extend({{[times[0]]: times[1]
+ ->copy()
+ ->reduce((inner: dict<any>, v: float) =>
+ extend({{
+ min: inner.min < v ? inner.min : v,
+ max: inner.max > v ? inner.max : v,
+ sum: (inner.sum + v),
+ avg: ((inner.sum + v) / inner.count),
+ }},
+ inner,
+ "keep"),
+ {{
+ min: v:numbermax - 0.0,
+ max: v:numbermin + 0.0,
+ sum: 0.0,
+ avg: 0.0,
+ count: len(times[1]),
+ }})}},
+ outer),
+ {{}}))],
+ (!empty($VIM_INDENT_TEST_LOG) && filewritable($VIM_INDENT_TEST_LOG))
+ ? $VIM_INDENT_TEST_LOG
+ : "testdir/00-TRACE_LOG.fail",
+ "a")
+ enddef
+
+ call g:IndentTestInitTracing()
+
+ else
+
+ def! g:IndentTestTrace(_: string, _: list<number>, result: any): any
+ return result
+ enddef
+
+ endif
+
+ END
+ return templet
+enddef
+
+def InstrumentMarkedEntry(): bool
+ const marker_start: string = 'VIM_INDENT_TEST_TRACE_START'
+ const start: number = search('\C\<' .. marker_start .. '\>', 'ceW')
+
+ if start == 0
+ return false
+ endif
+
+ const marker_end: string = 'VIM_INDENT_TEST_TRACE_END'
+ const end: number = search('\C\<' .. marker_end .. '\>', 'ceW')
+
+ if end == 0
+ return false
+ endif
+
+ const tracee: list<string> = matchlist(
+ getline(start + 1),
+ '\(^.\+\)\(\<search\%(pair\)\=\%(pos\)\=\s*(.*$\)')
+
+ if empty(get(tracee, 1, '')) || empty(get(tracee, 2, ''))
+ return false
+ endif
+
+ const end_line: string = getline(end)
+ const tracer: string = printf('%sg:IndentTestTrace("%s", reltime(), %s',
+ tracee[1],
+ strpart(end_line, (stridx(end_line, marker_end) + strlen(marker_end) + 1)),
+ tracee[2])
+
+ if (end - start) > 1
+ setline((start + 1), tracer)
+ setline((end - 1), getline(end - 1) .. ')')
+ else
+ setline((start + 1), tracer .. ')')
+ endif
+
+ return true
+enddef
+
+def ProcessIndentPluginCmdlineArgs()
+ const names: list<string> = range(char2nr('a'), char2nr('z'))
+ ->map((_: number, n: number) => nr2char(n, true))
+ var entries: number = 0
+ var next: number = 0
+
+ for fname: string in argv(-1)
+ if filereadable(fname) && filewritable(fname)
+ execute 'new ' .. fname
+ call cursor(1, 1)
+
+ while InstrumentMarkedEntry()
+ entries += 1
+ endwhile
+
+ if entries > 0
+ append(1, GenerateTempletForTracing(fname, get(names, next, names[-1])))
+ wq
+ endif
+
+ entries = 0
+ next += 1
+ endif
+ endfor
+enddef
+
+if empty(system('git status --porcelain=v1'))
+ ProcessIndentPluginCmdlineArgs()
+endif
+
+quitall
+
+# vim:fdm=syntax:sw=2:ts=8:noet:nosta: