xref: /src/contrib/llvm-project/lldb/source/Core/Progress.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
1344a3780SDimitry Andric //===-- Progress.cpp ------------------------------------------------------===//
2344a3780SDimitry Andric //
3344a3780SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4344a3780SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5344a3780SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6344a3780SDimitry Andric //
7344a3780SDimitry Andric //===----------------------------------------------------------------------===//
8344a3780SDimitry Andric 
9344a3780SDimitry Andric #include "lldb/Core/Progress.h"
10344a3780SDimitry Andric 
11344a3780SDimitry Andric #include "lldb/Core/Debugger.h"
12344a3780SDimitry Andric #include "lldb/Utility/StreamString.h"
13344a3780SDimitry Andric 
14ac9a064cSDimitry Andric #include <cstdint>
15ac9a064cSDimitry Andric #include <mutex>
164df029ccSDimitry Andric #include <optional>
174df029ccSDimitry Andric 
18344a3780SDimitry Andric using namespace lldb;
19344a3780SDimitry Andric using namespace lldb_private;
20344a3780SDimitry Andric 
21344a3780SDimitry Andric std::atomic<uint64_t> Progress::g_id(0);
22344a3780SDimitry Andric 
Progress(std::string title,std::string details,std::optional<uint64_t> total,lldb_private::Debugger * debugger)234df029ccSDimitry Andric Progress::Progress(std::string title, std::string details,
244df029ccSDimitry Andric                    std::optional<uint64_t> total,
25344a3780SDimitry Andric                    lldb_private::Debugger *debugger)
26ac9a064cSDimitry Andric     : m_details(details), m_completed(0),
27ac9a064cSDimitry Andric       m_total(Progress::kNonDeterministicTotal),
28ac9a064cSDimitry Andric       m_progress_data{title, ++g_id,
29ac9a064cSDimitry Andric                       /*m_progress_data.debugger_id=*/std::nullopt} {
304df029ccSDimitry Andric   if (total)
314df029ccSDimitry Andric     m_total = *total;
324df029ccSDimitry Andric 
33344a3780SDimitry Andric   if (debugger)
34ac9a064cSDimitry Andric     m_progress_data.debugger_id = debugger->GetID();
35ac9a064cSDimitry Andric 
36344a3780SDimitry Andric   std::lock_guard<std::mutex> guard(m_mutex);
37344a3780SDimitry Andric   ReportProgress();
38ac9a064cSDimitry Andric 
39ac9a064cSDimitry Andric   // Report to the ProgressManager if that subsystem is enabled.
40ac9a064cSDimitry Andric   if (ProgressManager::Enabled())
41ac9a064cSDimitry Andric     ProgressManager::Instance().Increment(m_progress_data);
42344a3780SDimitry Andric }
43344a3780SDimitry Andric 
~Progress()44344a3780SDimitry Andric Progress::~Progress() {
45344a3780SDimitry Andric   // Make sure to always report progress completed when this object is
46344a3780SDimitry Andric   // destructed so it indicates the progress dialog/activity should go away.
47344a3780SDimitry Andric   std::lock_guard<std::mutex> guard(m_mutex);
48b1c73532SDimitry Andric   if (!m_completed)
49344a3780SDimitry Andric     m_completed = m_total;
50344a3780SDimitry Andric   ReportProgress();
51ac9a064cSDimitry Andric 
52ac9a064cSDimitry Andric   // Report to the ProgressManager if that subsystem is enabled.
53ac9a064cSDimitry Andric   if (ProgressManager::Enabled())
54ac9a064cSDimitry Andric     ProgressManager::Instance().Decrement(m_progress_data);
55344a3780SDimitry Andric }
56344a3780SDimitry Andric 
Increment(uint64_t amount,std::optional<std::string> updated_detail)574df029ccSDimitry Andric void Progress::Increment(uint64_t amount,
584df029ccSDimitry Andric                          std::optional<std::string> updated_detail) {
59344a3780SDimitry Andric   if (amount > 0) {
60344a3780SDimitry Andric     std::lock_guard<std::mutex> guard(m_mutex);
614df029ccSDimitry Andric     if (updated_detail)
624df029ccSDimitry Andric       m_details = std::move(updated_detail.value());
63344a3780SDimitry Andric     // Watch out for unsigned overflow and make sure we don't increment too
64ac9a064cSDimitry Andric     // much and exceed the total.
654df029ccSDimitry Andric     if (m_total && (amount > (m_total - m_completed)))
66344a3780SDimitry Andric       m_completed = m_total;
67344a3780SDimitry Andric     else
68344a3780SDimitry Andric       m_completed += amount;
694df029ccSDimitry Andric     ReportProgress();
70344a3780SDimitry Andric   }
71344a3780SDimitry Andric }
72344a3780SDimitry Andric 
ReportProgress()734df029ccSDimitry Andric void Progress::ReportProgress() {
74344a3780SDimitry Andric   if (!m_complete) {
75344a3780SDimitry Andric     // Make sure we only send one notification that indicates the progress is
764df029ccSDimitry Andric     // complete
77344a3780SDimitry Andric     m_complete = m_completed == m_total;
78ac9a064cSDimitry Andric     Debugger::ReportProgress(m_progress_data.progress_id, m_progress_data.title,
79ac9a064cSDimitry Andric                              m_details, m_completed, m_total,
80ac9a064cSDimitry Andric                              m_progress_data.debugger_id);
81344a3780SDimitry Andric   }
82344a3780SDimitry Andric }
83ac9a064cSDimitry Andric 
ProgressManager()84ac9a064cSDimitry Andric ProgressManager::ProgressManager()
85ac9a064cSDimitry Andric     : m_entries(), m_alarm(std::chrono::milliseconds(100)) {}
86ac9a064cSDimitry Andric 
~ProgressManager()87ac9a064cSDimitry Andric ProgressManager::~ProgressManager() {}
88ac9a064cSDimitry Andric 
Initialize()89ac9a064cSDimitry Andric void ProgressManager::Initialize() {
90ac9a064cSDimitry Andric   assert(!InstanceImpl() && "Already initialized.");
91ac9a064cSDimitry Andric   InstanceImpl().emplace();
92ac9a064cSDimitry Andric }
93ac9a064cSDimitry Andric 
Terminate()94ac9a064cSDimitry Andric void ProgressManager::Terminate() {
95ac9a064cSDimitry Andric   assert(InstanceImpl() && "Already terminated.");
96ac9a064cSDimitry Andric   InstanceImpl().reset();
97ac9a064cSDimitry Andric }
98ac9a064cSDimitry Andric 
Enabled()99ac9a064cSDimitry Andric bool ProgressManager::Enabled() { return InstanceImpl().operator bool(); }
100ac9a064cSDimitry Andric 
Instance()101ac9a064cSDimitry Andric ProgressManager &ProgressManager::Instance() {
102ac9a064cSDimitry Andric   assert(InstanceImpl() && "ProgressManager must be initialized");
103ac9a064cSDimitry Andric   return *InstanceImpl();
104ac9a064cSDimitry Andric }
105ac9a064cSDimitry Andric 
InstanceImpl()106ac9a064cSDimitry Andric std::optional<ProgressManager> &ProgressManager::InstanceImpl() {
107ac9a064cSDimitry Andric   static std::optional<ProgressManager> g_progress_manager;
108ac9a064cSDimitry Andric   return g_progress_manager;
109ac9a064cSDimitry Andric }
110ac9a064cSDimitry Andric 
Increment(const Progress::ProgressData & progress_data)111ac9a064cSDimitry Andric void ProgressManager::Increment(const Progress::ProgressData &progress_data) {
112ac9a064cSDimitry Andric   std::lock_guard<std::mutex> lock(m_entries_mutex);
113ac9a064cSDimitry Andric 
114ac9a064cSDimitry Andric   llvm::StringRef key = progress_data.title;
115ac9a064cSDimitry Andric   bool new_entry = !m_entries.contains(key);
116ac9a064cSDimitry Andric   Entry &entry = m_entries[progress_data.title];
117ac9a064cSDimitry Andric 
118ac9a064cSDimitry Andric   if (new_entry) {
119ac9a064cSDimitry Andric     // This is a new progress event. Report progress and store the progress
120ac9a064cSDimitry Andric     // data.
121ac9a064cSDimitry Andric     ReportProgress(progress_data, EventType::Begin);
122ac9a064cSDimitry Andric     entry.data = progress_data;
123ac9a064cSDimitry Andric   } else if (entry.refcount == 0) {
124ac9a064cSDimitry Andric     // This is an existing entry that was scheduled to be deleted but a new one
125ac9a064cSDimitry Andric     // came in before the timer expired.
126ac9a064cSDimitry Andric     assert(entry.handle != Alarm::INVALID_HANDLE);
127ac9a064cSDimitry Andric 
128ac9a064cSDimitry Andric     if (!m_alarm.Cancel(entry.handle)) {
129ac9a064cSDimitry Andric       // The timer expired before we had a chance to cancel it. We have to treat
130ac9a064cSDimitry Andric       // this as an entirely new progress event.
131ac9a064cSDimitry Andric       ReportProgress(progress_data, EventType::Begin);
132ac9a064cSDimitry Andric     }
133ac9a064cSDimitry Andric     // Clear the alarm handle.
134ac9a064cSDimitry Andric     entry.handle = Alarm::INVALID_HANDLE;
135ac9a064cSDimitry Andric   }
136ac9a064cSDimitry Andric 
137ac9a064cSDimitry Andric   // Regardless of how we got here, we need to bump the reference count.
138ac9a064cSDimitry Andric   entry.refcount++;
139ac9a064cSDimitry Andric }
140ac9a064cSDimitry Andric 
Decrement(const Progress::ProgressData & progress_data)141ac9a064cSDimitry Andric void ProgressManager::Decrement(const Progress::ProgressData &progress_data) {
142ac9a064cSDimitry Andric   std::lock_guard<std::mutex> lock(m_entries_mutex);
143ac9a064cSDimitry Andric   llvm::StringRef key = progress_data.title;
144ac9a064cSDimitry Andric 
145ac9a064cSDimitry Andric   if (!m_entries.contains(key))
146ac9a064cSDimitry Andric     return;
147ac9a064cSDimitry Andric 
148ac9a064cSDimitry Andric   Entry &entry = m_entries[key];
149ac9a064cSDimitry Andric   entry.refcount--;
150ac9a064cSDimitry Andric 
151ac9a064cSDimitry Andric   if (entry.refcount == 0) {
152ac9a064cSDimitry Andric     assert(entry.handle == Alarm::INVALID_HANDLE);
153ac9a064cSDimitry Andric 
154ac9a064cSDimitry Andric     // Copy the key to a std::string so we can pass it by value to the lambda.
155ac9a064cSDimitry Andric     // The underlying StringRef will not exist by the time the callback is
156ac9a064cSDimitry Andric     // called.
157ac9a064cSDimitry Andric     std::string key_str = std::string(key);
158ac9a064cSDimitry Andric 
159ac9a064cSDimitry Andric     // Start a timer. If it expires before we see another progress event, it
160ac9a064cSDimitry Andric     // will be reported.
161ac9a064cSDimitry Andric     entry.handle = m_alarm.Create([=]() { Expire(key_str); });
162ac9a064cSDimitry Andric   }
163ac9a064cSDimitry Andric }
164ac9a064cSDimitry Andric 
ReportProgress(const Progress::ProgressData & progress_data,EventType type)165ac9a064cSDimitry Andric void ProgressManager::ReportProgress(
166ac9a064cSDimitry Andric     const Progress::ProgressData &progress_data, EventType type) {
167ac9a064cSDimitry Andric   // The category bit only keeps track of when progress report categories have
168ac9a064cSDimitry Andric   // started and ended, so clear the details and reset other fields when
169ac9a064cSDimitry Andric   // broadcasting to it since that bit doesn't need that information.
170ac9a064cSDimitry Andric   const uint64_t completed =
171ac9a064cSDimitry Andric       (type == EventType::Begin) ? 0 : Progress::kNonDeterministicTotal;
172ac9a064cSDimitry Andric   Debugger::ReportProgress(progress_data.progress_id, progress_data.title, "",
173ac9a064cSDimitry Andric                            completed, Progress::kNonDeterministicTotal,
174ac9a064cSDimitry Andric                            progress_data.debugger_id,
175ac9a064cSDimitry Andric                            lldb::eBroadcastBitProgressCategory);
176ac9a064cSDimitry Andric }
177ac9a064cSDimitry Andric 
Expire(llvm::StringRef key)178ac9a064cSDimitry Andric void ProgressManager::Expire(llvm::StringRef key) {
179ac9a064cSDimitry Andric   std::lock_guard<std::mutex> lock(m_entries_mutex);
180ac9a064cSDimitry Andric 
181ac9a064cSDimitry Andric   // This shouldn't happen but be resilient anyway.
182ac9a064cSDimitry Andric   if (!m_entries.contains(key))
183ac9a064cSDimitry Andric     return;
184ac9a064cSDimitry Andric 
185ac9a064cSDimitry Andric   // A new event came in and the alarm fired before we had a chance to restart
186ac9a064cSDimitry Andric   // it.
187ac9a064cSDimitry Andric   if (m_entries[key].refcount != 0)
188ac9a064cSDimitry Andric     return;
189ac9a064cSDimitry Andric 
190ac9a064cSDimitry Andric   // We're done with this entry.
191ac9a064cSDimitry Andric   ReportProgress(m_entries[key].data, EventType::End);
192ac9a064cSDimitry Andric   m_entries.erase(key);
193ac9a064cSDimitry Andric }
194