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 | |
15 | using namespace lldb_dap; |
16 | using namespace llvm; |
17 | |
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); |
25 | |
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(); |
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 | |
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; |
79 | |
80 | if (prev_event && prev_event->EqualsForIDE(other: event)) |
81 | return std::nullopt; |
82 | |
83 | return event; |
84 | } |
85 | |
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 && m_message == other.m_message; |
90 | } |
91 | |
92 | ProgressEventType ProgressEvent::GetEventType() const { return m_event_type; } |
93 | |
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" ; |
102 | } |
103 | llvm_unreachable("All cases handled above!" ); |
104 | } |
105 | |
106 | json::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 | |
133 | bool 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 | |
145 | bool ProgressEvent::Reported() const { return m_reported; } |
146 | |
147 | ProgressEventManager::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 | |
153 | bool 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 | |
166 | const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const { |
167 | return m_last_update_event ? *m_last_update_event : m_start_event; |
168 | } |
169 | |
170 | void 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 | |
182 | bool ProgressEventManager::Finished() const { return m_finished; } |
183 | |
184 | ProgressEventReporter::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 | |
196 | ProgressEventReporter::~ProgressEventReporter() { |
197 | m_thread_should_exit = true; |
198 | m_thread.join(); |
199 | } |
200 | |
201 | void 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 | |
219 | void 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 | |