1 //===-- ProgressEvent.cpp ---------------------------------------*- C++ -*-===//
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-exception
7 //===----------------------------------------------------------------------===//
9 #include "ProgressEvent.h"
11 #include "JSONUtils.h"
12 #include "llvm/Support/ErrorHandling.h"
15 using namespace lldb_dap
;
18 // The minimum duration of an event for it to be reported
19 const std::chrono::duration
<double> kStartProgressEventReportDelay
=
20 std::chrono::seconds(1);
21 // The minimum time interval between update events for reporting. If multiple
22 // updates fall within the same time interval, only the latest is reported.
23 const std::chrono::duration
<double> kUpdateProgressEventReportDelay
=
24 std::chrono::milliseconds(250);
26 ProgressEvent::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
) {
32 m_message
= message
->str();
34 const bool calculate_percentage
= total
!= UINT64_MAX
;
37 m_event_type
= progressStart
;
38 // Wait a bit before reporting the start event in case in completes really
40 m_minimum_allowed_report_time
=
41 m_creation_time
+ kStartProgressEventReportDelay
;
42 if (calculate_percentage
)
44 } else if (completed
== total
) {
46 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
)
53 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 reports
58 m_minimum_allowed_report_time
=
59 prev_event
->m_minimum_allowed_report_time
+
60 kUpdateProgressEventReportDelay
;
62 // We should use the previous timestamp, as it's still pending
63 m_minimum_allowed_report_time
= prev_event
->m_minimum_allowed_report_time
;
68 std::optional
<ProgressEvent
>
69 ProgressEvent::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 abort
73 if (completed
> 0 && completed
< total
&& !prev_event
)
75 ProgressEvent
event(progress_id
, message
, completed
, total
, prev_event
);
76 // We shouldn't show unnamed start events in the IDE
77 if (event
.GetEventType() == progressStart
&& event
.GetEventName().empty())
80 if (prev_event
&& prev_event
->EqualsForIDE(event
))
86 bool 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
;
92 ProgressEventType
ProgressEvent::GetEventType() const { return m_event_type
; }
94 StringRef
ProgressEvent::GetEventName() const {
95 switch (m_event_type
) {
97 return "progressStart";
99 return "progressUpdate";
101 return "progressEnd";
103 llvm_unreachable("All cases handled above!");
106 json::Value
ProgressEvent::ToJSON() const {
107 llvm::json::Object
event(CreateEventObject(GetEventName()));
108 llvm::json::Object body
;
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
);
115 if (m_event_type
== progressStart
) {
116 EmplaceSafeString(body
, "title", m_message
);
117 body
.try_emplace("cancellable", false);
120 std::string
timestamp(llvm::formatv("{0:f9}", m_creation_time
.count()));
121 EmplaceSafeString(body
, "timestamp", timestamp
);
124 body
.try_emplace("percentage", *m_percentage
);
126 event
.try_emplace("body", std::move(body
));
127 return json::Value(std::move(event
));
130 bool ProgressEvent::Report(ProgressEventReportCallback callback
) {
133 if (std::chrono::system_clock::now().time_since_epoch() <
134 m_minimum_allowed_report_time
)
142 bool ProgressEvent::Reported() const { return m_reported
; }
144 ProgressEventManager::ProgressEventManager(
145 const ProgressEvent
&start_event
,
146 ProgressEventReportCallback report_callback
)
147 : m_start_event(start_event
), m_finished(false),
148 m_report_callback(report_callback
) {}
150 bool ProgressEventManager::ReportIfNeeded() {
151 // The event finished before we were able to report it.
152 if (!m_start_event
.Reported() && Finished())
155 if (!m_start_event
.Report(m_report_callback
))
158 if (m_last_update_event
)
159 m_last_update_event
->Report(m_report_callback
);
163 const ProgressEvent
&ProgressEventManager::GetMostRecentEvent() const {
164 return m_last_update_event
? *m_last_update_event
: m_start_event
;
167 void ProgressEventManager::Update(uint64_t progress_id
, uint64_t completed
,
169 if (std::optional
<ProgressEvent
> event
= ProgressEvent::Create(
170 progress_id
, std::nullopt
, completed
, total
, &GetMostRecentEvent())) {
171 if (event
->GetEventType() == progressEnd
)
174 m_last_update_event
= *event
;
179 bool ProgressEventManager::Finished() const { return m_finished
; }
181 ProgressEventReporter::ProgressEventReporter(
182 ProgressEventReportCallback report_callback
)
183 : m_report_callback(report_callback
) {
184 m_thread_should_exit
= false;
185 m_thread
= std::thread([&] {
186 while (!m_thread_should_exit
) {
187 std::this_thread::sleep_for(kUpdateProgressEventReportDelay
);
193 ProgressEventReporter::~ProgressEventReporter() {
194 m_thread_should_exit
= true;
198 void ProgressEventReporter::ReportStartEvents() {
199 std::lock_guard
<std::mutex
> locker(m_mutex
);
201 while (!m_unreported_start_events
.empty()) {
202 ProgressEventManagerSP event_manager
= m_unreported_start_events
.front();
203 if (event_manager
->Finished())
204 m_unreported_start_events
.pop();
205 else if (event_manager
->ReportIfNeeded())
206 m_unreported_start_events
207 .pop(); // we remove it from the queue as it started reporting
208 // already, the Push method will be able to continue its
211 break; // If we couldn't report it, then the next event in the queue won't
212 // be able as well, as it came later.
216 void ProgressEventReporter::Push(uint64_t progress_id
, const char *message
,
217 uint64_t completed
, uint64_t total
) {
218 std::lock_guard
<std::mutex
> locker(m_mutex
);
220 auto it
= m_event_managers
.find(progress_id
);
221 if (it
== m_event_managers
.end()) {
222 if (std::optional
<ProgressEvent
> event
= ProgressEvent::Create(
223 progress_id
, StringRef(message
), completed
, total
)) {
224 ProgressEventManagerSP event_manager
=
225 std::make_shared
<ProgressEventManager
>(*event
, m_report_callback
);
226 m_event_managers
.insert({progress_id
, event_manager
});
227 m_unreported_start_events
.push(event_manager
);
230 it
->second
->Update(progress_id
, completed
, total
);
231 if (it
->second
->Finished())
232 m_event_managers
.erase(it
);