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