Files
llvm/lldb/source/Core/Progress.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

204 lines
6.7 KiB
C++
Raw Normal View History

//===-- Progress.cpp ------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "lldb/Core/Progress.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Utility/StreamString.h"
#include "llvm/Support/Signposts.h"
#include <cstdint>
#include <mutex>
#include <optional>
using namespace lldb;
using namespace lldb_private;
std::atomic<uint64_t> Progress::g_id(0);
// Instrument progress events with signposts when supported.
static llvm::ManagedStatic<llvm::SignpostEmitter> g_progress_signposts;
Progress::Progress(std::string title, std::string details,
std::optional<uint64_t> total,
lldb_private::Debugger *debugger)
: m_details(details), m_completed(0),
m_total(Progress::kNonDeterministicTotal),
m_progress_data{title, ++g_id,
/*m_progress_data.debugger_id=*/std::nullopt} {
if (total)
m_total = *total;
if (debugger)
m_progress_data.debugger_id = debugger->GetID();
std::lock_guard<std::mutex> guard(m_mutex);
ReportProgress();
[lldb] Implement coalescing of disjoint progress events (#84854) This implements coalescing of progress events using a timeout, as discussed in the RFC on Discourse [1]. This PR consists of two commits which, depending on the feedback, I may split up into two PRs. For now, I think it's easier to review this as a whole. 1. The first commit introduces a new generic `Alarm` class. The class lets you to schedule a function (callback) to be executed after a given timeout expires. You can cancel and reset a callback before its corresponding timeout expires. It achieves this with the help of a worker thread that sleeps until the next timeout expires. The only guarantee it provides is that your function is called no sooner than the requested timeout. Because the callback is called directly from the worker thread, a long running callback could potentially block the worker thread. I intentionally kept the implementation as simple as possible while addressing the needs for the `ProgressManager` use case. If we want to rely on this somewhere else, we can reassess whether we need to address those limitations. 2. The second commit uses the Alarm class to coalesce progress events. To recap the Discourse discussion, when multiple progress events with the same title execute in close succession, they get broadcast as one to `eBroadcastBitProgressCategory`. The `ProgressManager` keeps track of the in-flight progress events and when the refcount hits zero, the Alarm class is used to schedule broadcasting the event. If a new progress event comes in before the alarm fires, the alarm is reset (and the process repeats when the new progress event ends). If no new event comes in before the timeout expires, the progress event is broadcast. [1] https://discourse.llvm.org/t/rfc-improve-lldb-progress-reporting/75717/
2024-03-25 14:50:58 -07:00
// Report to the ProgressManager if that subsystem is enabled.
if (ProgressManager::Enabled())
ProgressManager::Instance().Increment(m_progress_data);
// Start signpost interval right before the meaningful work starts.
g_progress_signposts->startInterval(this, m_progress_data.title);
}
Progress::~Progress() {
// End signpost interval as soon as possible.
g_progress_signposts->endInterval(this, m_progress_data.title);
// Make sure to always report progress completed when this object is
// destructed so it indicates the progress dialog/activity should go away.
std::lock_guard<std::mutex> guard(m_mutex);
m_completed = m_total;
ReportProgress();
[lldb] Implement coalescing of disjoint progress events (#84854) This implements coalescing of progress events using a timeout, as discussed in the RFC on Discourse [1]. This PR consists of two commits which, depending on the feedback, I may split up into two PRs. For now, I think it's easier to review this as a whole. 1. The first commit introduces a new generic `Alarm` class. The class lets you to schedule a function (callback) to be executed after a given timeout expires. You can cancel and reset a callback before its corresponding timeout expires. It achieves this with the help of a worker thread that sleeps until the next timeout expires. The only guarantee it provides is that your function is called no sooner than the requested timeout. Because the callback is called directly from the worker thread, a long running callback could potentially block the worker thread. I intentionally kept the implementation as simple as possible while addressing the needs for the `ProgressManager` use case. If we want to rely on this somewhere else, we can reassess whether we need to address those limitations. 2. The second commit uses the Alarm class to coalesce progress events. To recap the Discourse discussion, when multiple progress events with the same title execute in close succession, they get broadcast as one to `eBroadcastBitProgressCategory`. The `ProgressManager` keeps track of the in-flight progress events and when the refcount hits zero, the Alarm class is used to schedule broadcasting the event. If a new progress event comes in before the alarm fires, the alarm is reset (and the process repeats when the new progress event ends). If no new event comes in before the timeout expires, the progress event is broadcast. [1] https://discourse.llvm.org/t/rfc-improve-lldb-progress-reporting/75717/
2024-03-25 14:50:58 -07:00
// Report to the ProgressManager if that subsystem is enabled.
if (ProgressManager::Enabled())
ProgressManager::Instance().Decrement(m_progress_data);
}
void Progress::Increment(uint64_t amount,
std::optional<std::string> updated_detail) {
if (amount > 0) {
std::lock_guard<std::mutex> guard(m_mutex);
if (updated_detail)
m_details = std::move(updated_detail.value());
// Watch out for unsigned overflow and make sure we don't increment too
// much and exceed the total.
if (m_total && (amount > (m_total - m_completed)))
m_completed = m_total;
else
m_completed += amount;
ReportProgress();
}
}
void Progress::ReportProgress() {
if (!m_complete) {
// Make sure we only send one notification that indicates the progress is
// complete
m_complete = m_completed == m_total;
Debugger::ReportProgress(m_progress_data.progress_id, m_progress_data.title,
m_details, m_completed, m_total,
m_progress_data.debugger_id);
}
}
[lldb] Implement coalescing of disjoint progress events (#84854) This implements coalescing of progress events using a timeout, as discussed in the RFC on Discourse [1]. This PR consists of two commits which, depending on the feedback, I may split up into two PRs. For now, I think it's easier to review this as a whole. 1. The first commit introduces a new generic `Alarm` class. The class lets you to schedule a function (callback) to be executed after a given timeout expires. You can cancel and reset a callback before its corresponding timeout expires. It achieves this with the help of a worker thread that sleeps until the next timeout expires. The only guarantee it provides is that your function is called no sooner than the requested timeout. Because the callback is called directly from the worker thread, a long running callback could potentially block the worker thread. I intentionally kept the implementation as simple as possible while addressing the needs for the `ProgressManager` use case. If we want to rely on this somewhere else, we can reassess whether we need to address those limitations. 2. The second commit uses the Alarm class to coalesce progress events. To recap the Discourse discussion, when multiple progress events with the same title execute in close succession, they get broadcast as one to `eBroadcastBitProgressCategory`. The `ProgressManager` keeps track of the in-flight progress events and when the refcount hits zero, the Alarm class is used to schedule broadcasting the event. If a new progress event comes in before the alarm fires, the alarm is reset (and the process repeats when the new progress event ends). If no new event comes in before the timeout expires, the progress event is broadcast. [1] https://discourse.llvm.org/t/rfc-improve-lldb-progress-reporting/75717/
2024-03-25 14:50:58 -07:00
ProgressManager::ProgressManager()
: m_entries(), m_alarm(std::chrono::milliseconds(100)) {}
ProgressManager::~ProgressManager() {}
[lldb] Implement coalescing of disjoint progress events (#84854) This implements coalescing of progress events using a timeout, as discussed in the RFC on Discourse [1]. This PR consists of two commits which, depending on the feedback, I may split up into two PRs. For now, I think it's easier to review this as a whole. 1. The first commit introduces a new generic `Alarm` class. The class lets you to schedule a function (callback) to be executed after a given timeout expires. You can cancel and reset a callback before its corresponding timeout expires. It achieves this with the help of a worker thread that sleeps until the next timeout expires. The only guarantee it provides is that your function is called no sooner than the requested timeout. Because the callback is called directly from the worker thread, a long running callback could potentially block the worker thread. I intentionally kept the implementation as simple as possible while addressing the needs for the `ProgressManager` use case. If we want to rely on this somewhere else, we can reassess whether we need to address those limitations. 2. The second commit uses the Alarm class to coalesce progress events. To recap the Discourse discussion, when multiple progress events with the same title execute in close succession, they get broadcast as one to `eBroadcastBitProgressCategory`. The `ProgressManager` keeps track of the in-flight progress events and when the refcount hits zero, the Alarm class is used to schedule broadcasting the event. If a new progress event comes in before the alarm fires, the alarm is reset (and the process repeats when the new progress event ends). If no new event comes in before the timeout expires, the progress event is broadcast. [1] https://discourse.llvm.org/t/rfc-improve-lldb-progress-reporting/75717/
2024-03-25 14:50:58 -07:00
void ProgressManager::Initialize() {
assert(!InstanceImpl() && "Already initialized.");
InstanceImpl().emplace();
}
void ProgressManager::Terminate() {
assert(InstanceImpl() && "Already terminated.");
InstanceImpl().reset();
}
bool ProgressManager::Enabled() { return InstanceImpl().operator bool(); }
ProgressManager &ProgressManager::Instance() {
[lldb] Implement coalescing of disjoint progress events (#84854) This implements coalescing of progress events using a timeout, as discussed in the RFC on Discourse [1]. This PR consists of two commits which, depending on the feedback, I may split up into two PRs. For now, I think it's easier to review this as a whole. 1. The first commit introduces a new generic `Alarm` class. The class lets you to schedule a function (callback) to be executed after a given timeout expires. You can cancel and reset a callback before its corresponding timeout expires. It achieves this with the help of a worker thread that sleeps until the next timeout expires. The only guarantee it provides is that your function is called no sooner than the requested timeout. Because the callback is called directly from the worker thread, a long running callback could potentially block the worker thread. I intentionally kept the implementation as simple as possible while addressing the needs for the `ProgressManager` use case. If we want to rely on this somewhere else, we can reassess whether we need to address those limitations. 2. The second commit uses the Alarm class to coalesce progress events. To recap the Discourse discussion, when multiple progress events with the same title execute in close succession, they get broadcast as one to `eBroadcastBitProgressCategory`. The `ProgressManager` keeps track of the in-flight progress events and when the refcount hits zero, the Alarm class is used to schedule broadcasting the event. If a new progress event comes in before the alarm fires, the alarm is reset (and the process repeats when the new progress event ends). If no new event comes in before the timeout expires, the progress event is broadcast. [1] https://discourse.llvm.org/t/rfc-improve-lldb-progress-reporting/75717/
2024-03-25 14:50:58 -07:00
assert(InstanceImpl() && "ProgressManager must be initialized");
return *InstanceImpl();
}
std::optional<ProgressManager> &ProgressManager::InstanceImpl() {
static std::optional<ProgressManager> g_progress_manager;
return g_progress_manager;
}
void ProgressManager::Increment(const Progress::ProgressData &progress_data) {
[lldb] Implement coalescing of disjoint progress events (#84854) This implements coalescing of progress events using a timeout, as discussed in the RFC on Discourse [1]. This PR consists of two commits which, depending on the feedback, I may split up into two PRs. For now, I think it's easier to review this as a whole. 1. The first commit introduces a new generic `Alarm` class. The class lets you to schedule a function (callback) to be executed after a given timeout expires. You can cancel and reset a callback before its corresponding timeout expires. It achieves this with the help of a worker thread that sleeps until the next timeout expires. The only guarantee it provides is that your function is called no sooner than the requested timeout. Because the callback is called directly from the worker thread, a long running callback could potentially block the worker thread. I intentionally kept the implementation as simple as possible while addressing the needs for the `ProgressManager` use case. If we want to rely on this somewhere else, we can reassess whether we need to address those limitations. 2. The second commit uses the Alarm class to coalesce progress events. To recap the Discourse discussion, when multiple progress events with the same title execute in close succession, they get broadcast as one to `eBroadcastBitProgressCategory`. The `ProgressManager` keeps track of the in-flight progress events and when the refcount hits zero, the Alarm class is used to schedule broadcasting the event. If a new progress event comes in before the alarm fires, the alarm is reset (and the process repeats when the new progress event ends). If no new event comes in before the timeout expires, the progress event is broadcast. [1] https://discourse.llvm.org/t/rfc-improve-lldb-progress-reporting/75717/
2024-03-25 14:50:58 -07:00
std::lock_guard<std::mutex> lock(m_entries_mutex);
llvm::StringRef key = progress_data.title;
bool new_entry = !m_entries.contains(key);
Entry &entry = m_entries[progress_data.title];
if (new_entry) {
// This is a new progress event. Report progress and store the progress
// data.
ReportProgress(progress_data, EventType::Begin);
[lldb] Implement coalescing of disjoint progress events (#84854) This implements coalescing of progress events using a timeout, as discussed in the RFC on Discourse [1]. This PR consists of two commits which, depending on the feedback, I may split up into two PRs. For now, I think it's easier to review this as a whole. 1. The first commit introduces a new generic `Alarm` class. The class lets you to schedule a function (callback) to be executed after a given timeout expires. You can cancel and reset a callback before its corresponding timeout expires. It achieves this with the help of a worker thread that sleeps until the next timeout expires. The only guarantee it provides is that your function is called no sooner than the requested timeout. Because the callback is called directly from the worker thread, a long running callback could potentially block the worker thread. I intentionally kept the implementation as simple as possible while addressing the needs for the `ProgressManager` use case. If we want to rely on this somewhere else, we can reassess whether we need to address those limitations. 2. The second commit uses the Alarm class to coalesce progress events. To recap the Discourse discussion, when multiple progress events with the same title execute in close succession, they get broadcast as one to `eBroadcastBitProgressCategory`. The `ProgressManager` keeps track of the in-flight progress events and when the refcount hits zero, the Alarm class is used to schedule broadcasting the event. If a new progress event comes in before the alarm fires, the alarm is reset (and the process repeats when the new progress event ends). If no new event comes in before the timeout expires, the progress event is broadcast. [1] https://discourse.llvm.org/t/rfc-improve-lldb-progress-reporting/75717/
2024-03-25 14:50:58 -07:00
entry.data = progress_data;
} else if (entry.refcount == 0) {
// This is an existing entry that was scheduled to be deleted but a new one
// came in before the timer expired.
assert(entry.handle != Alarm::INVALID_HANDLE);
if (!m_alarm.Cancel(entry.handle)) {
// The timer expired before we had a chance to cancel it. We have to treat
// this as an entirely new progress event.
ReportProgress(progress_data, EventType::Begin);
}
// Clear the alarm handle.
entry.handle = Alarm::INVALID_HANDLE;
}
[lldb] Implement coalescing of disjoint progress events (#84854) This implements coalescing of progress events using a timeout, as discussed in the RFC on Discourse [1]. This PR consists of two commits which, depending on the feedback, I may split up into two PRs. For now, I think it's easier to review this as a whole. 1. The first commit introduces a new generic `Alarm` class. The class lets you to schedule a function (callback) to be executed after a given timeout expires. You can cancel and reset a callback before its corresponding timeout expires. It achieves this with the help of a worker thread that sleeps until the next timeout expires. The only guarantee it provides is that your function is called no sooner than the requested timeout. Because the callback is called directly from the worker thread, a long running callback could potentially block the worker thread. I intentionally kept the implementation as simple as possible while addressing the needs for the `ProgressManager` use case. If we want to rely on this somewhere else, we can reassess whether we need to address those limitations. 2. The second commit uses the Alarm class to coalesce progress events. To recap the Discourse discussion, when multiple progress events with the same title execute in close succession, they get broadcast as one to `eBroadcastBitProgressCategory`. The `ProgressManager` keeps track of the in-flight progress events and when the refcount hits zero, the Alarm class is used to schedule broadcasting the event. If a new progress event comes in before the alarm fires, the alarm is reset (and the process repeats when the new progress event ends). If no new event comes in before the timeout expires, the progress event is broadcast. [1] https://discourse.llvm.org/t/rfc-improve-lldb-progress-reporting/75717/
2024-03-25 14:50:58 -07:00
// Regardless of how we got here, we need to bump the reference count.
entry.refcount++;
}
void ProgressManager::Decrement(const Progress::ProgressData &progress_data) {
[lldb] Implement coalescing of disjoint progress events (#84854) This implements coalescing of progress events using a timeout, as discussed in the RFC on Discourse [1]. This PR consists of two commits which, depending on the feedback, I may split up into two PRs. For now, I think it's easier to review this as a whole. 1. The first commit introduces a new generic `Alarm` class. The class lets you to schedule a function (callback) to be executed after a given timeout expires. You can cancel and reset a callback before its corresponding timeout expires. It achieves this with the help of a worker thread that sleeps until the next timeout expires. The only guarantee it provides is that your function is called no sooner than the requested timeout. Because the callback is called directly from the worker thread, a long running callback could potentially block the worker thread. I intentionally kept the implementation as simple as possible while addressing the needs for the `ProgressManager` use case. If we want to rely on this somewhere else, we can reassess whether we need to address those limitations. 2. The second commit uses the Alarm class to coalesce progress events. To recap the Discourse discussion, when multiple progress events with the same title execute in close succession, they get broadcast as one to `eBroadcastBitProgressCategory`. The `ProgressManager` keeps track of the in-flight progress events and when the refcount hits zero, the Alarm class is used to schedule broadcasting the event. If a new progress event comes in before the alarm fires, the alarm is reset (and the process repeats when the new progress event ends). If no new event comes in before the timeout expires, the progress event is broadcast. [1] https://discourse.llvm.org/t/rfc-improve-lldb-progress-reporting/75717/
2024-03-25 14:50:58 -07:00
std::lock_guard<std::mutex> lock(m_entries_mutex);
llvm::StringRef key = progress_data.title;
auto it = m_entries.find(key);
if (it == m_entries.end())
return;
Entry &entry = it->second;
[lldb] Implement coalescing of disjoint progress events (#84854) This implements coalescing of progress events using a timeout, as discussed in the RFC on Discourse [1]. This PR consists of two commits which, depending on the feedback, I may split up into two PRs. For now, I think it's easier to review this as a whole. 1. The first commit introduces a new generic `Alarm` class. The class lets you to schedule a function (callback) to be executed after a given timeout expires. You can cancel and reset a callback before its corresponding timeout expires. It achieves this with the help of a worker thread that sleeps until the next timeout expires. The only guarantee it provides is that your function is called no sooner than the requested timeout. Because the callback is called directly from the worker thread, a long running callback could potentially block the worker thread. I intentionally kept the implementation as simple as possible while addressing the needs for the `ProgressManager` use case. If we want to rely on this somewhere else, we can reassess whether we need to address those limitations. 2. The second commit uses the Alarm class to coalesce progress events. To recap the Discourse discussion, when multiple progress events with the same title execute in close succession, they get broadcast as one to `eBroadcastBitProgressCategory`. The `ProgressManager` keeps track of the in-flight progress events and when the refcount hits zero, the Alarm class is used to schedule broadcasting the event. If a new progress event comes in before the alarm fires, the alarm is reset (and the process repeats when the new progress event ends). If no new event comes in before the timeout expires, the progress event is broadcast. [1] https://discourse.llvm.org/t/rfc-improve-lldb-progress-reporting/75717/
2024-03-25 14:50:58 -07:00
entry.refcount--;
if (entry.refcount == 0) {
assert(entry.handle == Alarm::INVALID_HANDLE);
// Copy the key to a std::string so we can pass it by value to the lambda.
// The underlying StringRef will not exist by the time the callback is
// called.
std::string key_str = std::string(key);
// Start a timer. If it expires before we see another progress event, it
// will be reported.
entry.handle = m_alarm.Create([=]() { Expire(key_str); });
}
}
void ProgressManager::ReportProgress(
const Progress::ProgressData &progress_data, EventType type) {
// The category bit only keeps track of when progress report categories have
// started and ended, so clear the details and reset other fields when
// broadcasting to it since that bit doesn't need that information.
const uint64_t completed =
(type == EventType::Begin) ? 0 : Progress::kNonDeterministicTotal;
Debugger::ReportProgress(progress_data.progress_id, progress_data.title, "",
completed, Progress::kNonDeterministicTotal,
progress_data.debugger_id,
lldb::eBroadcastBitProgressCategory);
}
[lldb] Implement coalescing of disjoint progress events (#84854) This implements coalescing of progress events using a timeout, as discussed in the RFC on Discourse [1]. This PR consists of two commits which, depending on the feedback, I may split up into two PRs. For now, I think it's easier to review this as a whole. 1. The first commit introduces a new generic `Alarm` class. The class lets you to schedule a function (callback) to be executed after a given timeout expires. You can cancel and reset a callback before its corresponding timeout expires. It achieves this with the help of a worker thread that sleeps until the next timeout expires. The only guarantee it provides is that your function is called no sooner than the requested timeout. Because the callback is called directly from the worker thread, a long running callback could potentially block the worker thread. I intentionally kept the implementation as simple as possible while addressing the needs for the `ProgressManager` use case. If we want to rely on this somewhere else, we can reassess whether we need to address those limitations. 2. The second commit uses the Alarm class to coalesce progress events. To recap the Discourse discussion, when multiple progress events with the same title execute in close succession, they get broadcast as one to `eBroadcastBitProgressCategory`. The `ProgressManager` keeps track of the in-flight progress events and when the refcount hits zero, the Alarm class is used to schedule broadcasting the event. If a new progress event comes in before the alarm fires, the alarm is reset (and the process repeats when the new progress event ends). If no new event comes in before the timeout expires, the progress event is broadcast. [1] https://discourse.llvm.org/t/rfc-improve-lldb-progress-reporting/75717/
2024-03-25 14:50:58 -07:00
void ProgressManager::Expire(llvm::StringRef key) {
std::lock_guard<std::mutex> lock(m_entries_mutex);
// This shouldn't happen but be resilient anyway.
if (!m_entries.contains(key))
return;
// A new event came in and the alarm fired before we had a chance to restart
// it.
if (m_entries[key].refcount != 0)
return;
// We're done with this entry.
ReportProgress(m_entries[key].data, EventType::End);
m_entries.erase(key);
}