|  | // 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}, | 
|  | }), | 
|  | 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 |