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