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