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