Allow to configure scan roots.

Bug: 74584014
Change-Id: Idb306e321172e7d7ff239819fd8f975a352642e8
diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn
index d0b8a20..ce1088c 100644
--- a/gn/standalone/BUILD.gn
+++ b/gn/standalone/BUILD.gn
@@ -74,6 +74,7 @@
     cflags += [
       "-fcolor-diagnostics",
       "-Wno-unknown-warning-option",
+      "-fdiagnostics-show-template-tree",
     ]
   } else {
     cflags += [ "-Wno-unknown-warning" ]
diff --git a/include/perfetto/tracing/core/data_source_config.h b/include/perfetto/tracing/core/data_source_config.h
index f3fd793..34527ae 100644
--- a/include/perfetto/tracing/core/data_source_config.h
+++ b/include/perfetto/tracing/core/data_source_config.h
@@ -47,6 +47,7 @@
 class FtraceConfig;
 class ChromeConfig;
 class InodeFileConfig;
+class InodeFileConfig_MountPointMappingEntry;
 class TestConfig;
 }  // namespace protos
 }  // namespace perfetto
diff --git a/include/perfetto/tracing/core/inode_file_config.h b/include/perfetto/tracing/core/inode_file_config.h
index 65e1198..7704fcd 100644
--- a/include/perfetto/tracing/core/inode_file_config.h
+++ b/include/perfetto/tracing/core/inode_file_config.h
@@ -39,13 +39,48 @@
 namespace perfetto {
 namespace protos {
 class InodeFileConfig;
-}
+class InodeFileConfig_MountPointMappingEntry;
+}  // namespace protos
 }  // namespace perfetto
 
 namespace perfetto {
 
 class PERFETTO_EXPORT InodeFileConfig {
  public:
+  class PERFETTO_EXPORT MountPointMappingEntry {
+   public:
+    MountPointMappingEntry();
+    ~MountPointMappingEntry();
+    MountPointMappingEntry(MountPointMappingEntry&&) noexcept;
+    MountPointMappingEntry& operator=(MountPointMappingEntry&&);
+    MountPointMappingEntry(const MountPointMappingEntry&);
+    MountPointMappingEntry& operator=(const MountPointMappingEntry&);
+
+    // Conversion methods from/to the corresponding protobuf types.
+    void FromProto(
+        const perfetto::protos::InodeFileConfig_MountPointMappingEntry&);
+    void ToProto(
+        perfetto::protos::InodeFileConfig_MountPointMappingEntry*) const;
+
+    const std::string& mountpoint() const { return mountpoint_; }
+    void set_mountpoint(const std::string& value) { mountpoint_ = value; }
+
+    int scan_roots_size() const { return static_cast<int>(scan_roots_.size()); }
+    const std::vector<std::string>& scan_roots() const { return scan_roots_; }
+    std::string* add_scan_roots() {
+      scan_roots_.emplace_back();
+      return &scan_roots_.back();
+    }
+
+   private:
+    std::string mountpoint_ = {};
+    std::vector<std::string> scan_roots_;
+
+    // Allows to preserve unknown protobuf fields for compatibility
+    // with future versions of .proto files.
+    std::string unknown_fields_;
+  };
+
   InodeFileConfig();
   ~InodeFileConfig();
   InodeFileConfig(InodeFileConfig&&) noexcept;
@@ -69,11 +104,35 @@
   bool do_not_scan() const { return do_not_scan_; }
   void set_do_not_scan(bool value) { do_not_scan_ = value; }
 
+  int scan_mount_points_size() const {
+    return static_cast<int>(scan_mount_points_.size());
+  }
+  const std::vector<std::string>& scan_mount_points() const {
+    return scan_mount_points_;
+  }
+  std::string* add_scan_mount_points() {
+    scan_mount_points_.emplace_back();
+    return &scan_mount_points_.back();
+  }
+
+  int mount_point_mapping_size() const {
+    return static_cast<int>(mount_point_mapping_.size());
+  }
+  const std::vector<MountPointMappingEntry>& mount_point_mapping() const {
+    return mount_point_mapping_;
+  }
+  MountPointMappingEntry* add_mount_point_mapping() {
+    mount_point_mapping_.emplace_back();
+    return &mount_point_mapping_.back();
+  }
+
  private:
   uint64_t scan_interval_ms_ = {};
   uint64_t scan_delay_ms_ = {};
   uint64_t scan_batch_size_ = {};
   bool do_not_scan_ = {};
+  std::vector<std::string> scan_mount_points_;
+  std::vector<MountPointMappingEntry> mount_point_mapping_;
 
   // Allows to preserve unknown protobuf fields for compatibility
   // with future versions of .proto files.
diff --git a/include/perfetto/tracing/core/trace_config.h b/include/perfetto/tracing/core/trace_config.h
index 89da57e..8436340 100644
--- a/include/perfetto/tracing/core/trace_config.h
+++ b/include/perfetto/tracing/core/trace_config.h
@@ -47,6 +47,7 @@
 class FtraceConfig;
 class ChromeConfig;
 class InodeFileConfig;
+class InodeFileConfig_MountPointMappingEntry;
 class TestConfig;
 class TraceConfig_ProducerConfig;
 class TraceConfig_StatsdMetadata;
diff --git a/protos/perfetto/config/inode_file/inode_file_config.proto b/protos/perfetto/config/inode_file/inode_file_config.proto
index 7a57fd4..a1127ff 100644
--- a/protos/perfetto/config/inode_file/inode_file_config.proto
+++ b/protos/perfetto/config/inode_file/inode_file_config.proto
@@ -23,6 +23,11 @@
 // to reflect changes in the corresponding C++ headers.
 
 message InodeFileConfig {
+  message MountPointMappingEntry {
+    optional string mountpoint = 1;
+    repeated string scan_roots = 2;
+  }
+
   // How long to pause between batches.
   optional uint64 scan_interval_ms = 1;
 
@@ -34,4 +39,12 @@
 
   // Do not scan for inodes not found in the static map.
   optional bool do_not_scan = 4;
+
+  // If non-empty, only scan inodes corresponding to block devices named in
+  // this list.
+  repeated string scan_mount_points = 5;
+
+  // When encountering an inode belonging to a block device corresponding
+  // to one of the mount points in this map, scan its scan_roots instead.
+  repeated MountPointMappingEntry mount_point_mapping = 6;
 }
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 4119fef..01a3785 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -28,6 +28,11 @@
 // to reflect changes in the corresponding C++ headers.
 
 message InodeFileConfig {
+  message MountPointMappingEntry {
+    optional string mountpoint = 1;
+    repeated string scan_roots = 2;
+  }
+
   // How long to pause between batches.
   optional uint64 scan_interval_ms = 1;
 
@@ -39,6 +44,14 @@
 
   // Do not scan for inodes not found in the static map.
   optional bool do_not_scan = 4;
+
+  // If non-empty, only scan inodes corresponding to block devices named in
+  // this list.
+  repeated string scan_mount_points = 5;
+
+  // When encountering an inode belonging to a block device corresponding
+  // to one of the mount points in this map, scan its scan_roots instead.
+  repeated MountPointMappingEntry mount_point_mapping = 6;
 }
 
 // End of protos/perfetto/config/inode_file/inode_file_config.proto
diff --git a/src/traced/probes/filesystem/inode_file_data_source.cc b/src/traced/probes/filesystem/inode_file_data_source.cc
index d6fa6b5..fd70ff7 100644
--- a/src/traced/probes/filesystem/inode_file_data_source.cc
+++ b/src/traced/probes/filesystem/inode_file_data_source.cc
@@ -43,6 +43,28 @@
   return def;
 }
 
+std::string DbgFmt(const std::vector<std::string>& values) {
+  if (values.empty())
+    return "";
+
+  std::string result;
+  for (auto it = values.cbegin(); it != values.cend() - 1; ++it)
+    result += *it + ",";
+
+  result += values.back();
+  return result;
+}
+
+std::map<std::string, std::vector<std::string>> BuildMountpointMapping(
+    const DataSourceConfig& source_config) {
+  std::map<std::string, std::vector<std::string>> m;
+  for (const auto& map_entry :
+       source_config.inode_file_config().mount_point_mapping())
+    m.emplace(map_entry.mountpoint(), map_entry.scan_roots());
+
+  return m;
+}
+
 class StaticMapDelegate : public FileScanner::Delegate {
  public:
   StaticMapDelegate(
@@ -64,7 +86,8 @@
   void OnInodeScanDone() {}
   std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>* map_;
 };
-}
+
+}  // namespace
 
 void CreateStaticDeviceToInodeMap(
     const std::string& root_directory,
@@ -94,6 +117,10 @@
     LRUInodeCache* cache,
     std::unique_ptr<TraceWriter> writer)
     : source_config_(std::move(source_config)),
+      scan_mount_points_(
+          source_config_.inode_file_config().scan_mount_points().cbegin(),
+          source_config_.inode_file_config().scan_mount_points().cend()),
+      mount_point_mapping_(BuildMountpointMapping(source_config_)),
       task_runner_(task_runner),
       session_id_(id),
       static_file_map_(static_file_map),
@@ -176,6 +203,21 @@
     if (source_config_.inode_file_config().do_not_scan())
       inode_numbers.clear();
 
+    // If we defined mount points we want to scan in the config,
+    // skip inodes on other mount points.
+    if (!scan_mount_points_.empty()) {
+      bool process = true;
+      auto range = mount_points_.equal_range(block_device_id);
+      for (auto it = range.first; it != range.second; ++it) {
+        if (scan_mount_points_.count(it->second) == 0) {
+          process = false;
+          break;
+        }
+      }
+      if (!process)
+        continue;
+    }
+
     if (!inode_numbers.empty()) {
       // Try to piggy back the current scan.
       auto it = missing_inodes_.find(block_device_id);
@@ -227,12 +269,10 @@
     Inode inode_number,
     const std::string& path,
     protos::pbzero::InodeFileMap_Entry_Type type) {
-  PERFETTO_DLOG("Saw %s %lu", path.c_str(), block_device_id);
   auto it = missing_inodes_.find(block_device_id);
   if (it == missing_inodes_.end())
     return true;
 
-  PERFETTO_DLOG("Missing %lu / %lu", missing_inodes_.size(), it->second.size());
   size_t n = it->second.erase(inode_number);
   if (n == 0)
     return true;
@@ -284,8 +324,18 @@
 void InodeFileDataSource::AddRootsForBlockDevice(
     BlockDeviceID block_device_id,
     std::vector<std::string>* roots) {
-  auto p = mount_points_.equal_range(block_device_id);
-  for (auto it = p.first; it != p.second; ++it)
+  auto range = mount_points_.equal_range(block_device_id);
+  for (auto it = range.first; it != range.second; ++it) {
+    PERFETTO_DLOG("Trying to replace %s", it->second.c_str());
+    auto replace_it = mount_point_mapping_.find(it->second);
+    if (replace_it != mount_point_mapping_.end()) {
+      roots->insert(roots->end(), replace_it->second.cbegin(),
+                    replace_it->second.cend());
+      return;
+    }
+  }
+
+  for (auto it = range.first; it != range.second; ++it)
     roots->emplace_back(it->second);
 }
 
@@ -297,6 +347,7 @@
 
   PERFETTO_DCHECK(file_scanner_.get() == nullptr);
   auto weak_this = GetWeakPtr();
+  PERFETTO_DLOG("Starting scan of %s", DbgFmt(roots).c_str());
   file_scanner_ = std::unique_ptr<FileScanner>(new FileScanner(
       std::move(roots), this, GetScanIntervalMs(), GetScanBatchSize()));
 
diff --git a/src/traced/probes/filesystem/inode_file_data_source.h b/src/traced/probes/filesystem/inode_file_data_source.h
index 6b7501d..1f4197f 100644
--- a/src/traced/probes/filesystem/inode_file_data_source.h
+++ b/src/traced/probes/filesystem/inode_file_data_source.h
@@ -102,6 +102,9 @@
   uint64_t GetScanBatchSize();
 
   const DataSourceConfig source_config_;
+  std::set<std::string> scan_mount_points_;
+  std::map<std::string, std::vector<std::string>> mount_point_mapping_;
+
   base::TaskRunner* task_runner_;
   const TracingSessionID session_id_;
   std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>*
diff --git a/src/tracing/core/inode_file_config.cc b/src/tracing/core/inode_file_config.cc
index 0da3b61..a535d0a 100644
--- a/src/tracing/core/inode_file_config.cc
+++ b/src/tracing/core/inode_file_config.cc
@@ -57,6 +57,22 @@
   static_assert(sizeof(do_not_scan_) == sizeof(proto.do_not_scan()),
                 "size mismatch");
   do_not_scan_ = static_cast<decltype(do_not_scan_)>(proto.do_not_scan());
+
+  scan_mount_points_.clear();
+  for (const auto& field : proto.scan_mount_points()) {
+    scan_mount_points_.emplace_back();
+    static_assert(
+        sizeof(scan_mount_points_.back()) == sizeof(proto.scan_mount_points(0)),
+        "size mismatch");
+    scan_mount_points_.back() =
+        static_cast<decltype(scan_mount_points_)::value_type>(field);
+  }
+
+  mount_point_mapping_.clear();
+  for (const auto& field : proto.mount_point_mapping()) {
+    mount_point_mapping_.emplace_back();
+    mount_point_mapping_.back().FromProto(field);
+  }
   unknown_fields_ = proto.unknown_fields();
 }
 
