/Users/buildslave/jenkins/workspace/coverage/llvm-project/lldb/tools/lldb-vscode/ProgressEvent.cpp
Line | Count | Source (jump to first uncovered line) |
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_vscode; |
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 | 562 | : m_progress_id(progress_id) { |
31 | 562 | if (message) |
32 | 263 | m_message = message->str(); |
33 | | |
34 | 562 | const bool calculate_percentage = total != UINT64_MAX; |
35 | 562 | if (completed == 0) { |
36 | | // Start event |
37 | 263 | m_event_type = progressStart; |
38 | | // Wait a bit before reporting the start event in case in completes really |
39 | | // quickly. |
40 | 263 | m_minimum_allowed_report_time = |
41 | 263 | m_creation_time + kStartProgressEventReportDelay; |
42 | 263 | if (calculate_percentage) |
43 | 4 | m_percentage = 0; |
44 | 299 | } else if (completed == total) { |
45 | | // End event |
46 | 263 | m_event_type = progressEnd; |
47 | | // We should report the end event right away. |
48 | 263 | m_minimum_allowed_report_time = std::chrono::seconds::zero(); |
49 | 263 | if (calculate_percentage) |
50 | 4 | m_percentage = 100; |
51 | 263 | } else { |
52 | | // Update event |
53 | 36 | m_event_type = progressUpdate; |
54 | 36 | m_percentage = std::min( |
55 | 36 | (uint32_t)((double)completed / (double)total * 100.0), (uint32_t)99); |
56 | 36 | if (prev_event->Reported()) { |
57 | | // Add a small delay between reports |
58 | 0 | m_minimum_allowed_report_time = |
59 | 0 | prev_event->m_minimum_allowed_report_time + |
60 | 0 | kUpdateProgressEventReportDelay; |
61 | 36 | } else { |
62 | | // We should use the previous timestamp, as it's still pending |
63 | 36 | m_minimum_allowed_report_time = prev_event->m_minimum_allowed_report_time; |
64 | 36 | } |
65 | 36 | } |
66 | 562 | } |
67 | | |
68 | | std::optional<ProgressEvent> |
69 | | ProgressEvent::Create(uint64_t progress_id, std::optional<StringRef> message, |
70 | | uint64_t completed, uint64_t total, |
71 | 562 | const ProgressEvent *prev_event) { |
72 | | // If it's an update without a previous event, we abort |
73 | 562 | if (completed > 0 && completed < total299 && !prev_event36 ) |
74 | 0 | return std::nullopt; |
75 | 562 | ProgressEvent event(progress_id, message, completed, total, prev_event); |
76 | | // We shouldn't show unnamed start events in the IDE |
77 | 562 | if (event.GetEventType() == progressStart && event.GetEventName().empty()263 ) |
78 | 0 | return std::nullopt; |
79 | | |
80 | 562 | if (prev_event && prev_event->EqualsForIDE(event)299 ) |
81 | 0 | return std::nullopt; |
82 | | |
83 | 562 | return event; |
84 | 562 | } |
85 | | |
86 | 299 | bool ProgressEvent::EqualsForIDE(const ProgressEvent &other) const { |
87 | 299 | return m_progress_id == other.m_progress_id && |
88 | 299 | m_event_type == other.m_event_type && |
89 | 299 | m_percentage == other.m_percentage32 ; |
90 | 299 | } |
91 | | |
92 | 861 | ProgressEventType ProgressEvent::GetEventType() const { return m_event_type; } |
93 | | |
94 | 263 | StringRef ProgressEvent::GetEventName() const { |
95 | 263 | switch (m_event_type) { |
96 | 263 | case progressStart: |
97 | 263 | return "progressStart"; |
98 | 0 | case progressUpdate: |
99 | 0 | return "progressUpdate"; |
100 | 0 | case progressEnd: |
101 | 0 | return "progressEnd"; |
102 | 263 | } |
103 | 0 | llvm_unreachable("All cases handled above!"); |
104 | 0 | } |
105 | | |
106 | 0 | json::Value ProgressEvent::ToJSON() const { |
107 | 0 | llvm::json::Object event(CreateEventObject(GetEventName())); |
108 | 0 | llvm::json::Object body; |
109 | |
|
110 | 0 | std::string progress_id_str; |
111 | 0 | llvm::raw_string_ostream progress_id_strm(progress_id_str); |
112 | 0 | progress_id_strm << m_progress_id; |
113 | 0 | progress_id_strm.flush(); |
114 | 0 | body.try_emplace("progressId", progress_id_str); |
115 | |
|
116 | 0 | if (m_event_type == progressStart) { |
117 | 0 | EmplaceSafeString(body, "title", m_message); |
118 | 0 | body.try_emplace("cancellable", false); |
119 | 0 | } |
120 | |
|
121 | 0 | std::string timestamp(llvm::formatv("{0:f9}", m_creation_time.count())); |
122 | 0 | EmplaceSafeString(body, "timestamp", timestamp); |
123 | |
|
124 | 0 | if (m_percentage) |
125 | 0 | body.try_emplace("percentage", *m_percentage); |
126 | |
|
127 | 0 | event.try_emplace("body", std::move(body)); |
128 | 0 | return json::Value(std::move(event)); |
129 | 0 | } |
130 | | |
131 | 37 | bool ProgressEvent::Report(ProgressEventReportCallback callback) { |
132 | 37 | if (Reported()) |
133 | 0 | return true; |
134 | 37 | if (std::chrono::system_clock::now().time_since_epoch() < |
135 | 37 | m_minimum_allowed_report_time) |
136 | 37 | return false; |
137 | | |
138 | 0 | m_reported = true; |
139 | 0 | callback(*this); |
140 | 0 | return true; |
141 | 37 | } |
142 | | |
143 | 373 | bool ProgressEvent::Reported() const { return m_reported; } |
144 | | |
145 | | ProgressEventManager::ProgressEventManager( |
146 | | const ProgressEvent &start_event, |
147 | | ProgressEventReportCallback report_callback) |
148 | 263 | : m_start_event(start_event), m_finished(false), |
149 | 263 | m_report_callback(report_callback) {} |
150 | | |
151 | 300 | bool ProgressEventManager::ReportIfNeeded() { |
152 | | // The event finished before we were able to report it. |
153 | 300 | if (!m_start_event.Reported() && Finished()) |
154 | 263 | return true; |
155 | | |
156 | 37 | if (!m_start_event.Report(m_report_callback)) |
157 | 37 | return false; |
158 | | |
159 | 0 | if (m_last_update_event) |
160 | 0 | m_last_update_event->Report(m_report_callback); |
161 | 0 | return true; |
162 | 37 | } |
163 | | |
164 | 299 | const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const { |
165 | 299 | return m_last_update_event ? *m_last_update_event36 : m_start_event263 ; |
166 | 299 | } |
167 | | |
168 | | void ProgressEventManager::Update(uint64_t progress_id, uint64_t completed, |
169 | 299 | uint64_t total) { |
170 | 299 | if (std::optional<ProgressEvent> event = ProgressEvent::Create( |
171 | 299 | progress_id, std::nullopt, completed, total, &GetMostRecentEvent())) { |
172 | 299 | if (event->GetEventType() == progressEnd) |
173 | 263 | m_finished = true; |
174 | | |
175 | 299 | m_last_update_event = *event; |
176 | 299 | ReportIfNeeded(); |
177 | 299 | } |
178 | 299 | } |
179 | | |
180 | 863 | bool ProgressEventManager::Finished() const { return m_finished; } |
181 | | |
182 | | ProgressEventReporter::ProgressEventReporter( |
183 | | ProgressEventReportCallback report_callback) |
184 | 5 | : m_report_callback(report_callback) { |
185 | 5 | m_thread_should_exit = false; |
186 | 5 | m_thread = std::thread([&] { |
187 | 24 | while (!m_thread_should_exit) { |
188 | 19 | std::this_thread::sleep_for(kUpdateProgressEventReportDelay); |
189 | 19 | ReportStartEvents(); |
190 | 19 | } |
191 | 5 | }); |
192 | 5 | } |
193 | | |
194 | 0 | ProgressEventReporter::~ProgressEventReporter() { |
195 | 0 | m_thread_should_exit = true; |
196 | 0 | m_thread.join(); |
197 | 0 | } |
198 | | |
199 | 14 | void ProgressEventReporter::ReportStartEvents() { |
200 | 14 | std::lock_guard<std::mutex> locker(m_mutex); |
201 | | |
202 | 277 | while (!m_unreported_start_events.empty()) { |
203 | 264 | ProgressEventManagerSP event_manager = m_unreported_start_events.front(); |
204 | 264 | if (event_manager->Finished()) |
205 | 263 | m_unreported_start_events.pop(); |
206 | 1 | else if (event_manager->ReportIfNeeded()) |
207 | 0 | m_unreported_start_events |
208 | 0 | .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 | 1 | else |
212 | 1 | 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 | 264 | } |
215 | 14 | } |
216 | | |
217 | | void ProgressEventReporter::Push(uint64_t progress_id, const char *message, |
218 | 562 | uint64_t completed, uint64_t total) { |
219 | 562 | std::lock_guard<std::mutex> locker(m_mutex); |
220 | | |
221 | 562 | auto it = m_event_managers.find(progress_id); |
222 | 562 | if (it == m_event_managers.end()) { |
223 | 263 | if (std::optional<ProgressEvent> event = ProgressEvent::Create( |
224 | 263 | progress_id, StringRef(message), completed, total)) { |
225 | 263 | ProgressEventManagerSP event_manager = |
226 | 263 | std::make_shared<ProgressEventManager>(*event, m_report_callback); |
227 | 263 | m_event_managers.insert({progress_id, event_manager}); |
228 | 263 | m_unreported_start_events.push(event_manager); |
229 | 263 | } |
230 | 299 | } else { |
231 | 299 | it->second->Update(progress_id, completed, total); |
232 | 299 | if (it->second->Finished()) |
233 | 263 | m_event_managers.erase(it); |
234 | 299 | } |
235 | 562 | } |