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