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 14using namespace lldb_vscode; 15using namespace llvm; 16 17// The minimum duration of an event for it to be reported 18const 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. 22const std::chrono::duration<double> kUpdateProgressEventReportDelay = 23 std::chrono::milliseconds(250); 24 25ProgressEvent::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 67std::optional<ProgressEvent> 68ProgressEvent::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 85bool 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 91ProgressEventType ProgressEvent::GetEventType() const { return m_event_type; } 92 93StringRef 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 102json::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 127bool 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 139bool ProgressEvent::Reported() const { return m_reported; } 140 141ProgressEventManager::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 147bool 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 160const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const { 161 return m_last_update_event ? *m_last_update_event : m_start_event; 162} 163 164void 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 176bool ProgressEventManager::Finished() const { return m_finished; } 177 178ProgressEventReporter::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 190ProgressEventReporter::~ProgressEventReporter() { 191 m_thread_should_exit = true; 192 m_thread.join(); 193} 194 195void 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 213void 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