https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/210417
>From b41eda2098d782e1ed07f9dc1a4e02f0786587ee Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <[email protected]> Date: Fri, 17 Jul 2026 11:53:09 -0700 Subject: [PATCH] [lldb] Add an artifact provider mechanism to Diagnostics Add a hook that lets a subsystem contribute a file to the diagnostics bundle, without breaking layering and making Core depend on it. A plugin registers an ArtifactProvider callback and a file name via AddArtifactProvider. Providers run when a bundle is collected, and each one that yields content is written into the bundle and recorded in the report's attachments. This restores, in a simpler form, the collection point that the removed Diagnostics callback mechanism offered. The motivating consumer is the downstream Swift health check, which registers a provider to write its "swift-healthcheck.log" into the bundle. Unlike the old callback, a provider does not open or write its own file: it just returns the contents as a string and Diagnostics handles the rest. I have a follow-up PR that takes advantage of this new mechanism upstream to collect the GDB remote packet log. --- lldb/include/lldb/Core/Diagnostics.h | 30 +++++ lldb/source/Core/Diagnostics.cpp | 30 +++++ lldb/unittests/Core/CMakeLists.txt | 1 + lldb/unittests/Core/DiagnosticsTest.cpp | 140 ++++++++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 lldb/unittests/Core/DiagnosticsTest.cpp diff --git a/lldb/include/lldb/Core/Diagnostics.h b/lldb/include/lldb/Core/Diagnostics.h index 554314e32e8ce..bcacf3b9c301d 100644 --- a/lldb/include/lldb/Core/Diagnostics.h +++ b/lldb/include/lldb/Core/Diagnostics.h @@ -14,6 +14,8 @@ #include "lldb/Utility/Log.h" #include "llvm/Support/Error.h" +#include <functional> +#include <mutex> #include <optional> #include <string> #include <vector> @@ -90,6 +92,19 @@ class Diagnostics { /// Record a diagnostic message into the always-on, in-memory log. void Record(llvm::StringRef message); + /// Supplies an artifact's contents on demand. Subsystems register a provider + /// so Core need not depend on them. Each runs when a bundle is collected. + using ArtifactProvider = std::function<std::string()>; + using ArtifactProviderID = uint64_t; + + /// Register \p provider to contribute file \p name. Returns an id for + /// RemoveArtifactProvider. Thread-safe. + ArtifactProviderID AddArtifactProvider(std::string name, + ArtifactProvider provider); + + /// Unregister a provider. Thread-safe. + void RemoveArtifactProvider(ArtifactProviderID id); + static Diagnostics &Instance(); static DiagnosticsProperties &GetGlobalProperties(); @@ -122,6 +137,8 @@ class Diagnostics { static void CollectBinaries(const ExecutionContext &exe_ctx, const FileSpec &dir, std::vector<std::string> &files); + void CollectArtifactProviders(const FileSpec &dir, + std::vector<std::string> &files); /// @} /// Scalars carried in the report rather than written as files. @@ -131,6 +148,19 @@ class Diagnostics { /// @} RotatingLogHandler m_log_handler; + + struct ArtifactProviderEntry { + ArtifactProviderID id; + std::string name; + ArtifactProvider provider; + }; + + /// Registered artifact providers, guarded by the mutex. + /// @{ + ArtifactProviderID m_next_artifact_provider_id = 0; + std::vector<ArtifactProviderEntry> m_artifact_providers; + std::mutex m_artifact_providers_mutex; + /// @} }; /// Render a diagnostics report as JSON, for `diagnostics dump`'s terminal diff --git a/lldb/source/Core/Diagnostics.cpp b/lldb/source/Core/Diagnostics.cpp index 311d8fab2b1bc..08adcaebdf038 100644 --- a/lldb/source/Core/Diagnostics.cpp +++ b/lldb/source/Core/Diagnostics.cpp @@ -25,12 +25,14 @@ #include "lldb/Utility/ProcessInfo.h" #include "lldb/Version/Version.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" +#include <mutex> #include <optional> #include <string> #include <vector> @@ -149,6 +151,20 @@ void Diagnostics::Record(llvm::StringRef message) { m_log_handler.Emit(message); } +Diagnostics::ArtifactProviderID +Diagnostics::AddArtifactProvider(std::string name, ArtifactProvider provider) { + std::lock_guard<std::mutex> guard(m_artifact_providers_mutex); + ArtifactProviderID id = m_next_artifact_provider_id++; + m_artifact_providers.push_back({id, std::move(name), std::move(provider)}); + return id; +} + +void Diagnostics::RemoveArtifactProvider(ArtifactProviderID id) { + std::lock_guard<std::mutex> guard(m_artifact_providers_mutex); + llvm::erase_if(m_artifact_providers, + [id](const ArtifactProviderEntry &e) { return e.id == id; }); +} + // Write a single artifact into the bundle and, on success, record its name in // \p files. Best-effort: a write failure leaves the file out of the list, so a // missing artifact stays visible. The file is made owner-only because the @@ -253,6 +269,7 @@ Diagnostics::Collect(Debugger &debugger, const ExecutionContext &exe_ctx, CollectCommands(debugger, exe_ctx, dir, report.attachments.files); if (GetGlobalProperties().GetCollectBinaries()) CollectBinaries(exe_ctx, dir, report.attachments.files); + CollectArtifactProviders(dir, report.attachments.files); report.version = lldb_private::GetVersion(); report.os = GetHostDescription(exe_ctx); @@ -317,6 +334,19 @@ void Diagnostics::CollectBinaries(const ExecutionContext &exe_ctx, CopyBinary(process->GetCoreFile(), dir, files); } +void Diagnostics::CollectArtifactProviders(const FileSpec &dir, + std::vector<std::string> &files) { + // Snapshot under the lock, then run providers without it: a provider can be + // slow and must not block registration or removal. + std::vector<ArtifactProviderEntry> providers; + { + std::lock_guard<std::mutex> guard(m_artifact_providers_mutex); + providers = m_artifact_providers; + } + for (const ArtifactProviderEntry &entry : providers) + WriteArtifact(dir, entry.name, entry.provider(), files); +} + std::string Diagnostics::GetHostDescription(const ExecutionContext &exe_ctx) { std::string os = HostInfo::GetTargetTriple().str(); Target *target = exe_ctx.GetTargetPtr(); diff --git a/lldb/unittests/Core/CMakeLists.txt b/lldb/unittests/Core/CMakeLists.txt index d69432d332f44..959793d7d4de0 100644 --- a/lldb/unittests/Core/CMakeLists.txt +++ b/lldb/unittests/Core/CMakeLists.txt @@ -3,6 +3,7 @@ add_lldb_unittest(LLDBCoreTests DebuggerTest.cpp CommunicationTest.cpp DiagnosticEventTest.cpp + DiagnosticsTest.cpp DumpDataExtractorTest.cpp DumpRegisterInfoTest.cpp FormatEntityTest.cpp diff --git a/lldb/unittests/Core/DiagnosticsTest.cpp b/lldb/unittests/Core/DiagnosticsTest.cpp new file mode 100644 index 0000000000000..6bf0e7720b8fe --- /dev/null +++ b/lldb/unittests/Core/DiagnosticsTest.cpp @@ -0,0 +1,140 @@ +//===----------------------------------------------------------------------===// +// +// 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/Diagnostics.h" +#include "Plugins/Platform/MacOSX/PlatformMacOSX.h" +#include "Plugins/Platform/MacOSX/PlatformRemoteMacOSX.h" +#include "TestingSupport/TestUtilities.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Target/ExecutionContext.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +#include <mutex> + +using namespace lldb; +using namespace lldb_private; + +namespace { +class DiagnosticsTest : public ::testing::Test { +public: + void SetUp() override { + FileSystem::Initialize(); + HostInfo::Initialize(); + PlatformMacOSX::Initialize(); + std::call_once(TestUtilities::g_debugger_initialize_flag, + []() { Debugger::Initialize(nullptr); }); + ArchSpec arch("x86_64-apple-macosx-"); + Platform::SetHostPlatform( + PlatformRemoteMacOSX::CreateInstance(true, &arch)); + } + void TearDown() override { + PlatformMacOSX::Terminate(); + HostInfo::Terminate(); + FileSystem::Terminate(); + } +}; + +// Read a file from the bundle directory, or "" if it cannot be read. +std::string ReadBundleFile(const FileSpec &dir, llvm::StringRef name) { + FileSpec path = dir.CopyByAppendingPathComponent(name); + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buffer = + llvm::MemoryBuffer::getFile(path.GetPath()); + if (!buffer) + return ""; + return (*buffer)->getBuffer().str(); +} +} // namespace + +TEST_F(DiagnosticsTest, ArtifactProviderIDsAreUnique) { + Diagnostics diagnostics; + Diagnostics::ArtifactProviderID id0 = + diagnostics.AddArtifactProvider("a.txt", [] { return "a"; }); + Diagnostics::ArtifactProviderID id1 = + diagnostics.AddArtifactProvider("b.txt", [] { return "b"; }); + EXPECT_NE(id0, id1); +} + +TEST_F(DiagnosticsTest, ArtifactProviderContributesToBundle) { + DebuggerSP debugger_sp = Debugger::CreateInstance(); + ASSERT_TRUE(debugger_sp); + + Diagnostics diagnostics; + int call_count = 0; + Diagnostics::ArtifactProviderID id = diagnostics.AddArtifactProvider( + "my-artifact.txt", [&call_count]() -> std::string { + ++call_count; + return "hello diagnostics"; + }); + + ExecutionContext exe_ctx; + + // A registered provider is invoked and its content lands in the bundle and + // in the report's attachments. + { + llvm::Expected<FileSpec> dir = Diagnostics::CreateUniqueDirectory(); + ASSERT_THAT_EXPECTED(dir, llvm::Succeeded()); + llvm::Expected<Diagnostics::Report> report = + diagnostics.Collect(*debugger_sp, exe_ctx, *dir); + ASSERT_THAT_EXPECTED(report, llvm::Succeeded()); + + EXPECT_EQ(call_count, 1); + EXPECT_TRUE( + llvm::is_contained(report->attachments.files, "my-artifact.txt")); + EXPECT_EQ(ReadBundleFile(*dir, "my-artifact.txt"), "hello diagnostics"); + + llvm::sys::fs::remove_directories(dir->GetPath()); + } + + // After removal the provider is neither invoked nor present in the bundle. + diagnostics.RemoveArtifactProvider(id); + call_count = 0; + { + llvm::Expected<FileSpec> dir = Diagnostics::CreateUniqueDirectory(); + ASSERT_THAT_EXPECTED(dir, llvm::Succeeded()); + llvm::Expected<Diagnostics::Report> report = + diagnostics.Collect(*debugger_sp, exe_ctx, *dir); + ASSERT_THAT_EXPECTED(report, llvm::Succeeded()); + + EXPECT_EQ(call_count, 0); + EXPECT_FALSE( + llvm::is_contained(report->attachments.files, "my-artifact.txt")); + + llvm::sys::fs::remove_directories(dir->GetPath()); + } + + Debugger::Destroy(debugger_sp); +} + +TEST_F(DiagnosticsTest, RemoveArtifactProviderIgnoresUnknownID) { + Diagnostics diagnostics; + Diagnostics::ArtifactProviderID id = + diagnostics.AddArtifactProvider("kept.txt", [] { return "kept"; }); + + // Removing an id that was never handed out must not disturb other providers. + diagnostics.RemoveArtifactProvider(id + 1); + + DebuggerSP debugger_sp = Debugger::CreateInstance(); + ASSERT_TRUE(debugger_sp); + ExecutionContext exe_ctx; + llvm::Expected<FileSpec> dir = Diagnostics::CreateUniqueDirectory(); + ASSERT_THAT_EXPECTED(dir, llvm::Succeeded()); + llvm::Expected<Diagnostics::Report> report = + diagnostics.Collect(*debugger_sp, exe_ctx, *dir); + ASSERT_THAT_EXPECTED(report, llvm::Succeeded()); + + EXPECT_TRUE(llvm::is_contained(report->attachments.files, "kept.txt")); + + llvm::sys::fs::remove_directories(dir->GetPath()); + Debugger::Destroy(debugger_sp); +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
