blob: 3be512c5bb4dadfd3f42336b31b8de352944c06f [file] [log] [blame]
// Copyright 2021 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/logger.h"
#include <lib/syslog/cpp/macros.h>
#include "lib/zx/time.h"
#include "src/developer/memory/metrics/printer.h"
namespace monitor {
namespace {
constexpr size_t kMeasurementsCapacity = 100; // Max number of digest entries to store in inspect
constexpr std::string_view kMeasurementsEntry{"bucket_sizes"};
constexpr std::string_view kTimestamp{"@timestamp"};
constexpr std::string_view kMeasurementsKey{"measurements"};
constexpr std::string_view kBucketsKey{"buckets"};
std::once_flag bucket_names_flag;
void PushDigestToInspect(inspect::BoundedListNode& node, const memory::Digest& digest) {
node.CreateEntry([&digest](inspect::Node& n) {
n.RecordUint(kTimestamp, digest.time());
auto& buckets = digest.buckets();
auto ia = n.CreateUintArray(kMeasurementsEntry, buckets.size());
for (std::size_t i = 0; i < buckets.size(); i++) {
ia.Set(i, buckets[i].size());
}
n.Record(std::move(ia));
});
}
} // namespace
Logger::Logger(async_dispatcher_t* dispatcher, std::optional<monitor::HighWater*> high_water,
CaptureCb capture_cb, DigestCb digest_cb, memory_monitor_config::Config* config,
inspect::Node node)
: dispatcher_(dispatcher),
high_water_(high_water),
capture_cb_(std::move(capture_cb)),
digest_cb_(std::move(digest_cb)),
config_(config),
root_node_(std::move(node)),
inspect_bucket_digest_node_(root_node_.CreateChild(kMeasurementsKey), kMeasurementsCapacity) {
// Ensure that all delays are above 0, otherwise the logger risks busy
// looping, which is all the more problematic because it typically runs on
// memory_monitor's main thread, blocking it entirely.
FX_CHECK(config->imminent_oom_capture_delay_s() > 0);
FX_CHECK(config->critical_capture_delay_s() > 0);
FX_CHECK(config->warning_capture_delay_s() > 0);
FX_CHECK(config->normal_capture_delay_s() > 0);
}
void Logger::SetPressureLevel(pressure_signaler::Level l) {
switch (l) {
case pressure_signaler::kImminentOOM:
duration_ = zx::sec(config_->imminent_oom_capture_delay_s());
capture_high_water_ = true;
break;
case pressure_signaler::kCritical:
duration_ = zx::sec(config_->critical_capture_delay_s());
capture_high_water_ = false;
break;
case pressure_signaler::kWarning:
duration_ = zx::sec(config_->warning_capture_delay_s());
capture_high_water_ = false;
break;
case pressure_signaler::kNormal:
duration_ = zx::sec(config_->normal_capture_delay_s());
capture_high_water_ = false;
break;
case pressure_signaler::kNumLevels:
break;
}
if (config_->capture_on_pressure_change()) {
task_.Cancel();
task_.Post(dispatcher_);
} else if (task_.last_deadline() > zx::deadline_after(duration_)) {
task_.Cancel();
task_.PostDelayed(dispatcher_, duration_);
}
}
void Logger::Log() {
memory::Capture c;
auto s = capture_cb_(&c);
if (s != ZX_OK) {
FX_LOGS_FIRST_N(INFO, 1) << "Error getting Capture: " << s;
return;
}
memory::Digest d;
digest_cb_(c, &d);
// TODO(https://fxbug.dev/391360542): Remove the text output.
{
std::ostringstream oss;
memory::TextPrinter p(oss);
p.PrintDigest(d);
auto str = std::move(oss).str();
std::ranges::replace(str, '\n', ' ');
FX_LOGS(INFO) << str;
}
// Enshrine the order of buckets, once they have been populated.
std::call_once(bucket_names_flag, [this, &buckets = d.buckets()]() {
bucket_names_ = root_node_.CreateStringArray(kBucketsKey, buckets.size());
for (size_t i = 0; i < buckets.size(); i++) {
bucket_names_->Set(i, buckets[i].name());
}
});
PushDigestToInspect(inspect_bucket_digest_node_, d);
if (capture_high_water_ && high_water_) {
(*high_water_)->RecordHighWater(c);
(*high_water_)->RecordHighWaterDigest(d);
}
task_.PostDelayed(dispatcher_, duration_);
}
} // namespace monitor