101263c6cSJonas Devlieghere //===-- ProgressEvent.cpp ---------------------------------------*- C++ -*-===// 201263c6cSJonas Devlieghere // 301263c6cSJonas Devlieghere // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 401263c6cSJonas Devlieghere // See https://llvm.org/LICENSE.txt for license information. 501263c6cSJonas Devlieghere // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 601263c6cSJonas Devlieghere // 701263c6cSJonas Devlieghere //===----------------------------------------------------------------------===// 801263c6cSJonas Devlieghere 901263c6cSJonas Devlieghere #include "ProgressEvent.h" 1001263c6cSJonas Devlieghere 1101263c6cSJonas Devlieghere #include "JSONUtils.h" 1201263c6cSJonas Devlieghere #include "llvm/Support/ErrorHandling.h" 1301263c6cSJonas Devlieghere #include <optional> 1401263c6cSJonas Devlieghere 1501263c6cSJonas Devlieghere using namespace lldb_dap; 1601263c6cSJonas Devlieghere using namespace llvm; 1701263c6cSJonas Devlieghere 1801263c6cSJonas Devlieghere // The minimum duration of an event for it to be reported 1901263c6cSJonas Devlieghere const std::chrono::duration<double> kStartProgressEventReportDelay = 2001263c6cSJonas Devlieghere std::chrono::seconds(1); 2101263c6cSJonas Devlieghere // The minimum time interval between update events for reporting. If multiple 2201263c6cSJonas Devlieghere // updates fall within the same time interval, only the latest is reported. 2301263c6cSJonas Devlieghere const std::chrono::duration<double> kUpdateProgressEventReportDelay = 2401263c6cSJonas Devlieghere std::chrono::milliseconds(250); 2501263c6cSJonas Devlieghere 2601263c6cSJonas Devlieghere ProgressEvent::ProgressEvent(uint64_t progress_id, 2701263c6cSJonas Devlieghere std::optional<StringRef> message, 2801263c6cSJonas Devlieghere uint64_t completed, uint64_t total, 2901263c6cSJonas Devlieghere const ProgressEvent *prev_event) 3001263c6cSJonas Devlieghere : m_progress_id(progress_id) { 3101263c6cSJonas Devlieghere if (message) 3201263c6cSJonas Devlieghere m_message = message->str(); 3301263c6cSJonas Devlieghere 3401263c6cSJonas Devlieghere const bool calculate_percentage = total != UINT64_MAX; 3501263c6cSJonas Devlieghere if (completed == 0) { 3601263c6cSJonas Devlieghere // Start event 3701263c6cSJonas Devlieghere m_event_type = progressStart; 3801263c6cSJonas Devlieghere // Wait a bit before reporting the start event in case in completes really 3901263c6cSJonas Devlieghere // quickly. 4001263c6cSJonas Devlieghere m_minimum_allowed_report_time = 4101263c6cSJonas Devlieghere m_creation_time + kStartProgressEventReportDelay; 4201263c6cSJonas Devlieghere if (calculate_percentage) 4301263c6cSJonas Devlieghere m_percentage = 0; 4401263c6cSJonas Devlieghere } else if (completed == total) { 4501263c6cSJonas Devlieghere // End event 4601263c6cSJonas Devlieghere m_event_type = progressEnd; 4701263c6cSJonas Devlieghere // We should report the end event right away. 4801263c6cSJonas Devlieghere m_minimum_allowed_report_time = std::chrono::seconds::zero(); 4901263c6cSJonas Devlieghere if (calculate_percentage) 5001263c6cSJonas Devlieghere m_percentage = 100; 5101263c6cSJonas Devlieghere } else { 5201263c6cSJonas Devlieghere // Update event 5301263c6cSJonas Devlieghere m_event_type = progressUpdate; 5401263c6cSJonas Devlieghere m_percentage = std::min( 5501263c6cSJonas Devlieghere (uint32_t)((double)completed / (double)total * 100.0), (uint32_t)99); 5601263c6cSJonas Devlieghere if (prev_event->Reported()) { 5701263c6cSJonas Devlieghere // Add a small delay between reports 5801263c6cSJonas Devlieghere m_minimum_allowed_report_time = 5901263c6cSJonas Devlieghere prev_event->m_minimum_allowed_report_time + 6001263c6cSJonas Devlieghere kUpdateProgressEventReportDelay; 6101263c6cSJonas Devlieghere } else { 6201263c6cSJonas Devlieghere // We should use the previous timestamp, as it's still pending 6301263c6cSJonas Devlieghere m_minimum_allowed_report_time = prev_event->m_minimum_allowed_report_time; 6401263c6cSJonas Devlieghere } 6501263c6cSJonas Devlieghere } 6601263c6cSJonas Devlieghere } 6701263c6cSJonas Devlieghere 6801263c6cSJonas Devlieghere std::optional<ProgressEvent> 6901263c6cSJonas Devlieghere ProgressEvent::Create(uint64_t progress_id, std::optional<StringRef> message, 7001263c6cSJonas Devlieghere uint64_t completed, uint64_t total, 7101263c6cSJonas Devlieghere const ProgressEvent *prev_event) { 7201263c6cSJonas Devlieghere // If it's an update without a previous event, we abort 7301263c6cSJonas Devlieghere if (completed > 0 && completed < total && !prev_event) 7401263c6cSJonas Devlieghere return std::nullopt; 7501263c6cSJonas Devlieghere ProgressEvent event(progress_id, message, completed, total, prev_event); 7601263c6cSJonas Devlieghere // We shouldn't show unnamed start events in the IDE 7701263c6cSJonas Devlieghere if (event.GetEventType() == progressStart && event.GetEventName().empty()) 7801263c6cSJonas Devlieghere return std::nullopt; 7901263c6cSJonas Devlieghere 8001263c6cSJonas Devlieghere if (prev_event && prev_event->EqualsForIDE(event)) 8101263c6cSJonas Devlieghere return std::nullopt; 8201263c6cSJonas Devlieghere 8301263c6cSJonas Devlieghere return event; 8401263c6cSJonas Devlieghere } 8501263c6cSJonas Devlieghere 8601263c6cSJonas Devlieghere bool ProgressEvent::EqualsForIDE(const ProgressEvent &other) const { 8701263c6cSJonas Devlieghere return m_progress_id == other.m_progress_id && 8801263c6cSJonas Devlieghere m_event_type == other.m_event_type && 8901263c6cSJonas Devlieghere m_percentage == other.m_percentage; 9001263c6cSJonas Devlieghere } 9101263c6cSJonas Devlieghere 9201263c6cSJonas Devlieghere ProgressEventType ProgressEvent::GetEventType() const { return m_event_type; } 9301263c6cSJonas Devlieghere 9401263c6cSJonas Devlieghere StringRef ProgressEvent::GetEventName() const { 9501263c6cSJonas Devlieghere switch (m_event_type) { 9601263c6cSJonas Devlieghere case progressStart: 9701263c6cSJonas Devlieghere return "progressStart"; 9801263c6cSJonas Devlieghere case progressUpdate: 9901263c6cSJonas Devlieghere return "progressUpdate"; 10001263c6cSJonas Devlieghere case progressEnd: 10101263c6cSJonas Devlieghere return "progressEnd"; 10201263c6cSJonas Devlieghere } 10301263c6cSJonas Devlieghere llvm_unreachable("All cases handled above!"); 10401263c6cSJonas Devlieghere } 10501263c6cSJonas Devlieghere 10601263c6cSJonas Devlieghere json::Value ProgressEvent::ToJSON() const { 10701263c6cSJonas Devlieghere llvm::json::Object event(CreateEventObject(GetEventName())); 10801263c6cSJonas Devlieghere llvm::json::Object body; 10901263c6cSJonas Devlieghere 11001263c6cSJonas Devlieghere std::string progress_id_str; 11101263c6cSJonas Devlieghere llvm::raw_string_ostream progress_id_strm(progress_id_str); 11201263c6cSJonas Devlieghere progress_id_strm << m_progress_id; 11301263c6cSJonas Devlieghere body.try_emplace("progressId", progress_id_str); 11401263c6cSJonas Devlieghere 11501263c6cSJonas Devlieghere if (m_event_type == progressStart) { 11601263c6cSJonas Devlieghere EmplaceSafeString(body, "title", m_message); 11701263c6cSJonas Devlieghere body.try_emplace("cancellable", false); 11801263c6cSJonas Devlieghere } 11901263c6cSJonas Devlieghere 120*a939a9fdSJacob Lalonde if (m_event_type == progressUpdate) 121*a939a9fdSJacob Lalonde EmplaceSafeString(body, "message", m_message); 122*a939a9fdSJacob Lalonde 12301263c6cSJonas Devlieghere std::string timestamp(llvm::formatv("{0:f9}", m_creation_time.count())); 12401263c6cSJonas Devlieghere EmplaceSafeString(body, "timestamp", timestamp); 12501263c6cSJonas Devlieghere 12601263c6cSJonas Devlieghere if (m_percentage) 12701263c6cSJonas Devlieghere body.try_emplace("percentage", *m_percentage); 12801263c6cSJonas Devlieghere 12901263c6cSJonas Devlieghere event.try_emplace("body", std::move(body)); 13001263c6cSJonas Devlieghere return json::Value(std::move(event)); 13101263c6cSJonas Devlieghere } 13201263c6cSJonas Devlieghere 13301263c6cSJonas Devlieghere bool ProgressEvent::Report(ProgressEventReportCallback callback) { 13401263c6cSJonas Devlieghere if (Reported()) 13501263c6cSJonas Devlieghere return true; 13601263c6cSJonas Devlieghere if (std::chrono::system_clock::now().time_since_epoch() < 13701263c6cSJonas Devlieghere m_minimum_allowed_report_time) 13801263c6cSJonas Devlieghere return false; 13901263c6cSJonas Devlieghere 14001263c6cSJonas Devlieghere m_reported = true; 14101263c6cSJonas Devlieghere callback(*this); 14201263c6cSJonas Devlieghere return true; 14301263c6cSJonas Devlieghere } 14401263c6cSJonas Devlieghere 14501263c6cSJonas Devlieghere bool ProgressEvent::Reported() const { return m_reported; } 14601263c6cSJonas Devlieghere 14701263c6cSJonas Devlieghere ProgressEventManager::ProgressEventManager( 14801263c6cSJonas Devlieghere const ProgressEvent &start_event, 14901263c6cSJonas Devlieghere ProgressEventReportCallback report_callback) 15001263c6cSJonas Devlieghere : m_start_event(start_event), m_finished(false), 15101263c6cSJonas Devlieghere m_report_callback(report_callback) {} 15201263c6cSJonas Devlieghere 15301263c6cSJonas Devlieghere bool ProgressEventManager::ReportIfNeeded() { 15401263c6cSJonas Devlieghere // The event finished before we were able to report it. 15501263c6cSJonas Devlieghere if (!m_start_event.Reported() && Finished()) 15601263c6cSJonas Devlieghere return true; 15701263c6cSJonas Devlieghere 15801263c6cSJonas Devlieghere if (!m_start_event.Report(m_report_callback)) 15901263c6cSJonas Devlieghere return false; 16001263c6cSJonas Devlieghere 16101263c6cSJonas Devlieghere if (m_last_update_event) 16201263c6cSJonas Devlieghere m_last_update_event->Report(m_report_callback); 16301263c6cSJonas Devlieghere return true; 16401263c6cSJonas Devlieghere } 16501263c6cSJonas Devlieghere 16601263c6cSJonas Devlieghere const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const { 16701263c6cSJonas Devlieghere return m_last_update_event ? *m_last_update_event : m_start_event; 16801263c6cSJonas Devlieghere } 16901263c6cSJonas Devlieghere 170*a939a9fdSJacob Lalonde void ProgressEventManager::Update(uint64_t progress_id, llvm::StringRef message, 171*a939a9fdSJacob Lalonde uint64_t completed, uint64_t total) { 17201263c6cSJonas Devlieghere if (std::optional<ProgressEvent> event = ProgressEvent::Create( 173*a939a9fdSJacob Lalonde progress_id, message, completed, total, &GetMostRecentEvent())) { 17401263c6cSJonas Devlieghere if (event->GetEventType() == progressEnd) 17501263c6cSJonas Devlieghere m_finished = true; 17601263c6cSJonas Devlieghere 17701263c6cSJonas Devlieghere m_last_update_event = *event; 17801263c6cSJonas Devlieghere ReportIfNeeded(); 17901263c6cSJonas Devlieghere } 18001263c6cSJonas Devlieghere } 18101263c6cSJonas Devlieghere 18201263c6cSJonas Devlieghere bool ProgressEventManager::Finished() const { return m_finished; } 18301263c6cSJonas Devlieghere 18401263c6cSJonas Devlieghere ProgressEventReporter::ProgressEventReporter( 18501263c6cSJonas Devlieghere ProgressEventReportCallback report_callback) 18601263c6cSJonas Devlieghere : m_report_callback(report_callback) { 18701263c6cSJonas Devlieghere m_thread_should_exit = false; 18801263c6cSJonas Devlieghere m_thread = std::thread([&] { 18901263c6cSJonas Devlieghere while (!m_thread_should_exit) { 19001263c6cSJonas Devlieghere std::this_thread::sleep_for(kUpdateProgressEventReportDelay); 19101263c6cSJonas Devlieghere ReportStartEvents(); 19201263c6cSJonas Devlieghere } 19301263c6cSJonas Devlieghere }); 19401263c6cSJonas Devlieghere } 19501263c6cSJonas Devlieghere 19601263c6cSJonas Devlieghere ProgressEventReporter::~ProgressEventReporter() { 19701263c6cSJonas Devlieghere m_thread_should_exit = true; 19801263c6cSJonas Devlieghere m_thread.join(); 19901263c6cSJonas Devlieghere } 20001263c6cSJonas Devlieghere 20101263c6cSJonas Devlieghere void ProgressEventReporter::ReportStartEvents() { 20201263c6cSJonas Devlieghere std::lock_guard<std::mutex> locker(m_mutex); 20301263c6cSJonas Devlieghere 20401263c6cSJonas Devlieghere while (!m_unreported_start_events.empty()) { 20501263c6cSJonas Devlieghere ProgressEventManagerSP event_manager = m_unreported_start_events.front(); 20601263c6cSJonas Devlieghere if (event_manager->Finished()) 20701263c6cSJonas Devlieghere m_unreported_start_events.pop(); 20801263c6cSJonas Devlieghere else if (event_manager->ReportIfNeeded()) 20901263c6cSJonas Devlieghere m_unreported_start_events 21001263c6cSJonas Devlieghere .pop(); // we remove it from the queue as it started reporting 21101263c6cSJonas Devlieghere // already, the Push method will be able to continue its 21201263c6cSJonas Devlieghere // reports. 21301263c6cSJonas Devlieghere else 21401263c6cSJonas Devlieghere break; // If we couldn't report it, then the next event in the queue won't 21501263c6cSJonas Devlieghere // be able as well, as it came later. 21601263c6cSJonas Devlieghere } 21701263c6cSJonas Devlieghere } 21801263c6cSJonas Devlieghere 21901263c6cSJonas Devlieghere void ProgressEventReporter::Push(uint64_t progress_id, const char *message, 22001263c6cSJonas Devlieghere uint64_t completed, uint64_t total) { 22101263c6cSJonas Devlieghere std::lock_guard<std::mutex> locker(m_mutex); 22201263c6cSJonas Devlieghere 22301263c6cSJonas Devlieghere auto it = m_event_managers.find(progress_id); 22401263c6cSJonas Devlieghere if (it == m_event_managers.end()) { 22501263c6cSJonas Devlieghere if (std::optional<ProgressEvent> event = ProgressEvent::Create( 22601263c6cSJonas Devlieghere progress_id, StringRef(message), completed, total)) { 22701263c6cSJonas Devlieghere ProgressEventManagerSP event_manager = 22801263c6cSJonas Devlieghere std::make_shared<ProgressEventManager>(*event, m_report_callback); 22901263c6cSJonas Devlieghere m_event_managers.insert({progress_id, event_manager}); 23001263c6cSJonas Devlieghere m_unreported_start_events.push(event_manager); 23101263c6cSJonas Devlieghere } 23201263c6cSJonas Devlieghere } else { 233*a939a9fdSJacob Lalonde it->second->Update(progress_id, StringRef(message), completed, total); 23401263c6cSJonas Devlieghere if (it->second->Finished()) 23501263c6cSJonas Devlieghere m_event_managers.erase(it); 23601263c6cSJonas Devlieghere } 23701263c6cSJonas Devlieghere } 238