Skip to content

Expose TimeTraceProfiler for Async Events #83778

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 19, 2024
Merged
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
34 changes: 25 additions & 9 deletions llvm/include/llvm/Support/TimeProfiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ class raw_pwrite_stream;
struct TimeTraceProfiler;
TimeTraceProfiler *getTimeTraceProfilerInstance();

struct TimeTraceProfilerEntry;

/// Initialize the time trace profiler.
/// This sets up the global \p TimeTraceProfilerInstance
/// variable to be the profiler instance.
Expand Down Expand Up @@ -120,19 +122,30 @@ Error timeTraceProfilerWrite(StringRef PreferredFileName,
/// Profiler copies the string data, so the pointers can be given into
/// temporaries. Time sections can be hierarchical; every Begin must have a
/// matching End pair but they can nest.
void timeTraceProfilerBegin(StringRef Name, StringRef Detail);
void timeTraceProfilerBegin(StringRef Name,
llvm::function_ref<std::string()> Detail);
TimeTraceProfilerEntry *timeTraceProfilerBegin(StringRef Name,
StringRef Detail);
TimeTraceProfilerEntry *
timeTraceProfilerBegin(StringRef Name,
llvm::function_ref<std::string()> Detail);

/// Manually begin a time section, with the given \p Name and \p Detail.
/// This starts Async Events having \p Name as a category which is shown
/// separately from other traces. See
/// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.jh64i9l3vwa1
/// for more details.
TimeTraceProfilerEntry *timeTraceAsyncProfilerBegin(StringRef Name,
StringRef Detail);

/// Manually end the last time section.
void timeTraceProfilerEnd();
void timeTraceProfilerEnd(TimeTraceProfilerEntry *E);

/// The TimeTraceScope is a helper class to call the begin and end functions
/// of the time trace profiler. When the object is constructed, it begins
/// the section; and when it is destroyed, it stops it. If the time profiler
/// is not initialized, the overhead is a single branch.
struct TimeTraceScope {

class TimeTraceScope {
public:
TimeTraceScope() = delete;
TimeTraceScope(const TimeTraceScope &) = delete;
TimeTraceScope &operator=(const TimeTraceScope &) = delete;
Expand All @@ -141,20 +154,23 @@ struct TimeTraceScope {

TimeTraceScope(StringRef Name) {
if (getTimeTraceProfilerInstance() != nullptr)
timeTraceProfilerBegin(Name, StringRef(""));
Entry = timeTraceProfilerBegin(Name, StringRef(""));
}
TimeTraceScope(StringRef Name, StringRef Detail) {
if (getTimeTraceProfilerInstance() != nullptr)
timeTraceProfilerBegin(Name, Detail);
Entry = timeTraceProfilerBegin(Name, Detail);
}
TimeTraceScope(StringRef Name, llvm::function_ref<std::string()> Detail) {
if (getTimeTraceProfilerInstance() != nullptr)
timeTraceProfilerBegin(Name, Detail);
Entry = timeTraceProfilerBegin(Name, Detail);
}
~TimeTraceScope() {
if (getTimeTraceProfilerInstance() != nullptr)
timeTraceProfilerEnd();
timeTraceProfilerEnd(Entry);
}

private:
TimeTraceProfilerEntry *Entry = nullptr;
};

} // end namespace llvm
Expand Down
101 changes: 71 additions & 30 deletions llvm/lib/Support/TimeProfiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//

#include "llvm/Support/TimeProfiler.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/Support/JSON.h"
Expand All @@ -20,6 +21,7 @@
#include <algorithm>
#include <cassert>
#include <chrono>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
Expand Down Expand Up @@ -64,17 +66,19 @@ using CountAndDurationType = std::pair<size_t, DurationType>;
using NameAndCountAndDurationType =
std::pair<std::string, CountAndDurationType>;

} // anonymous namespace

/// Represents an open or completed time section entry to be captured.
struct TimeTraceProfilerEntry {
struct llvm::TimeTraceProfilerEntry {
const TimePointType Start;
TimePointType End;
const std::string Name;
const std::string Detail;

const bool AsyncEvent = false;
TimeTraceProfilerEntry(TimePointType &&S, TimePointType &&E, std::string &&N,
std::string &&Dt)
std::string &&Dt, bool Ae)
: Start(std::move(S)), End(std::move(E)), Name(std::move(N)),
Detail(std::move(Dt)) {}
Detail(std::move(Dt)), AsyncEvent(Ae) {}

// Calculate timings for FlameGraph. Cast time points to microsecond precision
// rather than casting duration. This avoids truncation issues causing inner
Expand All @@ -92,8 +96,6 @@ struct TimeTraceProfilerEntry {
}
};

} // anonymous namespace

