xref: /openbsd-src/gnu/llvm/lldb/tools/lldb-vscode/ProgressEvent.cpp (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
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