1 //===-- Progress.cpp ------------------------------------------------------===// 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 #include "lldb/Core/Progress.h" 10 11 #include "lldb/Core/Debugger.h" 12 #include "lldb/Utility/StreamString.h" 13 #include "llvm/Support/Signposts.h" 14 #include <atomic> 15 #include <chrono> 16 #include <cstdint> 17 #include <mutex> 18 #include <optional> 19 20 using namespace lldb; 21 using namespace lldb_private; 22 23 std::atomic<uint64_t> Progress::g_id(0); 24 25 // Instrument progress events with signposts when supported. 26 static llvm::ManagedStatic<llvm::SignpostEmitter> g_progress_signposts; 27 28 Progress::Progress(std::string title, std::string details, 29 std::optional<uint64_t> total, 30 lldb_private::Debugger *debugger, 31 Timeout<std::nano> minimum_report_time, 32 Progress::Origin origin) 33 : m_total(total.value_or(Progress::kNonDeterministicTotal)), 34 m_minimum_report_time(minimum_report_time), 35 m_progress_data{title, ++g_id, 36 debugger ? std::optional<user_id_t>(debugger->GetID()) 37 : std::nullopt, 38 origin}, 39 m_last_report_time_ns( 40 std::chrono::nanoseconds( 41 std::chrono::steady_clock::now().time_since_epoch()) 42 .count()), 43 m_details(std::move(details)) { 44 std::lock_guard<std::mutex> guard(m_mutex); 45 ReportProgress(); 46 47 // Report to the ProgressManager if that subsystem is enabled. 48 if (ProgressManager::Enabled()) 49 ProgressManager::Instance().Increment(m_progress_data); 50 51 // Start signpost interval right before the meaningful work starts. 52 g_progress_signposts->startInterval(this, m_progress_data.title); 53 } 54 55 Progress::~Progress() { 56 // End signpost interval as soon as possible. 57 g_progress_signposts->endInterval(this, m_progress_data.title); 58 59 // Make sure to always report progress completed when this object is 60 // destructed so it indicates the progress dialog/activity should go away. 61 std::lock_guard<std::mutex> guard(m_mutex); 62 m_completed = m_total; 63 ReportProgress(); 64 65 // Report to the ProgressManager if that subsystem is enabled. 66 if (ProgressManager::Enabled()) 67 ProgressManager::Instance().Decrement(m_progress_data); 68 } 69 70 void Progress::Increment(uint64_t amount, 71 std::optional<std::string> updated_detail) { 72 if (amount == 0) 73 return; 74 75 m_completed.fetch_add(amount, std::memory_order_relaxed); 76 77 if (m_minimum_report_time) { 78 using namespace std::chrono; 79 80 nanoseconds now; 81 uint64_t last_report_time_ns = 82 m_last_report_time_ns.load(std::memory_order_relaxed); 83 84 do { 85 now = steady_clock::now().time_since_epoch(); 86 if (now < nanoseconds(last_report_time_ns) + *m_minimum_report_time) 87 return; // Too little time has passed since the last report. 88 89 } while (!m_last_report_time_ns.compare_exchange_weak( 90 last_report_time_ns, now.count(), std::memory_order_relaxed, 91 std::memory_order_relaxed)); 92 } 93 94 std::lock_guard<std::mutex> guard(m_mutex); 95 if (updated_detail) 96 m_details = std::move(updated_detail.value()); 97 ReportProgress(); 98 } 99 100 void Progress::ReportProgress() { 101 // NB: Comparisons with optional<T> rely on the fact that std::nullopt is 102 // "smaller" than zero. 103 if (m_prev_completed >= m_total) 104 return; // We've reported completion already. 105 106 uint64_t completed = 107 std::min(m_completed.load(std::memory_order_relaxed), m_total); 108 if (completed < m_prev_completed) 109 return; // An overflow in the m_completed counter. Just ignore these events. 110 111 // Change the category bit if we're an internal or external progress. 112 uint32_t progress_category_bit = 113 m_progress_data.origin == Progress::Origin::eExternal 114 ? lldb::eBroadcastBitExternalProgress 115 : lldb::eBroadcastBitProgress; 116 117 Debugger::ReportProgress(m_progress_data.progress_id, m_progress_data.title, 118 m_details, completed, m_total, 119 m_progress_data.debugger_id, progress_category_bit); 120 m_prev_completed = completed; 121 } 122 123 ProgressManager::ProgressManager() 124 : m_entries(), m_alarm(std::chrono::milliseconds(100)) {} 125 126 ProgressManager::~ProgressManager() {} 127 128 void ProgressManager::Initialize() { 129 assert(!InstanceImpl() && "Already initialized."); 130 InstanceImpl().emplace(); 131 } 132 133 void ProgressManager::Terminate() { 134 assert(InstanceImpl() && "Already terminated."); 135 InstanceImpl().reset(); 136 } 137 138 bool ProgressManager::Enabled() { return InstanceImpl().operator bool(); } 139 140 ProgressManager &ProgressManager::Instance() { 141 assert(InstanceImpl() && "ProgressManager must be initialized"); 142 return *InstanceImpl(); 143 } 144 145 std::optional<ProgressManager> &ProgressManager::InstanceImpl() { 146 static std::optional<ProgressManager> g_progress_manager; 147 return g_progress_manager; 148 } 149 150 void ProgressManager::Increment(const Progress::ProgressData &progress_data) { 151 std::lock_guard<std::mutex> lock(m_entries_mutex); 152 153 llvm::StringRef key = progress_data.title; 154 bool new_entry = !m_entries.contains(key); 155 Entry &entry = m_entries[progress_data.title]; 156 157 if (new_entry) { 158 // This is a new progress event. Report progress and store the progress 159 // data. 160 ReportProgress(progress_data, EventType::Begin); 161 entry.data = progress_data; 162 } else if (entry.refcount == 0) { 163 // This is an existing entry that was scheduled to be deleted but a new one 164 // came in before the timer expired. 165 assert(entry.handle != Alarm::INVALID_HANDLE); 166 167 if (!m_alarm.Cancel(entry.handle)) { 168 // The timer expired before we had a chance to cancel it. We have to treat 169 // this as an entirely new progress event. 170 ReportProgress(progress_data, EventType::Begin); 171 } 172 // Clear the alarm handle. 173 entry.handle = Alarm::INVALID_HANDLE; 174 } 175 176 // Regardless of how we got here, we need to bump the reference count. 177 entry.refcount++; 178 } 179 180 void ProgressManager::Decrement(const Progress::ProgressData &progress_data) { 181 std::lock_guard<std::mutex> lock(m_entries_mutex); 182 llvm::StringRef key = progress_data.title; 183 184 auto it = m_entries.find(key); 185 if (it == m_entries.end()) 186 return; 187 188 Entry &entry = it->second; 189 entry.refcount--; 190 191 if (entry.refcount == 0) { 192 assert(entry.handle == Alarm::INVALID_HANDLE); 193 194 // Copy the key to a std::string so we can pass it by value to the lambda. 195 // The underlying StringRef will not exist by the time the callback is 196 // called. 197 std::string key_str = std::string(key); 198 199 // Start a timer. If it expires before we see another progress event, it 200 // will be reported. 201 entry.handle = m_alarm.Create([=]() { Expire(key_str); }); 202 } 203 } 204 205 void ProgressManager::ReportProgress( 206 const Progress::ProgressData &progress_data, EventType type) { 207 // The category bit only keeps track of when progress report categories have 208 // started and ended, so clear the details and reset other fields when 209 // broadcasting to it since that bit doesn't need that information. 210 const uint64_t completed = 211 (type == EventType::Begin) ? 0 : Progress::kNonDeterministicTotal; 212 const uint32_t progress_category_bit = 213 progress_data.origin == Progress::Origin::eExternal 214 ? lldb::eBroadcastBitExternalProgressCategory 215 : lldb::eBroadcastBitProgressCategory; 216 Debugger::ReportProgress(progress_data.progress_id, progress_data.title, "", 217 completed, Progress::kNonDeterministicTotal, 218 progress_data.debugger_id, progress_category_bit); 219 } 220 221 void ProgressManager::Expire(llvm::StringRef key) { 222 std::lock_guard<std::mutex> lock(m_entries_mutex); 223 224 // This shouldn't happen but be resilient anyway. 225 if (!m_entries.contains(key)) 226 return; 227 228 // A new event came in and the alarm fired before we had a chance to restart 229 // it. 230 if (m_entries[key].refcount != 0) 231 return; 232 233 // We're done with this entry. 234 ReportProgress(m_entries[key].data, EventType::End); 235 m_entries.erase(key); 236 } 237