1 //===-- Timer.cpp - Interval Timing Support -------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 /// \file Interval Timing implementation.
10 //
11 //===----------------------------------------------------------------------===//
12
13 #include "llvm/Support/Timer.h"
14 #include "llvm/ADT/Statistic.h"
15 #include "llvm/ADT/StringMap.h"
16 #include "llvm/Config/config.h"
17 #include "llvm/Support/CommandLine.h"
18 #include "llvm/Support/FileSystem.h"
19 #include "llvm/Support/Format.h"
20 #include "llvm/Support/ManagedStatic.h"
21 #include "llvm/Support/Mutex.h"
22 #include "llvm/Support/Process.h"
23 #include "llvm/Support/Signposts.h"
24 #include "llvm/Support/YAMLTraits.h"
25 #include "llvm/Support/raw_ostream.h"
26 #include <limits>
27
28 #if HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31
32 #ifdef HAVE_PROC_PID_RUSAGE
33 #include <libproc.h>
34 #endif
35
36 using namespace llvm;
37
38 // This ugly hack is brought to you courtesy of constructor/destructor ordering
39 // being unspecified by C++. Basically the problem is that a Statistic object
40 // gets destroyed, which ends up calling 'GetLibSupportInfoOutputFile()'
41 // (below), which calls this function. LibSupportInfoOutputFilename used to be
42 // a global variable, but sometimes it would get destroyed before the Statistic,
43 // causing havoc to ensue. We "fix" this by creating the string the first time
44 // it is needed and never destroying it.
45 static ManagedStatic<std::string> LibSupportInfoOutputFilename;
getLibSupportInfoOutputFilename()46 static std::string &getLibSupportInfoOutputFilename() {
47 return *LibSupportInfoOutputFilename;
48 }
49
50 static ManagedStatic<sys::SmartMutex<true> > TimerLock;
51
52 /// Allows llvm::Timer to emit signposts when supported.
53 static ManagedStatic<SignpostEmitter> Signposts;
54
55 namespace {
56 static cl::opt<bool>
57 TrackSpace("track-memory", cl::desc("Enable -time-passes memory "
58 "tracking (this may be slow)"),
59 cl::Hidden);
60
61 static cl::opt<std::string, true>
62 InfoOutputFilename("info-output-file", cl::value_desc("filename"),
63 cl::desc("File to append -stats and -timer output to"),
64 cl::Hidden, cl::location(getLibSupportInfoOutputFilename()));
65
66 static cl::opt<bool>
67 SortTimers("sort-timers", cl::desc("In the report, sort the timers in each group "
68 "in wall clock time order"),
69 cl::init(true), cl::Hidden);
70 }
71
CreateInfoOutputFile()72 std::unique_ptr<raw_fd_ostream> llvm::CreateInfoOutputFile() {
73 const std::string &OutputFilename = getLibSupportInfoOutputFilename();
74 if (OutputFilename.empty())
75 return std::make_unique<raw_fd_ostream>(2, false); // stderr.
76 if (OutputFilename == "-")
77 return std::make_unique<raw_fd_ostream>(1, false); // stdout.
78
79 // Append mode is used because the info output file is opened and closed
80 // each time -stats or -time-passes wants to print output to it. To
81 // compensate for this, the test-suite Makefiles have code to delete the
82 // info output file before running commands which write to it.
83 std::error_code EC;
84 auto Result = std::make_unique<raw_fd_ostream>(
85 OutputFilename, EC, sys::fs::OF_Append | sys::fs::OF_TextWithCRLF);
86 if (!EC)
87 return Result;
88
89 errs() << "Error opening info-output-file '"
90 << OutputFilename << " for appending!\n";
91 return std::make_unique<raw_fd_ostream>(2, false); // stderr.
92 }
93
94 namespace {
95 struct CreateDefaultTimerGroup {
call__anon3b76f5e80211::CreateDefaultTimerGroup96 static void *call() {
97 return new TimerGroup("misc", "Miscellaneous Ungrouped Timers");
98 }
99 };
100 } // namespace
101 static ManagedStatic<TimerGroup, CreateDefaultTimerGroup> DefaultTimerGroup;
getDefaultTimerGroup()102 static TimerGroup *getDefaultTimerGroup() { return &*DefaultTimerGroup; }
103
104 //===----------------------------------------------------------------------===//
105 // Timer Implementation
106 //===----------------------------------------------------------------------===//
107
init(StringRef TimerName,StringRef TimerDescription)108 void Timer::init(StringRef TimerName, StringRef TimerDescription) {
109 init(TimerName, TimerDescription, *getDefaultTimerGroup());
110 }
111
init(StringRef TimerName,StringRef TimerDescription,TimerGroup & tg)112 void Timer::init(StringRef TimerName, StringRef TimerDescription,
113 TimerGroup &tg) {
114 assert(!TG && "Timer already initialized");
115 Name.assign(TimerName.begin(), TimerName.end());
116 Description.assign(TimerDescription.begin(), TimerDescription.end());
117 Running = Triggered = false;
118 TG = &tg;
119 TG->addTimer(*this);
120 }
121
~Timer()122 Timer::~Timer() {
123 if (!TG) return; // Never initialized, or already cleared.
124 TG->removeTimer(*this);
125 }
126
getMemUsage()127 static inline size_t getMemUsage() {
128 if (!TrackSpace) return 0;
129 return sys::Process::GetMallocUsage();
130 }
131
getCurInstructionsExecuted()132 static uint64_t getCurInstructionsExecuted() {
133 #if defined(HAVE_UNISTD_H) && defined(HAVE_PROC_PID_RUSAGE) && \
134 defined(RUSAGE_INFO_V4)
135 struct rusage_info_v4 ru;
136 if (proc_pid_rusage(getpid(), RUSAGE_INFO_V4, (rusage_info_t *)&ru) == 0) {
137 return ru.ri_instructions;
138 }
139 #endif
140 return 0;
141 }
142
getCurrentTime(bool Start)143 TimeRecord TimeRecord::getCurrentTime(bool Start) {
144 using Seconds = std::chrono::duration<double, std::ratio<1>>;
145 TimeRecord Result;
146 sys::TimePoint<> now;
147 std::chrono::nanoseconds user, sys;
148
149 if (Start) {
150 Result.MemUsed = getMemUsage();
151 Result.InstructionsExecuted = getCurInstructionsExecuted();
152 sys::Process::GetTimeUsage(now, user, sys);
153 } else {
154 sys::Process::GetTimeUsage(now, user, sys);
155 Result.InstructionsExecuted = getCurInstructionsExecuted();
156 Result.MemUsed = getMemUsage();
157 }
158
159 Result.WallTime = Seconds(now.time_since_epoch()).count();
160 Result.UserTime = Seconds(user).count();
161 Result.SystemTime = Seconds(sys).count();
162 return Result;
163 }
164
startTimer()165 void Timer::startTimer() {
166 assert(!Running && "Cannot start a running timer");
167 Running = Triggered = true;
168 Signposts->startInterval(this, getName());
169 StartTime = TimeRecord::getCurrentTime(true);
170 }
171
stopTimer()172 void Timer::stopTimer() {
173 assert(Running && "Cannot stop a paused timer");
174 Running = false;
175 Time += TimeRecord::getCurrentTime(false);
176 Time -= StartTime;
177 Signposts->endInterval(this, getName());
178 }
179
clear()180 void Timer::clear() {
181 Running = Triggered = false;
182 Time = StartTime = TimeRecord();
183 }
184
printVal(double Val,double Total,raw_ostream & OS)185 static void printVal(double Val, double Total, raw_ostream &OS) {
186 if (Total < 1e-7) // Avoid dividing by zero.
187 OS << " ----- ";
188 else
189 OS << format(" %7.4f (%5.1f%%)", Val, Val*100/Total);
190 }
191
print(const TimeRecord & Total,raw_ostream & OS) const192 void TimeRecord::print(const TimeRecord &Total, raw_ostream &OS) const {
193 if (Total.getUserTime())
194 printVal(getUserTime(), Total.getUserTime(), OS);
195 if (Total.getSystemTime())
196 printVal(getSystemTime(), Total.getSystemTime(), OS);
197 if (Total.getProcessTime())
198 printVal(getProcessTime(), Total.getProcessTime(), OS);
199 printVal(getWallTime(), Total.getWallTime(), OS);
200
201 OS << " ";
202
203 if (Total.getMemUsed())
204 OS << format("%9" PRId64 " ", (int64_t)getMemUsed());
205 if (Total.getInstructionsExecuted())
206 OS << format("%9" PRId64 " ", (int64_t)getInstructionsExecuted());
207 }
208
209
210 //===----------------------------------------------------------------------===//
211 // NamedRegionTimer Implementation
212 //===----------------------------------------------------------------------===//
213
214 namespace {
215
216 typedef StringMap<Timer> Name2TimerMap;
217
218 class Name2PairMap {
219 StringMap<std::pair<TimerGroup*, Name2TimerMap> > Map;
220 public:
~Name2PairMap()221 ~Name2PairMap() {
222 for (StringMap<std::pair<TimerGroup*, Name2TimerMap> >::iterator
223 I = Map.begin(), E = Map.end(); I != E; ++I)
224 delete I->second.first;
225 }
226
get(StringRef Name,StringRef Description,StringRef GroupName,StringRef GroupDescription)227 Timer &get(StringRef Name, StringRef Description, StringRef GroupName,
228 StringRef GroupDescription) {
229 sys::SmartScopedLock<true> L(*TimerLock);
230
231 std::pair<TimerGroup*, Name2TimerMap> &GroupEntry = Map[GroupName];
232
233 if (!GroupEntry.first)
234 GroupEntry.first = new TimerGroup(GroupName, GroupDescription);
235
236 Timer &T = GroupEntry.second[Name];
237 if (!T.isInitialized())
238 T.init(Name, Description, *GroupEntry.first);
239 return T;
240 }
241 };
242
243 }
244
245 static ManagedStatic<Name2PairMap> NamedGroupedTimers;
246
NamedRegionTimer(StringRef Name,StringRef Description,StringRef GroupName,StringRef GroupDescription,bool Enabled)247 NamedRegionTimer::NamedRegionTimer(StringRef Name, StringRef Description,
248 StringRef GroupName,
249 StringRef GroupDescription, bool Enabled)
250 : TimeRegion(!Enabled ? nullptr
251 : &NamedGroupedTimers->get(Name, Description, GroupName,
252 GroupDescription)) {}
253
254 //===----------------------------------------------------------------------===//
255 // TimerGroup Implementation
256 //===----------------------------------------------------------------------===//
257
258 /// This is the global list of TimerGroups, maintained by the TimerGroup
259 /// ctor/dtor and is protected by the TimerLock lock.
260 static TimerGroup *TimerGroupList = nullptr;
261
TimerGroup(StringRef Name,StringRef Description)262 TimerGroup::TimerGroup(StringRef Name, StringRef Description)
263 : Name(Name.begin(), Name.end()),
264 Description(Description.begin(), Description.end()) {
265 // Add the group to TimerGroupList.
266 sys::SmartScopedLock<true> L(*TimerLock);
267 if (TimerGroupList)
268 TimerGroupList->Prev = &Next;
269 Next = TimerGroupList;
270 Prev = &TimerGroupList;
271 TimerGroupList = this;
272 }
273
TimerGroup(StringRef Name,StringRef Description,const StringMap<TimeRecord> & Records)274 TimerGroup::TimerGroup(StringRef Name, StringRef Description,
275 const StringMap<TimeRecord> &Records)
276 : TimerGroup(Name, Description) {
277 TimersToPrint.reserve(Records.size());
278 for (const auto &P : Records)
279 TimersToPrint.emplace_back(P.getValue(), std::string(P.getKey()),
280 std::string(P.getKey()));
281 assert(TimersToPrint.size() == Records.size() && "Size mismatch");
282 }
283
~TimerGroup()284 TimerGroup::~TimerGroup() {
285 // If the timer group is destroyed before the timers it owns, accumulate and
286 // print the timing data.
287 while (FirstTimer)
288 removeTimer(*FirstTimer);
289
290 // Remove the group from the TimerGroupList.
291 sys::SmartScopedLock<true> L(*TimerLock);
292 *Prev = Next;
293 if (Next)
294 Next->Prev = Prev;
295 }
296
297
removeTimer(Timer & T)298 void TimerGroup::removeTimer(Timer &T) {
299 sys::SmartScopedLock<true> L(*TimerLock);
300
301 // If the timer was started, move its data to TimersToPrint.
302 if (T.hasTriggered())
303 TimersToPrint.emplace_back(T.Time, T.Name, T.Description);
304
305 T.TG = nullptr;
306
307 // Unlink the timer from our list.
308 *T.Prev = T.Next;
309 if (T.Next)
310 T.Next->Prev = T.Prev;
311
312 // Print the report when all timers in this group are destroyed if some of
313 // them were started.
314 if (FirstTimer || TimersToPrint.empty())
315 return;
316
317 std::unique_ptr<raw_ostream> OutStream = CreateInfoOutputFile();
318 PrintQueuedTimers(*OutStream);
319 }
320
addTimer(Timer & T)321 void TimerGroup::addTimer(Timer &T) {
322 sys::SmartScopedLock<true> L(*TimerLock);
323
324 // Add the timer to our list.
325 if (FirstTimer)
326 FirstTimer->Prev = &T.Next;
327 T.Next = FirstTimer;
328 T.Prev = &FirstTimer;
329 FirstTimer = &T;
330 }
331
PrintQueuedTimers(raw_ostream & OS)332 void TimerGroup::PrintQueuedTimers(raw_ostream &OS) {
333 // Perhaps sort the timers in descending order by amount of time taken.
334 if (SortTimers)
335 llvm::sort(TimersToPrint);
336
337 TimeRecord Total;
338 for (const PrintRecord &Record : TimersToPrint)
339 Total += Record.Time;
340
341 // Print out timing header.
342 OS << "===" << std::string(73, '-') << "===\n";
343 // Figure out how many spaces to indent TimerGroup name.
344 unsigned Padding = (80-Description.length())/2;
345 if (Padding > 80) Padding = 0; // Don't allow "negative" numbers
346 OS.indent(Padding) << Description << '\n';
347 OS << "===" << std::string(73, '-') << "===\n";
348
349 // If this is not an collection of ungrouped times, print the total time.
350 // Ungrouped timers don't really make sense to add up. We still print the
351 // TOTAL line to make the percentages make sense.
352 if (this != getDefaultTimerGroup())
353 OS << format(" Total Execution Time: %5.4f seconds (%5.4f wall clock)\n",
354 Total.getProcessTime(), Total.getWallTime());
355 OS << '\n';
356
357 if (Total.getUserTime())
358 OS << " ---User Time---";
359 if (Total.getSystemTime())
360 OS << " --System Time--";
361 if (Total.getProcessTime())
362 OS << " --User+System--";
363 OS << " ---Wall Time---";
364 if (Total.getMemUsed())
365 OS << " ---Mem---";
366 if (Total.getInstructionsExecuted())
367 OS << " ---Instr---";
368 OS << " --- Name ---\n";
369
370 // Loop through all of the timing data, printing it out.
371 for (const PrintRecord &Record : make_range(TimersToPrint.rbegin(),
372 TimersToPrint.rend())) {
373 Record.Time.print(Total, OS);
374 OS << Record.Description << '\n';
375 }
376
377 Total.print(Total, OS);
378 OS << "Total\n\n";
379 OS.flush();
380
381 TimersToPrint.clear();
382 }
383
prepareToPrintList(bool ResetTime)384 void TimerGroup::prepareToPrintList(bool ResetTime) {
385 // See if any of our timers were started, if so add them to TimersToPrint.
386 for (Timer *T = FirstTimer; T; T = T->Next) {
387 if (!T->hasTriggered()) continue;
388 bool WasRunning = T->isRunning();
389 if (WasRunning)
390 T->stopTimer();
391
392 TimersToPrint.emplace_back(T->Time, T->Name, T->Description);
393
394 if (ResetTime)
395 T->clear();
396
397 if (WasRunning)
398 T->startTimer();
399 }
400 }
401
print(raw_ostream & OS,bool ResetAfterPrint)402 void TimerGroup::print(raw_ostream &OS, bool ResetAfterPrint) {
403 {
404 // After preparing the timers we can free the lock
405 sys::SmartScopedLock<true> L(*TimerLock);
406 prepareToPrintList(ResetAfterPrint);
407 }
408
409 // If any timers were started, print the group.
410 if (!TimersToPrint.empty())
411 PrintQueuedTimers(OS);
412 }
413
clear()414 void TimerGroup::clear() {
415 sys::SmartScopedLock<true> L(*TimerLock);
416 for (Timer *T = FirstTimer; T; T = T->Next)
417 T->clear();
418 }
419
printAll(raw_ostream & OS)420 void TimerGroup::printAll(raw_ostream &OS) {
421 sys::SmartScopedLock<true> L(*TimerLock);
422
423 for (TimerGroup *TG = TimerGroupList; TG; TG = TG->Next)
424 TG->print(OS);
425 }
426
clearAll()427 void TimerGroup::clearAll() {
428 sys::SmartScopedLock<true> L(*TimerLock);
429 for (TimerGroup *TG = TimerGroupList; TG; TG = TG->Next)
430 TG->clear();
431 }
432
printJSONValue(raw_ostream & OS,const PrintRecord & R,const char * suffix,double Value)433 void TimerGroup::printJSONValue(raw_ostream &OS, const PrintRecord &R,
434 const char *suffix, double Value) {
435 assert(yaml::needsQuotes(Name) == yaml::QuotingType::None &&
436 "TimerGroup name should not need quotes");
437 assert(yaml::needsQuotes(R.Name) == yaml::QuotingType::None &&
438 "Timer name should not need quotes");
439 constexpr auto max_digits10 = std::numeric_limits<double>::max_digits10;
440 OS << "\t\"time." << Name << '.' << R.Name << suffix
441 << "\": " << format("%.*e", max_digits10 - 1, Value);
442 }
443
printJSONValues(raw_ostream & OS,const char * delim)444 const char *TimerGroup::printJSONValues(raw_ostream &OS, const char *delim) {
445 sys::SmartScopedLock<true> L(*TimerLock);
446
447 prepareToPrintList(false);
448 for (const PrintRecord &R : TimersToPrint) {
449 OS << delim;
450 delim = ",\n";
451
452 const TimeRecord &T = R.Time;
453 printJSONValue(OS, R, ".wall", T.getWallTime());
454 OS << delim;
455 printJSONValue(OS, R, ".user", T.getUserTime());
456 OS << delim;
457 printJSONValue(OS, R, ".sys", T.getSystemTime());
458 if (T.getMemUsed()) {
459 OS << delim;
460 printJSONValue(OS, R, ".mem", T.getMemUsed());
461 }
462 if (T.getInstructionsExecuted()) {
463 OS << delim;
464 printJSONValue(OS, R, ".instr", T.getInstructionsExecuted());
465 }
466 }
467 TimersToPrint.clear();
468 return delim;
469 }
470
printAllJSONValues(raw_ostream & OS,const char * delim)471 const char *TimerGroup::printAllJSONValues(raw_ostream &OS, const char *delim) {
472 sys::SmartScopedLock<true> L(*TimerLock);
473 for (TimerGroup *TG = TimerGroupList; TG; TG = TG->Next)
474 delim = TG->printJSONValues(OS, delim);
475 return delim;
476 }
477
ConstructTimerLists()478 void TimerGroup::ConstructTimerLists() {
479 (void)*NamedGroupedTimers;
480 }
481
aquireDefaultGroup()482 std::unique_ptr<TimerGroup> TimerGroup::aquireDefaultGroup() {
483 return std::unique_ptr<TimerGroup>(DefaultTimerGroup.claim());
484 }
485