/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "src/traced/probes/filesystem/inode_file_data_source.h" #include #include #include #include #include #include #include "perfetto/base/logging.h" #include "perfetto/ext/base/scoped_file.h" #include "perfetto/ext/tracing/core/trace_packet.h" #include "perfetto/ext/tracing/core/trace_writer.h" #include "protos/perfetto/config/inode_file/inode_file_config.pbzero.h" #include "protos/perfetto/trace/filesystem/inode_file_map.pbzero.h" #include "protos/perfetto/trace/trace_packet.pbzero.h" #include "src/traced/probes/filesystem/file_scanner.h" namespace perfetto { namespace { constexpr uint32_t kScanIntervalMs = 10000; // 10s constexpr uint32_t kScanDelayMs = 10000; // 10s constexpr uint32_t kScanBatchSize = 15000; uint32_t OrDefault(uint32_t value, uint32_t def) { return value ? value : def; } std::string DbgFmt(const std::vector& 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; } class StaticMapDelegate : public FileScanner::Delegate { public: StaticMapDelegate( std::map>* map) : map_(map) {} ~StaticMapDelegate() override {} private: bool OnInodeFound(BlockDeviceID block_device_id, Inode inode_number, const std::string& path, InodeFileMap_Entry_Type type) override { std::unordered_map& inode_map = (*map_)[block_device_id]; inode_map[inode_number].SetType(type); inode_map[inode_number].AddPath(path); return true; } void OnInodeScanDone() override {} std::map>* map_; }; } // namespace // static const ProbesDataSource::Descriptor InodeFileDataSource::descriptor = { /*name*/ "linux.inode_file_map", /*flags*/ Descriptor::kFlagsNone, /*fill_descriptor_func*/ nullptr, }; void CreateStaticDeviceToInodeMap( const std::string& root_directory, std::map>* static_file_map) { StaticMapDelegate delegate(static_file_map); FileScanner scanner({root_directory}, &delegate); scanner.Scan(); } void InodeFileDataSource::FillInodeEntry(InodeFileMap* destination, Inode inode_number, const InodeMapValue& inode_map_value) { auto* entry = destination->add_entries(); entry->set_inode_number(inode_number); entry->set_type(static_cast( inode_map_value.type())); for (const auto& path : inode_map_value.paths()) entry->add_paths(path.c_str()); } InodeFileDataSource::InodeFileDataSource( const DataSourceConfig& ds_config, base::TaskRunner* task_runner, TracingSessionID session_id, std::map>* static_file_map, LRUInodeCache* cache, std::unique_ptr writer) : ProbesDataSource(session_id, &descriptor), task_runner_(task_runner), static_file_map_(static_file_map), cache_(cache), writer_(std::move(writer)), weak_factory_(this) { using protos::pbzero::InodeFileConfig; InodeFileConfig::Decoder cfg(ds_config.inode_file_config_raw()); for (auto mp = cfg.scan_mount_points(); mp; ++mp) scan_mount_points_.insert((*mp).ToStdString()); for (auto mpm = cfg.mount_point_mapping(); mpm; ++mpm) { InodeFileConfig::MountPointMappingEntry::Decoder entry(*mpm); std::vector scan_roots; for (auto scan_root = entry.scan_roots(); scan_root; ++scan_root) scan_roots.push_back((*scan_root).ToStdString()); std::string mountpoint = entry.mountpoint().ToStdString(); mount_point_mapping_.emplace(mountpoint, std::move(scan_roots)); } scan_interval_ms_ = OrDefault(cfg.scan_interval_ms(), kScanIntervalMs); scan_delay_ms_ = OrDefault(cfg.scan_delay_ms(), kScanDelayMs); scan_batch_size_ = OrDefault(cfg.scan_batch_size(), kScanBatchSize); do_not_scan_ = cfg.do_not_scan(); } InodeFileDataSource::~InodeFileDataSource() = default; void InodeFileDataSource::Start() { // Nothing special to do, this data source is only reacting to on-demand // events such as OnInodes(). } void InodeFileDataSource::AddInodesFromStaticMap( BlockDeviceID block_device_id, std::set* inode_numbers) { // Check if block device id exists in static file map auto static_map_entry = static_file_map_->find(block_device_id); if (static_map_entry == static_file_map_->end()) return; uint64_t system_found_count = 0; for (auto it = inode_numbers->begin(); it != inode_numbers->end();) { Inode inode_number = *it; // Check if inode number exists in static file map for given block device id auto inode_it = static_map_entry->second.find(inode_number); if (inode_it == static_map_entry->second.end()) { ++it; continue; } system_found_count++; it = inode_numbers->erase(it); FillInodeEntry(AddToCurrentTracePacket(block_device_id), inode_number, inode_it->second); } PERFETTO_DLOG("%" PRIu64 " inodes found in static file map", system_found_count); } void InodeFileDataSource::AddInodesFromLRUCache( BlockDeviceID block_device_id, std::set* inode_numbers) { uint64_t cache_found_count = 0; for (auto it = inode_numbers->begin(); it != inode_numbers->end();) { Inode inode_number = *it; auto value = cache_->Get(std::make_pair(block_device_id, inode_number)); if (value == nullptr) { ++it; continue; } cache_found_count++; it = inode_numbers->erase(it); FillInodeEntry(AddToCurrentTracePacket(block_device_id), inode_number, *value); } if (cache_found_count > 0) PERFETTO_DLOG("%" PRIu64 " inodes found in cache", cache_found_count); } void InodeFileDataSource::Flush(FlushRequestID, std::function callback) { ResetTracePacket(); writer_->Flush(callback); } void InodeFileDataSource::OnInodes( const base::FlatSet& inodes) { if (mount_points_.empty()) { mount_points_ = ParseMounts(); } // Group inodes from FtraceMetadata by block device std::map> inode_file_maps; for (const auto& inodes_pair : inodes) { Inode inode_number = inodes_pair.first; BlockDeviceID block_device_id = inodes_pair.second; inode_file_maps[block_device_id].emplace(inode_number); } if (inode_file_maps.size() > 1) PERFETTO_DLOG("Saw %zu block devices.", inode_file_maps.size()); // Write a TracePacket with an InodeFileMap proto for each block device id for (auto& inode_file_map_data : inode_file_maps) { BlockDeviceID block_device_id = inode_file_map_data.first; std::set& inode_numbers = inode_file_map_data.second; PERFETTO_DLOG("Saw %zu unique inode numbers.", inode_numbers.size()); // Add entries to InodeFileMap as inodes are found and resolved to their // paths/type AddInodesFromStaticMap(block_device_id, &inode_numbers); AddInodesFromLRUCache(block_device_id, &inode_numbers); if (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); if (it != missing_inodes_.end()) { it->second.insert(inode_numbers.cbegin(), inode_numbers.cend()); } next_missing_inodes_[block_device_id].insert(inode_numbers.cbegin(), inode_numbers.cend()); if (!scan_running_) { scan_running_ = true; auto weak_this = GetWeakPtr(); task_runner_->PostDelayedTask( [weak_this] { if (!weak_this) { PERFETTO_DLOG("Giving up filesystem scan."); return; } weak_this->FindMissingInodes(); }, scan_delay_ms_); } } } } InodeFileMap* InodeFileDataSource::AddToCurrentTracePacket( BlockDeviceID block_device_id) { seen_block_devices_.emplace(block_device_id); if (!has_current_trace_packet_ || current_block_device_id_ != block_device_id) { if (has_current_trace_packet_) current_trace_packet_->Finalize(); current_trace_packet_ = writer_->NewTracePacket(); current_file_map_ = current_trace_packet_->set_inode_file_map(); has_current_trace_packet_ = true; // Add block device id to InodeFileMap current_file_map_->set_block_device_id( static_cast(block_device_id)); // Add mount points to InodeFileMap auto range = mount_points_.equal_range(block_device_id); for (std::multimap::iterator it = range.first; it != range.second; ++it) current_file_map_->add_mount_points(it->second.c_str()); } return current_file_map_; } void InodeFileDataSource::RemoveFromNextMissingInodes( BlockDeviceID block_device_id, Inode inode_number) { auto it = next_missing_inodes_.find(block_device_id); if (it == next_missing_inodes_.end()) return; it->second.erase(inode_number); } bool InodeFileDataSource::OnInodeFound(BlockDeviceID block_device_id, Inode inode_number, const std::string& path, InodeFileMap_Entry_Type inode_type) { auto it = missing_inodes_.find(block_device_id); if (it == missing_inodes_.end()) return true; size_t n = it->second.erase(inode_number); if (n == 0) return true; if (it->second.empty()) missing_inodes_.erase(it); RemoveFromNextMissingInodes(block_device_id, inode_number); std::pair key{block_device_id, inode_number}; auto cur_val = cache_->Get(key); if (cur_val) { cur_val->AddPath(path); FillInodeEntry(AddToCurrentTracePacket(block_device_id), inode_number, *cur_val); } else { InodeMapValue new_val(InodeMapValue(inode_type, {path})); cache_->Insert(key, new_val); FillInodeEntry(AddToCurrentTracePacket(block_device_id), inode_number, new_val); } PERFETTO_DLOG("Filled %s", path.c_str()); return !missing_inodes_.empty(); } void InodeFileDataSource::ResetTracePacket() { current_block_device_id_ = 0; current_file_map_ = nullptr; if (has_current_trace_packet_) current_trace_packet_->Finalize(); has_current_trace_packet_ = false; } void InodeFileDataSource::OnInodeScanDone() { // Finalize the accumulated trace packets. ResetTracePacket(); file_scanner_.reset(); if (!missing_inodes_.empty()) { // At least write mount point mapping for inodes that are not found. for (const auto& p : missing_inodes_) { if (seen_block_devices_.count(p.first) == 0) AddToCurrentTracePacket(p.first); } } if (next_missing_inodes_.empty()) { scan_running_ = false; } else { auto weak_this = GetWeakPtr(); PERFETTO_DLOG("Starting another filesystem scan."); task_runner_->PostDelayedTask( [weak_this] { if (!weak_this) { PERFETTO_DLOG("Giving up filesystem scan."); return; } weak_this->FindMissingInodes(); }, scan_delay_ms_); } } void InodeFileDataSource::AddRootsForBlockDevice( BlockDeviceID block_device_id, std::vector* roots) { 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); } void InodeFileDataSource::FindMissingInodes() { missing_inodes_ = std::move(next_missing_inodes_); std::vector roots; for (auto& p : missing_inodes_) AddRootsForBlockDevice(p.first, &roots); 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(new FileScanner( std::move(roots), this, scan_interval_ms_, scan_batch_size_)); file_scanner_->Scan(task_runner_); } base::WeakPtr InodeFileDataSource::GetWeakPtr() const { return weak_factory_.GetWeakPtr(); } } // namespace perfetto