xref: /llvm-project/lldb/tools/lldb-dap/ProgressEvent.cpp (revision a939a9fd53d98f33b94f9121646d5906a2b9f598)
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         (uint32_t)((double)completed / (double)total * 100.0), (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(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(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("progressId", progress_id_str);
114 
115   if (m_event_type == progressStart) {
116     EmplaceSafeString(body, "title", m_message);
117     body.try_emplace("cancellable", false);
118   }
119 
120   if (m_event_type == progressUpdate)
121     EmplaceSafeString(body, "message", m_message);
122 
123   std::string timestamp(llvm::formatv("{0:f9}", m_creation_time.count()));
124   EmplaceSafeString(body, "timestamp", timestamp);
125 
126   if (m_percentage)
127     body.try_emplace("percentage", *m_percentage);
128 
129   event.try_emplace("body", 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(m_report_callback))
159     return false;
160 
161   if (m_last_update_event)
162     m_last_update_event->Report(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, &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(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(progress_id);
224   if (it == m_event_managers.end()) {
225     if (std::optional<ProgressEvent> event = ProgressEvent::Create(
226             progress_id, StringRef(message), completed, total)) {
227       ProgressEventManagerSP event_manager =
228           std::make_shared<ProgressEventManager>(*event, m_report_callback);
229       m_event_managers.insert({progress_id, event_manager});
230       m_unreported_start_events.push(event_manager);
231     }
232   } else {
233     it->second->Update(progress_id, StringRef(message), completed, total);
234     if (it->second->Finished())
235       m_event_managers.erase(it);
236   }
237 }
238