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 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> 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 85 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 91 ProgressEventType ProgressEvent::GetEventType() const { return m_event_type; } 92 93 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 102 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 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 139 bool ProgressEvent::Reported() const { return m_reported; } 140 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 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 160 const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const { 161 return m_last_update_event ? *m_last_update_event : m_start_event; 162 } 163 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 176 bool ProgressEventManager::Finished() const { return m_finished; } 177 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 190 ProgressEventReporter::~ProgressEventReporter() { 191 m_thread_should_exit = true; 192 m_thread.join(); 193 } 194 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 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