blob: 8185a6ee6df091f0cb4ddc62c2af2875bbdde4d4 [file] [log] [blame]
/*
* 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 <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <queue>
#include <unordered_map>
#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<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;
}
class StaticMapDelegate : public FileScanner::Delegate {
public:
StaticMapDelegate(
std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>* 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, InodeMapValue>& 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<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>* 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<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>*
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<protos::pbzero::InodeFileMap_Entry_Type>(
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<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>*
static_file_map,
LRUInodeCache* cache,
std::unique_ptr<TraceWriter> 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<std::string> 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>* 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>* 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<void()> callback) {
ResetTracePacket();
writer_->Flush(callback);
}
void InodeFileDataSource::OnInodes(
const base::FlatSet<InodeBlockPair>& inodes) {
if (mount_points_.empty()) {
mount_points_ = ParseMounts();
}
// Group inodes from FtraceMetadata by block device
std::map<BlockDeviceID, std::set<Inode>> 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>& 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<uint64_t>(block_device_id));
// Add mount points to InodeFileMap
auto range = mount_points_.equal_range(block_device_id);
for (std::multimap<BlockDeviceID, std::string>::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<BlockDeviceID, Inode> 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<std::string>* 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<std::string> 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<FileScanner>(new FileScanner(
std::move(roots), this, scan_interval_ms_, scan_batch_size_));
file_scanner_->Scan(task_runner_);
}
base::WeakPtr<InodeFileDataSource> InodeFileDataSource::GetWeakPtr() const {
return weak_factory_.GetWeakPtr();
}
} // namespace perfetto