blob: 48e8183011351ec56c73b8e668c161369b8468fb [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/pressure_observer.h"
#include <fuchsia/kernel/c/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/fdio/directory.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/channel.h>
#include <lib/zx/event.h>
#include <lib/zx/job.h>
#include <sys/stat.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include "src/developer/memory/monitor/pressure_notifier.h"
namespace monitor {
PressureObserver::PressureObserver(bool watch_for_changes, PressureNotifier* notifier)
: notifier_(notifier) {
if (InitMemPressureEvents() != ZX_OK) {
return;
}
if (watch_for_changes) {
// Set up a new thread (memory-pressure-loop) that watches for memory pressure changes from the
// kernel. All this thread does is wait on memory pressure events in a loop, hence is kept
// separate from memory_monitor's main dispatcher thread. Once the |PressureObserver| object has
// been created, it is run entirely on the memory-pressure-loop thread.
loop_.StartThread("memory-pressure-loop");
watch_task_.Post(loop_.dispatcher());
}
}
// Called from the constructor to set up waiting on kernel memory pressure events.
zx_status_t PressureObserver::InitMemPressureEvents() {
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "zx::channel::create returned " << zx_status_get_string(status);
return status;
}
const char* root_job_svc = "/svc/fuchsia.kernel.RootJobForInspect";
status = fdio_service_connect(root_job_svc, remote.release());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "fdio_service_connect returned " << zx_status_get_string(status);
return status;
}
zx::job root_job;
status = fuchsia_kernel_RootJobForInspectGet(local.get(), root_job.reset_and_get_address());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "fuchsia_kernel_RootJobForInspectGet returned "
<< zx_status_get_string(status);
return status;
}
status = zx_system_get_event(root_job.get(), ZX_SYSTEM_EVENT_MEMORY_PRESSURE_CRITICAL,
events_[Level::kCritical].reset_and_get_address());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "zx_system_get_event [CRITICAL] returned " << zx_status_get_string(status);
return status;
}
status = zx_system_get_event(root_job.get(), ZX_SYSTEM_EVENT_MEMORY_PRESSURE_WARNING,
events_[Level::kWarning].reset_and_get_address());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "zx_system_get_event [WARNING] returned " << zx_status_get_string(status);
return status;
}
status = zx_system_get_event(root_job.get(), ZX_SYSTEM_EVENT_MEMORY_PRESSURE_NORMAL,
events_[Level::kNormal].reset_and_get_address());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "zx_system_get_event [NORMAL] returned " << zx_status_get_string(status);
return status;
}
for (size_t i = 0; i < Level::kNumLevels; i++) {
wait_items_[i].handle = events_[i].get();
wait_items_[i].waitfor = ZX_EVENT_SIGNALED;
wait_items_[i].pending = 0;
}
return ZX_OK;
}
void PressureObserver::WatchForChanges() {
WaitOnLevelChange();
watch_task_.Post(loop_.dispatcher());
}
void PressureObserver::WaitOnLevelChange() {
// Wait on all events the first time around.
size_t num_wait_items = (level_ == Level::kNumLevels) ? Level::kNumLevels : Level::kNumLevels - 1;
zx_status_t status = zx_object_wait_many(wait_items_.data(), num_wait_items, ZX_TIME_INFINITE);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "zx_object_wait_many returned " << zx_status_get_string(status);
return;
}
for (size_t i = 0; i < Level::kNumLevels; i++) {
if (wait_items_[i].pending) {
wait_items_[i].pending = 0;
OnLevelChanged(wait_items_[i].handle);
// Move the event currently asserted to the end of the array.
// Wait on only the first |kNumLevels| - 1 items next time around.
std::swap(wait_items_[i].handle, wait_items_[Level::kNumLevels - 1].handle);
break;
}
}
}
void PressureObserver::OnLevelChanged(zx_handle_t handle) {
Level old_level = level_;
for (size_t i = 0; i < Level::kNumLevels; i++) {
if (events_[i].get() == handle) {
level_ = Level(i);
break;
}
}
FX_LOGS(INFO) << "Memory pressure level changed from " << kLevelNames[old_level] << " to "
<< kLevelNames[level_];
if (notifier_ != nullptr) {
// Notify the |PressureNotifier| that the level has changed. |PressureNotifier::Notify()| is a
// lightweight call which simply causes a notification task to be queued on the
// |PressureNotifier|'s thread. The notification task is not executed on our thread, whose only
// job is to observe kernel memory pressure changes.
notifier_->Notify();
}
}
} // namespace monitor