struct llvm::TimeTraceProfiler {
TimeTraceProfiler(unsigned TimeTraceGranularity = 0, StringRef ProcName = "")
: BeginningOfTime(system_clock::now()), StartTime(ClockType::now()),
Expand All @@ -102,22 +104,23 @@ struct llvm::TimeTraceProfiler {
llvm::get_thread_name(ThreadName);
}

void begin(std::string Name, llvm::function_ref<std::string()> Detail) {
Stack.emplace_back(ClockType::now(), TimePointType(), std::move(Name),
Detail());
TimeTraceProfilerEntry *begin(std::string Name,
llvm::function_ref<std::string()> Detail,
bool AsyncEvent = false) {
Stack.emplace_back(std::make_unique<TimeTraceProfilerEntry>(
ClockType::now(), TimePointType(), std::move(Name), Detail(),
AsyncEvent));
return Stack.back().get();
}

void end() {
assert(!Stack.empty() && "Must call begin() first");
TimeTraceProfilerEntry &E = Stack.back();
E.End = ClockType::now();
end(*Stack.back().get());
}

// Check that end times monotonically increase.
assert((Entries.empty() ||
(E.getFlameGraphStartUs(StartTime) + E.getFlameGraphDurUs() >=
Entries.back().getFlameGraphStartUs(StartTime) +
Entries.back().getFlameGraphDurUs())) &&
"TimeProfiler scope ended earlier than previous scope");
void end(TimeTraceProfilerEntry &E) {
assert(!Stack.empty() && "Must call begin() first");
E.End = ClockType::now();

// Calculate duration at full precision for overall counts.
DurationType Duration = E.End - E.Start;
Expand All @@ -132,15 +135,18 @@ struct llvm::TimeTraceProfiler {
// happens to be the ones that don't have any currently open entries above
// itself.
if (llvm::none_of(llvm::drop_begin(llvm::reverse(Stack)),
[&](const TimeTraceProfilerEntry &Val) {
return Val.Name == E.Name;
[&](const std::unique_ptr<TimeTraceProfilerEntry> &Val) {
return Val->Name == E.Name;
})) {
auto &CountAndTotal = CountAndTotalPerName[E.Name];
CountAndTotal.first++;
CountAndTotal.second += Duration;
}
};

Stack.pop_back();
llvm::erase_if(Stack,
[&](const std::unique_ptr<TimeTraceProfilerEntry> &Val) {
return Val.get() == &E;
});
}

// Write events from this TimeTraceProfilerInstance and
Expand Down Expand Up @@ -168,14 +174,32 @@ struct llvm::TimeTraceProfiler {
J.object([&] {
J.attribute("pid", Pid);
J.attribute("tid", int64_t(Tid));
J.attribute("ph", "X");
J.attribute("ts", StartUs);
J.attribute("dur", DurUs);
if (E.AsyncEvent) {
J.attribute("cat", E.Name);
J.attribute("ph", "b");
J.attribute("id", 0);
} else {
J.attribute("ph", "X");
J.attribute("dur", DurUs);
}
J.attribute("name", E.Name);
if (!E.Detail.empty()) {
J.attributeObject("args", [&] { J.attribute("detail", E.Detail); });
}
});

if (E.AsyncEvent) {
J.object([&] {
J.attribute("pid", Pid);
J.attribute("tid", int64_t(Tid));
J.attribute("ts", StartUs + DurUs);
J.attribute("cat", E.Name);
J.attribute("ph", "e");
J.attribute("id", 0);
J.attribute("name", E.Name);
});
}
};
for (const TimeTraceProfilerEntry &E : Entries)
writeEvent(E, this->Tid);
Expand Down Expand Up @@ -269,7 +293,7 @@ struct llvm::TimeTraceProfiler {
J.objectEnd();
}

SmallVector<TimeTraceProfilerEntry, 16> Stack;
SmallVector<std::unique_ptr<TimeTraceProfilerEntry>, 16> Stack;
SmallVector<TimeTraceProfilerEntry, 128> Entries;
StringMap<CountAndDurationType> CountAndTotalPerName;
// System clock time when the session was begun.
Expand Down Expand Up @@ -341,19 +365,36 @@ Error llvm::timeTraceProfilerWrite(StringRef PreferredFileName,
return Error::success();
}

void llvm::timeTraceProfilerBegin(StringRef Name, StringRef Detail) {
TimeTraceProfilerEntry *llvm::timeTraceProfilerBegin(StringRef Name,
StringRef Detail) {
if (TimeTraceProfilerInstance != nullptr)
TimeTraceProfilerInstance->begin(std::string(Name),
[&]() { return std::string(Detail); });
return TimeTraceProfilerInstance->begin(
std::string(Name), [&]() { return std::string(Detail); }, false);
return nullptr;
}

void llvm::timeTraceProfilerBegin(StringRef Name,
llvm::function_ref<std::string()> Detail) {
TimeTraceProfilerEntry *
llvm::timeTraceProfilerBegin(StringRef Name,
llvm::function_ref<std::string()> Detail) {
if (TimeTraceProfilerInstance != nullptr)
TimeTraceProfilerInstance->begin(std::string(Name), Detail);
return TimeTraceProfilerInstance->begin(std::string(Name), Detail, false);
return nullptr;
}

TimeTraceProfilerEntry *llvm::timeTraceAsyncProfilerBegin(StringRef Name,
StringRef Detail) {
if (TimeTraceProfilerInstance != nullptr)
return TimeTraceProfilerInstance->begin(
std::string(Name), [&]() { return std::string(Detail); }, true);
return nullptr;
}

void llvm::timeTraceProfilerEnd() {
if (TimeTraceProfilerInstance != nullptr)
TimeTraceProfilerInstance->end();
}

void llvm::timeTraceProfilerEnd(TimeTraceProfilerEntry *E) {
if (TimeTraceProfilerInstance != nullptr)
TimeTraceProfilerInstance->end(*E);
}
11 changes: 11 additions & 0 deletions llvm/unittests/Support/TimeProfilerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ TEST(TimeProfiler, Begin_End_Smoke) {
ASSERT_TRUE(json.find(R"("detail":"detail")") != std::string::npos);
}

TEST(TimeProfiler, Async_Begin_End_Smoke) {
setupProfiler();

auto *Profiler = timeTraceAsyncProfilerBegin("event", "detail");
timeTraceProfilerEnd(Profiler);

std::string json = teardownProfiler();
ASSERT_TRUE(json.find(R"("name":"event")") != std::string::npos);
ASSERT_TRUE(json.find(R"("detail":"detail")") != std::string::npos);
}

TEST(TimeProfiler, Begin_End_Disabled) {
// Nothing should be observable here. The test is really just making sure
// we've not got a stray nullptr deref.
Expand Down