From 2cb15b4d97f59baba07511806ec8f5768ea89bb7 Mon Sep 17 00:00:00 2001 From: vittorioromeo Date: Fri, 18 Apr 2025 19:37:09 +0200 Subject: [PATCH 1/6] Display origin of template instantiations --- CMakeLists.txt | 37 +++++++++++++++++++++++++++++-------- src/Analysis.cpp | 17 ++++++++++++++++- src/BuildEvents.cpp | 42 ++++++++++++++++++++++++++++++++++-------- src/BuildEvents.h | 1 + 4 files changed, 80 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 182ede9..0447b50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,18 +3,12 @@ project(ClangBuildAnalyzer) include(GNUInstallDirs) -set(SRC +set(EXTERNAL_SRC # C sources "src/external/cwalk/cwalk.c" "src/external/inih/ini.c" "src/external/xxHash/xxhash.c" # C++ sources - "src/Analysis.cpp" - "src/Arena.cpp" - "src/BuildEvents.cpp" - "src/Colors.cpp" - "src/main.cpp" - "src/Utils.cpp" "src/external/enkiTS/TaskScheduler.cpp" "src/external/inih/cpp/INIReader.cpp" "src/external/llvm-Demangle/lib/Demangle.cpp" @@ -23,7 +17,34 @@ set(SRC "src/external/llvm-Demangle/lib/MicrosoftDemangleNodes.cpp" "src/external/simdjson/simdjson.cpp" ) -add_executable(ClangBuildAnalyzer "${SRC}") + +set(SRC +# C++ sources + "src/Analysis.cpp" + "src/Arena.cpp" + "src/BuildEvents.cpp" + "src/Colors.cpp" + "src/main.cpp" + "src/Utils.cpp" +) + +set(DISABLE_WARNINGS_FLAG "") + +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang|Intel") # Check C++ compiler first + set(DISABLE_WARNINGS_FLAG "-w") +elseif(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang|AppleClang|Intel") # Fallback for C-only + set(DISABLE_WARNINGS_FLAG "-w") +elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + set(DISABLE_WARNINGS_FLAG "/w") # MSVC uses /w +elseif(CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(DISABLE_WARNINGS_FLAG "/w") +endif() + +set_source_files_properties(${EXTERNAL_SRC} + PROPERTIES COMPILE_FLAGS "${DISABLE_WARNINGS_FLAG}" +) + +add_executable(ClangBuildAnalyzer "${EXTERNAL_SRC}" "${SRC}") target_compile_features(ClangBuildAnalyzer PRIVATE cxx_std_17) find_library(LIBRT rt) diff --git a/src/Analysis.cpp b/src/Analysis.cpp index 052434c..4ef797f 100755 --- a/src/Analysis.cpp +++ b/src/Analysis.cpp @@ -1,6 +1,7 @@ // Clang Build Analyzer https://github.com/aras-p/ClangBuildAnalyzer // SPDX-License-Identifier: Unlicense +#include #ifdef _MSC_VER struct IUnknown; // workaround for old Win SDK header failures when using /permissive- #endif @@ -87,6 +88,8 @@ struct Analysis { int count = 0; int64_t us = 0; + DetailIndex file; + std::unordered_map templateCount; }; struct FileEntry { @@ -136,7 +139,6 @@ DetailIndex Analysis::FindPath(EventIndex eventIndex) const while(eventIndex > EventIndex()) { const BuildEvent& ev = events[eventIndex]; - if (ev.type == BuildEventType::kCompiler || ev.type == BuildEventType::kFrontend || ev.type == BuildEventType::kBackend || ev.type == BuildEventType::kOptModule) if (ev.detailIndex != DetailIndex()) return ev.detailIndex; eventIndex = ev.parent; @@ -160,6 +162,7 @@ void Analysis::ProcessEvent(EventIndex eventIndex) auto& e = instantiations[eventIndex]; ++e.count; e.us += event.dur; + e.file = event.filenameDetailIndex; } if (event.type == BuildEventType::kFrontend) @@ -305,6 +308,11 @@ void Analysis::EmitCollapsedInfo( int ms = int(elt.second.us / 1000); int avg = int(ms / elt.second.count); fprintf(out, "%s%6i%s ms: %s (%i times, avg %i ms)\n", col::kBold, ms, col::kReset, dname.c_str(), elt.second.count, avg); + + for (const auto& t : elt.second.templateCount) + { + fprintf(out, " %s%6i%s time(s): %s\n", col::kBold, t.second, col::kReset, GetBuildName(DetailIndex{t.first}).data()); + } } fprintf(out, "\n"); } @@ -336,6 +344,7 @@ void Analysis::EmitCollapsedTemplates() { stats.us += inst.second.us; stats.count += inst.second.count; + stats.templateCount[inst.second.file.idx] += 1; } } EmitCollapsedInfo(collapsed, "Template sets that took longest to instantiate"); @@ -423,6 +432,7 @@ void Analysis::EndAnalysis() instArray[d.idx].first = d; instArray[d.idx].second.us += inst.second.us; instArray[d.idx].second.count += inst.second.count; + instArray[d.idx].second.templateCount[inst.second.file.idx] += 1; } size_t n = std::min(config.templateCount, instArray.size()); auto cmp = [&](const auto&a, const auto &b) { @@ -441,6 +451,11 @@ void Analysis::EndAnalysis() int ms = int(e.second.us / 1000); int avg = int(ms / std::max(e.second.count,1)); fprintf(out, "%s%6i%s ms: %s (%i times, avg %i ms)\n", col::kBold, ms, col::kReset, dname.c_str(), e.second.count, avg); + + for (const auto& t : e.second.templateCount) + { + fprintf(out, " %s%6i%s time(s): %s\n", col::kBold, t.second, col::kReset, GetBuildName(DetailIndex{t.first}).data()); + } } fprintf(out, "\n"); diff --git a/src/BuildEvents.cpp b/src/BuildEvents.cpp index dcf714b..5ab1e4a 100755 --- a/src/BuildEvents.cpp +++ b/src/BuildEvents.cpp @@ -215,8 +215,10 @@ struct BuildEventsParser BuildEvent& ev = resultEvents[EventIndex(int(i))]; if (ev.parent.idx >= 0) ev.parent.idx += offset; + for (auto& ch : ev.children) ch.idx += offset; + if (ev.detailIndex.idx != 0) { assert(ev.detailIndex.idx >= 0); @@ -225,6 +227,15 @@ struct BuildEventsParser assert(ev.detailIndex.idx >= 0); assert(ev.detailIndex.idx < static_cast(resultNameToIndex.size())); } + + if (ev.filenameDetailIndex.idx != 0) + { + assert(ev.filenameDetailIndex.idx >= 0); + assert(ev.filenameDetailIndex.idx < static_cast(nameToIndex.size())); + ev.filenameDetailIndex = detailRemap[ev.filenameDetailIndex]; + assert(ev.filenameDetailIndex.idx >= 0); + assert(ev.filenameDetailIndex.idx < static_cast(resultNameToIndex.size())); + } } assert(resultNameToIndex.size() == resultNames.size()); @@ -321,6 +332,7 @@ struct BuildEventsParser BuildEvent event; bool valid = true; std::string_view detailPtr; + std::string_view filenameDetailPtr; for (const simdjson::dom::key_value_pair& kv : node) { std::string_view nodeKey = kv.key; @@ -400,20 +412,20 @@ struct BuildEventsParser if (event.type== BuildEventType::kUnknown || !valid) return; - // if the "compiler" event has no detail name, use the current json file name - if (detailPtr.empty() && event.type == BuildEventType::kCompiler) - detailPtr = curFileName; - if (!detailPtr.empty()) + const auto processDetail = [this, &event, &nameToIndexLocal](std::string_view& theDetailPtr) -> DetailIndex { + if (theDetailPtr.empty()) + return DetailIndex{}; + std::string detailString; if (event.type == BuildEventType::kParseFile || event.type == BuildEventType::kOptModule) { // do various cleanups/nice-ifications of the detail name: // make paths shorter (i.e. relative to project) where possible - detailString = utils::GetNicePath(detailPtr); + detailString = utils::GetNicePath(theDetailPtr); } else - detailString = detailPtr; + detailString = theDetailPtr; // don't report the clang trace .json file, instead get the object file at the same location if it's there if (utils::EndsWith(detailString, ".json")) @@ -435,8 +447,20 @@ struct BuildEventsParser if (event.type == BuildEventType::kOptFunction) detailString = llvm::demangle(detailString); - event.detailIndex = NameToIndex(detailString.c_str(), nameToIndexLocal); - } + return NameToIndex(detailString.c_str(), nameToIndexLocal); + }; + + // if the "compiler" event has no detail name, use the current json file name + if (detailPtr.empty() && event.type == BuildEventType::kCompiler) + detailPtr = curFileName; + + event.detailIndex = processDetail(detailPtr); + + // use the current json file name for the "instantiateX" event filename detail + if (event.type == BuildEventType::kInstantiateClass || event.type == BuildEventType::kInstantiateFunction) + filenameDetailPtr = curFileName; + + event.filenameDetailIndex = processDetail(filenameDetailPtr); // starting with clang 19, some Source events are pairs of "b" immediately followed // by "e" events. Handle merging of those if needed; do not support other cases of @@ -610,6 +634,7 @@ bool SaveBuildEvents(BuildEventsParser* parser, const std::string& fileName) w.Write(e.ts); w.Write(e.dur); w.Write(e.detailIndex.idx); + w.Write(e.filenameDetailIndex.idx); w.Write(e.parent.idx); int64_t childCount = e.children.size(); w.Write(childCount); @@ -670,6 +695,7 @@ bool LoadBuildEvents(const std::string& fileName, BuildEvents& outEvents, BuildN r.Read(e.ts); r.Read(e.dur); r.Read(e.detailIndex.idx); + r.Read(e.filenameDetailIndex.idx); r.Read(e.parent.idx); int64_t childCount = 0; r.Read(childCount); diff --git a/src/BuildEvents.h b/src/BuildEvents.h index f2c19b6..1ac3570 100755 --- a/src/BuildEvents.h +++ b/src/BuildEvents.h @@ -80,6 +80,7 @@ struct BuildEvent int64_t ts = 0; int64_t dur = 0; DetailIndex detailIndex; + DetailIndex filenameDetailIndex; EventIndex parent{ -1 }; char phase = 0; std::vector children; From 756b7ca4b624791b74411fe37ffb83d3d2debd3c Mon Sep 17 00:00:00 2001 From: Vittorio Romeo Date: Fri, 9 Jan 2026 18:10:25 +0100 Subject: [PATCH 2/6] Sort template instantiation sources by count --- src/Analysis.cpp | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/Analysis.cpp b/src/Analysis.cpp index 4ef797f..8c2f055 100755 --- a/src/Analysis.cpp +++ b/src/Analysis.cpp @@ -1,7 +1,6 @@ // Clang Build Analyzer https://github.com/aras-p/ClangBuildAnalyzer // SPDX-License-Identifier: Unlicense -#include #ifdef _MSC_VER struct IUnknown; // workaround for old Win SDK header failures when using /permissive- #endif @@ -118,6 +117,8 @@ struct Analysis const ska::bytell_hash_map &collapsed, const char *header_string); + void PrintSortedTemplateCounts(const InstantiateEntry& e); + // key is (name,objfile), value is milliseconds typedef std::pair IndexPair; ska::bytell_hash_map functions; @@ -285,6 +286,33 @@ std::string_view Analysis::GetCollapsedName(DetailIndex detail) return name; } +void Analysis::PrintSortedTemplateCounts(const InstantiateEntry& e) +{ + std::vector> templateCounts; + templateCounts.reserve(e.templateCount.size()); + + for (const auto& t : e.templateCount) + { + if (t.second < 3) // TODO: make customizable + continue; + + templateCounts.emplace_back(t.first, t.second); + } + + std::sort(templateCounts.begin(), templateCounts.end(), [](const auto& a, const auto& b) + { + if (a.second != b.second) + return a.second > b.second; + + return a.first < b.first; + }); + + for (const auto& t : e.templateCount) + { + fprintf(out, " %s%6i%s time(s): %s\n", col::kBold, t.second, col::kReset, GetBuildName(DetailIndex{t.first}).data()); + } +} + void Analysis::EmitCollapsedInfo( const ska::bytell_hash_map &collapsed, const char *header_string) @@ -309,10 +337,7 @@ void Analysis::EmitCollapsedInfo( int avg = int(ms / elt.second.count); fprintf(out, "%s%6i%s ms: %s (%i times, avg %i ms)\n", col::kBold, ms, col::kReset, dname.c_str(), elt.second.count, avg); - for (const auto& t : elt.second.templateCount) - { - fprintf(out, " %s%6i%s time(s): %s\n", col::kBold, t.second, col::kReset, GetBuildName(DetailIndex{t.first}).data()); - } + PrintSortedTemplateCounts(elt.second); } fprintf(out, "\n"); } @@ -452,10 +477,7 @@ void Analysis::EndAnalysis() int avg = int(ms / std::max(e.second.count,1)); fprintf(out, "%s%6i%s ms: %s (%i times, avg %i ms)\n", col::kBold, ms, col::kReset, dname.c_str(), e.second.count, avg); - for (const auto& t : e.second.templateCount) - { - fprintf(out, " %s%6i%s time(s): %s\n", col::kBold, t.second, col::kReset, GetBuildName(DetailIndex{t.first}).data()); - } + PrintSortedTemplateCounts(e.second); } fprintf(out, "\n"); From 8c0ba66fd8615fb8fb3cc959bf289253294e5938 Mon Sep 17 00:00:00 2001 From: Vittorio Romeo Date: Fri, 9 Jan 2026 18:12:18 +0100 Subject: [PATCH 3/6] Actually print what I sorted --- src/Analysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analysis.cpp b/src/Analysis.cpp index 8c2f055..0974ddd 100755 --- a/src/Analysis.cpp +++ b/src/Analysis.cpp @@ -307,7 +307,7 @@ void Analysis::PrintSortedTemplateCounts(const InstantiateEntry& e) return a.first < b.first; }); - for (const auto& t : e.templateCount) + for (const auto& t : templateCounts) { fprintf(out, " %s%6i%s time(s): %s\n", col::kBold, t.second, col::kReset, GetBuildName(DetailIndex{t.first}).data()); } From 50ca91364b0b01381f832512d59dc0e1a09de1fa Mon Sep 17 00:00:00 2001 From: Vittorio Romeo Date: Thu, 19 Mar 2026 17:13:33 +0100 Subject: [PATCH 4/6] Change ini defaults --- ClangBuildAnalyzer.ini | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ClangBuildAnalyzer.ini b/ClangBuildAnalyzer.ini index 72f578c..6a9a2e3 100644 --- a/ClangBuildAnalyzer.ini +++ b/ClangBuildAnalyzer.ini @@ -6,17 +6,17 @@ [counts] # files that took most time to parse -fileParse = 10 +fileParse = 20 # files that took most time to generate code for -fileCodegen = 10 +fileCodegen = 20 # functions that took most time to generate code for function = 30 # header files that were most expensive to include -header = 10 +header = 20 # for each expensive header, this many include paths to it are shown -headerChain = 5 +headerChain = 10 # templates that took longest to instantiate -template = 30 +template = 50 # Minimum times (in ms) for things to be recorded into trace @@ -29,7 +29,7 @@ file = 10 [misc] # Maximum length of symbol names printed; longer names will get truncated -maxNameLength = 70 +maxNameLength = 512 # Only print "root" headers in expensive header report, i.e. # only headers that are directly included by at least one source file From 5eafab1a5dc0e93773fe313f26b388d38679ccc9 Mon Sep 17 00:00:00 2001 From: Vittorio Romeo Date: Thu, 19 Mar 2026 17:15:20 +0100 Subject: [PATCH 5/6] Adjust defaults to match ini --- src/Analysis.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Analysis.cpp b/src/Analysis.cpp index 0974ddd..b6b1e19 100755 --- a/src/Analysis.cpp +++ b/src/Analysis.cpp @@ -21,16 +21,16 @@ struct IUnknown; // workaround for old Win SDK header failures when using /permi struct Config { - int fileParseCount = 10; - int fileCodegenCount = 10; - int templateCount = 30; + int fileParseCount = 20; + int fileCodegenCount = 20; + int templateCount = 50; int functionCount = 30; - int headerCount = 10; - int headerChainCount = 5; + int headerCount = 20; + int headerChainCount = 10; int minFileTime = 10; - int maxName = 70; + int maxName = 512; bool onlyRootHeaders = true; }; @@ -111,7 +111,7 @@ struct Analysis ska::bytell_hash_map collapsedNames; std::string_view GetCollapsedName(DetailIndex idx); - void EmitCollapsedTemplates(); + void EmitCollapsedTemplates();5 void EmitCollapsedTemplateOpt(); void EmitCollapsedInfo( const ska::bytell_hash_map &collapsed, From fe1dd443f78f38530ebec0117c698904763a5d2d Mon Sep 17 00:00:00 2001 From: Vittorio Romeo Date: Thu, 19 Mar 2026 17:17:39 +0100 Subject: [PATCH 6/6] Remove stray char --- src/Analysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analysis.cpp b/src/Analysis.cpp index b6b1e19..65b36ab 100755 --- a/src/Analysis.cpp +++ b/src/Analysis.cpp @@ -111,7 +111,7 @@ struct Analysis ska::bytell_hash_map collapsedNames; std::string_view GetCollapsedName(DetailIndex idx); - void EmitCollapsedTemplates();5 + void EmitCollapsedTemplates(); void EmitCollapsedTemplateOpt(); void EmitCollapsedInfo( const ska::bytell_hash_map &collapsed,