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