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