Home | History | Annotate | Download | only in filesystem
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "src/traced/probes/filesystem/inode_file_data_source.h"
     18 
     19 #include <dirent.h>
     20 #include <sys/stat.h>
     21 #include <sys/types.h>
     22 #include <unistd.h>
     23 #include <queue>
     24 #include <unordered_map>
     25 
     26 #include "perfetto/base/logging.h"
     27 #include "perfetto/base/scoped_file.h"
     28 #include "perfetto/tracing/core/trace_packet.h"
     29 #include "perfetto/tracing/core/trace_writer.h"
     30 
     31 #include "perfetto/trace/trace_packet.pbzero.h"
     32 #include "src/traced/probes/filesystem/file_scanner.h"
     33 
     34 namespace perfetto {
     35 namespace {
     36 constexpr uint32_t kScanIntervalMs = 10000;  // 10s
     37 constexpr uint32_t kScanDelayMs = 10000;     // 10s
     38 constexpr uint32_t kScanBatchSize = 15000;
     39 
     40 uint32_t OrDefault(uint32_t value, uint32_t def) {
     41   return value ? value : def;
     42 }
     43 
     44 std::string DbgFmt(const std::vector<std::string>& values) {
     45   if (values.empty())
     46     return "";
     47 
     48   std::string result;
     49   for (auto it = values.cbegin(); it != values.cend() - 1; ++it)
     50     result += *it + ",";
     51 
     52   result += values.back();
     53   return result;
     54 }
     55 
     56 std::map<std::string, std::vector<std::string>> BuildMountpointMapping(
     57     const DataSourceConfig& source_config) {
     58   std::map<std::string, std::vector<std::string>> m;
     59   for (const auto& map_entry :
     60        source_config.inode_file_config().mount_point_mapping())
     61     m.emplace(map_entry.mountpoint(), map_entry.scan_roots());
     62 
     63   return m;
     64 }
     65 
     66 class StaticMapDelegate : public FileScanner::Delegate {
     67  public:
     68   StaticMapDelegate(
     69       std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>* map)
     70       : map_(map) {}
     71   ~StaticMapDelegate() {}
     72 
     73  private:
     74   bool OnInodeFound(BlockDeviceID block_device_id,
     75                     Inode inode_number,
     76                     const std::string& path,
     77                     protos::pbzero::InodeFileMap_Entry_Type type) {
     78     std::unordered_map<Inode, InodeMapValue>& inode_map =
     79         (*map_)[block_device_id];
     80     inode_map[inode_number].SetType(type);
     81     inode_map[inode_number].AddPath(path);
     82     return true;
     83   }
     84   void OnInodeScanDone() {}
     85   std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>* map_;
     86 };
     87 
     88 }  // namespace
     89 
     90 // static
     91 constexpr int InodeFileDataSource::kTypeId;
     92 
     93 void CreateStaticDeviceToInodeMap(
     94     const std::string& root_directory,
     95     std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>*
     96         static_file_map) {
     97   StaticMapDelegate delegate(static_file_map);
     98   FileScanner scanner({root_directory}, &delegate);
     99   scanner.Scan();
    100 }
    101 
    102 void InodeFileDataSource::FillInodeEntry(InodeFileMap* destination,
    103                                          Inode inode_number,
    104                                          const InodeMapValue& inode_map_value) {
    105   auto* entry = destination->add_entries();
    106   entry->set_inode_number(inode_number);
    107   entry->set_type(inode_map_value.type());
    108   for (const auto& path : inode_map_value.paths())
    109     entry->add_paths(path.c_str());
    110 }
    111 
    112 InodeFileDataSource::InodeFileDataSource(
    113     DataSourceConfig source_config,
    114     base::TaskRunner* task_runner,
    115     TracingSessionID session_id,
    116     std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>*
    117         static_file_map,
    118     LRUInodeCache* cache,
    119     std::unique_ptr<TraceWriter> writer)
    120     : ProbesDataSource(session_id, kTypeId),
    121       source_config_(std::move(source_config)),
    122       scan_mount_points_(
    123           source_config_.inode_file_config().scan_mount_points().cbegin(),
    124           source_config_.inode_file_config().scan_mount_points().cend()),
    125       mount_point_mapping_(BuildMountpointMapping(source_config_)),
    126       task_runner_(task_runner),
    127       static_file_map_(static_file_map),
    128       cache_(cache),
    129       writer_(std::move(writer)),
    130       weak_factory_(this) {}
    131 
    132 InodeFileDataSource::~InodeFileDataSource() = default;
    133 
    134 void InodeFileDataSource::Start() {
    135   // Nothing special to do, this data source is only reacting to on-demand
    136   // events such as OnInodes().
    137 }
    138 
    139 void InodeFileDataSource::AddInodesFromStaticMap(
    140     BlockDeviceID block_device_id,
    141     std::set<Inode>* inode_numbers) {
    142   // Check if block device id exists in static file map
    143   auto static_map_entry = static_file_map_->find(block_device_id);
    144   if (static_map_entry == static_file_map_->end())
    145     return;
    146 
    147   uint64_t system_found_count = 0;
    148   for (auto it = inode_numbers->begin(); it != inode_numbers->end();) {
    149     Inode inode_number = *it;
    150     // Check if inode number exists in static file map for given block device id
    151     auto inode_it = static_map_entry->second.find(inode_number);
    152     if (inode_it == static_map_entry->second.end()) {
    153       ++it;
    154       continue;
    155     }
    156     system_found_count++;
    157     it = inode_numbers->erase(it);
    158     FillInodeEntry(AddToCurrentTracePacket(block_device_id), inode_number,
    159                    inode_it->second);
    160   }
    161   PERFETTO_DLOG("%" PRIu64 " inodes found in static file map",
    162                 system_found_count);
    163 }
    164 
    165 void InodeFileDataSource::AddInodesFromLRUCache(
    166     BlockDeviceID block_device_id,
    167     std::set<Inode>* inode_numbers) {
    168   uint64_t cache_found_count = 0;
    169   for (auto it = inode_numbers->begin(); it != inode_numbers->end();) {
    170     Inode inode_number = *it;
    171     auto value = cache_->Get(std::make_pair(block_device_id, inode_number));
    172     if (value == nullptr) {
    173       ++it;
    174       continue;
    175     }
    176     cache_found_count++;
    177     it = inode_numbers->erase(it);
    178     FillInodeEntry(AddToCurrentTracePacket(block_device_id), inode_number,
    179                    *value);
    180   }
    181   if (cache_found_count > 0)
    182     PERFETTO_DLOG("%" PRIu64 " inodes found in cache", cache_found_count);
    183 }
    184 
    185 void InodeFileDataSource::Flush(FlushRequestID,
    186                                 std::function<void()> callback) {
    187   ResetTracePacket();
    188   writer_->Flush(callback);
    189 }
    190 
    191 void InodeFileDataSource::OnInodes(
    192     const std::vector<std::pair<Inode, BlockDeviceID>>& inodes) {
    193   if (mount_points_.empty()) {
    194     mount_points_ = ParseMounts();
    195   }
    196   // Group inodes from FtraceMetadata by block device
    197   std::map<BlockDeviceID, std::set<Inode>> inode_file_maps;
    198   for (const auto& inodes_pair : inodes) {
    199     Inode inode_number = inodes_pair.first;
    200     BlockDeviceID block_device_id = inodes_pair.second;
    201     inode_file_maps[block_device_id].emplace(inode_number);
    202   }
    203   if (inode_file_maps.size() > 1)
    204     PERFETTO_DLOG("Saw %zu block devices.", inode_file_maps.size());
    205 
    206   // Write a TracePacket with an InodeFileMap proto for each block device id
    207   for (auto& inode_file_map_data : inode_file_maps) {
    208     BlockDeviceID block_device_id = inode_file_map_data.first;
    209     std::set<Inode>& inode_numbers = inode_file_map_data.second;
    210     PERFETTO_DLOG("Saw %zu unique inode numbers.", inode_numbers.size());
    211 
    212     // Add entries to InodeFileMap as inodes are found and resolved to their
    213     // paths/type
    214     AddInodesFromStaticMap(block_device_id, &inode_numbers);
    215     AddInodesFromLRUCache(block_device_id, &inode_numbers);
    216 
    217     if (source_config_.inode_file_config().do_not_scan())
    218       inode_numbers.clear();
    219 
    220     // If we defined mount points we want to scan in the config,
    221     // skip inodes on other mount points.
    222     if (!scan_mount_points_.empty()) {
    223       bool process = true;
    224       auto range = mount_points_.equal_range(block_device_id);
    225       for (auto it = range.first; it != range.second; ++it) {
    226         if (scan_mount_points_.count(it->second) == 0) {
    227           process = false;
    228           break;
    229         }
    230       }
    231       if (!process)
    232         continue;
    233     }
    234 
    235     if (!inode_numbers.empty()) {
    236       // Try to piggy back the current scan.
    237       auto it = missing_inodes_.find(block_device_id);
    238       if (it != missing_inodes_.end()) {
    239         it->second.insert(inode_numbers.cbegin(), inode_numbers.cend());
    240       }
    241       next_missing_inodes_[block_device_id].insert(inode_numbers.cbegin(),
    242                                                    inode_numbers.cend());
    243       if (!scan_running_) {
    244         scan_running_ = true;
    245         auto weak_this = GetWeakPtr();
    246         task_runner_->PostDelayedTask(
    247             [weak_this] {
    248               if (!weak_this) {
    249                 PERFETTO_DLOG("Giving up filesystem scan.");
    250                 return;
    251               }
    252               weak_this.get()->FindMissingInodes();
    253             },
    254             GetScanDelayMs());
    255       }
    256     }
    257   }
    258 }
    259 
    260 InodeFileMap* InodeFileDataSource::AddToCurrentTracePacket(
    261     BlockDeviceID block_device_id) {
    262   seen_block_devices_.emplace(block_device_id);
    263   if (!has_current_trace_packet_ ||
    264       current_block_device_id_ != block_device_id) {
    265     if (has_current_trace_packet_)
    266       current_trace_packet_->Finalize();
    267     current_trace_packet_ = writer_->NewTracePacket();
    268     current_file_map_ = current_trace_packet_->set_inode_file_map();
    269     has_current_trace_packet_ = true;
    270 
    271     // Add block device id to InodeFileMap
    272     current_file_map_->set_block_device_id(
    273         static_cast<uint64_t>(block_device_id));
    274     // Add mount points to InodeFileMap
    275     auto range = mount_points_.equal_range(block_device_id);
    276     for (std::multimap<BlockDeviceID, std::string>::iterator it = range.first;
    277          it != range.second; ++it)
    278       current_file_map_->add_mount_points(it->second.c_str());
    279   }
    280   return current_file_map_;
    281 }
    282 
    283 void InodeFileDataSource::RemoveFromNextMissingInodes(
    284     BlockDeviceID block_device_id,
    285     Inode inode_number) {
    286   auto it = next_missing_inodes_.find(block_device_id);
    287   if (it == next_missing_inodes_.end())
    288     return;
    289   it->second.erase(inode_number);
    290 }
    291 
    292 bool InodeFileDataSource::OnInodeFound(
    293     BlockDeviceID block_device_id,
    294     Inode inode_number,
    295     const std::string& path,
    296     protos::pbzero::InodeFileMap_Entry_Type type) {
    297   auto it = missing_inodes_.find(block_device_id);
    298   if (it == missing_inodes_.end())
    299     return true;
    300 
    301   size_t n = it->second.erase(inode_number);
    302   if (n == 0)
    303     return true;
    304 
    305   if (it->second.empty())
    306     missing_inodes_.erase(it);
    307 
    308   RemoveFromNextMissingInodes(block_device_id, inode_number);
    309 
    310   std::pair<BlockDeviceID, Inode> key{block_device_id, inode_number};
    311   auto cur_val = cache_->Get(key);
    312   if (cur_val) {
    313     cur_val->AddPath(path);
    314     FillInodeEntry(AddToCurrentTracePacket(block_device_id), inode_number,
    315                    *cur_val);
    316   } else {
    317     InodeMapValue new_val(InodeMapValue(type, {path}));
    318     cache_->Insert(key, new_val);
    319     FillInodeEntry(AddToCurrentTracePacket(block_device_id), inode_number,
    320                    new_val);
    321   }
    322   PERFETTO_DLOG("Filled %s", path.c_str());
    323   return !missing_inodes_.empty();
    324 }
    325 
    326 void InodeFileDataSource::ResetTracePacket() {
    327   current_block_device_id_ = 0;
    328   current_file_map_ = nullptr;
    329   if (has_current_trace_packet_)
    330     current_trace_packet_->Finalize();
    331   has_current_trace_packet_ = false;
    332 }
    333 
    334 void InodeFileDataSource::OnInodeScanDone() {
    335   // Finalize the accumulated trace packets.
    336   ResetTracePacket();
    337   file_scanner_.reset();
    338   if (!missing_inodes_.empty()) {
    339     // At least write mount point mapping for inodes that are not found.
    340     for (const auto& p : missing_inodes_) {
    341       if (seen_block_devices_.count(p.first) == 0)
    342         AddToCurrentTracePacket(p.first);
    343     }
    344   }
    345 
    346   if (next_missing_inodes_.empty()) {
    347     scan_running_ = false;
    348   } else {
    349     auto weak_this = GetWeakPtr();
    350     PERFETTO_DLOG("Starting another filesystem scan.");
    351     task_runner_->PostDelayedTask(
    352         [weak_this] {
    353           if (!weak_this) {
    354             PERFETTO_DLOG("Giving up filesystem scan.");
    355             return;
    356           }
    357           weak_this->FindMissingInodes();
    358         },
    359         GetScanDelayMs());
    360   }
    361 }
    362 
    363 void InodeFileDataSource::AddRootsForBlockDevice(
    364     BlockDeviceID block_device_id,
    365     std::vector<std::string>* roots) {
    366   auto range = mount_points_.equal_range(block_device_id);
    367   for (auto it = range.first; it != range.second; ++it) {
    368     PERFETTO_DLOG("Trying to replace %s", it->second.c_str());
    369     auto replace_it = mount_point_mapping_.find(it->second);
    370     if (replace_it != mount_point_mapping_.end()) {
    371       roots->insert(roots->end(), replace_it->second.cbegin(),
    372                     replace_it->second.cend());
    373       return;
    374     }
    375   }
    376 
    377   for (auto it = range.first; it != range.second; ++it)
    378     roots->emplace_back(it->second);
    379 }
    380 
    381 void InodeFileDataSource::FindMissingInodes() {
    382   missing_inodes_ = std::move(next_missing_inodes_);
    383   std::vector<std::string> roots;
    384   for (auto& p : missing_inodes_)
    385     AddRootsForBlockDevice(p.first, &roots);
    386 
    387   PERFETTO_DCHECK(file_scanner_.get() == nullptr);
    388   auto weak_this = GetWeakPtr();
    389   PERFETTO_DLOG("Starting scan of %s", DbgFmt(roots).c_str());
    390   file_scanner_ = std::unique_ptr<FileScanner>(new FileScanner(
    391       std::move(roots), this, GetScanIntervalMs(), GetScanBatchSize()));
    392 
    393   file_scanner_->Scan(task_runner_);
    394 }
    395 
    396 uint32_t InodeFileDataSource::GetScanIntervalMs() const {
    397   return OrDefault(source_config_.inode_file_config().scan_interval_ms(),
    398                    kScanIntervalMs);
    399 }
    400 
    401 uint32_t InodeFileDataSource::GetScanDelayMs() const {
    402   return OrDefault(source_config_.inode_file_config().scan_delay_ms(),
    403                    kScanDelayMs);
    404 }
    405 
    406 uint32_t InodeFileDataSource::GetScanBatchSize() const {
    407   return OrDefault(source_config_.inode_file_config().scan_batch_size(),
    408                    kScanBatchSize);
    409 }
    410 
    411 base::WeakPtr<InodeFileDataSource> InodeFileDataSource::GetWeakPtr() const {
    412   return weak_factory_.GetWeakPtr();
    413 }
    414 
    415 }  // namespace perfetto
    416