blob: 9de1cb8c859c9bc2ccb316080c1bf3ee823bee82 [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/metric_event_builder.h>
#include "lib/fpromise/result.h"
#include "src/developer/memory/metrics/digest.h"
#include "src/developer/memory/metrics/printer.h"
namespace monitor {
using namespace memory;
using cobalt_registry::MemoryMigratedMetricDimensionBucket;
using TimeSinceBoot = cobalt_registry::MemoryLeakMigratedMetricDimensionTimeSinceBoot;
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(const std::vector<memory::BucketMatch>& bucket_matches,
zx::duration poll_frequency, async_dispatcher_t* dispatcher,
sys::ComponentInspector* inspector,
fuchsia::metrics::MetricEventLogger_Sync* logger, CaptureCb capture_cb,
DigestCb digest_cb)
: poll_frequency_(poll_frequency),
dispatcher_(dispatcher),
logger_(logger),
capture_cb_(std::move(capture_cb)),
digest_cb_(std::move(digest_cb)),
bucket_name_to_code_({
{"TotalBytes", MemoryMigratedMetricDimensionBucket::TotalBytes},
{"Free", MemoryMigratedMetricDimensionBucket::Free},
{"Kernel", MemoryMigratedMetricDimensionBucket::Kernel},
{"Orphaned", MemoryMigratedMetricDimensionBucket::Orphaned},
{"Undigested", MemoryMigratedMetricDimensionBucket::Undigested},
{"[Addl]PagerTotal", MemoryMigratedMetricDimensionBucket::__Addl_PagerTotal},
{"[Addl]PagerNewest", MemoryMigratedMetricDimensionBucket::__Addl_PagerNewest},
{"[Addl]PagerOldest", MemoryMigratedMetricDimensionBucket::__Addl_PagerOldest},
{"[Addl]DiscardableLocked",
MemoryMigratedMetricDimensionBucket::__Addl_DiscardableLocked},
{"[Addl]DiscardableUnlocked",
MemoryMigratedMetricDimensionBucket::__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 (const auto& bucket : bucket_matches) {
if (!bucket.event_code().has_value()) {
continue;
}
bucket_name_to_code_.emplace(
bucket.name(), static_cast<MemoryMigratedMetricDimensionBucket>(*bucket.event_code()));
}
for (const 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);
Digest digest;
digest_cb_(capture, &digest);
WriteDigestToInspect(digest);
std::vector<fuchsia::metrics::MetricEvent> events;
const auto& kmem = capture.kmem();
AddKmemEvents(kmem, &events);
AddKmemEventsWithUptime(kmem, capture.time(), &events);
auto builder = cobalt::MetricEventBuilder(cobalt_registry::kMemoryMigratedMetricId);
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_integer(bucket.size()));
}
fuchsia::metrics::MetricEventLogger_LogMetricEvents_Result response =
fpromise::error(fuchsia::metrics::Error::INTERNAL_ERROR);
logger_->LogMetricEvents(std::move(events), &response);
if (response.is_err() && response.err() == fuchsia::metrics::Error::INVALID_ARGUMENTS) {
FX_LOGS(ERROR) << "LogMetricEvents() 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()) {
auto it = inspect_memory_usages_.find(bucket.name());
if (it == inspect_memory_usages_.end()) {
FX_LOGS_FIRST_N(INFO, 3) << "Not_in_map: " << bucket.name() << ": " << bucket.size() << "\n";
continue;
}
it->second.Set(bucket.size());
}
}
void Metrics::AddKmemEvents(const zx_info_kmem_stats_t& kmem,
std::vector<fuchsia::metrics::MetricEvent>* events) {
TRACE_DURATION("memory_monitor", "Metrics::AddKmemEvents");
auto builder =
cobalt::MetricEventBuilder(cobalt_registry::kMemoryGeneralBreakdownMigratedMetricId);
using Breakdown = cobalt_registry::MemoryGeneralBreakdownMigratedMetricDimensionGeneralBreakdown;
events->push_back(
builder.Clone().with_event_code(Breakdown::TotalBytes).as_integer(kmem.total_bytes));
events->push_back(builder.Clone()
.with_event_code(Breakdown::UsedBytes)
.as_integer(kmem.total_bytes - kmem.free_bytes));
events->push_back(
builder.Clone().with_event_code(Breakdown::FreeBytes).as_integer(kmem.free_bytes));
events->push_back(
builder.Clone().with_event_code(Breakdown::VmoBytes).as_integer(kmem.vmo_bytes));
events->push_back(builder.Clone()
.with_event_code(Breakdown::KernelFreeHeapBytes)
.as_integer(kmem.free_heap_bytes));
events->push_back(
builder.Clone().with_event_code(Breakdown::MmuBytes).as_integer(kmem.mmu_overhead_bytes));
events->push_back(
builder.Clone().with_event_code(Breakdown::IpcBytes).as_integer(kmem.ipc_bytes));
events->push_back(builder.Clone()
.with_event_code(Breakdown::KernelTotalHeapBytes)
.as_integer(kmem.total_heap_bytes));
events->push_back(
builder.Clone().with_event_code(Breakdown::WiredBytes).as_integer(kmem.wired_bytes));
events->push_back(
builder.Clone().with_event_code(Breakdown::OtherBytes).as_integer(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::metrics::MetricEvent>* events) {
TRACE_DURATION("memory_monitor", "Metrics::AddKmemEventsWithUptime");
auto builder = std::move(cobalt::MetricEventBuilder(cobalt_registry::kMemoryLeakMigratedMetricId)
.with_event_code_at(1, GetUpTimeEventCode(capture_time)));
using Breakdown = cobalt_registry::MemoryLeakMigratedMetricDimensionGeneralBreakdown;
events->push_back(
builder.Clone().with_event_code_at(0, Breakdown::TotalBytes).as_integer(kmem.total_bytes));
events->push_back(builder.Clone()
.with_event_code_at(0, Breakdown::UsedBytes)
.as_integer(kmem.total_bytes - kmem.free_bytes));
events->push_back(
builder.Clone().with_event_code_at(0, Breakdown::FreeBytes).as_integer(kmem.free_bytes));
events->push_back(
builder.Clone().with_event_code_at(0, Breakdown::VmoBytes).as_integer(kmem.vmo_bytes));
events->push_back(builder.Clone()
.with_event_code_at(0, Breakdown::KernelFreeHeapBytes)
.as_integer(kmem.free_heap_bytes));
events->push_back(builder.Clone()
.with_event_code_at(0, Breakdown::MmuBytes)
.as_integer(kmem.mmu_overhead_bytes));
events->push_back(
builder.Clone().with_event_code_at(0, Breakdown::IpcBytes).as_integer(kmem.ipc_bytes));
events->push_back(builder.Clone()
.with_event_code_at(0, Breakdown::KernelTotalHeapBytes)
.as_integer(kmem.total_heap_bytes));
events->push_back(
builder.Clone().with_event_code_at(0, Breakdown::WiredBytes).as_integer(kmem.wired_bytes));
events->push_back(
builder.Clone().with_event_code_at(0, Breakdown::OtherBytes).as_integer(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