@@ -82,6 +98,62 @@
                 "size mismatch");
   proto->set_do_not_scan(
       static_cast<decltype(proto->do_not_scan())>(do_not_scan_));
+
+  for (const auto& it : scan_mount_points_) {
+    proto->add_scan_mount_points(it);
+    static_assert(sizeof(it) == sizeof(proto->scan_mount_points(0)),
+                  "size mismatch");
+  }
+
+  for (const auto& it : mount_point_mapping_) {
+    auto* entry = proto->add_mount_point_mapping();
+    it.ToProto(entry);
+  }
+  *(proto->mutable_unknown_fields()) = unknown_fields_;
+}
+
+InodeFileConfig::MountPointMappingEntry::MountPointMappingEntry() = default;
+InodeFileConfig::MountPointMappingEntry::~MountPointMappingEntry() = default;
+InodeFileConfig::MountPointMappingEntry::MountPointMappingEntry(
+    const InodeFileConfig::MountPointMappingEntry&) = default;
+InodeFileConfig::MountPointMappingEntry&
+InodeFileConfig::MountPointMappingEntry::operator=(
+    const InodeFileConfig::MountPointMappingEntry&) = default;
+InodeFileConfig::MountPointMappingEntry::MountPointMappingEntry(
+    InodeFileConfig::MountPointMappingEntry&&) noexcept = default;
+InodeFileConfig::MountPointMappingEntry&
+InodeFileConfig::MountPointMappingEntry::operator=(
+    InodeFileConfig::MountPointMappingEntry&&) = default;
+
+void InodeFileConfig::MountPointMappingEntry::FromProto(
+    const perfetto::protos::InodeFileConfig_MountPointMappingEntry& proto) {
+  static_assert(sizeof(mountpoint_) == sizeof(proto.mountpoint()),
+                "size mismatch");
+  mountpoint_ = static_cast<decltype(mountpoint_)>(proto.mountpoint());
+
+  scan_roots_.clear();
+  for (const auto& field : proto.scan_roots()) {
+    scan_roots_.emplace_back();
+    static_assert(sizeof(scan_roots_.back()) == sizeof(proto.scan_roots(0)),
+                  "size mismatch");
+    scan_roots_.back() = static_cast<decltype(scan_roots_)::value_type>(field);
+  }
+  unknown_fields_ = proto.unknown_fields();
+}
+
+void InodeFileConfig::MountPointMappingEntry::ToProto(
+    perfetto::protos::InodeFileConfig_MountPointMappingEntry* proto) const {
+  proto->Clear();
+
+  static_assert(sizeof(mountpoint_) == sizeof(proto->mountpoint()),
+                "size mismatch");
+  proto->set_mountpoint(
+      static_cast<decltype(proto->mountpoint())>(mountpoint_));
+
+  for (const auto& it : scan_roots_) {
+    proto->add_scan_roots(it);
+    static_assert(sizeof(it) == sizeof(proto->scan_roots(0)), "size mismatch");
+  }
   *(proto->mutable_unknown_fields()) = unknown_fields_;
 }
 
diff --git a/test/configs/ftrace.cfg b/test/configs/ftrace.cfg
index 65ea849..4fbc0a5 100644
--- a/test/configs/ftrace.cfg
+++ b/test/configs/ftrace.cfg
@@ -254,6 +254,16 @@
   config {
     name: "linux.inode_file_map"
     target_buffer: 0
+    inode_file_config {
+      scan_delay_ms: 1000
+      scan_interval_ms: 1000
+      scan_batch_size: 100000
+      mount_point_mapping: {
+        mountpoint: "/data"
+        scan_roots: "/data/app"
+        scan_roots: "/data/dalvik-cache"
+      }
+    }
   }
 }