[NFC][Coroutines] Use structured binding with llvm::enumerate in CoroSplit (#116879)
[llvm-project.git] / lldb / tools / lldb-dap / ProgressEvent.cpp
blob0dcc2ee81001d501aa5d232859048167c18d97b5
1 //===-- ProgressEvent.cpp ---------------------------------------*- C++ -*-===//
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 //===----------------------------------------------------------------------===//
9 #include "ProgressEvent.h"
11 #include "JSONUtils.h"
12 #include "llvm/Support/ErrorHandling.h"
13 #include <optional>
15 using namespace lldb_dap;
16 using namespace llvm;
18 // The minimum duration of an event for it to be reported
19 const std::chrono::duration<double> kStartProgressEventReportDelay =
20 std::chrono::seconds(1);
21 // The minimum time interval between update events for reporting. If multiple
22 // updates fall within the same time interval, only the latest is reported.
23 const std::chrono::duration<double> kUpdateProgressEventReportDelay =
24 std::chrono::milliseconds(250);
26 ProgressEvent::ProgressEvent(uint64_t progress_id,
27 std::optional<StringRef> message,
28 uint64_t completed, uint64_t total,
29 const ProgressEvent *prev_event)
30 : m_progress_id(progress_id) {
31 if (message)
32 m_message = message->str();
34 const bool calculate_percentage = total != UINT64_MAX;
35 if (completed == 0) {
36 // Start event
37 m_event_type = progressStart;
38 // Wait a bit before reporting the start event in case in completes really
39 // quickly.
40 m_minimum_allowed_report_time =
41 m_creation_time + kStartProgressEventReportDelay;
42 if (calculate_percentage)
43 m_percentage = 0;
44 } else if (completed == total) {
45 // End event
46 m_event_type = progressEnd;
47 // We should report the end event right away.
48 m_minimum_allowed_report_time = std::chrono::seconds::zero();
49 if (calculate_percentage)
50 m_percentage = 100;
51 } else {
52 // Update event
53 m_event_type = progressUpdate;
54 m_percentage = std::min(
55 (uint32_t)((double)completed / (double)total * 100.0), (uint32_t)99);
56 if (prev_event->Reported()) {
57 // Add a small delay between reports
58 m_minimum_allowed_report_time =
59 prev_event->m_minimum_allowed_report_time +
60 kUpdateProgressEventReportDelay;
61 } else {
62 // We should use the previous timestamp, as it's still pending
63 m_minimum_allowed_report_time = prev_event->m_minimum_allowed_report_time;
68 std::optional<ProgressEvent>
69 ProgressEvent::Create(uint64_t progress_id, std::optional<StringRef> message,
70 uint64_t completed, uint64_t total,
71 const ProgressEvent *prev_event) {
72 // If it's an update without a previous event, we abort
73 if (completed > 0 && completed < total && !prev_event)
74 return std::nullopt;
75 ProgressEvent event(progress_id, message, completed, total, prev_event);
76 // We shouldn't show unnamed start events in the IDE
77 if (event.GetEventType() == progressStart && event.GetEventName().empty())
78 return std::nullopt;
80 if (prev_event && prev_event->EqualsForIDE(event))
81 return std::nullopt;
83 return event;
86 bool ProgressEvent::EqualsForIDE(const ProgressEvent &other) const {
87 return m_progress_id == other.m_progress_id &&
88 m_event_type == other.m_event_type &&
89 m_percentage == other.m_percentage;
92 ProgressEventType ProgressEvent::GetEventType() const { return m_event_type; }
94 StringRef ProgressEvent::GetEventName() const {
95 switch (m_event_type) {
96 case progressStart:
97 return "progressStart";
98 case progressUpdate:
99 return "progressUpdate";
100 case progressEnd:
101 return "progressEnd";
103 llvm_unreachable("All cases handled above!");
106 json::Value ProgressEvent::ToJSON() const {
107 llvm::json::Object event(CreateEventObject(GetEventName()));
108 llvm::json::Object body;
110 std::string progress_id_str;
111 llvm::raw_string_ostream progress_id_strm(progress_id_str);
112 progress_id_strm << m_progress_id;
113 body.try_emplace("progressId", progress_id_str);
115 if (m_event_type == progressStart) {
116 EmplaceSafeString(body, "title", m_message);
117 body.try_emplace("cancellable", false);
120 std::string timestamp(llvm::formatv("{0:f9}", m_creation_time.count()));
121 EmplaceSafeString(body, "timestamp", timestamp);
123 if (m_percentage)
124 body.try_emplace("percentage", *m_percentage);
126 event.try_emplace("body", std::move(body));
127 return json::Value(std::move(event));
130 bool ProgressEvent::Report(ProgressEventReportCallback callback) {
131 if (Reported())
132 return true;
133 if (std::chrono::system_clock::now().time_since_epoch() <
134 m_minimum_allowed_report_time)
135 return false;
137 m_reported = true;
138 callback(*this);
139 return true;
142 bool ProgressEvent::Reported() const { return m_reported; }
144 ProgressEventManager::ProgressEventManager(
145 const ProgressEvent &start_event,
146 ProgressEventReportCallback report_callback)
147 : m_start_event(start_event), m_finished(false),
148 m_report_callback(report_callback) {}
150 bool ProgressEventManager::ReportIfNeeded() {
151 // The event finished before we were able to report it.
152 if (!m_start_event.Reported() && Finished())
153 return true;
155 if (!m_start_event.Report(m_report_callback))
156 return false;
158 if (m_last_update_event)
159 m_last_update_event->Report(m_report_callback);
160 return true;
163 const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const {
164 return m_last_update_event ? *m_last_update_event : m_start_event;
167 void ProgressEventManager::Update(uint64_t progress_id, uint64_t completed,
168 uint64_t total) {
169 if (std::optional<ProgressEvent> event = ProgressEvent::Create(
170 progress_id, std::nullopt, completed, total, &GetMostRecentEvent())) {
171 if (event->GetEventType() == progressEnd)
172 m_finished = true;
174 m_last_update_event = *event;
175 ReportIfNeeded();
179 bool ProgressEventManager::Finished() const { return m_finished; }
181 ProgressEventReporter::ProgressEventReporter(
182 ProgressEventReportCallback report_callback)
183 : m_report_callback(report_callback) {
184 m_thread_should_exit = false;
185 m_thread = std::thread([&] {
186 while (!m_thread_should_exit) {
187 std::this_thread::sleep_for(kUpdateProgressEventReportDelay);
188 ReportStartEvents();
193 ProgressEventReporter::~ProgressEventReporter() {
194 m_thread_should_exit = true;
195 m_thread.join();
198 void ProgressEventReporter::ReportStartEvents() {
199 std::lock_guard<std::mutex> locker(m_mutex);
201 while (!m_unreported_start_events.empty()) {
202 ProgressEventManagerSP event_manager = m_unreported_start_events.front();
203 if (event_manager->Finished())
204 m_unreported_start_events.pop();
205 else if (event_manager->ReportIfNeeded())
206 m_unreported_start_events
207 .pop(); // we remove it from the queue as it started reporting
208 // already, the Push method will be able to continue its
209 // reports.
210 else
211 break; // If we couldn't report it, then the next event in the queue won't
212 // be able as well, as it came later.
216 void ProgressEventReporter::Push(uint64_t progress_id, const char *message,
217 uint64_t completed, uint64_t total) {
218 std::lock_guard<std::mutex> locker(m_mutex);
220 auto it = m_event_managers.find(progress_id);
221 if (it == m_event_managers.end()) {
222 if (std::optional<ProgressEvent> event = ProgressEvent::Create(
223 progress_id, StringRef(message), completed, total)) {
224 ProgressEventManagerSP event_manager =
225 std::make_shared<ProgressEventManager>(*event, m_report_callback);
226 m_event_managers.insert({progress_id, event_manager});
227 m_unreported_start_events.push(event_manager);
229 } else {
230 it->second->Update(progress_id, completed, total);
231 if (it->second->Finished())
232 m_event_managers.erase(it);