wallace updated this revision to Diff 342386. wallace marked 12 inline comments as done. wallace added a comment.
Address all comments Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D101128/new/ https://reviews.llvm.org/D101128 Files: lldb/tools/lldb-vscode/ProgressEvent.cpp lldb/tools/lldb-vscode/ProgressEvent.h lldb/tools/lldb-vscode/VSCode.h
Index: lldb/tools/lldb-vscode/VSCode.h =================================================================== --- lldb/tools/lldb-vscode/VSCode.h +++ lldb/tools/lldb-vscode/VSCode.h @@ -125,10 +125,6 @@ int64_t GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const; ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter); ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id); - // Send the JSON in "json_str" to the "out" stream. Correctly send the - // "Content-Length:" field followed by the length, followed by the raw - // JSON bytes. - void SendJSON(const std::string &json_str); // Serialize the JSON value into a string and send the JSON packet to // the "out" stream. @@ -208,6 +204,12 @@ /// The callback to execute when the given request is triggered by the /// IDE. void RegisterRequestCallback(std::string request, RequestCallback callback); + +private: + // Send the JSON in "json_str" to the "out" stream. Correctly send the + // "Content-Length:" field followed by the length, followed by the raw + // JSON bytes. + void SendJSON(const std::string &json_str); }; extern VSCode g_vsc; Index: lldb/tools/lldb-vscode/ProgressEvent.h =================================================================== --- lldb/tools/lldb-vscode/ProgressEvent.h +++ lldb/tools/lldb-vscode/ProgressEvent.h @@ -6,6 +6,10 @@ // //===----------------------------------------------------------------------===// +#include <mutex> +#include <queue> +#include <thread> + #include "VSCodeForward.h" #include "llvm/Support/JSON.h" @@ -28,35 +32,108 @@ llvm::json::Value ToJSON() const; - /// This operator returns \b true if two event messages - /// would result in the same event for the IDE, e.g. - /// same rounded percentage. - bool operator==(const ProgressEvent &other) const; + /// \return + /// \b true if two event messages would result in the same event for the + /// IDE, e.g. same rounded percentage. + bool EqualsForIDE(const ProgressEvent &other) const; const char *GetEventName() const; + ProgressEventType GetEventType() const; + bool IsValid() const; uint64_t GetID() const; + void MarkAsReported(); + + bool Reported() const; + + std::chrono::duration<double> GetTimestamp() const; + private: uint64_t m_progress_id; - const char *m_message; + std::string m_message; ProgressEventType m_event_type; llvm::Optional<uint32_t> m_percentage; + std::chrono::duration<double> m_timestamp; + bool m_reported; +}; + +using ProgressEventReportCallback = std::function<void(ProgressEvent &)>; + +/// Class that keeps the start event and its most recent update. +/// It controls when the event should start being reported to the IDE. +class ProgressEventManager { +public: + ProgressEventManager(const ProgressEvent &start_event, + ProgressEventReportCallback report_callback); + + /// Report the start event and the most recent update if the event has lasted + /// for long enough. + /// + /// \return + /// \b false if the event hasn't finished and hasn't reported anything + /// yet. + bool TryReport(); + + /// Receive a new progress event for the start event and try to report it if + /// appropriate. + void Update(const ProgressEvent &event); + + /// \return + /// \b true if a \a progressEnd event has been notified. There's no + /// need to try to report manually an event that has finished. + bool Finished() const; + + /// \return + /// The ID of the underlying start event. + uint64_t GetID() const; + +private: + ProgressEvent m_start_event; + llvm::Optional<ProgressEvent> m_last_update_event; + std::chrono::duration<double> m_start_event_timestamp; + bool m_finished; + ProgressEventReportCallback m_report_callback; }; +using ProgressEventManagerSP = std::shared_ptr<ProgressEventManager>; + /// Class that filters out progress event messages that shouldn't be reported -/// to the IDE, either because they are invalid or because they are too chatty. +/// to the IDE, because they are invalid, they carry no new information, or they +/// don't last long enough. +/// +/// We need to limit the amount of events that are sent to the IDE, as they slow +/// the render thread of the UI user, and they end up spamming the DAP +/// connection, which also takes some processing time out of the IDE. class ProgressEventFilterQueue { public: - ProgressEventFilterQueue(std::function<void(ProgressEvent)> callback); + /// \param[in] report_callback + /// Function to invoke to report the event to the IDE. + ProgressEventFilterQueue(ProgressEventReportCallback report_callback); + + ~ProgressEventFilterQueue(); + /// Add a new event to the internal queue and reports the event if + /// appropriate. void Push(const ProgressEvent &event); private: - std::function<void(ProgressEvent)> m_callback; - std::map<uint64_t, ProgressEvent> m_last_events; + /// Report to the IDE events that haven't been reported to the IDE and have + /// lasted long enough. + void ReportWaitingEvents(); + + ProgressEventReportCallback m_report_callback; + std::map<uint64_t, ProgressEventManagerSP> m_event_managers; + /// Queue of event managers in chronological order + std::queue<ProgressEventManagerSP> m_sorted_event_managers; + /// Thread used to invoke \a ReportWaitingEvents periodically. + std::thread m_aux_reporter_thread; + bool m_aux_reporter_thread_should_exit; + /// Mutex that prevents running \a Push and \a ReportWaitingEvents + /// simultaneously, as both read and modify the same underlying objects. + std::mutex m_reporter_mutex; }; } // namespace lldb_vscode Index: lldb/tools/lldb-vscode/ProgressEvent.cpp =================================================================== --- lldb/tools/lldb-vscode/ProgressEvent.cpp +++ lldb/tools/lldb-vscode/ProgressEvent.cpp @@ -13,28 +13,40 @@ using namespace lldb_vscode; using namespace llvm; -ProgressEvent::ProgressEvent(uint64_t progress_id, const char *message, - uint64_t completed, uint64_t total) - : m_progress_id(progress_id), m_message(message) { +static ProgressEventType DetermineProgressEventType(uint64_t completed, + uint64_t total) { if (completed == total) - m_event_type = progressEnd; + return progressEnd; else if (completed == 0) - m_event_type = progressStart; + return progressStart; else if (completed < total) - m_event_type = progressUpdate; + return progressUpdate; else - m_event_type = progressInvalid; + return progressInvalid; +} + +ProgressEvent::ProgressEvent(uint64_t progress_id, const char *message, + uint64_t completed, uint64_t total) + : m_progress_id(progress_id), + m_event_type(DetermineProgressEventType(completed, total)), + m_timestamp(std::chrono::system_clock::now().time_since_epoch()), + m_reported(false) { + + if (m_event_type == progressStart) + m_message = message; if (0 < total && total < UINT64_MAX) m_percentage = (uint32_t)(((float)completed / (float)total) * 100.0); } -bool ProgressEvent::operator==(const ProgressEvent &other) const { +bool ProgressEvent::EqualsForIDE(const ProgressEvent &other) const { return m_progress_id == other.m_progress_id && m_event_type == other.m_event_type && m_percentage == other.m_percentage; } +ProgressEventType ProgressEvent::GetEventType() const { return m_event_type; } + const char *ProgressEvent::GetEventName() const { if (m_event_type == progressStart) return "progressStart"; @@ -65,9 +77,7 @@ body.try_emplace("cancellable", false); } - auto now = std::chrono::duration<double>( - std::chrono::system_clock::now().time_since_epoch()); - std::string timestamp(llvm::formatv("{0:f9}", now.count())); + std::string timestamp(llvm::formatv("{0:f9}", m_timestamp.count())); EmplaceSafeString(body, "timestamp", timestamp); if (m_percentage) @@ -77,17 +87,106 @@ return json::Value(std::move(event)); } +void ProgressEvent::MarkAsReported() { m_reported = true; } + +bool ProgressEvent::Reported() const { return m_reported; } + +std::chrono::duration<double> ProgressEvent::GetTimestamp() const { + return m_timestamp; +} + +ProgressEventManager::ProgressEventManager( + const ProgressEvent &start_event, + ProgressEventReportCallback report_callback) + : m_start_event(start_event), + m_start_event_timestamp( + std::chrono::system_clock::now().time_since_epoch()), + m_finished(false), m_report_callback(report_callback) {} + +bool ProgressEventManager::TryReport() { + // We only report if we have already started reporting or if enough time has + // passed since the start event. + if (!m_start_event.Reported() && + std::chrono::system_clock::now().time_since_epoch() - + m_start_event.GetTimestamp() < + std::chrono::seconds(1)) + return false; + + if (!m_start_event.Reported() && !Finished()) { + m_report_callback(m_start_event); + m_start_event.MarkAsReported(); + } + if (m_last_update_event && !m_last_update_event->Reported()) { + m_report_callback(*m_last_update_event); + m_last_update_event->MarkAsReported(); + } + return true; +} + +void ProgressEventManager::Update(const ProgressEvent &event) { + if (event.GetEventType() == progressEnd) + m_finished = true; + + if (!m_last_update_event || !event.EqualsForIDE(*m_last_update_event)) { + m_last_update_event = event; + TryReport(); + } +} + +uint64_t ProgressEventManager::GetID() const { return m_start_event.GetID(); } + +bool ProgressEventManager::Finished() const { return m_finished; } + ProgressEventFilterQueue::ProgressEventFilterQueue( - std::function<void(ProgressEvent)> callback) - : m_callback(callback) {} + ProgressEventReportCallback report_callback) + : m_report_callback(report_callback) { + m_aux_reporter_thread_should_exit = false; + m_aux_reporter_thread = std::thread([&] { + while (!m_aux_reporter_thread_should_exit) { + std::this_thread::sleep_for(std::chrono::seconds(2)); + ReportWaitingEvents(); + } + }); +} + +ProgressEventFilterQueue::~ProgressEventFilterQueue() { + m_aux_reporter_thread_should_exit = true; + m_aux_reporter_thread.join(); +} + +void ProgressEventFilterQueue::ReportWaitingEvents() { + std::lock_guard<std::mutex> locker(m_reporter_mutex); + + while (!m_sorted_event_managers.empty()) { + ProgressEventManagerSP event_manager = m_sorted_event_managers.front(); + if (event_manager->Finished()) + m_sorted_event_managers.pop(); + else if (event_manager->TryReport()) + m_sorted_event_managers + .pop(); // we remove it from the queue as it started reporting + // already, the Push method will be able to continue its + // reports. + else + break; // If we couldn't report it, then the next event in the queue won't + // be able as well, as it came later. + } +} void ProgressEventFilterQueue::Push(const ProgressEvent &event) { if (!event.IsValid()) return; - auto it = m_last_events.find(event.GetID()); - if (it == m_last_events.end() || !(it->second == event)) { - m_last_events[event.GetID()] = event; - m_callback(event); + std::lock_guard<std::mutex> locker(m_reporter_mutex); + + auto it = m_event_managers.find(event.GetID()); + if (it == m_event_managers.end()) { + ProgressEventManagerSP event_manager = + std::make_shared<ProgressEventManager>(event, m_report_callback); + m_event_managers.insert({event.GetID(), event_manager}); + m_sorted_event_managers.push(event_manager); + } else { + it->second->Update(event); + if (it->second->Finished()) + m_event_managers.erase(it); } }
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits