Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down
12 changes: 6 additions & 6 deletions ClangBuildAnalyzer.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
51 changes: 44 additions & 7 deletions src/Analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -87,6 +87,8 @@ struct Analysis
{
int count = 0;
int64_t us = 0;
DetailIndex file;
std::unordered_map<int, int> templateCount;
Comment on lines +90 to +91
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this approach, but I need guidance on where these things should go.

};
struct FileEntry
{
Expand Down Expand Up @@ -115,6 +117,8 @@ struct Analysis
const ska::bytell_hash_map<std::string_view, InstantiateEntry> &collapsed,
const char *header_string);

void PrintSortedTemplateCounts(const InstantiateEntry& e);

// key is (name,objfile), value is milliseconds
typedef std::pair<DetailIndex, DetailIndex> IndexPair;
ska::bytell_hash_map<IndexPair, int64_t, pair_hash> functions;
Expand All @@ -136,7 +140,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;
Expand All @@ -160,6 +163,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)
Expand Down Expand Up @@ -282,6 +286,33 @@ std::string_view Analysis::GetCollapsedName(DetailIndex detail)
return name;
}

void Analysis::PrintSortedTemplateCounts(const InstantiateEntry& e)
{
std::vector<std::pair<int, int>> 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 : templateCounts)
{
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<std::string_view, InstantiateEntry> &collapsed,
const char *header_string)
Expand All @@ -305,6 +336,8 @@ 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);

PrintSortedTemplateCounts(elt.second);
}
fprintf(out, "\n");
}
Expand Down Expand Up @@ -336,6 +369,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");
Expand Down Expand Up @@ -423,6 +457,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<size_t>(config.templateCount, instArray.size());
auto cmp = [&](const auto&a, const auto &b) {
Expand All @@ -441,6 +476,8 @@ 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);

PrintSortedTemplateCounts(e.second);
}
fprintf(out, "\n");

Expand Down
42 changes: 34 additions & 8 deletions src/BuildEvents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -225,6 +227,15 @@ struct BuildEventsParser
assert(ev.detailIndex.idx >= 0);
assert(ev.detailIndex.idx < static_cast<int>(resultNameToIndex.size()));
}

if (ev.filenameDetailIndex.idx != 0)
{
assert(ev.filenameDetailIndex.idx >= 0);
assert(ev.filenameDetailIndex.idx < static_cast<int>(nameToIndex.size()));
ev.filenameDetailIndex = detailRemap[ev.filenameDetailIndex];
assert(ev.filenameDetailIndex.idx >= 0);
assert(ev.filenameDetailIndex.idx < static_cast<int>(resultNameToIndex.size()));
}
Comment on lines +231 to +238
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way we can avoid adding the extra field?

}

assert(resultNameToIndex.size() == resultNames.size());
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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"))
Expand All @@ -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;
Comment on lines +459 to +461
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get more precise information other than the event filename, somehow?


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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/BuildEvents.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<EventIndex> children;
Expand Down
Loading