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;
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 progress_id_strm.flush();
114 body.try_emplace(K: "progressId", Args&: progress_id_str);
115
116 if (m_event_type == progressStart) {
117 EmplaceSafeString(obj&: body, key: "title", str: m_message);
118 body.try_emplace(K: "cancellable", Args: false);
119 }
120
121 std::string timestamp(llvm::formatv(Fmt: "{0:f9}", Vals: m_creation_time.count()));
122 EmplaceSafeString(obj&: body, key: "timestamp", str: timestamp);
123
124 if (m_percentage)
125 body.try_emplace(K: "percentage", Args: *m_percentage);
126
127 event.try_emplace(K: "body", Args: std::move(body));
128 return json::Value(std::move(event));
129}
130
131bool ProgressEvent::Report(ProgressEventReportCallback callback) {
132 if (Reported())
133 return true;
134 if (std::chrono::system_clock::now().time_since_epoch() <
135 m_minimum_allowed_report_time)
136 return false;
137
138 m_reported = true;
139 callback(*this);
140 return true;
141}
142
143bool ProgressEvent::Reported() const { return m_reported; }
144
145ProgressEventManager::ProgressEventManager(
146 const ProgressEvent &start_event,
147 ProgressEventReportCallback report_callback)
148 : m_start_event(start_event), m_finished(false),
149 m_report_callback(report_callback) {}
150
151bool ProgressEventManager::ReportIfNeeded() {
152 // The event finished before we were able to report it.
153 if (!m_start_event.Reported() && Finished())
154 return true;
155
156 if (!m_start_event.Report(callback: m_report_callback))
157 return false;
158
159 if (m_last_update_event)
160 m_last_update_event->Report(callback: m_report_callback);
161 return true;
162}
163
164const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const {
165 return m_last_update_event ? *m_last_update_event : m_start_event;
166}
167
168void ProgressEventManager::Update(uint64_t progress_id, uint64_t completed,
169 uint64_t total) {
170 if (std::optional<ProgressEvent> event = ProgressEvent::Create(
171 progress_id, message: std::nullopt, completed, total, prev_event: &GetMostRecentEvent())) {
172 if (event->GetEventType() == progressEnd)
173 m_finished = true;
174
175 m_last_update_event = *event;
176 ReportIfNeeded();
177 }
178}
179
180bool ProgressEventManager::Finished() const { return m_finished; }
181
182ProgressEventReporter::ProgressEventReporter(
183 ProgressEventReportCallback report_callback)
184 : m_report_callback(report_callback) {
185 m_thread_should_exit = false;
186 m_thread = std::thread([&] {
187 while (!m_thread_should_exit) {
188 std::this_thread::sleep_for(rtime: kUpdateProgressEventReportDelay);
189 ReportStartEvents();
190 }
191 });
192}
193
194ProgressEventReporter::~ProgressEventReporter() {
195 m_thread_should_exit = true;
196 m_thread.join();
197}
198
199void ProgressEventReporter::ReportStartEvents() {
200 std::lock_guard<std::mutex> locker(m_mutex);
201
202 while (!m_unreported_start_events.empty()) {
203 ProgressEventManagerSP event_manager = m_unreported_start_events.front();
204 if (event_manager->Finished())
205 m_unreported_start_events.pop();
206 else if (event_manager->ReportIfNeeded())
207 m_unreported_start_events
208 .pop(); // we remove it from the queue as it started reporting
209 // already, the Push method will be able to continue its
210 // reports.
211 else
212 break; // If we couldn't report it, then the next event in the queue won't
213 // be able as well, as it came later.
214 }
215}
216
217void ProgressEventReporter::Push(uint64_t progress_id, const char *message,
218 uint64_t completed, uint64_t total) {
219 std::lock_guard<std::mutex> locker(m_mutex);
220
221 auto it = m_event_managers.find(x: progress_id);
222 if (it == m_event_managers.end()) {
223 if (std::optional<ProgressEvent> event = ProgressEvent::Create(
224 progress_id, message: StringRef(message), completed, total)) {
225 ProgressEventManagerSP event_manager =
226 std::make_shared<ProgressEventManager>(args&: *event, args&: m_report_callback);
227 m_event_managers.insert(x: {progress_id, event_manager});
228 m_unreported_start_events.push(x: event_manager);
229 }
230 } else {
231 it->second->Update(progress_id, completed, total);
232 if (it->second->Finished())
233 m_event_managers.erase(position: it);
234 }
235}
236

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