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