blob: 3039c1f19e951eded47dde61f02415573105ea61 [file] [log] [blame]
// 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