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