xref: /llvm-project/lldb/source/Core/Progress.cpp (revision 774c22686330f3ca43e48a1b8076eb30ae03dbd8)
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