238 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "ProgressEvent.h"10 11#include "JSONUtils.h"12#include "llvm/Support/ErrorHandling.h"13#include <optional>14 15using namespace lldb_dap;16using namespace llvm;17 18// The minimum duration of an event for it to be reported19const std::chrono::duration<double> kStartProgressEventReportDelay =20 std::chrono::seconds(1);21// The minimum time interval between update events for reporting. If multiple22// updates fall within the same time interval, only the latest is reported.23const std::chrono::duration<double> kUpdateProgressEventReportDelay =24 std::chrono::milliseconds(250);25 26ProgressEvent::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 event37 m_event_type = progressStart;38 // Wait a bit before reporting the start event in case in completes really39 // 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 event46 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 event53 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 reports58 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 pending63 m_minimum_allowed_report_time = prev_event->m_minimum_allowed_report_time;64 }65 }66}67 68std::optional<ProgressEvent>69ProgressEvent::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 abort73 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 IDE77 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 86bool 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 && m_message == other.m_message;90}91 92ProgressEventType ProgressEvent::GetEventType() const { return m_event_type; }93 94StringRef 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 106json::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 133bool 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 145bool ProgressEvent::Reported() const { return m_reported; }146 147ProgressEventManager::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 153bool 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 166const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const {167 return m_last_update_event ? *m_last_update_event : m_start_event;168}169 170void 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 182bool ProgressEventManager::Finished() const { return m_finished; }183 184ProgressEventReporter::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 196ProgressEventReporter::~ProgressEventReporter() {197 m_thread_should_exit = true;198 m_thread.join();199}200 201void 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_events210 .pop(); // we remove it from the queue as it started reporting211 // already, the Push method will be able to continue its212 // reports.213 else214 break; // If we couldn't report it, then the next event in the queue won't215 // be able as well, as it came later.216 }217}218 219void 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