TUScheduler::InvalidateOnUpdate);
}
-std::vector<std::pair<Path, std::size_t>>
-ClangdServer::getUsedBytesPerFile() const {
- return WorkScheduler.getUsedBytesPerFile();
+llvm::StringMap<TUScheduler::FileStats> ClangdServer::fileStats() const {
+ return WorkScheduler.fileStats();
}
LLVM_NODISCARD bool
void semanticHighlights(PathRef File,
Callback<std::vector<HighlightingToken>>);
- /// Returns estimated memory usage for each of the currently open files.
- /// The order of results is unspecified.
+ /// Returns estimated memory usage and other statistics for each of the
+ /// currently open files.
/// Overall memory usage of clangd may be significantly more than reported
/// here, as this metric does not account (at least) for:
/// - memory occupied by static and dynamic index,
/// - memory required for in-flight requests,
/// FIXME: those metrics might be useful too, we should add them.
- std::vector<std::pair<Path, std::size_t>> getUsedBytesPerFile() const;
+ llvm::StringMap<TUScheduler::FileStats> fileStats() const;
// Blocks the main thread until the server is idle. Only for use in tests.
// Returns false if the timeout expires.
/// getPossiblyStalePreamble() can be null even after this function returns.
void waitForFirstPreamble() const;
- std::size_t getUsedBytes() const;
+ TUScheduler::FileStats stats() const;
bool isASTCached() const;
private:
// don't. When the old handle is destroyed, the old worker will stop reporting
// any results to the user.
bool CanPublishResults = true; /* GUARDED_BY(PublishMu) */
+ std::atomic<unsigned> ASTBuildCount = {0};
+ std::atomic<unsigned> PreambleBuildCount = {0};
SynchronizedTUStatus Status;
PreambleThread PreamblePeer;
// FIXME: We might need to build a patched ast once preamble thread starts
// running async. Currently getPossiblyStalePreamble below will always
// return a compatible preamble as ASTWorker::update blocks.
- llvm::Optional<ParsedAST> NewAST =
- Invocation ? buildAST(FileName, std::move(Invocation),
- CompilerInvocationDiagConsumer.take(),
- FileInputs, getPossiblyStalePreamble())
- : None;
+ llvm::Optional<ParsedAST> NewAST;
+ if (Invocation) {
+ NewAST = buildAST(FileName, std::move(Invocation),
+ CompilerInvocationDiagConsumer.take(), FileInputs,
+ getPossiblyStalePreamble());
+ ++ASTBuildCount;
+ }
AST = NewAST ? std::make_unique<ParsedAST>(std::move(*NewAST)) : nullptr;
}
// Make sure we put the AST back into the LRU cache.
// running inside ASTWorker assumes internals won't change until it
// finishes.
if (Preamble != LatestPreamble) {
+ ++PreambleBuildCount;
// Cached AST is no longer valid.
IdleASTs.take(this);
RanASTCallback = false;
llvm::Optional<ParsedAST> NewAST = buildAST(
FileName, std::move(Invocation), CIDiags, Inputs, LatestPreamble);
auto RebuildDuration = DebouncePolicy::clock::now() - RebuildStartTime;
+ ++ASTBuildCount;
// Try to record the AST-build time, to inform future update debouncing.
// This is best-effort only: if the lock is held, don't bother.
std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock);
return FileInputs.CompileCommand;
}
-std::size_t ASTWorker::getUsedBytes() const {
+TUScheduler::FileStats ASTWorker::stats() const {
+ TUScheduler::FileStats Result;
+ Result.ASTBuilds = ASTBuildCount;
+ Result.PreambleBuilds = PreambleBuildCount;
// Note that we don't report the size of ASTs currently used for processing
// the in-flight requests. We used this information for debugging purposes
// only, so this should be fine.
- std::size_t Result = IdleASTs.getUsedBytes(this);
+ Result.UsedBytes = IdleASTs.getUsedBytes(this);
if (auto Preamble = getPossiblyStalePreamble())
- Result += Preamble->Preamble.getSize();
+ Result.UsedBytes += Preamble->Preamble.getSize();
return Result;
}
std::move(Task));
}
-std::vector<std::pair<Path, std::size_t>>
-TUScheduler::getUsedBytesPerFile() const {
- std::vector<std::pair<Path, std::size_t>> Result;
- Result.reserve(Files.size());
- for (auto &&PathAndFile : Files)
- Result.push_back({std::string(PathAndFile.first()),
- PathAndFile.second->Worker->getUsedBytes()});
+llvm::StringMap<TUScheduler::FileStats> TUScheduler::fileStats() const {
+ llvm::StringMap<TUScheduler::FileStats> Result;
+ for (const auto &PathAndFile : Files)
+ Result.try_emplace(PathAndFile.first(),
+ PathAndFile.second->Worker->stats());
return Result;
}
std::unique_ptr<ParsingCallbacks> ASTCallbacks = nullptr);
~TUScheduler();
- /// Returns estimated memory usage for each of the currently open files.
- /// The order of results is unspecified.
- std::vector<std::pair<Path, std::size_t>> getUsedBytesPerFile() const;
+ struct FileStats {
+ std::size_t UsedBytes = 0;
+ unsigned PreambleBuilds = 0;
+ unsigned ASTBuilds = 0;
+ };
+ /// Returns resources used for each of the currently open files.
+ /// Results are inherently racy as they measure activity of other threads.
+ llvm::StringMap<FileStats> fileStats() const;
/// Returns a list of files with ASTs currently stored in memory. This method
/// is not very reliable and is only used for test. E.g., the results will not
namespace {
+using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::Gt;
EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("two"))));
}
-TEST_F(ClangdVFSTest, MemoryUsage) {
+MATCHER_P4(Stats, Name, UsesMemory, PreambleBuilds, ASTBuilds, "") {
+ return arg.first() == Name && (arg.second.UsedBytes != 0) == UsesMemory &&
+ std::tie(arg.second.PreambleBuilds, ASTBuilds) ==
+ std::tie(PreambleBuilds, ASTBuilds);
+}
+
+TEST_F(ClangdVFSTest, FileStats) {
MockFSProvider FS;
ErrorCheckingCallbacks DiagConsumer;
MockCompilationDatabase CDB;
FS.Files[FooCpp] = "";
FS.Files[BarCpp] = "";
- EXPECT_THAT(Server.getUsedBytesPerFile(), IsEmpty());
+ EXPECT_THAT(Server.fileStats(), IsEmpty());
Server.addDocument(FooCpp, SourceContents);
Server.addDocument(BarCpp, SourceContents);
ASSERT_TRUE(Server.blockUntilIdleForTest());
- EXPECT_THAT(Server.getUsedBytesPerFile(),
- UnorderedElementsAre(Pair(FooCpp, Gt(0u)), Pair(BarCpp, Gt(0u))));
+ EXPECT_THAT(Server.fileStats(),
+ UnorderedElementsAre(Stats(FooCpp, true, 1, 1),
+ Stats(BarCpp, true, 1, 1)));
Server.removeDocument(FooCpp);
ASSERT_TRUE(Server.blockUntilIdleForTest());
- EXPECT_THAT(Server.getUsedBytesPerFile(), ElementsAre(Pair(BarCpp, Gt(0u))));
+ EXPECT_THAT(Server.fileStats(), ElementsAre(Stats(BarCpp, true, 1, 1)));
Server.removeDocument(BarCpp);
ASSERT_TRUE(Server.blockUntilIdleForTest());
- EXPECT_THAT(Server.getUsedBytesPerFile(), IsEmpty());
+ EXPECT_THAT(Server.fileStats(), IsEmpty());
}
TEST_F(ClangdVFSTest, InvalidCompileCommand) {
UnorderedElementsAre(Foo, AnyOf(Bar, Baz)));
}
+// We send "empty" changes to TUScheduler when we think some external event
+// *might* have invalidated current state (e.g. a header was edited).
+// Verify that this doesn't evict our cache entries.
+TEST_F(TUSchedulerTests, NoopChangesDontThrashCache) {
+ auto Opts = optsForTest();
+ Opts.RetentionPolicy.MaxRetainedASTs = 1;
+ TUScheduler S(CDB, Opts);
+
+ auto Foo = testPath("foo.cpp");
+ auto FooInputs = getInputs(Foo, "int x=1;");
+ auto Bar = testPath("bar.cpp");
+ auto BarInputs = getInputs(Bar, "int x=2;");
+
+ // After opening Foo then Bar, AST cache contains Bar.
+ S.update(Foo, FooInputs, WantDiagnostics::Auto);
+ ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
+ S.update(Bar, BarInputs, WantDiagnostics::Auto);
+ ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
+ ASSERT_THAT(S.getFilesWithCachedAST(), ElementsAre(Bar));
+
+ // Any number of no-op updates to Foo don't dislodge Bar from the cache.
+ S.update(Foo, FooInputs, WantDiagnostics::Auto);
+ S.update(Foo, FooInputs, WantDiagnostics::Auto);
+ S.update(Foo, FooInputs, WantDiagnostics::Auto);
+ ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
+ ASSERT_THAT(S.getFilesWithCachedAST(), ElementsAre(Bar));
+ // In fact each file has been built only once.
+ ASSERT_EQ(S.fileStats().lookup(Foo).ASTBuilds, 1u);
+ ASSERT_EQ(S.fileStats().lookup(Bar).ASTBuilds, 1u);
+}
+
TEST_F(TUSchedulerTests, EmptyPreamble) {
TUScheduler S(CDB, optsForTest());
Files[Header] = "int a;";
Timestamps[Header] = time_t(0);
- auto SourceContents = R"cpp(
+ std::string SourceContents = R"cpp(
#include "foo.h"
int b = a;
)cpp";
// Test that subsequent updates with the same inputs do not cause rebuilds.
ASSERT_TRUE(DoUpdate(SourceContents));
+ ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 1u);
+ ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 1u);
ASSERT_FALSE(DoUpdate(SourceContents));
+ ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 1u);
+ ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 1u);
// Update to a header should cause a rebuild, though.
Timestamps[Header] = time_t(1);
ASSERT_TRUE(DoUpdate(SourceContents));
ASSERT_FALSE(DoUpdate(SourceContents));
+ ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 2u);
+ ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 2u);
// Update to the contents should cause a rebuild.
- auto OtherSourceContents = R"cpp(
- #include "foo.h"
- int c = d;
- )cpp";
- ASSERT_TRUE(DoUpdate(OtherSourceContents));
- ASSERT_FALSE(DoUpdate(OtherSourceContents));
+ SourceContents += "\nint c = b;";
+ ASSERT_TRUE(DoUpdate(SourceContents));
+ ASSERT_FALSE(DoUpdate(SourceContents));
+ ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 3u);
+ ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 2u);
// Update to the compile commands should also cause a rebuild.
CDB.ExtraClangFlags.push_back("-DSOMETHING");
- ASSERT_TRUE(DoUpdate(OtherSourceContents));
- ASSERT_FALSE(DoUpdate(OtherSourceContents));
+ ASSERT_TRUE(DoUpdate(SourceContents));
+ ASSERT_FALSE(DoUpdate(SourceContents));
+ ASSERT_EQ(S.fileStats().lookup(Source).ASTBuilds, 4u);
+ ASSERT_EQ(S.fileStats().lookup(Source).PreambleBuilds, 3u);
}
TEST_F(TUSchedulerTests, ForceRebuild) {