Home | History | Annotate | Line # | Download | only in Support
      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;
     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 
     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 {
     96   static void *call() {
     97     return new TimerGroup("misc", "Miscellaneous Ungrouped Timers");
     98   }
     99 };
    100 } // namespace
    101 static ManagedStatic<TimerGroup, CreateDefaultTimerGroup> DefaultTimerGroup;
    102 static TimerGroup *getDefaultTimerGroup() { return &*DefaultTimerGroup; }
    103 
    104 //===----------------------------------------------------------------------===//
    105 // Timer Implementation
    106 //===----------------------------------------------------------------------===//
    107 
    108 void Timer::init(StringRef TimerName, StringRef TimerDescription) {
    109   init(TimerName, TimerDescription, *getDefaultTimerGroup());
    110 }
    111 
    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 
    122 Timer::~Timer() {
    123   if (!TG) return;  // Never initialized, or already cleared.
    124   TG->removeTimer(*this);
    125 }
    126 
    127 static inline size_t getMemUsage() {
    128   if (!TrackSpace) return 0;
    129   return sys::Process::GetMallocUsage();
    130 }
    131 
    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 
    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 
    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 
    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 
    180 void Timer::clear() {
    181   Running = Triggered = false;
    182   Time = StartTime = TimeRecord();
    183 }
    184 
    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 
    192 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:
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    414 void TimerGroup::clear() {
    415   sys::SmartScopedLock<true> L(*TimerLock);
    416   for (Timer *T = FirstTimer; T; T = T->Next)
    417     T->clear();
    418 }
    419 
    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 
    427 void TimerGroup::clearAll() {
    428   sys::SmartScopedLock<true> L(*TimerLock);
    429   for (TimerGroup *TG = TimerGroupList; TG; TG = TG->Next)
    430     TG->clear();
    431 }
    432 
    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 
    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 
    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 
    478 void TimerGroup::ConstructTimerLists() {
    479   (void)*NamedGroupedTimers;
    480 }
    481 
    482 std::unique_ptr<TimerGroup> TimerGroup::aquireDefaultGroup() {
    483   return std::unique_ptr<TimerGroup>(DefaultTimerGroup.claim());
    484 }
    485