diff --git a/clang-tools-extra/clangd/support/CMakeLists.txt b/clang-tools-extra/clangd/support/CMakeLists.txt --- a/clang-tools-extra/clangd/support/CMakeLists.txt +++ b/clang-tools-extra/clangd/support/CMakeLists.txt @@ -21,6 +21,7 @@ Context.cpp Logger.cpp Markup.cpp + MemoryTree.cpp Shutdown.cpp Threading.cpp ThreadsafeFS.cpp diff --git a/clang-tools-extra/clangd/support/MemoryTree.h b/clang-tools-extra/clangd/support/MemoryTree.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/support/MemoryTree.h @@ -0,0 +1,52 @@ +//===--- MemoryTree.h - A special tree for components and sizes -*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_MEMORYTREE_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_MEMORYTREE_H_ + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include +#include + +namespace clang { +namespace clangd { + +/// A tree that can be used to represent memory usage of nested components while +/// preserving the hierarchy. +struct MemoryTree { +public: + void addChild(llvm::StringRef Name, MemoryTree MT, bool IsDetail = false); + void addChild(llvm::StringRef Name, size_t Size); + + /// Performs a pre-order traversal of the tree, while concatenating compenent + /// names with dots("."). + /// If CollapseDetails is set, accumulates the size of subtrees marked as + /// detail to root, and doesn't report any descendants. + void traverseTree(bool CollapseDetails, + llvm::function_ref + CB, + llvm::StringRef ComponentPrefix = "") const; + +private: + // Enables optional traversal of the tree by collapsing any descendants into + // this node. + bool IsDetail = false; + // A human-readable identifier for the component. + std::string Name; + // Bytes owned by this component specifically. + size_t Size = 0; + // Significant components nested under current one. + std::vector Children; +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clang-tools-extra/clangd/support/MemoryTree.cpp b/clang-tools-extra/clangd/support/MemoryTree.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/support/MemoryTree.cpp @@ -0,0 +1,44 @@ +#include "support/MemoryTree.h" + +namespace clang { +namespace clangd { + +void MemoryTree::addChild(llvm::StringRef Name, MemoryTree MT, bool IsDetail) { + MT.IsDetail = IsDetail; + MT.Name = Name.str(); + Children.emplace_back(std::move(MT)); +} + +void MemoryTree::addChild(llvm::StringRef Name, size_t Size) { + Children.emplace_back(); + Children.back().Name = Name.str(); + Children.back().Size = Size; +} + +void MemoryTree::traverseTree( + bool CollapseDetails, llvm::function_ref CB, + llvm::StringRef ComponentPrefix) const { + std::string ComponentName = ComponentPrefix.str(); + if (!ComponentName.empty()) + ComponentName += '.'; + ComponentName += Name; + + if (CollapseDetails && IsDetail) { + size_t ChildrenSize = 0; + for (const auto &C : Children) { + C.traverseTree(CollapseDetails, + [&ChildrenSize](size_t Size, llvm::StringRef) { + ChildrenSize += Size; + }); + } + CB(Size + ChildrenSize, ComponentName); + return; + } + + CB(Size, ComponentName); + for (const auto &C : Children) + C.traverseTree(CollapseDetails, CB, ComponentName); +} + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -99,6 +99,7 @@ support/ContextTests.cpp support/FunctionTests.cpp support/MarkupTests.cpp + support/MemoryTreeTests.cpp support/ThreadingTests.cpp support/TestTracer.cpp support/TraceTests.cpp diff --git a/clang-tools-extra/clangd/unittests/support/MemoryTreeTests.cpp b/clang-tools-extra/clangd/unittests/support/MemoryTreeTests.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/support/MemoryTreeTests.cpp @@ -0,0 +1,81 @@ +//===-- MemoryTreeTests.cpp -------------------------------------*- C++ -*-===// +// +// 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 "support/MemoryTree.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { +using testing::ElementsAreArray; + +TEST(MemoryTree, Traversal) { + // * + // | - leaf + // | - child1 + // | - leaf + // | - child2 (detailed) + // | - leaf + // | - child + // | - leaf + MemoryTree MT; + MT.addChild("leaf", 1); + { + MemoryTree Child1; + Child1.addChild("leaf", 1); + MT.addChild("child1", Child1); + { + MemoryTree Child2; + Child2.addChild("leaf", 1); + Child1.addChild("child", std::move(Child2)); + MT.addChild("child2", std::move(Child1), true); + } + } + + struct Component { + std::string Name; + size_t Size; + bool operator==(const Component &RHS) const { + return std::tie(Name, Size) == std::tie(RHS.Name, RHS.Size); + } + }; + std::vector SeenComponents; + auto CB = [&SeenComponents](size_t Size, llvm::StringRef CompName) { + Component C; + C.Name = CompName.str(); + C.Size = Size; + SeenComponents.emplace_back(std::move(C)); + }; + + // Ensure sizes and component names are reported correctly. + Component ExpectedComponents[] = { + {"", 0}, + {"leaf", 1}, + {"child1", 0}, + {"child1.leaf", 1}, + {"child2", 0}, + {"child2.leaf", 1}, + {"child2.child", 0}, + {"child2.child.leaf", 1}, + }; + MT.traverseTree(false, CB); + EXPECT_THAT(SeenComponents, ElementsAreArray(ExpectedComponents)); + SeenComponents.clear(); + + // Ensure detailed sub-trees are collapsed when requested. + Component ExpectedComponentsHidden[] = { + {"", 0}, {"leaf", 1}, {"child1", 0}, {"child1.leaf", 1}, {"child2", 2}, + }; + MT.traverseTree(true, CB); + EXPECT_THAT(SeenComponents, ElementsAreArray(ExpectedComponentsHidden)); +} + +} // namespace +} // namespace clangd +} // namespace clang