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