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