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