1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
|
//===-- ProgressEvent.cpp ---------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "ProgressEvent.h"
#include "JSONUtils.h"
#include <optional>
using namespace lldb_dap;
using namespace llvm;
// The minimum duration of an event for it to be reported
const std::chrono::duration<double> kStartProgressEventReportDelay =
std::chrono::seconds(1);
// The minimum time interval between update events for reporting. If multiple
// updates fall within the same time interval, only the latest is reported.
const std::chrono::duration<double> kUpdateProgressEventReportDelay =
std::chrono::milliseconds(250);
ProgressEvent::ProgressEvent(uint64_t progress_id,
std::optional<StringRef> message,
uint64_t completed, uint64_t total,
const ProgressEvent *prev_event)
: m_progress_id(progress_id) {
if (message)
m_message = message->str();
const bool calculate_percentage = total != UINT64_MAX;
if (completed == 0) {
// Start event
m_event_type = progressStart;
// Wait a bit before reporting the start event in case in completes really
// quickly.
m_minimum_allowed_report_time =
m_creation_time + kStartProgressEventReportDelay;
if (calculate_percentage)
m_percentage = 0;
} else if (completed == total) {
// End event
m_event_type = progressEnd;
// We should report the end event right away.
m_minimum_allowed_report_time = std::chrono::seconds::zero();
if (calculate_percentage)
m_percentage = 100;
} else {
// Update event
m_event_type = progressUpdate;
m_percentage = std::min(
(uint32_t)((double)completed / (double)total * 100.0), (uint32_t)99);
if (prev_event->Reported()) {
// Add a small delay between reports
m_minimum_allowed_report_time =
prev_event->m_minimum_allowed_report_time +
kUpdateProgressEventReportDelay;
} else {
// We should use the previous timestamp, as it's still pending
m_minimum_allowed_report_time = prev_event->m_minimum_allowed_report_time;
}
}
}
std::optional<ProgressEvent>
ProgressEvent::Create(uint64_t progress_id, std::optional<StringRef> message,
uint64_t completed, uint64_t total,
const ProgressEvent *prev_event) {
// If it's an update without a previous event, we abort
if (completed > 0 && completed < total && !prev_event)
return std::nullopt;
ProgressEvent event(progress_id, message, completed, total, prev_event);
// We shouldn't show unnamed start events in the IDE
if (event.GetEventType() == progressStart && event.GetEventName().empty())
return std::nullopt;
if (prev_event && prev_event->EqualsForIDE(event))
return std::nullopt;
return event;
}
bool ProgressEvent::EqualsForIDE(const ProgressEvent &other) const {
return m_progress_id == other.m_progress_id &&
m_event_type == other.m_event_type &&
m_percentage == other.m_percentage;
}
ProgressEventType ProgressEvent::GetEventType() const { return m_event_type; }
StringRef ProgressEvent::GetEventName() const {
if (m_event_type == progressStart)
return "progressStart";
else if (m_event_type == progressEnd)
return "progressEnd";
else
return "progressUpdate";
}
json::Value ProgressEvent::ToJSON() const {
llvm::json::Object event(CreateEventObject(GetEventName()));
llvm::json::Object body;
std::string progress_id_str;
llvm::raw_string_ostream progress_id_strm(progress_id_str);
progress_id_strm << m_progress_id;
progress_id_strm.flush();
body.try_emplace("progressId", progress_id_str);
if (m_event_type == progressStart) {
EmplaceSafeString(body, "title", m_message);
body.try_emplace("cancellable", false);
}
std::string timestamp(llvm::formatv("{0:f9}", m_creation_time.count()));
EmplaceSafeString(body, "timestamp", timestamp);
if (m_percentage)
body.try_emplace("percentage", *m_percentage);
event.try_emplace("body", std::move(body));
return json::Value(std::move(event));
}
bool ProgressEvent::Report(ProgressEventReportCallback callback) {
if (Reported())
return true;
if (std::chrono::system_clock::now().time_since_epoch() <
m_minimum_allowed_report_time)
return false;
m_reported = true;
callback(*this);
return true;
}
bool ProgressEvent::Reported() const { return m_reported; }
ProgressEventManager::ProgressEventManager(
const ProgressEvent &start_event,
ProgressEventReportCallback report_callback)
: m_start_event(start_event), m_finished(false),
m_report_callback(report_callback) {}
bool ProgressEventManager::ReportIfNeeded() {
// The event finished before we were able to report it.
if (!m_start_event.Reported() && Finished())
return true;
if (!m_start_event.Report(m_report_callback))
return false;
if (m_last_update_event)
m_last_update_event->Report(m_report_callback);
return true;
}
const ProgressEvent &ProgressEventManager::GetMostRecentEvent() const {
return m_last_update_event ? *m_last_update_event : m_start_event;
}
void ProgressEventManager::Update(uint64_t progress_id, uint64_t completed,
uint64_t total) {
if (std::optional<ProgressEvent> event = ProgressEvent::Create(
progress_id, std::nullopt, completed, total, &GetMostRecentEvent())) {
if (event->GetEventType() == progressEnd)
m_finished = true;
m_last_update_event = *event;
ReportIfNeeded();
}
}
bool ProgressEventManager::Finished() const { return m_finished; }
ProgressEventReporter::ProgressEventReporter(
ProgressEventReportCallback report_callback)
: m_report_callback(report_callback) {
m_thread_should_exit = false;
m_thread = std::thread([&] {
while (!m_thread_should_exit) {
std::this_thread::sleep_for(kUpdateProgressEventReportDelay);
ReportStartEvents();
}
});
}
ProgressEventReporter::~ProgressEventReporter() {
m_thread_should_exit = true;
m_thread.join();
}
void ProgressEventReporter::ReportStartEvents() {
std::lock_guard<std::mutex> locker(m_mutex);
while (!m_unreported_start_events.empty()) {
ProgressEventManagerSP event_manager = m_unreported_start_events.front();
if (event_manager->Finished())
m_unreported_start_events.pop();
else if (event_manager->ReportIfNeeded())
m_unreported_start_events
.pop(); // we remove it from the queue as it started reporting
// already, the Push method will be able to continue its
// reports.
else
break; // If we couldn't report it, then the next event in the queue won't
// be able as well, as it came later.
}
}
void ProgressEventReporter::Push(uint64_t progress_id, const char *message,
uint64_t completed, uint64_t total) {
std::lock_guard<std::mutex> locker(m_mutex);
auto it = m_event_managers.find(progress_id);
if (it == m_event_managers.end()) {
if (std::optional<ProgressEvent> event = ProgressEvent::Create(
progress_id, StringRef(message), completed, total)) {
ProgressEventManagerSP event_manager =
std::make_shared<ProgressEventManager>(*event, m_report_callback);
m_event_managers.insert({progress_id, event_manager});
m_unreported_start_events.push(event_manager);
}
} else {
it->second->Update(progress_id, completed, total);
if (it->second->Finished())
m_event_managers.erase(it);
}
}
|