diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "ClangdLSPServer.h" +#include "ClangdServer.h" #include "CodeComplete.h" #include "Diagnostics.h" #include "DraftStore.h" @@ -26,6 +27,7 @@ #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator_range.h" +#include "llvm/Support/Allocator.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" @@ -145,6 +147,16 @@ LastInvalidFile, InvalidFileCount - 1); } +void profile(const ClangdServer &Server, bool Detailed) { + trace::Span Tracer(Detailed ? "ProfileDetailed" : "ProfileBrief"); + // Exit early if tracing is disabled. + if (!Tracer.Args) + return; + llvm::BumpPtrAllocator DetailAlloc; + MemoryTree MT(Detailed ? &DetailAlloc : nullptr); + Server.profile(MT); + trace::recordMemoryUsage(MT, "clangd_server"); +} } // namespace // MessageHandler dispatches incoming LSP messages. @@ -163,14 +175,18 @@ log("<-- {0}", Method); if (Method == "exit") return false; - if (!Server.Server) + if (!Server.Server) { elog("Notification {0} before initialization", Method); - else if (Method == "$/cancelRequest") + } else if (Method == "$/cancelRequest") { onCancel(std::move(Params)); - else if (auto Handler = Notifications.lookup(Method)) + } else if (auto Handler = Notifications.lookup(Method)) { Handler(std::move(Params)); - else + // Record memory usage after each notification. This currently takes <1ms, + // so it is safe to do frequently. + profile(*Server.Server, /*Detailed=*/false); + } else { log("unhandled notification {0}", Method); + } return true; } diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -25,6 +25,7 @@ #include "refactor/Tweak.h" #include "support/Cancellation.h" #include "support/Function.h" +#include "support/MemoryTree.h" #include "support/ThreadsafeFS.h" #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/Core/Replacement.h" @@ -340,6 +341,9 @@ LLVM_NODISCARD bool blockUntilIdleForTest(llvm::Optional TimeoutSeconds = 10); + /// Builds a nested representation of memory used by components. + void profile(MemoryTree &MT) const; + private: void formatCode(PathRef File, llvm::StringRef Code, ArrayRef Ranges, diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -28,6 +28,7 @@ #include "refactor/Tweak.h" #include "support/Logger.h" #include "support/Markup.h" +#include "support/MemoryTree.h" #include "support/ThreadsafeFS.h" #include "support/Trace.h" #include "clang/Format/Format.h" @@ -824,5 +825,12 @@ BackgroundIdx->blockUntilIdleForTest(TimeoutSeconds)); } +void ClangdServer::profile(MemoryTree &MT) const { + if (DynamicIdx) + DynamicIdx->profile(MT.child("dynamic_index")); + if (BackgroundIdx) + BackgroundIdx->profile(MT.child("background_index")); + WorkScheduler.profile(MT.child("tuscheduler")); +} } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp --- a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp @@ -164,6 +164,17 @@ stop(); EXPECT_THAT(Tracer.takeMetric("lsp_latency", MethodName), testing::SizeIs(1)); } + +TEST_F(LSPTest, RecordsMemoryUsage) { + trace::TestTracer Tracer; + auto &Client = start(); + EXPECT_THAT(Tracer.takeMetric("memory_usage", "clangd_server"), + testing::SizeIs(0)); + Client.notify("", {}); + stop(); + EXPECT_THAT(Tracer.takeMetric("memory_usage", "clangd_server"), + testing::SizeIs(1)); +} } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/ClangdTests.cpp b/clang-tools-extra/clangd/unittests/ClangdTests.cpp --- a/clang-tools-extra/clangd/unittests/ClangdTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdTests.cpp @@ -17,6 +17,7 @@ #include "TestFS.h" #include "TestTU.h" #include "URI.h" +#include "support/MemoryTree.h" #include "support/Path.h" #include "support/Threading.h" #include "clang/Config/config.h" @@ -27,6 +28,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Allocator.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Path.h" #include "llvm/Support/Regex.h" @@ -48,6 +50,7 @@ namespace { using ::testing::AllOf; +using ::testing::Contains; using ::testing::ElementsAre; using ::testing::Field; using ::testing::Gt; @@ -1236,6 +1239,21 @@ EXPECT_FALSE(DiagConsumer.HadDiagsInLastCallback); } +TEST(ClangdServer, MemoryUsageTest) { + MockFS FS; + MockCompilationDatabase CDB; + ClangdServer Server(CDB, FS, ClangdServer::optsForTest()); + + auto FooCpp = testPath("foo.cpp"); + Server.addDocument(FooCpp, ""); + ASSERT_TRUE(Server.blockUntilIdleForTest()); + + llvm::BumpPtrAllocator Alloc; + MemoryTree MT(&Alloc); + Server.profile(MT); + ASSERT_TRUE(MT.children().count("tuscheduler")); + EXPECT_TRUE(MT.child("tuscheduler").children().count(FooCpp)); +} } // namespace } // namespace clangd } // namespace clang