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