| |
| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/sys/appmgr/storage_metrics.h" |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fit/defer.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <sys/stat.h> |
| #include <threads.h> |
| #include <zircon/errors.h> |
| #include <zircon/syscalls.h> |
| |
| #include <mutex> |
| #include <unordered_map> |
| |
| #include <fbl/unique_fd.h> |
| #include <src/lib/files/directory.h> |
| #include <src/lib/files/path.h> |
| |
| namespace { |
| |
| constexpr uint32_t kRecursionLimit = 64; |
| |
| // Called with the fd for a component directory. Recursively iterates through the structure |
| // returning the total bytes and inodes usage. |
| StorageMetrics::Usage SumDirUsage(fbl::unique_fd unique_fd, uint32_t recursion = 0) { |
| if (recursion >= kRecursionLimit) { |
| return {0, 0}; |
| } |
| |
| DIR* dir_stream = fdopendir(unique_fd.get()); |
| int fd = -1; |
| if (dir_stream) { |
| // This is now handled by closedir() |
| fd = unique_fd.release(); |
| } else { |
| FX_LOGS(WARNING) << "Failed to read dir listing. Skipping."; |
| return {0, 0}; |
| } |
| auto close_dir = fit::defer([dir_stream]() { closedir(dir_stream); }); |
| |
| struct dirent* ent = nullptr; |
| size_t total_bytes = 0; |
| size_t total_files = 0; |
| while ((ent = readdir(dir_stream))) { |
| if (strncmp(ent->d_name, ".", 1) == 0) { |
| // Don't recurse on '.', do nothing. |
| } else if (ent->d_type == DT_LNK) { |
| // TODO(fxbug.dev/69017): Handle symlink sizes properly since they can consume blocks |
| // depending on fs, but currently none of our filesystems support this. |
| total_files++; |
| } else { |
| fbl::unique_fd child(openat(fd, ent->d_name, O_RDONLY)); |
| if (!child) { |
| FX_LOGS(WARNING) << "Failed to open subdir: " << ent->d_name; |
| continue; |
| } |
| struct stat st; |
| if (fstat(child.get(), &st) != 0) { |
| FX_LOGS(WARNING) << "Failed to stat file: " << ent->d_name; |
| } else { |
| total_bytes += st.st_blocks * st.st_blksize; |
| total_files++; |
| } |
| if (ent->d_type == DT_DIR) { |
| auto bytes_files = SumDirUsage(std::move(child), recursion + 1); |
| total_bytes += bytes_files.bytes; |
| total_files += bytes_files.inodes; |
| } |
| } |
| } |
| return {total_bytes, total_files}; |
| } |
| |
| // Alternate recursion, so forward declaring. |
| void SumComponentsForPath(fbl::unique_fd unique_fd, StorageMetrics::UsageMap* usage); |
| |
| // Takes an fd for a realm directory and enters each realm to sum them as a top level component |
| // storage path. |
| void SumRealmForPath(fbl::unique_fd unique_fd, StorageMetrics::UsageMap* usage) { |
| DIR* dir_stream = fdopendir(unique_fd.get()); |
| int fd = -1; |
| if (dir_stream) { |
| // This is now handled by closedir() |
| fd = unique_fd.release(); |
| } else { |
| FX_LOGS(WARNING) << "Failed to read watched dir listing. Skipping."; |
| return; |
| } |
| auto close_dir = fit::defer([dir_stream]() { closedir(dir_stream); }); |
| |
| struct dirent* ent = nullptr; |
| while ((ent = readdir(dir_stream))) { |
| if (strncmp(ent->d_name, ".", 1) == 0) { |
| // Don't treat `.` as a component directory, do nothing. |
| } else { |
| fbl::unique_fd child(openat(fd, ent->d_name, O_DIRECTORY | O_RDONLY)); |
| if (!child) { |
| FX_LOGS(WARNING) << "Failed to open subdir: " << ent->d_name; |
| continue; |
| } |
| SumComponentsForPath(std::move(child), usage); |
| } |
| } |
| } |
| |
| // Given the fd for a top level component storage directory it will add all usage to the usage |
| // map keyed on the top level directory name inside it. |
| void SumComponentsForPath(fbl::unique_fd unique_fd, StorageMetrics::UsageMap* usage) { |
| DIR* dir_stream = fdopendir(unique_fd.get()); |
| int fd = -1; |
| if (dir_stream) { |
| // This is now handled by closedir() |
| fd = unique_fd.release(); |
| } else { |
| FX_LOGS(WARNING) << "Failed to read watched dir listing. Skipping."; |
| return; |
| } |
| auto close_dir = fit::defer([dir_stream]() { closedir(dir_stream); }); |
| |
| struct dirent* ent = nullptr; |
| while ((ent = readdir(dir_stream))) { |
| if (strncmp(ent->d_name, ".", 1) == 0) { |
| // Don't treat `.` as a component directory, do nothing. |
| } else if (strncmp(ent->d_name, "r", 1) == 0) { |
| // Entering a realm, recurse. |
| fbl::unique_fd child(openat(fd, ent->d_name, O_DIRECTORY | O_RDONLY)); |
| if (!child) { |
| FX_LOGS(WARNING) << "Failed to open subdir: " << ent->d_name; |
| continue; |
| } |
| SumRealmForPath(std::move(child), usage); |
| } else { |
| fbl::unique_fd child(openat(fd, ent->d_name, O_DIRECTORY | O_RDONLY)); |
| if (!child) { |
| FX_LOGS(WARNING) << "Failed to open subdir: " << ent->d_name; |
| continue; |
| } |
| usage->AddForKey(ent->d_name, SumDirUsage(std::move(child))); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| void StorageMetrics::UsageMap::AddForKey(const std::string& name, const Usage& usage) { |
| auto node = map_.find(name); |
| if (node == map_.end()) { |
| map_[name] = usage; |
| } else { |
| Usage& old = node->second; |
| map_[name] = {old.bytes + usage.bytes, old.inodes + usage.inodes}; |
| } |
| } |
| |
| fit::promise<inspect::Inspector> StorageMetrics::InspectByteUsage(const std::string& path) const { |
| inspect::Inspector inspector; |
| std::lock_guard<std::mutex> lock(usage_lock_); |
| auto entry = usage_.find(path); |
| if (entry == usage_.end()) { |
| // No data populated yet for this path. |
| return fit::make_ok_promise(inspector); |
| } |
| for (const auto& it : entry->second.map()) { |
| inspector.GetRoot().CreateUint(it.first, it.second.bytes, &inspector); |
| } |
| return fit::make_ok_promise(inspector); |
| } |
| |
| fit::promise<inspect::Inspector> StorageMetrics::InspectInodeUsage(const std::string& path) const { |
| inspect::Inspector inspector; |
| std::lock_guard<std::mutex> lock(usage_lock_); |
| auto entry = usage_.find(path); |
| if (entry == usage_.end()) { |
| // No data populated yet for this path. |
| return fit::make_ok_promise(inspector); |
| } |
| for (const auto& it : entry->second.map()) { |
| inspector.GetRoot().CreateUint(it.first, it.second.inodes, &inspector); |
| } |
| return fit::make_ok_promise(inspector); |
| } |
| |
| StorageMetrics::StorageMetrics(std::vector<std::string> paths_to_watch, inspect::Node inspect_node) |
| : paths_to_watch_(std::move(paths_to_watch)), |
| inspect_root_(std::move(inspect_node)), |
| inspect_bytes_stats_(inspect_root_.CreateChild("bytes")), |
| inspect_inode_stats_(inspect_root_.CreateChild("inodes")) { |
| for (auto& it : paths_to_watch_) { |
| lazy_nodes_.push_back(inspect_bytes_stats_.CreateLazyNode( |
| it, [this, &it] { return this->InspectByteUsage(it); })); |
| lazy_nodes_.push_back(inspect_inode_stats_.CreateLazyNode( |
| it, [this, &it] { return this->InspectInodeUsage(it); })); |
| } |
| } |
| |
| std::unordered_map<std::string, StorageMetrics::UsageMap> StorageMetrics::GatherStorageUsage() |
| const { |
| std::unordered_map<std::string, StorageMetrics::UsageMap> usage_by_path; |
| for (const std::string& dir : paths_to_watch_) { |
| UsageMap usage; |
| fbl::unique_fd fd(open(dir.c_str(), O_DIRECTORY | O_RDONLY)); |
| if (!fd) { |
| FX_LOGS(WARNING) << "Failed to open path: " << dir; |
| continue; |
| } |
| SumComponentsForPath(std::move(fd), &usage); |
| usage_by_path[dir] = std::move(usage); |
| } |
| return usage_by_path; |
| } |
| |
| void StorageMetrics::PollStorage() { |
| auto new_usage = GatherStorageUsage(); |
| { |
| std::lock_guard<std::mutex> lock(usage_lock_); |
| usage_ = std::move(new_usage); |
| } |
| |
| async::PostDelayedTask( |
| loop_.dispatcher(), [this] { this->PollStorage(); }, kPollCycle); |
| } |
| |
| zx::status<> StorageMetrics::Run() { |
| zx_status_t status = loop_.StartThread("StorageMetrics"); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| status = async::PostTask(loop_.dispatcher(), [this] { this->PollStorage(); }); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| return zx::ok(); |
| } |