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