| // Copyright 2019 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/developer/memory/metrics/digest.h" |
| |
| #include <lib/trace/event.h> |
| |
| #include <regex> |
| |
| namespace memory { |
| |
| bool BlobfsIsActiveVmo(const Vmo& vmo) { |
| if (std::regex_match(vmo.name, std::regex("blob-[0-9a-f]+"))) { |
| // Data VMOs are only active when they have clients |
| return vmo.num_children > 0; |
| } |
| // Any other VMO is considered active |
| return true; |
| } |
| |
| // VMOs are bucketed into the first matching entry. |
| const std::vector<const BucketMatch> Digester::kDefaultBucketMatches = { |
| {"ZBI Buffer", ".*", "uncompressed-bootfs"}, |
| // Memory used with the GPU or display hardware. |
| {"Graphics", ".*", "magma_create_buffer|Mali .*|Magma.*|ImagePipe2Surface.*"}, |
| // Unused protected pool memory. |
| {"ProtectedPool", "driver_host:.*", "SysmemAmlogicProtectedPool"}, |
| // Unused contiguous pool memory. |
| {"ContiguousPool", "driver_host:.*", "SysmemContiguousPool"}, |
| {"Fshost", "fshost.cm", ".*"}, |
| {"Minfs", ".*minfs", ".*"}, |
| {"Blobfs", ".*blobfs", ".*", [](const Vmo& vmo) { return BlobfsIsActiveVmo(vmo); }}, |
| {"BlobfsInactive", ".*blobfs", ".*", [](const Vmo& vmo) { return !BlobfsIsActiveVmo(vmo); }}, |
| {"FlutterApps", "io\\.flutter\\..*", "dart.*"}, |
| {"Flutter", "io\\.flutter\\..*", ".*"}, |
| {"Web", "web_engine_exe:.*", ".*"}, |
| {"Kronk", "kronk.cmx|kronk_for_testing.cmx", ".*"}, |
| {"Scenic", "scenic.cmx", ".*"}, |
| {"Amlogic", "driver_host:pdev:05:00:f", ".*"}, |
| {"Netstack", "netstack.cmx", ".*"}, |
| {"Pkgfs", "pkgfs", ".*"}, |
| {"Cast", "cast_agent.cmx", ".*"}, |
| {"Archivist", "archivist.cm", ".*"}, |
| {"Cobalt", "cobalt.cmx", ".*"}, |
| {"Audio", "audio_core.cmx", ".*"}, |
| {"Context", "context_provider.cmx", ".*"}, |
| }; |
| |
| BucketMatch::BucketMatch(const std::string& name, const std::string& process, |
| const std::string& vmo) |
| : name_(name), process_(process), vmo_(vmo) {} |
| |
| BucketMatch::BucketMatch(const std::string& name, const std::string& process, |
| const std::string& vmo, VmoMatcher vmo_matcher) |
| : name_(name), process_(process), vmo_(vmo), vmo_matcher_(vmo_matcher) {} |
| |
| bool BucketMatch::ProcessMatch(const std::string& process) { |
| const auto& pi = process_match_.find(process); |
| if (pi != process_match_.end()) { |
| return pi->second; |
| } |
| bool match = std::regex_match(process, process_); |
| process_match_.emplace(process, match); |
| return match; |
| } |
| |
| bool BucketMatch::VmoMatch(const Vmo& vmo) { |
| const auto& vi = vmo_match_.find(vmo.name); |
| if (vi != vmo_match_.end()) { |
| return vi->second; |
| } |
| bool match = std::regex_match(vmo.name, vmo_); |
| if (vmo_matcher_) { |
| match &= (*vmo_matcher_)(vmo); |
| } |
| vmo_match_.emplace(vmo.name, match); |
| return match; |
| } |
| |
| Digest::Digest(const Capture& capture, Digester* digester) { digester->Digest(capture, this); } |
| |
| Digester::Digester(const std::vector<const BucketMatch>& bucket_matches) { |
| for (const auto& bucket_match : bucket_matches) { |
| bucket_matches_.emplace_back(bucket_match); |
| } |
| } |
| |
| void Digester::Digest(const Capture& capture, class Digest* digest) { |
| TRACE_DURATION("memory_metrics", "Digester::Digest"); |
| digest->time_ = capture.time(); |
| digest->undigested_vmos_.reserve(capture.koid_to_vmo().size()); |
| for (const auto& [koid, vmo] : capture.koid_to_vmo()) { |
| digest->undigested_vmos_.emplace(koid); |
| } |
| |
| digest->buckets_.reserve(bucket_matches_.size()); |
| for (auto& bucket_match : bucket_matches_) { |
| auto& bucket = digest->buckets_.emplace_back(bucket_match.name(), 0); |
| for (const auto& [koid, process] : capture.koid_to_process()) { |
| if (!bucket_match.ProcessMatch(process.name)) { |
| continue; |
| } |
| for (const auto& v : process.vmos) { |
| if (digest->undigested_vmos_.count(v) == 0) { |
| continue; |
| } |
| const auto& vmo = capture.vmo_for_koid(v); |
| if (!bucket_match.VmoMatch(vmo)) { |
| continue; |
| } |
| bucket.size_ += vmo.committed_bytes; |
| digest->undigested_vmos_.erase(v); |
| } |
| } |
| } |
| |
| std::sort(digest->buckets_.begin(), digest->buckets_.end(), |
| [](const Bucket& a, const Bucket& b) { return a.size() > b.size(); }); |
| uint64_t undigested_size = 0; |
| for (auto v : digest->undigested_vmos_) { |
| undigested_size += capture.vmo_for_koid(v).committed_bytes; |
| } |
| if (undigested_size > 0) { |
| digest->buckets_.emplace_back("Undigested", undigested_size); |
| } |
| |
| const auto& kmem = capture.kmem(); |
| if (kmem.total_bytes > 0) { |
| uint64_t vmo_size = 0; |
| for (const auto& bucket : digest->buckets_) { |
| vmo_size += bucket.size_; |
| } |
| if (vmo_size < kmem.vmo_bytes) { |
| digest->buckets_.emplace_back("Orphaned", kmem.vmo_bytes - vmo_size); |
| } |
| |
| digest->buckets_.emplace_back("Kernel", kmem.wired_bytes + kmem.total_heap_bytes + |
| kmem.mmu_overhead_bytes + kmem.ipc_bytes + |
| kmem.other_bytes); |
| digest->buckets_.emplace_back("Free", kmem.free_bytes); |
| |
| const auto& kmem_ext = capture.kmem_extended(); |
| if (kmem_ext.vmo_pager_total_bytes > 0) { |
| digest->buckets_.emplace_back("[Addl]PagerTotal", kmem_ext.vmo_pager_total_bytes); |
| digest->buckets_.emplace_back("[Addl]PagerNewest", kmem_ext.vmo_pager_newest_bytes); |
| digest->buckets_.emplace_back("[Addl]PagerOldest", kmem_ext.vmo_pager_oldest_bytes); |
| } |
| |
| if (kmem_ext.vmo_discardable_locked_bytes > 0 || kmem_ext.vmo_discardable_unlocked_bytes > 0) { |
| digest->buckets_.emplace_back("[Addl]DiscardableLocked", |
| kmem_ext.vmo_discardable_locked_bytes); |
| digest->buckets_.emplace_back("[Addl]DiscardableUnlocked", |
| kmem_ext.vmo_discardable_unlocked_bytes); |
| } |
| } |
| } |
| |
| } // namespace memory |