forked from OSchip/llvm-project
231 lines
7.7 KiB
C++
231 lines
7.7 KiB
C++
//===-- 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"
|
|
|
|
using namespace lldb_vscode;
|
|
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, 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
Optional<ProgressEvent> ProgressEvent::Create(uint64_t progress_id,
|
|
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 None;
|
|
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 None;
|
|
|
|
if (prev_event && prev_event->EqualsForIDE(event))
|
|
return None;
|
|
|
|
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 (Optional<ProgressEvent> event = ProgressEvent::Create(
|
|
progress_id, None, 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 (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);
|
|
}
|
|
}
|