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