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; |
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 | 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 | |
131 | bool 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 | |
143 | bool ProgressEvent::Reported() const { return m_reported; } |
144 | |
145 | ProgressEventManager::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 | |
151 | bool 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 | |
164 | const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const { |
165 | return m_last_update_event ? *m_last_update_event : m_start_event; |
166 | } |
167 | |
168 | void 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 | |
180 | bool ProgressEventManager::Finished() const { return m_finished; } |
181 | |
182 | ProgressEventReporter::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 | |
194 | ProgressEventReporter::~ProgressEventReporter() { |
195 | m_thread_should_exit = true; |
196 | m_thread.join(); |
197 | } |
198 | |
199 | void 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 | |
217 | void 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 | |