xref: /llvm-project/llvm/lib/Support/Timer.cpp (revision 24bd9bc0b59d51c82e9a4d84c21d86d58d0ef6ce)
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 
15 #include "DebugOptions.h"
16 
17 #include "llvm/ADT/Statistic.h"
18 #include "llvm/ADT/StringMap.h"
19 #include "llvm/Config/config.h"
20 #include "llvm/Support/CommandLine.h"
21 #include "llvm/Support/FileSystem.h"
22 #include "llvm/Support/Format.h"
23 #include "llvm/Support/ManagedStatic.h"
24 #include "llvm/Support/Mutex.h"
25 #include "llvm/Support/Process.h"
26 #include "llvm/Support/Signposts.h"
27 #include "llvm/Support/YAMLTraits.h"
28 #include "llvm/Support/raw_ostream.h"
29 #include <limits>
30 #include <optional>
31 
32 #if HAVE_UNISTD_H
33 #include <unistd.h>
34 #endif
35 
36 #ifdef HAVE_PROC_PID_RUSAGE
37 #include <libproc.h>
38 #endif
39 
40 using namespace llvm;
41 
42 //===----------------------------------------------------------------------===//
43 // Forward declarations for Managed Timer Globals getters.
44 //
45 // Globals have been placed at the end of the file to restrict direct
46 // access. Use of getters also has the benefit of making it a bit more explicit
47 // that a global is being used.
48 //===----------------------------------------------------------------------===//
49 namespace {
50 class Name2PairMap;
51 }
52 
53 static std::string &libSupportInfoOutputFilename();
54 static bool trackSpace();
55 static bool sortTimers();
56 [[maybe_unused]]
57 static SignpostEmitter &signposts();
58 static sys::SmartMutex<true> &timerLock();
59 static TimerGroup &defaultTimerGroup();
60 static Name2PairMap &namedGroupedTimers();
61 
62 //===----------------------------------------------------------------------===//
63 //
64 //===----------------------------------------------------------------------===//
65 
66 std::unique_ptr<raw_ostream> llvm::CreateInfoOutputFile() {
67   const std::string &OutputFilename = libSupportInfoOutputFilename();
68   if (OutputFilename.empty())
69     return std::make_unique<raw_fd_ostream>(2, false); // stderr.
70   if (OutputFilename == "-")
71     return std::make_unique<raw_fd_ostream>(1, false); // stdout.
72 
73   // Append mode is used because the info output file is opened and closed
74   // each time -stats or -time-passes wants to print output to it. To
75   // compensate for this, the test-suite Makefiles have code to delete the
76   // info output file before running commands which write to it.
77   std::error_code EC;
78   auto Result = std::make_unique<raw_fd_ostream>(
79       OutputFilename, EC, sys::fs::OF_Append | sys::fs::OF_TextWithCRLF);
80   if (!EC)
81     return Result;
82 
83   errs() << "Error opening info-output-file '"
84     << OutputFilename << " for appending!\n";
85   return std::make_unique<raw_fd_ostream>(2, false); // stderr.
86 }
87 
88 //===----------------------------------------------------------------------===//
89 // Timer Implementation
90 //===----------------------------------------------------------------------===//
91 
92 void Timer::init(StringRef TimerName, StringRef TimerDescription) {
93   init(TimerName, TimerDescription, defaultTimerGroup());
94 }
95 
96 void Timer::init(StringRef TimerName, StringRef TimerDescription,
97                  TimerGroup &tg) {
98   assert(!TG && "Timer already initialized");
99   Name.assign(TimerName.begin(), TimerName.end());
100   Description.assign(TimerDescription.begin(), TimerDescription.end());
101   Running = Triggered = false;
102   TG = &tg;
103   TG->addTimer(*this);
104 }
105 
106 Timer::~Timer() {
107   if (!TG) return;  // Never initialized, or already cleared.
108   TG->removeTimer(*this);
109 }
110 
111 static inline size_t getMemUsage() {
112   if (!trackSpace())
113     return 0;
114   return sys::Process::GetMallocUsage();
115 }
116 
117 static uint64_t getCurInstructionsExecuted() {
118 #if defined(HAVE_UNISTD_H) && defined(HAVE_PROC_PID_RUSAGE) &&                 \
119     defined(RUSAGE_INFO_V4)
120   struct rusage_info_v4 ru;
121   if (proc_pid_rusage(getpid(), RUSAGE_INFO_V4, (rusage_info_t *)&ru) == 0) {
122     return ru.ri_instructions;
123   }
124 #endif
125   return 0;
126 }
127 
128 TimeRecord TimeRecord::getCurrentTime(bool Start) {
129   using Seconds = std::chrono::duration<double, std::ratio<1>>;
130   TimeRecord Result;
131   sys::TimePoint<> now;
132   std::chrono::nanoseconds user, sys;
133 
134   if (Start) {
135     Result.MemUsed = getMemUsage();
136     Result.InstructionsExecuted = getCurInstructionsExecuted();
137     sys::Process::GetTimeUsage(now, user, sys);
138   } else {
139     sys::Process::GetTimeUsage(now, user, sys);
140     Result.InstructionsExecuted = getCurInstructionsExecuted();
141     Result.MemUsed = getMemUsage();
142   }
143 
144   Result.WallTime = Seconds(now.time_since_epoch()).count();
145   Result.UserTime = Seconds(user).count();
146   Result.SystemTime = Seconds(sys).count();
147   return Result;
148 }
149 
150 void Timer::startTimer() {
151   assert(!Running && "Cannot start a running timer");
152   Running = Triggered = true;
153 #if LLVM_SUPPORT_XCODE_SIGNPOSTS
154   signposts().startInterval(this, getName());
155 #endif
156   StartTime = TimeRecord::getCurrentTime(true);
157 }
158 
159 void Timer::stopTimer() {
160   assert(Running && "Cannot stop a paused timer");
161   Running = false;
162   Time += TimeRecord::getCurrentTime(false);
163   Time -= StartTime;
164 #if LLVM_SUPPORT_XCODE_SIGNPOSTS
165   signposts().endInterval(this, getName());
166 #endif
167 }
168 
169 void Timer::clear() {
170   Running = Triggered = false;
171   Time = StartTime = TimeRecord();
172 }
173 
174 void Timer::yieldTo(Timer &O) {
175   stopTimer();
176   O.startTimer();
177 }
178 
179 static void printVal(double Val, double Total, raw_ostream &OS) {
180   if (Total < 1e-7)   // Avoid dividing by zero.
181     OS << "        -----     ";
182   else
183     OS << format("  %7.4f (%5.1f%%)", Val, Val*100/Total);
184 }
185 
186 void TimeRecord::print(const TimeRecord &Total, raw_ostream &OS) const {
187   if (Total.getUserTime())
188     printVal(getUserTime(), Total.getUserTime(), OS);
189   if (Total.getSystemTime())
190     printVal(getSystemTime(), Total.getSystemTime(), OS);
191   if (Total.getProcessTime())
192     printVal(getProcessTime(), Total.getProcessTime(), OS);
193   printVal(getWallTime(), Total.getWallTime(), OS);
194 
195   OS << "  ";
196 
197   if (Total.getMemUsed())
198     OS << format("%9" PRId64 "  ", (int64_t)getMemUsed());
199   if (Total.getInstructionsExecuted())
200     OS << format("%9" PRId64 "  ", (int64_t)getInstructionsExecuted());
201 }
202 
203 
204 //===----------------------------------------------------------------------===//
205 //   NamedRegionTimer Implementation
206 //===----------------------------------------------------------------------===//
207 
208 namespace {
209 
210 typedef StringMap<Timer> Name2TimerMap;
211 
212 class Name2PairMap {
213   StringMap<std::pair<TimerGroup*, Name2TimerMap> > Map;
214 public:
215   ~Name2PairMap() {
216     for (StringMap<std::pair<TimerGroup*, Name2TimerMap> >::iterator
217          I = Map.begin(), E = Map.end(); I != E; ++I)
218       delete I->second.first;
219   }
220 
221   Timer &get(StringRef Name, StringRef Description, StringRef GroupName,
222              StringRef GroupDescription) {
223     sys::SmartScopedLock<true> L(timerLock());
224 
225     std::pair<TimerGroup*, Name2TimerMap> &GroupEntry = Map[GroupName];
226 
227     if (!GroupEntry.first)
228       GroupEntry.first = new TimerGroup(GroupName, GroupDescription);
229 
230     Timer &T = GroupEntry.second[Name];
231     if (!T.isInitialized())
232       T.init(Name, Description, *GroupEntry.first);
233     return T;
234   }
235 };
236 
237 }
238 
239 NamedRegionTimer::NamedRegionTimer(StringRef Name, StringRef Description,
240                                    StringRef GroupName,
241                                    StringRef GroupDescription, bool Enabled)
242     : TimeRegion(!Enabled
243                      ? nullptr
244                      : &namedGroupedTimers().get(Name, Description, GroupName,
245                                                  GroupDescription)) {}
246 
247 //===----------------------------------------------------------------------===//
248 //   TimerGroup Implementation
249 //===----------------------------------------------------------------------===//
250 
251 /// This is the global list of TimerGroups, maintained by the TimerGroup
252 /// ctor/dtor and is protected by the timerLock lock.
253 static TimerGroup *TimerGroupList = nullptr;
254 
255 TimerGroup::TimerGroup(StringRef Name, StringRef Description,
256                        sys::SmartMutex<true> &lock)
257     : Name(Name.begin(), Name.end()),
258       Description(Description.begin(), Description.end()) {
259   // Add the group to TimerGroupList.
260   sys::SmartScopedLock<true> L(lock);
261   if (TimerGroupList)
262     TimerGroupList->Prev = &Next;
263   Next = TimerGroupList;
264   Prev = &TimerGroupList;
265   TimerGroupList = this;
266 }
267 
268 TimerGroup::TimerGroup(StringRef Name, StringRef Description)
269     : TimerGroup(Name, Description, timerLock()) {}
270 
271 TimerGroup::TimerGroup(StringRef Name, StringRef Description,
272                        const StringMap<TimeRecord> &Records)
273     : TimerGroup(Name, Description) {
274   TimersToPrint.reserve(Records.size());
275   for (const auto &P : Records)
276     TimersToPrint.emplace_back(P.getValue(), std::string(P.getKey()),
277                                std::string(P.getKey()));
278   assert(TimersToPrint.size() == Records.size() && "Size mismatch");
279 }
280 
281 TimerGroup::~TimerGroup() {
282   // If the timer group is destroyed before the timers it owns, accumulate and
283   // print the timing data.
284   while (FirstTimer)
285     removeTimer(*FirstTimer);
286 
287   // Remove the group from the TimerGroupList.
288   sys::SmartScopedLock<true> L(timerLock());
289   *Prev = Next;
290   if (Next)
291     Next->Prev = Prev;
292 }
293 
294 
295 void TimerGroup::removeTimer(Timer &T) {
296   sys::SmartScopedLock<true> L(timerLock());
297 
298   // If the timer was started, move its data to TimersToPrint.
299   if (T.hasTriggered())
300     TimersToPrint.emplace_back(T.Time, T.Name, T.Description);
301 
302   T.TG = nullptr;
303 
304   // Unlink the timer from our list.
305   *T.Prev = T.Next;
306   if (T.Next)
307     T.Next->Prev = T.Prev;
308 
309   // Print the report when all timers in this group are destroyed if some of
310   // them were started.
311   if (FirstTimer || TimersToPrint.empty())
312     return;
313 
314   std::unique_ptr<raw_ostream> OutStream = CreateInfoOutputFile();
315   PrintQueuedTimers(*OutStream);
316 }
317 
318 void TimerGroup::addTimer(Timer &T) {
319   sys::SmartScopedLock<true> L(timerLock());
320 
321   // Add the timer to our list.
322   if (FirstTimer)
323     FirstTimer->Prev = &T.Next;
324   T.Next = FirstTimer;
325   T.Prev = &FirstTimer;
326   FirstTimer = &T;
327 }
328 
329 void TimerGroup::PrintQueuedTimers(raw_ostream &OS) {
330   // Perhaps sort the timers in descending order by amount of time taken.
331   if (sortTimers())
332     llvm::sort(TimersToPrint);
333 
334   TimeRecord Total;
335   for (const PrintRecord &Record : TimersToPrint)
336     Total += Record.Time;
337 
338   // Print out timing header.
339   OS << "===" << std::string(73, '-') << "===\n";
340   // Figure out how many spaces to indent TimerGroup name.
341   unsigned Padding = (80-Description.length())/2;
342   if (Padding > 80) Padding = 0;         // Don't allow "negative" numbers
343   OS.indent(Padding) << Description << '\n';
344   OS << "===" << std::string(73, '-') << "===\n";
345 
346   // If this is not an collection of ungrouped times, print the total time.
347   // Ungrouped timers don't really make sense to add up.  We still print the
348   // TOTAL line to make the percentages make sense.
349   if (this != &defaultTimerGroup())
350     OS << format("  Total Execution Time: %5.4f seconds (%5.4f wall clock)\n",
351                  Total.getProcessTime(), Total.getWallTime());
352   OS << '\n';
353 
354   if (Total.getUserTime())
355     OS << "   ---User Time---";
356   if (Total.getSystemTime())
357     OS << "   --System Time--";
358   if (Total.getProcessTime())
359     OS << "   --User+System--";
360   OS << "   ---Wall Time---";
361   if (Total.getMemUsed())
362     OS << "  ---Mem---";
363   if (Total.getInstructionsExecuted())
364     OS << "  ---Instr---";
365   OS << "  --- Name ---\n";
366 
367   // Loop through all of the timing data, printing it out.
368   for (const PrintRecord &Record : llvm::reverse(TimersToPrint)) {
369     Record.Time.print(Total, OS);
370     OS << Record.Description << '\n';
371   }
372 
373   Total.print(Total, OS);
374   OS << "Total\n\n";
375   OS.flush();
376 
377   TimersToPrint.clear();
378 }
379 
380 void TimerGroup::prepareToPrintList(bool ResetTime) {
381   // See if any of our timers were started, if so add them to TimersToPrint.
382   for (Timer *T = FirstTimer; T; T = T->Next) {
383     if (!T->hasTriggered()) continue;
384     bool WasRunning = T->isRunning();
385     if (WasRunning)
386       T->stopTimer();
387 
388     TimersToPrint.emplace_back(T->Time, T->Name, T->Description);
389 
390     if (ResetTime)
391       T->clear();
392 
393     if (WasRunning)
394       T->startTimer();
395   }
396 }
397 
398 void TimerGroup::print(raw_ostream &OS, bool ResetAfterPrint) {
399   {
400     // After preparing the timers we can free the lock
401     sys::SmartScopedLock<true> L(timerLock());
402     prepareToPrintList(ResetAfterPrint);
403   }
404 
405   // If any timers were started, print the group.
406   if (!TimersToPrint.empty())
407     PrintQueuedTimers(OS);
408 }
409 
410 void TimerGroup::clear() {
411   sys::SmartScopedLock<true> L(timerLock());
412   for (Timer *T = FirstTimer; T; T = T->Next)
413     T->clear();
414 }
415 
416 void TimerGroup::printAll(raw_ostream &OS) {
417   sys::SmartScopedLock<true> L(timerLock());
418 
419   for (TimerGroup *TG = TimerGroupList; TG; TG = TG->Next)
420     TG->print(OS);
421 }
422 
423 void TimerGroup::clearAll() {
424   sys::SmartScopedLock<true> L(timerLock());
425   for (TimerGroup *TG = TimerGroupList; TG; TG = TG->Next)
426     TG->clear();
427 }
428 
429 void TimerGroup::printJSONValue(raw_ostream &OS, const PrintRecord &R,
430                                 const char *suffix, double Value) {
431   assert(yaml::needsQuotes(Name) == yaml::QuotingType::None &&
432          "TimerGroup name should not need quotes");
433   assert(yaml::needsQuotes(R.Name) == yaml::QuotingType::None &&
434          "Timer name should not need quotes");
435   constexpr auto max_digits10 = std::numeric_limits<double>::max_digits10;
436   OS << "\t\"time." << Name << '.' << R.Name << suffix
437      << "\": " << format("%.*e", max_digits10 - 1, Value);
438 }
439 
440 const char *TimerGroup::printJSONValues(raw_ostream &OS, const char *delim) {
441   sys::SmartScopedLock<true> L(timerLock());
442 
443   prepareToPrintList(false);
444   for (const PrintRecord &R : TimersToPrint) {
445     OS << delim;
446     delim = ",\n";
447 
448     const TimeRecord &T = R.Time;
449     printJSONValue(OS, R, ".wall", T.getWallTime());
450     OS << delim;
451     printJSONValue(OS, R, ".user", T.getUserTime());
452     OS << delim;
453     printJSONValue(OS, R, ".sys", T.getSystemTime());
454     if (T.getMemUsed()) {
455       OS << delim;
456       printJSONValue(OS, R, ".mem", T.getMemUsed());
457     }
458     if (T.getInstructionsExecuted()) {
459       OS << delim;
460       printJSONValue(OS, R, ".instr", T.getInstructionsExecuted());
461     }
462   }
463   TimersToPrint.clear();
464   return delim;
465 }
466 
467 const char *TimerGroup::printAllJSONValues(raw_ostream &OS, const char *delim) {
468   sys::SmartScopedLock<true> L(timerLock());
469   for (TimerGroup *TG = TimerGroupList; TG; TG = TG->Next)
470     delim = TG->printJSONValues(OS, delim);
471   return delim;
472 }
473 
474 //===----------------------------------------------------------------------===//
475 // Timer Globals
476 //
477 // Previously, these were independent ManagedStatics. This led to bugs because
478 // there are dependencies between the globals, but no reliable mechanism to
479 // control relative lifetimes.
480 //
481 // Placing the globals within one class instance lets us control the lifetimes
482 // of the various data members and ensure that no global uses another that has
483 // been deleted.
484 //
485 // Globals fall into two categories. First are simple data types and
486 // command-line options. These are cheap to construct and/or required early
487 // during launch. They are created when the ManagedTimerGlobals singleton is
488 // constructed. Second are types that are more expensive to construct or not
489 // needed until later during compilation. These are lazily constructed in order
490 // to reduce launch time.
491 //===----------------------------------------------------------------------===//
492 class llvm::TimerGlobals {
493 public:
494   std::string LibSupportInfoOutputFilename;
495   cl::opt<std::string, true> InfoOutputFilename{
496       "info-output-file", cl::value_desc("filename"),
497       cl::desc("File to append -stats and -timer output to"), cl::Hidden,
498       cl::location(LibSupportInfoOutputFilename)};
499   cl::opt<bool> TrackSpace{
500       "track-memory",
501       cl::desc("Enable -time-passes memory tracking (this may be slow)"),
502       cl::Hidden};
503   cl::opt<bool> SortTimers{
504       "sort-timers",
505       cl::desc("In the report, sort the timers in each group in wall clock"
506                " time order"),
507       cl::init(true), cl::Hidden};
508 
509   sys::SmartMutex<true> TimerLock;
510   TimerGroup DefaultTimerGroup{"misc", "Miscellaneous Ungrouped Timers",
511                                TimerLock};
512   SignpostEmitter Signposts;
513 
514   // Order of these members and initialization below is important. For example
515   // the defaultTimerGroup uses the timerLock. Most of these also depend on the
516   // options above.
517   std::once_flag InitDeferredFlag;
518   std::optional<Name2PairMap> NamedGroupedTimersPtr;
519 
520   TimerGlobals &initDeferred() {
521     std::call_once(InitDeferredFlag,
522                    [this]() { NamedGroupedTimersPtr.emplace(); });
523     return *this;
524   }
525 };
526 
527 static ManagedStatic<TimerGlobals> ManagedTimerGlobals;
528 
529 static std::string &libSupportInfoOutputFilename() {
530   return ManagedTimerGlobals->LibSupportInfoOutputFilename;
531 }
532 static bool trackSpace() { return ManagedTimerGlobals->TrackSpace; }
533 static bool sortTimers() { return ManagedTimerGlobals->SortTimers; }
534 static SignpostEmitter &signposts() { return ManagedTimerGlobals->Signposts; }
535 static sys::SmartMutex<true> &timerLock() {
536   return ManagedTimerGlobals->TimerLock;
537 }
538 static TimerGroup &defaultTimerGroup() {
539   return ManagedTimerGlobals->DefaultTimerGroup;
540 }
541 static Name2PairMap &namedGroupedTimers() {
542   return *ManagedTimerGlobals->initDeferred().NamedGroupedTimersPtr;
543 }
544 
545 void llvm::initTimerOptions() { *ManagedTimerGlobals; }
546 void TimerGroup::constructForStatistics() {
547   ManagedTimerGlobals->initDeferred();
548 }
549 
550 void *TimerGroup::acquireTimerGlobals() { return ManagedTimerGlobals.claim(); }
551