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_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 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 84 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 90 ProgressEventType ProgressEvent::GetEventType() const { return m_event_type; } 91 92 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 101 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 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 138 bool ProgressEvent::Reported() const { return m_reported; } 139 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 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 159 const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const { 160 return m_last_update_event ? *m_last_update_event : m_start_event; 161 } 162 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 175 bool ProgressEventManager::Finished() const { return m_finished; } 176 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 189 ProgressEventReporter::~ProgressEventReporter() { 190 m_thread_should_exit = true; 191 m_thread.join(); 192 } 193 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 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