[clang] [llvm] [llvm][clang] Trace VFS calls (PR #88326)

Jan Svoboda via cfe-commits cfe-commits at lists.llvm.org
Wed Apr 10 16:04:09 PDT 2024


https://github.com/jansvoboda11 created https://github.com/llvm/llvm-project/pull/88326

None

>From 540321e84dbd3c5687cfcc60e9deec79d790896e Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Wed, 10 Apr 2024 16:03:19 -0700
Subject: [PATCH] [llvm][clang] Trace VFS calls

---
 clang/include/clang/Driver/Compilation.h      | 11 +++++
 clang/include/clang/Driver/Options.td         |  7 ++++
 .../include/clang/Frontend/CompilerInstance.h |  8 ++++
 .../clang/Frontend/CompilerInvocation.h       | 10 +++--
 .../include/clang/Frontend/FrontendOptions.h  |  3 ++
 clang/lib/Driver/Driver.cpp                   | 34 ++++++++++++++-
 clang/lib/Driver/ToolChains/Clang.cpp         |  3 ++
 clang/lib/Frontend/CompilerInstance.cpp       |  2 +-
 clang/lib/Frontend/CompilerInvocation.cpp     | 19 ++++++---
 .../DependencyScanningWorker.cpp              |  1 +
 .../DependencyScanning/ModuleDepCollector.cpp |  7 ++++
 clang/test/ClangScanDeps/modules-inferred.m   |  2 +-
 clang/tools/clang-scan-deps/ClangScanDeps.cpp | 25 ++++++++++-
 clang/tools/clang-scan-deps/Opts.td           |  1 +
 clang/tools/driver/cc1_main.cpp               | 13 ++++++
 llvm/include/llvm/Support/VirtualFileSystem.h | 29 +++++++++++++
 llvm/lib/Support/VirtualFileSystem.cpp        | 42 +++++++++++++++++++
 .../Support/VirtualFileSystemTest.cpp         | 37 ++++++++++++++++
 18 files changed, 241 insertions(+), 13 deletions(-)

diff --git a/clang/include/clang/Driver/Compilation.h b/clang/include/clang/Driver/Compilation.h
index 36ae85c4245143..baf55d6b0f6061 100644
--- a/clang/include/clang/Driver/Compilation.h
+++ b/clang/include/clang/Driver/Compilation.h
@@ -115,6 +115,9 @@ class Compilation {
   /// -ftime-trace result files.
   ArgStringMap TimeTraceFiles;
 
+  /// -fvfs-trace result files.
+  ArgStringMap VFSTraceFiles;
+
   /// Optional redirection for stdin, stdout, stderr.
   std::vector<std::optional<StringRef>> Redirects;
 
@@ -280,6 +283,14 @@ class Compilation {
     TimeTraceFiles[JA] = Name;
   }
 
+  const char *getVFSTraceFile(const JobAction *JA) const {
+    return VFSTraceFiles.lookup(JA);
+  }
+  void addVFSTraceFile(const char *Name, const JobAction *JA) {
+    assert(!VFSTraceFiles.contains(JA));
+    VFSTraceFiles[JA] = Name;
+  }
+
   /// CleanupFile - Delete a given file.
   ///
   /// \param IssueErrors - Report failures as errors.
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 0a74e6c75f95bb..630c1c763b5180 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -3904,6 +3904,13 @@ def ftime_trace_EQ : Joined<["-"], "ftime-trace=">, Group<f_Group>,
   HelpText<"Similar to -ftime-trace. Specify the JSON file or a directory which will contain the JSON file">,
   Visibility<[ClangOption, CC1Option, CLOption, DXCOption]>,
   MarshallingInfoString<FrontendOpts<"TimeTracePath">>;
+def fvfs_trace : Flag<["-"], "fvfs-trace">, Group<f_Group>,
+  HelpText<"Turn of virtual file system profiler. Generates text file based on output filename.">,
+  Visibility<[ClangOption, CLOption, DXCOption]>;
+def fvfs_trace_EQ : Joined<["-"], "fvfs-trace=">, Group<f_Group>,
+  HelpText<"Similar to -fvfs-trace. Specify the text file or a directory that will contain the text file">,
+  Visibility<[ClangOption, CC1Option, CLOption, DXCOption]>,
+  MarshallingInfoString<FrontendOpts<"VFSTracePath">>;
 def fproc_stat_report : Joined<["-"], "fproc-stat-report">, Group<f_Group>,
   HelpText<"Print subprocess statistics">;
 def fproc_stat_report_EQ : Joined<["-"], "fproc-stat-report=">, Group<f_Group>,
diff --git a/clang/include/clang/Frontend/CompilerInstance.h b/clang/include/clang/Frontend/CompilerInstance.h
index 3464654284f199..2135002366a33a 100644
--- a/clang/include/clang/Frontend/CompilerInstance.h
+++ b/clang/include/clang/Frontend/CompilerInstance.h
@@ -35,6 +35,9 @@ namespace llvm {
 class raw_fd_ostream;
 class Timer;
 class TimerGroup;
+namespace vfs {
+struct InstrumentingFileSystem;
+}
 }
 
 namespace clang {
@@ -89,6 +92,11 @@ class CompilerInstance : public ModuleLoader {
   /// Auxiliary Target info.
   IntrusiveRefCntPtr<TargetInfo> AuxTarget;
 
+public:
+  /// The instrumenting file system.
+  IntrusiveRefCntPtr<llvm::vfs::InstrumentingFileSystem> IVFS;
+private:
+
   /// The file manager.
   IntrusiveRefCntPtr<FileManager> FileMgr;
 
diff --git a/clang/include/clang/Frontend/CompilerInvocation.h b/clang/include/clang/Frontend/CompilerInvocation.h
index 1a2a39411e58d8..b50e7c7da636ec 100644
--- a/clang/include/clang/Frontend/CompilerInvocation.h
+++ b/clang/include/clang/Frontend/CompilerInvocation.h
@@ -39,6 +39,7 @@ class ArgList;
 namespace vfs {
 
 class FileSystem;
+struct InstrumentingFileSystem;
 
 } // namespace vfs
 
@@ -390,13 +391,14 @@ class CowCompilerInvocation : public CompilerInvocationBase {
   /// @}
 };
 
-IntrusiveRefCntPtr<llvm::vfs::FileSystem>
-createVFSFromCompilerInvocation(const CompilerInvocation &CI,
-                                DiagnosticsEngine &Diags);
+IntrusiveRefCntPtr<llvm::vfs::FileSystem> createVFSFromCompilerInvocation(
+    const CompilerInvocation &CI, DiagnosticsEngine &Diags,
+    IntrusiveRefCntPtr<llvm::vfs::InstrumentingFileSystem> *TracingFS = {});
 
 IntrusiveRefCntPtr<llvm::vfs::FileSystem> createVFSFromCompilerInvocation(
     const CompilerInvocation &CI, DiagnosticsEngine &Diags,
-    IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS);
+    IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS,
+    IntrusiveRefCntPtr<llvm::vfs::InstrumentingFileSystem> *TracingFS = {});
 
 IntrusiveRefCntPtr<llvm::vfs::FileSystem>
 createVFSFromOverlayFiles(ArrayRef<std::string> VFSOverlayFiles,
diff --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h
index 5ee4d471670f48..2cbf8bd7735888 100644
--- a/clang/include/clang/Frontend/FrontendOptions.h
+++ b/clang/include/clang/Frontend/FrontendOptions.h
@@ -568,6 +568,9 @@ class FrontendOptions {
   /// Path which stores the output files for -ftime-trace
   std::string TimeTracePath;
 
+  /// Path which stores the output files for -fvfs-trace
+  std::string VFSTracePath;
+
 public:
   FrontendOptions()
       : DisableFree(false), RelocatablePCH(false), ShowHelp(false),
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index e7335a61b10c53..9bd13a14f48b6e 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -5429,6 +5429,36 @@ static void handleTimeTrace(Compilation &C, const ArgList &Args,
   C.addResultFile(ResultFile, JA);
 }
 
+static void handleVFSTrace(Compilation &C, const ArgList &Args,
+                           const JobAction *JA, const char *BaseInput,
+                           const InputInfo &Result) {
+  Arg *A = Args.getLastArg(options::OPT_fvfs_trace, options::OPT_fvfs_trace_EQ);
+  if (!A)
+    return;
+  SmallString<128> Path;
+  if (A->getOption().matches(options::OPT_fvfs_trace_EQ)) {
+    Path = A->getValue();
+    if (llvm::sys::fs::is_directory(Path)) {
+      SmallString<128> Tmp(Result.getFilename());
+      llvm::sys::path::replace_extension(Tmp, "vfs.txt");
+      llvm::sys::path::append(Path, llvm::sys::path::filename(Tmp));
+    }
+  } else {
+    if (Arg *DumpDir = Args.getLastArgNoClaim(options::OPT_dumpdir)) {
+      // The trace file is ${dumpdir}${basename}.vfs.txt. Note that dumpdir may
+      // not end with a path separator.
+      Path = DumpDir->getValue();
+      Path += llvm::sys::path::filename(BaseInput);
+    } else {
+      Path = Result.getFilename();
+    }
+    llvm::sys::path::replace_extension(Path, "vfs.txt");
+  }
+  const char *ResultFile = C.getArgs().MakeArgString(Path);
+  C.addVFSTraceFile(ResultFile, JA);
+  C.addResultFile(ResultFile, JA);
+}
+
 InputInfoList Driver::BuildJobsForActionNoCache(
     Compilation &C, const Action *A, const ToolChain *TC, StringRef BoundArch,
     bool AtTopLevel, bool MultipleArchs, const char *LinkingOutput,
@@ -5678,8 +5708,10 @@ InputInfoList Driver::BuildJobsForActionNoCache(
                                              AtTopLevel, MultipleArchs,
                                              OffloadingPrefix),
                        BaseInput);
-    if (T->canEmitIR() && OffloadingPrefix.empty())
+    if (T->canEmitIR() && OffloadingPrefix.empty()) {
       handleTimeTrace(C, Args, JA, BaseInput, Result);
+      handleVFSTrace(C, Args, JA, BaseInput, Result);
+    }
   }
 
   if (CCCPrintBindings && !CCGenDiagnostics) {
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 766a9b91e3c0ad..5e179d0497fab0 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6759,6 +6759,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
     Args.AddLastArg(CmdArgs, options::OPT_ftime_trace_granularity_EQ);
   }
 
+  if (const char *Name = C.getVFSTraceFile(&JA))
+    CmdArgs.push_back(Args.MakeArgString("-fvfs-trace=" + Twine(Name)));
+
   if (Arg *A = Args.getLastArg(options::OPT_ftrapv_handler_EQ)) {
     CmdArgs.push_back("-ftrapv-handler");
     CmdArgs.push_back(A->getValue());
diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp
index 6e3baf83864415..4620184a5f6b06 100644
--- a/clang/lib/Frontend/CompilerInstance.cpp
+++ b/clang/lib/Frontend/CompilerInstance.cpp
@@ -379,7 +379,7 @@ FileManager *CompilerInstance::createFileManager(
   if (!VFS)
     VFS = FileMgr ? &FileMgr->getVirtualFileSystem()
                   : createVFSFromCompilerInvocation(getInvocation(),
-                                                    getDiagnostics());
+                                                    getDiagnostics(), &IVFS);
   assert(VFS && "FileManager has no VFS?");
   FileMgr = new FileManager(getFileSystemOpts(), std::move(VFS));
   return FileMgr.get();
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 1f1f5440ddd75f..f6cae9d40591d4 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -4935,16 +4935,25 @@ void CompilerInvocation::clearImplicitModuleBuildOptions() {
 }
 
 IntrusiveRefCntPtr<llvm::vfs::FileSystem>
-clang::createVFSFromCompilerInvocation(const CompilerInvocation &CI,
-                                       DiagnosticsEngine &Diags) {
-  return createVFSFromCompilerInvocation(CI, Diags,
-                                         llvm::vfs::getRealFileSystem());
+clang::createVFSFromCompilerInvocation(
+    const CompilerInvocation &CI, DiagnosticsEngine &Diags,
+    IntrusiveRefCntPtr<llvm::vfs::InstrumentingFileSystem> *TracingFS) {
+  return createVFSFromCompilerInvocation(
+      CI, Diags, llvm::vfs::getRealFileSystem(), TracingFS);
 }
 
 IntrusiveRefCntPtr<llvm::vfs::FileSystem>
 clang::createVFSFromCompilerInvocation(
     const CompilerInvocation &CI, DiagnosticsEngine &Diags,
-    IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS) {
+    IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS,
+    IntrusiveRefCntPtr<llvm::vfs::InstrumentingFileSystem> *TracingFS) {
+  if (!CI.getFrontendOpts().VFSTracePath.empty()) {
+    auto TFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InstrumentingFileSystem>(
+        std::move(BaseFS));
+    if (TracingFS)
+      *TracingFS = TFS;
+    BaseFS = std::move(TFS);
+  }
   return createVFSFromOverlayFiles(CI.getHeaderSearchOpts().VFSOverlayFiles,
                                    Diags, std::move(BaseFS));
 }
diff --git a/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp b/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp
index 32850f5eea92a9..39f8eb4bbb1e2e 100644
--- a/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp
+++ b/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp
@@ -338,6 +338,7 @@ class DependencyScanningAction : public tooling::ToolAction {
     ScanInstance.getFrontendOpts().GenerateGlobalModuleIndex = false;
     ScanInstance.getFrontendOpts().UseGlobalModuleIndex = false;
     ScanInstance.getFrontendOpts().ModulesShareFileManager = false;
+    ScanInstance.getFrontendOpts().VFSTracePath.clear();
     ScanInstance.getHeaderSearchOpts().ModuleFormat = "raw";
     ScanInstance.getHeaderSearchOpts().ModulesIncludeVFSUsage =
         any(OptimizeArgs & ScanningOptimizations::VFS);
diff --git a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp
index 94ccbd3351b09d..f8b43c682d5e52 100644
--- a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp
+++ b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp
@@ -152,6 +152,11 @@ void ModuleDepCollector::addOutputPaths(CowCompilerInvocation &CI,
       CI.getMutDependencyOutputOpts().Targets.push_back(std::string(Target));
     }
   }
+  if (!CI.getMutFrontendOpts().VFSTracePath.empty()) {
+    SmallString<128> VFSTracePath{CI.getMutFrontendOpts().OutputFile};
+    llvm::sys::path::replace_extension(VFSTracePath, "vfs.txt");
+    CI.getMutFrontendOpts().VFSTracePath = VFSTracePath.str();
+  }
 }
 
 static CowCompilerInvocation
@@ -186,6 +191,8 @@ makeCommonInvocationForModuleBuild(CompilerInvocation CI) {
     CI.getDiagnosticOpts().DiagnosticSerializationFile = "-";
   if (!CI.getDependencyOutputOpts().OutputFile.empty())
     CI.getDependencyOutputOpts().OutputFile = "-";
+  if (!CI.getFrontendOpts().VFSTracePath.empty())
+    CI.getFrontendOpts().VFSTracePath = "-";
   CI.getDependencyOutputOpts().Targets.clear();
 
   CI.getFrontendOpts().ProgramAction = frontend::GenerateModule;
diff --git a/clang/test/ClangScanDeps/modules-inferred.m b/clang/test/ClangScanDeps/modules-inferred.m
index 4d18a20949205f..a10c7e4d9bde66 100644
--- a/clang/test/ClangScanDeps/modules-inferred.m
+++ b/clang/test/ClangScanDeps/modules-inferred.m
@@ -21,7 +21,7 @@
 [{
   "directory": "DIR",
   "file": "DIR/tu.m",
-  "command": "clang -fmodules -fimplicit-module-maps -fmodules-cache-path=DIR/cache -F DIR/frameworks -c DIR/tu.m -o DIR/tu.o"
+  "command": "clang -fmodules -fimplicit-module-maps -fmodules-cache-path=DIR/cache -F DIR/frameworks -c DIR/tu.m -o DIR/tu.o -fvfs-trace"
 }]
 
 // RUN: sed "s|DIR|%/t|g" %t/cdb.json.template > %t/cdb.json
diff --git a/clang/tools/clang-scan-deps/ClangScanDeps.cpp b/clang/tools/clang-scan-deps/ClangScanDeps.cpp
index eaa76dd43e41dd..a8f0e0cf5a7f18 100644
--- a/clang/tools/clang-scan-deps/ClangScanDeps.cpp
+++ b/clang/tools/clang-scan-deps/ClangScanDeps.cpp
@@ -84,6 +84,7 @@ static std::vector<std::string> ModuleDepTargets;
 static bool DeprecatedDriverCommand;
 static ResourceDirRecipeKind ResourceDirRecipe;
 static bool Verbose;
+static bool PrintVFSTrace;
 static bool PrintTiming;
 static std::vector<const char *> CommandLine;
 
@@ -219,6 +220,8 @@ static void ParseArgs(int argc, char **argv) {
     ResourceDirRecipe = *Kind;
   }
 
+  PrintVFSTrace = Args.hasArg(OPT_vfs_trace);
+
   PrintTiming = Args.hasArg(OPT_print_timing);
 
   Verbose = Args.hasArg(OPT_verbose);
@@ -886,8 +889,16 @@ int clang_scan_deps_main(int argc, char **argv, const llvm::ToolContext &) {
   if (Format == ScanningOutputFormat::Full)
     FD.emplace(ModuleName.empty() ? Inputs.size() : 0);
 
+  std::atomic<std::size_t> NumStatusCalls = 0;
+  std::atomic<std::size_t> NumOpenCalls = 0;
+  std::atomic<std::size_t> NumDirBeginCalls = 0;
+  std::atomic<std::size_t> NumRealPathCalls = 0;
+
   auto ScanningTask = [&](DependencyScanningService &Service) {
-    DependencyScanningTool WorkerTool(Service);
+    auto TracingFS =
+        llvm::makeIntrusiveRefCnt<llvm::vfs::InstrumentingFileSystem>(
+            llvm::vfs::createPhysicalFileSystem());
+    DependencyScanningTool WorkerTool(Service, TracingFS);
 
     llvm::DenseSet<ModuleID> AlreadySeenModules;
     while (auto MaybeInputIndex = GetNextInputIndex()) {
@@ -970,6 +981,11 @@ int clang_scan_deps_main(int argc, char **argv, const llvm::ToolContext &) {
           HadErrors = true;
       }
     }
+
+    NumStatusCalls += TracingFS->NumStatusCalls;
+    NumOpenCalls += TracingFS->NumOpenCalls;
+    NumDirBeginCalls += TracingFS->NumDirBeginCalls;
+    NumRealPathCalls += TracingFS->NumRealPathCalls;
   };
 
   DependencyScanningService Service(ScanMode, Format, OptimizeArgs,
@@ -1001,6 +1017,13 @@ int clang_scan_deps_main(int argc, char **argv, const llvm::ToolContext &) {
         "clang-scan-deps timing: %0.2fs wall, %0.2fs process\n",
         T.getTotalTime().getWallTime(), T.getTotalTime().getProcessTime());
 
+  if (PrintVFSTrace)
+    llvm::errs() << llvm::format(
+        "clang-scan-deps VFS trace: %d status, %d openFileForRead, %d "
+        "dir_begin, %d getRealPath\n",
+        NumStatusCalls.load(), NumOpenCalls.load(), NumDirBeginCalls.load(),
+        NumRealPathCalls.load());
+
   if (RoundTripArgs)
     if (FD && FD->roundTripCommands(llvm::errs()))
       HadErrors = true;
diff --git a/clang/tools/clang-scan-deps/Opts.td b/clang/tools/clang-scan-deps/Opts.td
index 5cd5d1a9fb37bc..07bc72c60d27db 100644
--- a/clang/tools/clang-scan-deps/Opts.td
+++ b/clang/tools/clang-scan-deps/Opts.td
@@ -31,6 +31,7 @@ def deprecated_driver_command : F<"deprecated-driver-command", "use a single dri
 
 defm resource_dir_recipe : Eq<"resource-dir-recipe", "How to produce missing '-resource-dir' argument">;
 
+def vfs_trace: F<"fvfs-trace", "Profile virtual file system calls">;
 def print_timing : F<"print-timing", "Print timing information">;
 
 def verbose : F<"v", "Use verbose output">;
diff --git a/clang/tools/driver/cc1_main.cpp b/clang/tools/driver/cc1_main.cpp
index b5c6be3c557bb3..408a942ff1f9b7 100644
--- a/clang/tools/driver/cc1_main.cpp
+++ b/clang/tools/driver/cc1_main.cpp
@@ -261,6 +261,19 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
     }
   }
 
+  if (!Clang->getFrontendOpts().VFSTracePath.empty()) {
+    assert(Clang->IVFS);
+    if (auto VFSOutput = Clang->createOutputFile(
+            Clang->getFrontendOpts().VFSTracePath, /*Binary=*/false,
+            /*RemoveFileOnSignal=*/false,
+            /*useTemporary=*/false)) {
+      *VFSOutput << "status\t" << Clang->IVFS->NumStatusCalls << "\n"
+                 << "openFileForRead\t" << Clang->IVFS->NumOpenCalls << "\n"
+                 << "dir_begin\t" << Clang->IVFS->NumDirBeginCalls << "\n"
+                 << "getRealPath\t" << Clang->IVFS->NumRealPathCalls << "\n";
+    }
+  }
+
   // Our error handler depends on the Diagnostics object, which we're
   // potentially about to delete. Uninstall the handler now so that any
   // later errors use the default handling behavior instead.
diff --git a/llvm/include/llvm/Support/VirtualFileSystem.h b/llvm/include/llvm/Support/VirtualFileSystem.h
index 770ca8764426a4..e175996ee1c651 100644
--- a/llvm/include/llvm/Support/VirtualFileSystem.h
+++ b/llvm/include/llvm/Support/VirtualFileSystem.h
@@ -26,6 +26,7 @@
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/SourceMgr.h"
+#include <atomic>
 #include <cassert>
 #include <cstdint>
 #include <ctime>
@@ -1125,6 +1126,34 @@ class YAMLVFSWriter {
   void write(llvm::raw_ostream &OS);
 };
 
+/// File system that tracks the number of calls to the underlying file system.
+/// This is particularly useful when wrapped around \c RealFileSystem to add
+/// lightweight tracking of expensive syscalls.
+struct InstrumentingFileSystem
+    : llvm::RTTIExtends<InstrumentingFileSystem, OverlayFileSystem> {
+  static const char ID;
+
+  std::atomic<std::size_t> NumStatusCalls = 0;
+  std::atomic<std::size_t> NumOpenCalls = 0;
+  std::atomic<std::size_t> NumDirBeginCalls = 0;
+  mutable std::atomic<std::size_t> NumRealPathCalls = 0;
+
+  InstrumentingFileSystem(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS);
+
+  ErrorOr<Status> status(const Twine &Path) override;
+
+  ErrorOr<std::unique_ptr<File>> openFileForRead(const Twine &Path) override;
+
+  directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override;
+
+  std::error_code getRealPath(const Twine &Path,
+                              SmallVectorImpl<char> &Output) const override;
+
+protected:
+  void printImpl(raw_ostream &OS, PrintType Type,
+                 unsigned IndentLevel) const override;
+};
+
 } // namespace vfs
 } // namespace llvm
 
diff --git a/llvm/lib/Support/VirtualFileSystem.cpp b/llvm/lib/Support/VirtualFileSystem.cpp
index 057f8eae0552c6..70bc8c28a18ae4 100644
--- a/llvm/lib/Support/VirtualFileSystem.cpp
+++ b/llvm/lib/Support/VirtualFileSystem.cpp
@@ -2880,8 +2880,50 @@ recursive_directory_iterator::increment(std::error_code &EC) {
   return *this;
 }
 
+InstrumentingFileSystem::InstrumentingFileSystem(
+    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
+    : RTTIExtends(std::move(FS)) {}
+
+ErrorOr<Status> InstrumentingFileSystem::status(const Twine &Path) {
+  ++NumStatusCalls;
+  return OverlayFileSystem::status(Path);
+}
+
+ErrorOr<std::unique_ptr<File>>
+InstrumentingFileSystem::openFileForRead(const Twine &Path) {
+  ++NumOpenCalls;
+  return OverlayFileSystem::openFileForRead(Path);
+}
+
+directory_iterator InstrumentingFileSystem::dir_begin(const Twine &Dir,
+                                                      std::error_code &EC) {
+  ++NumDirBeginCalls;
+  return OverlayFileSystem::dir_begin(Dir, EC);
+}
+
+std::error_code
+InstrumentingFileSystem::getRealPath(const Twine &Path,
+                                     SmallVectorImpl<char> &Output) const {
+  ++NumRealPathCalls;
+  return OverlayFileSystem::getRealPath(Path, Output);
+}
+
+void InstrumentingFileSystem::printImpl(raw_ostream &OS, PrintType Type,
+                                        unsigned IndentLevel) const {
+  printIndent(OS, IndentLevel);
+  OS << "InstrumentingFileSystem\n";
+  if (Type == PrintType::Summary)
+    return;
+
+  if (Type == PrintType::Contents)
+    Type = PrintType::Summary;
+  for (const auto &FS : overlays_range())
+    FS->print(OS, Type, IndentLevel + 1);
+}
+
 const char FileSystem::ID = 0;
 const char OverlayFileSystem::ID = 0;
 const char ProxyFileSystem::ID = 0;
 const char InMemoryFileSystem::ID = 0;
 const char RedirectingFileSystem::ID = 0;
+const char InstrumentingFileSystem::ID = 0;
diff --git a/llvm/unittests/Support/VirtualFileSystemTest.cpp b/llvm/unittests/Support/VirtualFileSystemTest.cpp
index 695b09343257f1..604d00b70757e0 100644
--- a/llvm/unittests/Support/VirtualFileSystemTest.cpp
+++ b/llvm/unittests/Support/VirtualFileSystemTest.cpp
@@ -3395,3 +3395,40 @@ TEST(RedirectingFileSystemTest, ExternalPaths) {
 
   EXPECT_EQ(CheckFS->SeenPaths, Expected);
 }
+
+TEST(InstrumentingFileSystemTest, Instrumentation) {
+  auto InMemoryFS = makeIntrusiveRefCnt<vfs::InMemoryFileSystem>();
+  auto InstrumentingFS =
+      makeIntrusiveRefCnt<vfs::InstrumentingFileSystem>(std::move(InMemoryFS));
+
+  EXPECT_EQ(InstrumentingFS->NumStatusCalls, 0u);
+  EXPECT_EQ(InstrumentingFS->NumOpenCalls, 0u);
+  EXPECT_EQ(InstrumentingFS->NumDirBeginCalls, 0u);
+  EXPECT_EQ(InstrumentingFS->NumRealPathCalls, 0u);
+
+  (void)InstrumentingFS->status("/foo");
+  EXPECT_EQ(InstrumentingFS->NumStatusCalls, 1u);
+  EXPECT_EQ(InstrumentingFS->NumOpenCalls, 0u);
+  EXPECT_EQ(InstrumentingFS->NumDirBeginCalls, 0u);
+  EXPECT_EQ(InstrumentingFS->NumRealPathCalls, 0u);
+
+  (void)InstrumentingFS->openFileForRead("/foo");
+  EXPECT_EQ(InstrumentingFS->NumStatusCalls, 1u);
+  EXPECT_EQ(InstrumentingFS->NumOpenCalls, 1u);
+  EXPECT_EQ(InstrumentingFS->NumDirBeginCalls, 0u);
+  EXPECT_EQ(InstrumentingFS->NumRealPathCalls, 0u);
+
+  std::error_code EC;
+  (void)InstrumentingFS->dir_begin("/foo", EC);
+  EXPECT_EQ(InstrumentingFS->NumStatusCalls, 1u);
+  EXPECT_EQ(InstrumentingFS->NumOpenCalls, 1u);
+  EXPECT_EQ(InstrumentingFS->NumDirBeginCalls, 1u);
+  EXPECT_EQ(InstrumentingFS->NumRealPathCalls, 0u);
+
+  SmallString<128> RealPath;
+  (void)InstrumentingFS->getRealPath("/foo", RealPath);
+  EXPECT_EQ(InstrumentingFS->NumStatusCalls, 1u);
+  EXPECT_EQ(InstrumentingFS->NumOpenCalls, 1u);
+  EXPECT_EQ(InstrumentingFS->NumDirBeginCalls, 1u);
+  EXPECT_EQ(InstrumentingFS->NumRealPathCalls, 1u);
+}



More information about the cfe-commits mailing list