| //===-- TimeProfiler.cpp - Hierarchical Time Profiler ---------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file implements hierarchical time profiler. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/Support/TimeProfiler.h" | |
| #include "llvm/ADT/StringMap.h" | |
| #include "llvm/Support/CommandLine.h" | |
| #include "llvm/Support/JSON.h" | |
| #include "llvm/Support/Path.h" | |
| #include <cassert> | |
| #include <chrono> | |
| #include <string> | |
| #include <vector> | |
| using namespace std::chrono; | |
| namespace llvm { | |
| TimeTraceProfiler *TimeTraceProfilerInstance = nullptr; | |
| typedef duration<steady_clock::rep, steady_clock::period> DurationType; | |
| typedef time_point<steady_clock> TimePointType; | |
| typedef std::pair<size_t, DurationType> CountAndDurationType; | |
| typedef std::pair<std::string, CountAndDurationType> | |
| NameAndCountAndDurationType; | |
| struct Entry { | |
| const TimePointType Start; | |
| TimePointType End; | |
| const std::string Name; | |
| const std::string Detail; | |
| Entry(TimePointType &&S, TimePointType &&E, std::string &&N, std::string &&Dt) | |
| : Start(std::move(S)), End(std::move(E)), Name(std::move(N)), | |
| Detail(std::move(Dt)) {} | |
| // Calculate timings for FlameGraph. Cast time points to microsecond precision | |
| // rather than casting duration. This avoid truncation issues causing inner | |
| // scopes overruning outer scopes. | |
| steady_clock::rep getFlameGraphStartUs(TimePointType StartTime) const { | |
| return (time_point_cast<microseconds>(Start) - | |
| time_point_cast<microseconds>(StartTime)) | |
| .count(); | |
| } | |
| steady_clock::rep getFlameGraphDurUs() const { | |
| return (time_point_cast<microseconds>(End) - | |
| time_point_cast<microseconds>(Start)) | |
| .count(); | |
| } | |
| }; | |
| struct TimeTraceProfiler { | |
| TimeTraceProfiler(unsigned TimeTraceGranularity = 0, StringRef ProcName = "") | |
| : StartTime(steady_clock::now()), ProcName(ProcName), | |
| TimeTraceGranularity(TimeTraceGranularity) {} | |
| void begin(std::string Name, llvm::function_ref<std::string()> Detail) { | |
| Stack.emplace_back(steady_clock::now(), TimePointType(), std::move(Name), | |
| Detail()); | |
| } | |
| void end() { | |
| assert(!Stack.empty() && "Must call begin() first"); | |
| auto &E = Stack.back(); | |
| E.End = steady_clock::now(); | |
| // 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"); | |
| // Calculate duration at full precision for overall counts. | |
| DurationType Duration = E.End - E.Start; | |
| // Only include sections longer or equal to TimeTraceGranularity msec. | |
| if (duration_cast<microseconds>(Duration).count() >= TimeTraceGranularity) | |
| Entries.emplace_back(E); | |
| // Track total time taken by each "name", but only the topmost levels of | |
| // them; e.g. if there's a template instantiation that instantiates other | |
| // templates from within, we only want to add the topmost one. "topmost" | |
| // happens to be the ones that don't have any currently open entries above | |
| // itself. | |
| if (std::find_if(++Stack.rbegin(), Stack.rend(), [&](const Entry &Val) { | |
| return Val.Name == E.Name; | |
| }) == Stack.rend()) { | |
| auto &CountAndTotal = CountAndTotalPerName[E.Name]; | |
| CountAndTotal.first++; | |
| CountAndTotal.second += Duration; | |
| } | |
| Stack.pop_back(); | |
| } | |
| void Write(raw_pwrite_stream &OS) { | |
| assert(Stack.empty() && | |
| "All profiler sections should be ended when calling Write"); | |
| json::OStream J(OS); | |
| J.objectBegin(); | |
| J.attributeBegin("traceEvents"); | |
| J.arrayBegin(); | |
| // Emit all events for the main flame graph. | |
| for (const auto &E : Entries) { | |
| auto StartUs = E.getFlameGraphStartUs(StartTime); | |
| auto DurUs = E.getFlameGraphDurUs(); | |
| J.object([&]{ | |
| J.attribute("pid", 1); | |
| J.attribute("tid", 0); | |
| J.attribute("ph", "X"); | |
| J.attribute("ts", StartUs); | |
| J.attribute("dur", DurUs); | |
| J.attribute("name", E.Name); | |
| if (!E.Detail.empty()) { | |
| J.attributeObject("args", [&] { J.attribute("detail", E.Detail); }); | |
| } | |
| }); | |
| } | |
| // Emit totals by section name as additional "thread" events, sorted from | |
| // longest one. | |
| int Tid = 1; | |
| std::vector<NameAndCountAndDurationType> SortedTotals; | |
| SortedTotals.reserve(CountAndTotalPerName.size()); | |
| for (const auto &E : CountAndTotalPerName) | |
| SortedTotals.emplace_back(E.getKey(), E.getValue()); | |
| llvm::sort(SortedTotals.begin(), SortedTotals.end(), | |
| [](const NameAndCountAndDurationType &A, | |
| const NameAndCountAndDurationType &B) { | |
| return A.second.second > B.second.second; | |
| }); | |
| for (const auto &E : SortedTotals) { | |
| auto DurUs = duration_cast<microseconds>(E.second.second).count(); | |
| auto Count = CountAndTotalPerName[E.first].first; | |
| J.object([&]{ | |
| J.attribute("pid", 1); | |
| J.attribute("tid", Tid); | |
| J.attribute("ph", "X"); | |
| J.attribute("ts", 0); | |
| J.attribute("dur", DurUs); | |
| J.attribute("name", "Total " + E.first); | |
| J.attributeObject("args", [&] { | |
| J.attribute("count", int64_t(Count)); | |
| J.attribute("avg ms", int64_t(DurUs / Count / 1000)); | |
| }); | |
| }); | |
| ++Tid; | |
| } | |
| // Emit metadata event with process name. | |
| J.object([&] { | |
| J.attribute("cat", ""); | |
| J.attribute("pid", 1); | |
| J.attribute("tid", 0); | |
| J.attribute("ts", 0); | |
| J.attribute("ph", "M"); | |
| J.attribute("name", "process_name"); | |
| J.attributeObject("args", [&] { J.attribute("name", ProcName); }); | |
| }); | |
| J.arrayEnd(); | |
| J.attributeEnd(); | |
| J.objectEnd(); | |
| } | |
| SmallVector<Entry, 16> Stack; | |
| SmallVector<Entry, 128> Entries; | |
| StringMap<CountAndDurationType> CountAndTotalPerName; | |
| const TimePointType StartTime; | |
| const std::string ProcName; | |
| // Minimum time granularity (in microseconds) | |
| const unsigned TimeTraceGranularity; | |
| }; | |
| void timeTraceProfilerInitialize(unsigned TimeTraceGranularity, | |
| StringRef ProcName) { | |
| assert(TimeTraceProfilerInstance == nullptr && | |
| "Profiler should not be initialized"); | |
| TimeTraceProfilerInstance = new TimeTraceProfiler( | |
| TimeTraceGranularity, llvm::sys::path::filename(ProcName)); | |
| } | |
| void timeTraceProfilerCleanup() { | |
| delete TimeTraceProfilerInstance; | |
| TimeTraceProfilerInstance = nullptr; | |
| } | |
| void timeTraceProfilerWrite(raw_pwrite_stream &OS) { | |
| assert(TimeTraceProfilerInstance != nullptr && | |
| "Profiler object can't be null"); | |
| TimeTraceProfilerInstance->Write(OS); | |
| } | |
| void timeTraceProfilerBegin(StringRef Name, StringRef Detail) { | |
| if (TimeTraceProfilerInstance != nullptr) | |
| TimeTraceProfilerInstance->begin(Name, [&]() { return Detail; }); | |
| } | |
| void timeTraceProfilerBegin(StringRef Name, | |
| llvm::function_ref<std::string()> Detail) { | |
| if (TimeTraceProfilerInstance != nullptr) | |
| TimeTraceProfilerInstance->begin(Name, Detail); | |
| } | |
| void timeTraceProfilerEnd() { | |
| if (TimeTraceProfilerInstance != nullptr) | |
| TimeTraceProfilerInstance->end(); | |
| } | |
| } // namespace llvm |