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-exception
6//
7//===----------------------------------------------------------------------===//
8
9#ifndef LLDB_TOOLS_LLDB_DAP_PROGRESS_EVENT_H
10#define LLDB_TOOLS_LLDB_DAP_PROGRESS_EVENT_H
11
12#include <atomic>
13#include <chrono>
14#include <mutex>
15#include <optional>
16#include <queue>
17#include <thread>
18
19#include "llvm/Support/JSON.h"
20
21namespace lldb_dap {
22
23enum ProgressEventType { progressStart, progressUpdate, progressEnd };
24
25class ProgressEvent;
26using ProgressEventReportCallback = std::function<void(ProgressEvent &)>;
27
28class ProgressEvent {
29public:
30 /// Actual constructor to use that returns an optional, as the event might be
31 /// not apt for the IDE, e.g. an unnamed start event, or a redundant one.
32 ///
33 /// \param[in] progress_id
34 /// ID for this event.
35 ///
36 /// \param[in] message
37 /// Message to display in the UI. Required for start events.
38 ///
39 /// \param[in] completed
40 /// Number of jobs completed.
41 ///
42 /// \param[in] total
43 /// Total number of jobs, or \b UINT64_MAX if not determined.
44 ///
45 /// \param[in] prev_event
46 /// Previous event if this one is an update. If \b nullptr, then a start
47 /// event will be created.
48 static std::optional<ProgressEvent>
49 Create(uint64_t progress_id, std::optional<llvm::StringRef> message,
50 uint64_t completed, uint64_t total,
51 const ProgressEvent *prev_event = nullptr);
52
53 llvm::json::Value ToJSON() const;
54
55 /// \return
56 /// \b true if two event messages would result in the same event for the
57 /// IDE, e.g. same rounded percentage.
58 bool EqualsForIDE(const ProgressEvent &other) const;
59
60 llvm::StringRef GetEventName() const;
61
62 ProgressEventType GetEventType() const;
63
64 /// Report this progress event to the provided callback only if enough time
65 /// has passed since the creation of the event and since the previous reported
66 /// update.
67 bool Report(ProgressEventReportCallback callback);
68
69 bool Reported() const;
70
71private:
72 ProgressEvent(uint64_t progress_id, std::optional<llvm::StringRef> message,
73 uint64_t completed, uint64_t total,
74 const ProgressEvent *prev_event);
75
76 uint64_t m_progress_id;
77 std::string m_message;
78 ProgressEventType m_event_type;
79 std::optional<uint32_t> m_percentage;
80 std::chrono::duration<double> m_creation_time =
81 std::chrono::system_clock::now().time_since_epoch();
82 std::chrono::duration<double> m_minimum_allowed_report_time;
83 bool m_reported = false;
84};
85
86/// Class that keeps the start event and its most recent update.
87/// It controls when the event should start being reported to the IDE.
88class ProgressEventManager {
89public:
90 ProgressEventManager(const ProgressEvent &start_event,
91 ProgressEventReportCallback report_callback);
92
93 /// Report the start event and the most recent update if the event has lasted
94 /// for long enough.
95 ///
96 /// \return
97 /// \b false if the event hasn't finished and hasn't reported anything
98 /// yet.
99 bool ReportIfNeeded();
100
101 /// Receive a new progress event for the start event and try to report it if
102 /// appropriate.
103 void Update(uint64_t progress_id, llvm::StringRef message, uint64_t completed,
104 uint64_t total);
105
106 /// \return
107 /// \b true if a \a progressEnd event has been notified. There's no
108 /// need to try to report manually an event that has finished.
109 bool Finished() const;
110
111 const ProgressEvent &GetMostRecentEvent() const;
112
113private:
114 ProgressEvent m_start_event;
115 std::optional<ProgressEvent> m_last_update_event;
116 bool m_finished;
117 ProgressEventReportCallback m_report_callback;
118};
119
120using ProgressEventManagerSP = std::shared_ptr<ProgressEventManager>;
121
122/// Class that filters out progress event messages that shouldn't be reported
123/// to the IDE, because they are invalid, they carry no new information, or they
124/// don't last long enough.
125///
126/// We need to limit the amount of events that are sent to the IDE, as they slow
127/// the render thread of the UI user, and they end up spamming the DAP
128/// connection, which also takes some processing time out of the IDE.
129class ProgressEventReporter {
130public:
131 /// \param[in] report_callback
132 /// Function to invoke to report the event to the IDE.
133 explicit ProgressEventReporter(ProgressEventReportCallback report_callback);
134
135 ProgressEventReporter(const ProgressEventReporter &) = delete;
136 ProgressEventReporter(ProgressEventReporter &&) = delete;
137 ProgressEventReporter &operator=(const ProgressEventReporter &) = delete;
138 ProgressEventReporter &operator=(ProgressEventReporter &&) = delete;
139 ~ProgressEventReporter();
140
141 /// Add a new event to the internal queue and report the event if
142 /// appropriate.
143 void Push(uint64_t progress_id, const char *message, uint64_t completed,
144 uint64_t total);
145
146private:
147 /// Report to the IDE events that haven't been reported to the IDE and have
148 /// lasted long enough.
149 void ReportStartEvents();
150
151 ProgressEventReportCallback m_report_callback;
152 std::map<uint64_t, ProgressEventManagerSP> m_event_managers;
153 /// Queue of start events in chronological order
154 std::queue<ProgressEventManagerSP> m_unreported_start_events;
155 /// Thread used to invoke \a ReportStartEvents periodically.
156 std::thread m_thread;
157 std::atomic<bool> m_thread_should_exit;
158 /// Mutex that prevents running \a Push and \a ReportStartEvents
159 /// simultaneously, as both read and modify the same underlying objects.
160 std::mutex m_mutex;
161};
162
163} // namespace lldb_dap
164
165#endif // LLDB_TOOLS_LLDB_DAP_PROGRESS_EVENT_H
166

source code of lldb/tools/lldb-dap/ProgressEvent.h