xref: /netbsd-src/external/apache2/llvm/dist/llvm/lib/Support/Timer.cpp (revision 82d56013d7b633d116a93943de88e08335357a7c)
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