This page describes how to use buildbloat to profile a C++ CMake build and generate a treemap visualization of compile times with webtreemap using geos as a nontrivial C++ example project.
This can be useful to understand what parts of your build take the most time at a larger granularity
than you get with tools like
https://github.com/royjacobson/externis or
https://github.com/jrmadsen/compile-time-perf. This also doesn't tell you what about your build is slow; is it because your Translation
Units are massive? Or is it template instantiation? or is it a CMake
add_custom_command
that runs every time you build?
If you're using ccache, you probably want to disable it for the sake of accurate profiling.
rm -rf ./build/
export CCACHE_DISABLE=1
cmake -B ./build/ -G Ninja
cmake --build ./build/ --parallel
Depending on your project size and build system capabilities, you may want to limit the number of
jobs more than just "use all the cores". You can do this with
--parallel $(($(nproc) - 4)) or similar.
Ninja generates build/.ninja_log which we will use for generating the treemap. But first,
we need to convert it to a format that webtreemap understands.
I can't find "official" documentation for this file format, but I did find https://pkg.go.dev/go.fuchsia.dev/fuchsia/tools/build/ninjago/ninjalog which documents it as
# ninja log v5
<start>\t<end>\t<restat>\t<target>\t<hash>
but it appears the format version was bumped to v6 with Ninja 1.12, and to v7 with Ninja 1.13. From
looking at
https://github.com/ninja-build/ninja/commits/master/src/build_log.cc
it appears the only differences between v5, v6, and v7 are the implementation details of how the
<hash> field is calculated and formatted, which we don't care about for our
purposes.
webtreemap wants its input in the format
<size>\t<path>
but the Ninja build log is
<start>\t<end>\t<restat>\t<target>\t<hash>
so we use buildbloat to convert between the two format.
git clone https://github.com/Notgnoshi/buildbloat.git
cd geos
../buildbloat/buildbloat.py --build-dir ./build >./build/bloat.log
I found that the build for the project I was working on mixed relative and absolute paths in the
.ninja_log file, hence the addition of the --build-dir ./build argument,
which causes buildbloat.py to convert each path to be relative to the given build
directory.
First we have to install webtreemap
git clone git@github.com:Notgnoshi/webtreemap.git
cd webtreemap
# Install dependencies in a local env
npm install
# Build webtreemap
npm run build
# Make webtreemap script globally available
npm link
Preface: I'm not a web developer.
I wanted easier installation instructions than this, but I struggled to make it work any other way.
If you run npm install webtreemap, that installs a prebuilt version from
<npmjs.com>. But if you run npm install evmar/webtreemap, that installs from
GitHub (not <npmjs.com>) and does not install the CLI tool (because it hasn't been built).
Searching around lead me to believe that adding
"scripts": {
"prepare": "npm install && npm run build"
}
to the package.json would run the build during
npm install Notgnoshi/webtreemap but that did not work, seemingly because the
npm install was screwing with my global node_modules installation.
If you know how to make this work, please let me know!
Now we can generate the treemap HTML file:
webtreemap --title GEOS -i ./build/bloat.log -o ./build/bloat.html
xdg-open ./build/bloat.html
It's a bit easier to run all in one line than to stash the intermediate log file as this lets you run and re-run it over and over while you're iterating:
~/src/buildbloat/buildbloat.py --build-dir ./build/ ./build/.ninja_log | webtreemap --title GEOS -o ./build/bloat.html
If you're trying to tell a story with the results, you likely want to colorize some of your treemap.
It'd be cool if webtreemap could do this automatically using heuristics (i.e., high
saturation for large sizes, decreasing as you get smaller; children sharing the same hue as their
parent, except if a sibling is large enough proportional to another child, it should get its own hue?),
but for now you can do it manually by defining your own substrings with
-c <substring>:
~/src/buildbloat/buildbloat.py --build-dir ./build/ ./build/.ninja_log |
webtreemap --title GEOS -o ./build/bloat.html -c operation -c geom -c algorithm -c noding -c test
I hacked that capability into my fork of webtreemap, so the poor quality substring matching
is all my fault, not the original author's.
We can see that the majority of build time is spent building GEOS's tests. After that, it's the
operation module.
shopt -s globstar
wc -l --total=never ./**/*.{cpp,h,in,hpp,c} |
awk '{$1=$1;print}' |
webtreemap --title "GEOS SLOC" -o ./build/sloc.html -c geom -c operation -c algorithm -c deps -c vend -c tests