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//===----------------------------------------------------------------------===//
8
9#include "ProgressEvent.h"
10
11#include "JSONUtils.h"
12#include "llvm/Support/ErrorHandling.h"
13#include <optional>
14
15using namespace lldb_dap;
16using namespace llvm;
17
18// The minimum duration of an event for it to be reported
19const 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.
23const std::chrono::duration<double> kUpdateProgressEventReportDelay =
24 std::chrono::milliseconds(250);
25
26ProgressEvent::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();
33
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 a: (uint32_t)((double)completed / (double)total * 100.0), b: (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;
64 }
65 }
66}
67
68std::optional<ProgressEvent>
69ProgressEvent::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;
79
80 if (prev_event && prev_event->EqualsForIDE(other: event))
81 return std::nullopt;
82
83 return event;
84}
85
86bool 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 && m_message == other.m_message;
90}
91
92ProgressEventType ProgressEvent::GetEventType() const { return m_event_type; }
93
94StringRef 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";
102 }
103 llvm_unreachable("All cases handled above!");
104}
105
106json::Value ProgressEvent::ToJSON() const {
107 llvm::json::Object event(CreateEventObject(event_name: GetEventName()));
108 llvm::json::Object body;
109
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(K: "progressId", Args&: progress_id_str);
114
115 if (m_event_type == progressStart) {
116 EmplaceSafeString(obj&: body, key: "title", str: m_message);
117 body.try_emplace(K: "cancellable", Args: false);
118 }
119
120 if (m_event_type == progressUpdate)
121 EmplaceSafeString(obj&: body, key: "message", str: m_message);
122
123 std::string timestamp(llvm::formatv(Fmt: "{0:f9}", Vals: m_creation_time.count()));
124 EmplaceSafeString(obj&: body, key: "timestamp", str: timestamp);
125
126 if (m_percentage)
127 body.try_emplace(K: "percentage", Args: *m_percentage);
128
129 event.try_emplace(K: "body", Args: std::move(body));
130 return json::Value(std::move(event));
131}
132
133bool ProgressEvent::Report(ProgressEventReportCallback callback) {
134 if (Reported())
135 return true;
136 if (std::chrono::system_clock::now().time_since_epoch() <
137 m_minimum_allowed_report_time)
138 return false;
139
140 m_reported = true;
141 callback(*this);
142 return true;
143}
144
145bool ProgressEvent::Reported() const { return m_reported; }
146
147ProgressEventManager::ProgressEventManager(
148 const ProgressEvent &start_event,
149 ProgressEventReportCallback report_callback)
150 : m_start_event(start_event), m_finished(false),
151 m_report_callback(report_callback) {}
152
153bool ProgressEventManager::ReportIfNeeded() {
154 // The event finished before we were able to report it.
155 if (!m_start_event.Reported() && Finished())
156 return true;
157
158 if (!m_start_event.Report(callback: m_report_callback))
159 return false;
160
161 if (m_last_update_event)
162 m_last_update_event->Report(callback: m_report_callback);
163 return true;
164}
165
166const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const {
167 return m_last_update_event ? *m_last_update_event : m_start_event;
168}
169
170void ProgressEventManager::Update(uint64_t progress_id, llvm::StringRef message,
171 uint64_t completed, uint64_t total) {
172 if (std::optional<ProgressEvent> event = ProgressEvent::Create(
173 progress_id, message, completed, total, prev_event: &GetMostRecentEvent())) {
174 if (event->GetEventType() == progressEnd)
175 m_finished = true;
176
177 m_last_update_event = *event;
178 ReportIfNeeded();
179 }
180}
181
182bool ProgressEventManager::Finished() const { return m_finished; }
183
184ProgressEventReporter::ProgressEventReporter(
185 ProgressEventReportCallback report_callback)
186 : m_report_callback(report_callback) {
187 m_thread_should_exit = false;
188 m_thread = std::thread([&] {
189 while (!m_thread_should_exit) {
190 std::this_thread::sleep_for(rtime: kUpdateProgressEventReportDelay);
191 ReportStartEvents();
192 }
193 });
194}
195
196ProgressEventReporter::~ProgressEventReporter() {
197 m_thread_should_exit = true;
198 m_thread.join();
199}
200
201void ProgressEventReporter::ReportStartEvents() {
202 std::lock_guard<std::mutex> locker(m_mutex);
203
204 while (!m_unreported_start_events.empty()) {
205 ProgressEventManagerSP event_manager = m_unreported_start_events.front();
206 if (event_manager->Finished())
207 m_unreported_start_events.pop();
208 else if (event_manager->ReportIfNeeded())
209 m_unreported_start_events
210 .pop(); // we remove it from the queue as it started reporting
211 // already, the Push method will be able to continue its
212 // reports.
213 else
214 break; // If we couldn't report it, then the next event in the queue won't
215 // be able as well, as it came later.
216 }
217}
218
219void ProgressEventReporter::Push(uint64_t progress_id, const char *message,
220 uint64_t completed, uint64_t total) {
221 std::lock_guard<std::mutex> locker(m_mutex);
222
223 auto it = m_event_managers.find(x: progress_id);
224 if (it == m_event_managers.end()) {
225 if (std::optional<ProgressEvent> event = ProgressEvent::Create(
226 progress_id, message: StringRef(message), completed, total)) {
227 ProgressEventManagerSP event_manager =
228 std::make_shared<ProgressEventManager>(args&: *event, args&: m_report_callback);
229 m_event_managers.insert(x: {progress_id, event_manager});
230 m_unreported_start_events.push(x: event_manager);
231 }
232 } else {
233 it->second->Update(progress_id, message: StringRef(message), completed, total);
234 if (it->second->Finished())
235 m_event_managers.erase(position: it);
236 }
237}
238

source code of lldb/tools/lldb-dap/ProgressEvent.cpp