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