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 
13 using namespace lldb_vscode;
14 using namespace llvm;
15 
16 // The minimum duration of an event for it to be reported
17 const std::chrono::duration<double> kStartProgressEventReportDelay =
18     std::chrono::seconds(1);
19 // The minimum time interval between update events for reporting. If multiple
20 // updates fall within the same time interval, only the latest is reported.
21 const std::chrono::duration<double> kUpdateProgressEventReportDelay =
22     std::chrono::milliseconds(250);
23 
ProgressEvent(uint64_t progress_id,Optional<StringRef> message,uint64_t completed,uint64_t total,const ProgressEvent * prev_event)24 ProgressEvent::ProgressEvent(uint64_t progress_id, Optional<StringRef> message,
25                              uint64_t completed, uint64_t total,
26                              const ProgressEvent *prev_event)
27     : m_progress_id(progress_id) {
28   if (message)
29     m_message = message->str();
30 
31   const bool calculate_percentage = total != UINT64_MAX;
32   if (completed == 0) {
33     // Start event
34     m_event_type = progressStart;
35     // Wait a bit before reporting the start event in case in completes really
36     // quickly.
37     m_minimum_allowed_report_time =
38         m_creation_time + kStartProgressEventReportDelay;
39     if (calculate_percentage)
40       m_percentage = 0;
41   } else if (completed == total) {
42     // End event
43     m_event_type = progressEnd;
44     // We should report the end event right away.
45     m_minimum_allowed_report_time = std::chrono::seconds::zero();
46     if (calculate_percentage)
47       m_percentage = 100;
48   } else {
49     // Update event
50     m_event_type = progressUpdate;
51     m_percentage = std::min(
52         (uint32_t)((double)completed / (double)total * 100.0), (uint32_t)99);
53     if (prev_event->Reported()) {
54       // Add a small delay between reports
55       m_minimum_allowed_report_time =
56           prev_event->m_minimum_allowed_report_time +
57           kUpdateProgressEventReportDelay;
58     } else {
59       // We should use the previous timestamp, as it's still pending
60       m_minimum_allowed_report_time = prev_event->m_minimum_allowed_report_time;
61     }
62   }
63 }
64 
Create(uint64_t progress_id,Optional<StringRef> message,uint64_t completed,uint64_t total,const ProgressEvent * prev_event)65 Optional<ProgressEvent> ProgressEvent::Create(uint64_t progress_id,
66                                               Optional<StringRef> message,
67                                               uint64_t completed,
68                                               uint64_t total,
69                                               const ProgressEvent *prev_event) {
70   // If it's an update without a previous event, we abort
71   if (completed > 0 && completed < total && !prev_event)
72     return None;
73   ProgressEvent event(progress_id, message, completed, total, prev_event);
74   // We shouldn't show unnamed start events in the IDE
75   if (event.GetEventType() == progressStart && event.GetEventName().empty())
76     return None;
77 
78   if (prev_event && prev_event->EqualsForIDE(event))
79     return None;
80 
81   return event;
82 }
83 
EqualsForIDE(const ProgressEvent & other) const84 bool ProgressEvent::EqualsForIDE(const ProgressEvent &other) const {
85   return m_progress_id == other.m_progress_id &&
86          m_event_type == other.m_event_type &&
87          m_percentage == other.m_percentage;
88 }
89 
GetEventType() const90 ProgressEventType ProgressEvent::GetEventType() const { return m_event_type; }
91 
GetEventName() const92 StringRef ProgressEvent::GetEventName() const {
93   if (m_event_type == progressStart)
94     return "progressStart";
95   else if (m_event_type == progressEnd)
96     return "progressEnd";
97   else
98     return "progressUpdate";
99 }
100 
ToJSON() const101 json::Value ProgressEvent::ToJSON() const {
102   llvm::json::Object event(CreateEventObject(GetEventName()));
103   llvm::json::Object body;
104 
105   std::string progress_id_str;
106   llvm::raw_string_ostream progress_id_strm(progress_id_str);
107   progress_id_strm << m_progress_id;
108   progress_id_strm.flush();
109   body.try_emplace("progressId", progress_id_str);
110 
111   if (m_event_type == progressStart) {
112     EmplaceSafeString(body, "title", m_message);
113     body.try_emplace("cancellable", false);
114   }
115 
116   std::string timestamp(llvm::formatv("{0:f9}", m_creation_time.count()));
117   EmplaceSafeString(body, "timestamp", timestamp);
118 
119   if (m_percentage)
120     body.try_emplace("percentage", *m_percentage);
121 
122   event.try_emplace("body", std::move(body));
123   return json::Value(std::move(event));
124 }
125 
Report(ProgressEventReportCallback callback)126 bool ProgressEvent::Report(ProgressEventReportCallback callback) {
127   if (Reported())
128     return true;
129   if (std::chrono::system_clock::now().time_since_epoch() <
130       m_minimum_allowed_report_time)
131     return false;
132 
133   m_reported = true;
134   callback(*this);
135   return true;
136 }
137 
Reported() const138 bool ProgressEvent::Reported() const { return m_reported; }
139 
ProgressEventManager(const ProgressEvent & start_event,ProgressEventReportCallback report_callback)140 ProgressEventManager::ProgressEventManager(
141     const ProgressEvent &start_event,
142     ProgressEventReportCallback report_callback)
143     : m_start_event(start_event), m_finished(false),
144       m_report_callback(report_callback) {}
145 
ReportIfNeeded()146 bool ProgressEventManager::ReportIfNeeded() {
147   // The event finished before we were able to report it.
148   if (!m_start_event.Reported() && Finished())
149     return true;
150 
151   if (!m_start_event.Report(m_report_callback))
152     return false;
153 
154   if (m_last_update_event)
155     m_last_update_event->Report(m_report_callback);
156   return true;
157 }
158 
GetMostRecentEvent() const159 const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const {
160   return m_last_update_event ? *m_last_update_event : m_start_event;
161 }
162 
Update(uint64_t progress_id,uint64_t completed,uint64_t total)163 void ProgressEventManager::Update(uint64_t progress_id, uint64_t completed,
164                                   uint64_t total) {
165   if (Optional<ProgressEvent> event = ProgressEvent::Create(
166           progress_id, None, completed, total, &GetMostRecentEvent())) {
167     if (event->GetEventType() == progressEnd)
168       m_finished = true;
169 
170     m_last_update_event = *event;
171     ReportIfNeeded();
172   }
173 }
174 
Finished() const175 bool ProgressEventManager::Finished() const { return m_finished; }
176 
ProgressEventReporter(ProgressEventReportCallback report_callback)177 ProgressEventReporter::ProgressEventReporter(
178     ProgressEventReportCallback report_callback)
179     : m_report_callback(report_callback) {
180   m_thread_should_exit = false;
181   m_thread = std::thread([&] {
182     while (!m_thread_should_exit) {
183       std::this_thread::sleep_for(kUpdateProgressEventReportDelay);
184       ReportStartEvents();
185     }
186   });
187 }
188 
~ProgressEventReporter()189 ProgressEventReporter::~ProgressEventReporter() {
190   m_thread_should_exit = true;
191   m_thread.join();
192 }
193 
ReportStartEvents()194 void ProgressEventReporter::ReportStartEvents() {
195   std::lock_guard<std::mutex> locker(m_mutex);
196 
197   while (!m_unreported_start_events.empty()) {
198     ProgressEventManagerSP event_manager = m_unreported_start_events.front();
199     if (event_manager->Finished())
200       m_unreported_start_events.pop();
201     else if (event_manager->ReportIfNeeded())
202       m_unreported_start_events
203           .pop(); // we remove it from the queue as it started reporting
204                   // already, the Push method will be able to continue its
205                   // reports.
206     else
207       break; // If we couldn't report it, then the next event in the queue won't
208              // be able as well, as it came later.
209   }
210 }
211 
Push(uint64_t progress_id,const char * message,uint64_t completed,uint64_t total)212 void ProgressEventReporter::Push(uint64_t progress_id, const char *message,
213                                  uint64_t completed, uint64_t total) {
214   std::lock_guard<std::mutex> locker(m_mutex);
215 
216   auto it = m_event_managers.find(progress_id);
217   if (it == m_event_managers.end()) {
218     if (Optional<ProgressEvent> event =
219             ProgressEvent::Create(progress_id, StringRef(message), completed, total)) {
220       ProgressEventManagerSP event_manager =
221           std::make_shared<ProgressEventManager>(*event, m_report_callback);
222       m_event_managers.insert({progress_id, event_manager});
223       m_unreported_start_events.push(event_manager);
224     }
225   } else {
226     it->second->Update(progress_id, completed, total);
227     if (it->second->Finished())
228       m_event_managers.erase(it);
229   }
230 }
231