Actually, LaTeX already provide some hooks to add some code to be executed on package load:
\makeatletter
\AddToHook{package/before}{\wlog{::::start loading \@currname : \the\pdf@elapsedtime}}
\AddToHook{package/after}{\wlog{::::done loading \@currname : \the\pdf@elapsedtime}}
Need package pdftexcmds for \pdf@elapsedtime (for portability between different engines)
Then just search for the lines with :::: in the log file.
However... after getting the benchmark result, how to visualize them nicely is a bit nontrivial because there are so much content.
I wrote a small Python script to visualize the result (okay, use the right tool for the job. I could have written it in expl3 but what's the point?)
input_log_file_path="/path/to/a.log"
output_file_name="/path/to/b.tex"
^^ fill in those 2. remember to escape `' if you use them
extra feature: \wlog{::::mark something: \the\pdf@elapsedtime}' adds a marker with contentsomething'
time_scale=100
colors=["green!30!white", "blue!30!white", "red!30!white"]
first_coordinate=0.5
box_width=1.5
layer_separation=2
time_axis_length=27
import functools
lines=open(input_log_file_path, encoding='latin1').read()
printy=functools.partial(print, file=open(output_file_name, "w", encoding='u8'))
def set_globals_locals(globals, locals)->None:
#if both are originally None
#return caller's parent frame's globals and locals
import sys
# because of a Python bug https://bugs.python.org/issue21161 let's pass both as globals
out=sys._getframe(1).f_locals
if globals is None and locals is None:
f=sys._getframe(2)
return {f.f_globals, f.f_locals}, None
else:
pass
return globals, locals
def evalz(s: str, globals=None, locals=None, escape_char=None)->None:
globals, locals=set_globals_locals(globals, locals)
if escape_char is None:
escape_char=next(ch for ch in "%!@" if ch in s)
parts=s.split(escape_char)
result=[]
for i in range(len(parts)):
if i%2==0:
result.append(parts[i])
elif parts[i]=="": # %% → %
result.append(escape_char)
else:
try:
item=eval(parts[i], globals, locals)
except:
import sys
printy("Error while executing code ========\n"+ parts[i] + "\n========", file=sys.stderr)
raise
result.append(
item if isinstance(item, str) else
str(item) if isinstance(item, int) else
__import__("sympy").latex(item)
)
return "".join(result)
def printz(s: str, globals=None, locals=None, escape_char: str=None, end: str=None)->None:
globals, locals=set_globals_locals(globals, locals)
printy(evalz(s, globals, locals, escape_char=escape_char), end=end)
printz(
r"""%! TEX program = lualatex
\documentclass{article}
\usepackage{tikz}
\usepackage[a4paper, margin=0.5cm]{geometry}
%\usepackage{tooltip}
%\usepackage{pdfcomment}
\begin{document}
\begin{tikzpicture}[yscale=-1]
\draw [->] (0, 0) -- (0, $time_axis_length$);
\def\a#1#2{
\draw (0.1, #1) -- (-0.1, #1) node [left] {#2};
}
"""
, escape_char="$")
import re
items=[]
mark_event=object()
for line in lines.splitlines():
if not line.startswith("::::"): continue
match=re.fullmatch(r"::::(?:(start|done) loading ([a-zA-Z0-9-]+)|mark (.*)): (\d+)", line)
assert match, line
moment: float=int(match[4])/65536
if match[3]:
items.append((mark_event, match[3], moment))
else:
is_start: bool=match[1]=="start"
package_name: str=match[2]
items.append((is_start, package_name, moment))
initial_skip=items[0][2]
def time_to_coordinate(t: float)->float:
return (t-initial_skip)*time_scale
for t in range(0, 50):
t/=50
if t>=initial_skip and time_to_coordinate(t)<=time_axis_length:
printz(r"\a{%time_to_coordinate(t)%}{%t%}")
mark_last_y=time_to_coordinate(-1)
mark_y_separation=0.25
package_stack=[]
for is_start, package_name, moment in items:
if not is_start and not package_stack:
continue
if is_start is mark_event:
layer=len(package_stack)
red_line_left=first_coordinate+layer_separation*(layer-1)+box_width
red_line_right=red_line_left+box_width/2
y_coordinate=time_to_coordinate(moment)
node_y_coordinate=mark_last_y=max(mark_last_y+mark_y_separation, y_coordinate)
printz(
r"""\draw[red] (%red_line_left%, %y_coordinate%)
-- (%red_line_right%, %y_coordinate%);
\path (%red_line_right%, %node_y_coordinate%) node [black, right] {\small %package_name%};
"""
)
elif is_start:
package_stack.append((package_name, moment))
else:
package_name1, start_moment=package_stack.pop()
assert package_name1==package_name
layer=len(package_stack)
#package_name_t=evalz(r"\pdftooltip{t}{%package_name%}")
package_name_t=package_name
color=colors[layer%len(colors)]
printz(
r"""\fill[%color%] (
%first_coordinate+layer_separation*layer%,
%time_to_coordinate(start_moment)%
) rectangle
(
%first_coordinate+box_width+layer_separation*layer%,
%time_to_coordinate(moment)%
) node [black, midway] {\small %package_name_t%};
"""
)
printy(
r"""
\end{tikzpicture}
\end{document}
"""
)
Run the script, then compile the generated TeX file.
Output may look like this

(turns out my packages doesn't load that slow, relatively speaking. And why is the gap between packages so large?)
titlepsloads faster thanfancyhdr– Mario S. E. Oct 31 '13 at 23:05