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