| // 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/developer/memory/monitor/metrics.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/trace/event.h> |
| #include <zircon/time.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <string> |
| |
| #include <src/lib/cobalt/cpp/cobalt_event_builder.h> |
| |
| #include "src/developer/memory/metrics/digest.h" |
| #include "src/developer/memory/metrics/printer.h" |
| |
| namespace monitor { |
| |
| using namespace memory; |
| using cobalt_registry::MemoryMetricDimensionBucket; |
| using TimeSinceBoot = cobalt_registry::MemoryLeakMetricDimensionTimeSinceBoot; |
| |
| namespace { |
| static const std::map<zx_duration_t, TimeSinceBoot> UptimeLevelMap = { |
| {zx_duration_from_min(1), TimeSinceBoot::Up}, |
| {zx_duration_from_min(30), TimeSinceBoot::UpOneMinute}, |
| {zx_duration_from_hour(1), TimeSinceBoot::UpThirtyMinutes}, |
| {zx_duration_from_hour(6), TimeSinceBoot::UpOneHour}, |
| {zx_duration_from_hour(12), TimeSinceBoot::UpSixHours}, |
| {zx_duration_from_hour(24), TimeSinceBoot::UpTwelveHours}, |
| {zx_duration_from_hour(48), TimeSinceBoot::UpOneDay}, |
| {zx_duration_from_hour(72), TimeSinceBoot::UpTwoDays}, |
| {zx_duration_from_hour(144), TimeSinceBoot::UpThreeDays}, |
| }; |
| } // namespace |
| |
| // Metrics polls the memory state periodically asynchroniously using the passed dispatcher and sends |
| // information about the memory Digests to Cobalt, in the form of several Events. |
| Metrics::Metrics(zx::duration poll_frequency, async_dispatcher_t* dispatcher, |
| sys::ComponentInspector* inspector, fuchsia::cobalt::Logger_Sync* logger, |
| CaptureFn capture_cb) |
| : poll_frequency_(poll_frequency), |
| dispatcher_(dispatcher), |
| logger_(logger), |
| capture_cb_(std::move(capture_cb)), |
| bucket_name_to_code_({ |
| {"TotalBytes", MemoryMetricDimensionBucket::TotalBytes}, |
| {"ZBI Buffer", MemoryMetricDimensionBucket::ZbiBuffer}, |
| {"Graphics", MemoryMetricDimensionBucket::Graphics}, |
| {"Video Buffer", MemoryMetricDimensionBucket::VideoBuffer}, |
| {"Minfs", MemoryMetricDimensionBucket::Minfs}, |
| {"Blobfs", MemoryMetricDimensionBucket::Blobfs}, |
| {"BlobfsInactive", MemoryMetricDimensionBucket::BlobfsInactive}, |
| {"Opal", MemoryMetricDimensionBucket::Opal}, |
| {"Web", MemoryMetricDimensionBucket::Web}, |
| {"Kronk", MemoryMetricDimensionBucket::Kronk}, |
| {"Scenic", MemoryMetricDimensionBucket::Scenic}, |
| {"Amlogic", MemoryMetricDimensionBucket::Amlogic}, |
| {"Netstack", MemoryMetricDimensionBucket::Netstack}, |
| {"Amber", MemoryMetricDimensionBucket::Amber}, |
| {"Pkgfs", MemoryMetricDimensionBucket::Pkgfs}, |
| {"Cast", MemoryMetricDimensionBucket::Cast}, |
| {"Chromium", MemoryMetricDimensionBucket::Chromium}, |
| {"Free", MemoryMetricDimensionBucket::Free}, |
| {"Kernel", MemoryMetricDimensionBucket::Kernel}, |
| {"Orphaned", MemoryMetricDimensionBucket::Orphaned}, |
| {"Undigested", MemoryMetricDimensionBucket::Undigested}, |
| {"Fshost", MemoryMetricDimensionBucket::Fshost}, |
| {"Flutter", MemoryMetricDimensionBucket::Flutter}, |
| {"Archivist", MemoryMetricDimensionBucket::Archivist}, |
| {"Cobalt", MemoryMetricDimensionBucket::Cobalt}, |
| {"Audio", MemoryMetricDimensionBucket::Audio}, |
| {"Context", MemoryMetricDimensionBucket::Context}, |
| {"ContiguousPool", MemoryMetricDimensionBucket::ContiguousPool}, |
| {"ProtectedPool", MemoryMetricDimensionBucket::ProtectedPool}, |
| {"FlutterApps", MemoryMetricDimensionBucket::FlutterApps}, |
| {"[Addl]PagerTotal", MemoryMetricDimensionBucket::__Addl_PagerTotal}, |
| {"[Addl]PagerNewest", MemoryMetricDimensionBucket::__Addl_PagerNewest}, |
| {"[Addl]PagerOldest", MemoryMetricDimensionBucket::__Addl_PagerOldest}, |
| {"[Addl]DiscardableLocked", MemoryMetricDimensionBucket::__Addl_DiscardableLocked}, |
| {"[Addl]DiscardableUnlocked", MemoryMetricDimensionBucket::__Addl_DiscardableUnlocked}, |
| }), |
| inspector_(inspector), |
| platform_metric_node_(inspector_->root().CreateChild(kInspectPlatformNodeName)), |
| // Diagram of hierarchy can be seen below: |
| // root |
| // - platform_metrics |
| // - memory_usages |
| // - Amber |
| // - Amlogic |
| // - ... |
| // - timestamp |
| // - memory_bandwidth |
| // - readings |
| metric_memory_node_(platform_metric_node_.CreateChild(kMemoryNodeName)), |
| inspect_memory_timestamp_(metric_memory_node_.CreateInt(kReadingMemoryTimestamp, 0)), |
| metric_memory_bandwidth_node_(platform_metric_node_.CreateChild(kMemoryBandwidthNodeName)), |
| inspect_memory_bandwidth_( |
| metric_memory_bandwidth_node_.CreateUintArray(kReadings, kMemoryBandwidthArraySize)), |
| inspect_memory_bandwidth_timestamp_( |
| metric_memory_bandwidth_node_.CreateInt(kReadingMemoryTimestamp, 0)) { |
| for (auto& element : bucket_name_to_code_) { |
| inspect_memory_usages_.insert(std::pair<std::string, inspect::UintProperty>( |
| element.first, metric_memory_node_.CreateUint(element.first, 0))); |
| } |
| |
| task_.PostDelayed(dispatcher_, zx::usec(1)); |
| } |
| |
| void Metrics::CollectMetrics() { |
| TRACE_DURATION("memory_monitor", "Watcher::Metrics::CollectMetrics"); |
| Capture capture; |
| capture_cb_(&capture, VMO); |
| Digest digest(capture, &digester_); |
| std::ostringstream oss; |
| Printer p(oss); |
| |
| p.PrintDigest(digest); |
| auto str = oss.str(); |
| std::replace(str.begin(), str.end(), '\n', ' '); |
| FX_LOGS(INFO) << str; |
| |
| WriteDigestToInspect(digest); |
| |
| std::vector<fuchsia::cobalt::CobaltEvent> events; |
| const auto& kmem = capture.kmem(); |
| AddKmemEvents(kmem, &events); |
| AddKmemEventsWithUptime(kmem, capture.time(), &events); |
| auto builder = cobalt::CobaltEventBuilder(cobalt_registry::kMemoryMetricId); |
| for (const auto& bucket : digest.buckets()) { |
| if (bucket.size() == 0) { |
| continue; |
| } |
| const auto& code_iter = bucket_name_to_code_.find(bucket.name()); |
| if (code_iter == bucket_name_to_code_.end()) { |
| FX_LOGS_FIRST_N(ERROR, 3) << "Metrics::CollectMetrics: Invalid bucket name: " |
| << bucket.name(); |
| continue; |
| } |
| events.push_back( |
| builder.Clone().with_event_code(code_iter->second).as_memory_usage(bucket.size())); |
| } |
| fuchsia::cobalt::Status status = fuchsia::cobalt::Status::INTERNAL_ERROR; |
| logger_->LogCobaltEvents(std::move(events), &status); |
| if (status == fuchsia::cobalt::Status::INVALID_ARGUMENTS) { |
| FX_LOGS(ERROR) << "LogCobaltEvents() returned status INVALID_ARGUMENTS"; |
| } |
| task_.PostDelayed(dispatcher_, poll_frequency_); |
| } |
| |
| void Metrics::WriteDigestToInspect(const memory::Digest& digest) { |
| TRACE_DURATION("memory_monitor", "Watcher::Metrics::WriteDigestToInspect"); |
| // The division is to address JSON int range problem in b/156523968 |
| // JSON does not suport 64 bit integers and some readers using JSON libraries will |
| // crash if we write a value greater than MAX_INT_32. |
| // The unit in seconds is sufficient. |
| inspect_memory_timestamp_.Set(digest.time() / 1000000000); |
| for (auto const& bucket : digest.buckets()) { |
| if (inspect_memory_usages_.find(bucket.name()) != inspect_memory_usages_.end()) { |
| inspect_memory_usages_[bucket.name()].Set(bucket.size()); |
| } else { |
| FX_LOGS(INFO) << "Not_in_map: " << bucket.name() << ": " << bucket.size() << "\n"; |
| } |
| } |
| } |
| |
| void Metrics::AddKmemEvents(const zx_info_kmem_stats_t& kmem, |
| std::vector<fuchsia::cobalt::CobaltEvent>* events) { |
| TRACE_DURATION("memory_monitor", "Metrics::AddKmemEvents"); |
| auto builder = cobalt::CobaltEventBuilder(cobalt_registry::kMemoryGeneralBreakdownMetricId); |
| using Breakdown = cobalt_registry::MemoryGeneralBreakdownMetricDimensionGeneralBreakdown; |
| events->push_back( |
| builder.Clone().with_event_code(Breakdown::TotalBytes).as_memory_usage(kmem.total_bytes)); |
| events->push_back(builder.Clone() |
| .with_event_code(Breakdown::UsedBytes) |
| .as_memory_usage(kmem.total_bytes - kmem.free_bytes)); |
| events->push_back( |
| builder.Clone().with_event_code(Breakdown::FreeBytes).as_memory_usage(kmem.free_bytes)); |
| events->push_back( |
| builder.Clone().with_event_code(Breakdown::VmoBytes).as_memory_usage(kmem.vmo_bytes)); |
| events->push_back(builder.Clone() |
| .with_event_code(Breakdown::KernelFreeHeapBytes) |
| .as_memory_usage(kmem.free_heap_bytes)); |
| events->push_back(builder.Clone() |
| .with_event_code(Breakdown::MmuBytes) |
| .as_memory_usage(kmem.mmu_overhead_bytes)); |
| events->push_back( |
| builder.Clone().with_event_code(Breakdown::IpcBytes).as_memory_usage(kmem.ipc_bytes)); |
| events->push_back(builder.Clone() |
| .with_event_code(Breakdown::KernelTotalHeapBytes) |
| .as_memory_usage(kmem.total_heap_bytes)); |
| events->push_back( |
| builder.Clone().with_event_code(Breakdown::WiredBytes).as_memory_usage(kmem.wired_bytes)); |
| events->push_back( |
| builder.Clone().with_event_code(Breakdown::OtherBytes).as_memory_usage(kmem.other_bytes)); |
| } |
| |
| // TODO(fxbug.dev/3778): Refactor this when dedup enum is availble in generated |
| // cobalt config source code. |
| void Metrics::AddKmemEventsWithUptime(const zx_info_kmem_stats_t& kmem, |
| const zx_time_t capture_time, |
| std::vector<fuchsia::cobalt::CobaltEvent>* events) { |
| TRACE_DURATION("memory_monitor", "Metrics::AddKmemEventsWithUptime"); |
| auto builder = std::move(cobalt::CobaltEventBuilder(cobalt_registry::kMemoryLeakMetricId) |
| .with_event_code_at(1, GetUpTimeEventCode(capture_time))); |
| using Breakdown = cobalt_registry::MemoryLeakMetricDimensionGeneralBreakdown; |
| events->push_back(builder.Clone() |
| .with_event_code_at(0, Breakdown::TotalBytes) |
| .as_memory_usage(kmem.total_bytes)); |
| events->push_back(builder.Clone() |
| .with_event_code_at(0, Breakdown::UsedBytes) |
| .as_memory_usage(kmem.total_bytes - kmem.free_bytes)); |
| events->push_back( |
| builder.Clone().with_event_code_at(0, Breakdown::FreeBytes).as_memory_usage(kmem.free_bytes)); |
| events->push_back( |
| builder.Clone().with_event_code_at(0, Breakdown::VmoBytes).as_memory_usage(kmem.vmo_bytes)); |
| events->push_back(builder.Clone() |
| .with_event_code_at(0, Breakdown::KernelFreeHeapBytes) |
| .as_memory_usage(kmem.free_heap_bytes)); |
| events->push_back(builder.Clone() |
| .with_event_code_at(0, Breakdown::MmuBytes) |
| .as_memory_usage(kmem.mmu_overhead_bytes)); |
| events->push_back( |
| builder.Clone().with_event_code_at(0, Breakdown::IpcBytes).as_memory_usage(kmem.ipc_bytes)); |
| events->push_back(builder.Clone() |
| .with_event_code_at(0, Breakdown::KernelTotalHeapBytes) |
| .as_memory_usage(kmem.total_heap_bytes)); |
| events->push_back(builder.Clone() |
| .with_event_code_at(0, Breakdown::WiredBytes) |
| .as_memory_usage(kmem.wired_bytes)); |
| events->push_back(builder.Clone() |
| .with_event_code_at(0, Breakdown::OtherBytes) |
| .as_memory_usage(kmem.other_bytes)); |
| } |
| |
| TimeSinceBoot Metrics::GetUpTimeEventCode(const zx_time_t capture_time) { |
| zx_duration_t uptime = zx_duration_from_nsec(capture_time); |
| for (auto const& map : UptimeLevelMap) { |
| if (uptime < map.first) { |
| return map.second; |
| } |
| } |
| return TimeSinceBoot::UpSixDays; |
| } |
| |
| void Metrics::NextMemoryBandwidthReading(uint64_t reading, zx_time_t ts) { |
| inspect_memory_bandwidth_.Set(memory_bandwidth_index_, reading); |
| inspect_memory_bandwidth_timestamp_.Set(ts); |
| if (++memory_bandwidth_index_ >= kMemoryBandwidthArraySize) |
| memory_bandwidth_index_ = 0; |
| } |
| |
| } // namespace monitor |