blob: 299ba05d26b8eeffdece4354ceb976a5b0937a61 [file] [log] [blame] [edit]
// Copyright 2024 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <inttypes.h>
#include <lib/console.h>
#include <object/memory_watchdog.h>
#include <vm/pmm.h>
#include <vm/scanner.h>
// Allocate memory until the PMM returns ZX_ERR_NO_MEMORY, then free it all.
//
// The idea is to cause the system to briefly dip into an out of memory state.
static int cmd_oom_dip() {
list_node list = LIST_INITIAL_VALUE(list);
while (pmm_alloc_pages(1, 0, &list) != ZX_ERR_NO_MEMORY) {
}
pmm_free(&list);
printf("allocated until ZX_ERR_NO_MEMORY (and then freed)\n");
return ZX_OK;
}
static int cmd_oom(int argc, const cmd_args* argv) {
uint64_t rate = 0;
bool hard = false;
if (argc > 2) {
if (!strcmp(argv[2].str, "dip")) {
return cmd_oom_dip();
}
if (!strcmp(argv[2].str, "hard")) {
hard = true;
} else {
rate = strtoul(argv[2].str, nullptr, 0) * 1024 * 1024 / PAGE_SIZE;
}
}
// When we reach the oom state the kernel may 'try harder' to reclaim memory and prevent us from
// hitting OOM. To avoid this we disable the scanner (and evictor) to prevent additional memory
// from becoming classified as evictable, and then evict anything that is already considered.
printf("Disabling VM scanner\n");
scanner_push_disable_count();
uint64_t pages_evicted =
pmm_evictor()
->EvictSynchronous(/*min_mem_to_free=*/UINT64_MAX, /*free_mem_target=*/0,
Evictor::EvictionLevel::IncludeNewest, Evictor::Output::NoPrint)
.counts.non_loaned_total();
if (pages_evicted > 0) {
printf("Leaked %" PRIu64 " pages from eviction\n", pages_evicted);
}
uint64_t pages_till_oom;
// In case we are racing with someone freeing pages we will leak in a loop until we are sure
// we have hit the oom state.
while ((pages_till_oom = GetMemoryWatchdog().DebugNumBytesTillPressureLevel(
MemoryWatchdog::PressureLevel::kOutOfMemory) /
PAGE_SIZE) > 0) {
list_node list = LIST_INITIAL_VALUE(list);
if (rate > 0) {
uint64_t pages_leaked = 0;
while (pages_leaked < pages_till_oom) {
uint64_t alloc_pages = ktl::min(rate, pages_till_oom - pages_leaked);
if (pmm_alloc_pages(alloc_pages, 0, &list) == ZX_OK) {
pages_leaked += alloc_pages;
printf("Leaked %lu pages\n", pages_leaked);
}
Thread::Current::SleepRelative(ZX_SEC(1));
}
} else {
if (pmm_alloc_pages(pages_till_oom, 0, &list) == ZX_OK) {
printf("Leaked %lu pages\n", pages_till_oom);
}
}
// Ignore any errors under the assumption we had a racy allocation and try again next time
// around the loop.
}
if (hard) {
printf("Continuing to leak pages forever\n");
// Keep leaking as fast possible.
while (true) {
vm_page_t* p;
pmm_alloc_page(0, &p);
}
}
return ZX_OK;
}
static int cmd_usage(const char* cmd_name) {
printf("usage:\n");
printf("%s dump : dump memory availability state info \n",
cmd_name);
printf(
"%s oom [<rate>] : leak memory until OOM is triggered, "
"optionally specify the rate at which to leak (in MB per second). "
"This tries to reliably get the system into OOM state, so might disable some kinds of "
"kernel reclamation that prevent going into OOM. If you want to test system response "
"to memory pressure, unaltered from the default behavior, use avail_state instead.\n",
cmd_name);
printf("%s oom dip : allocate until no mem, then free\n",
cmd_name);
printf(
"%s oom hard : leak memory aggressively and keep on "
"leaking\n",
cmd_name);
printf(
"%s avail_state [step] <state> [<nsecs>] : allocate memory to go to memstate "
"<state>, hold the state for <nsecs> (10s by default). Only works if going to <state> "
"from current state requires allocating memory, can't free up pre-allocated memory. In "
"optional [step] mode, allocation pauses for 1 second at each intermediate memory "
"availability state until <state> is reached.\n",
cmd_name);
return ZX_ERR_INTERNAL;
}
static int cmd_mem(int argc, const cmd_args* argv, uint32_t flags) {
const char* name = argv[0].str;
if (argc < 2) {
printf("not enough arguments\n");
return cmd_usage(name);
}
if (!strcmp(argv[1].str, "dump")) {
GetMemoryWatchdog().Dump();
} else if (!strcmp(argv[1].str, "oom")) {
if (argc > 3) {
return cmd_usage(name);
}
return cmd_oom(argc, argv);
} else if (!strcmp(argv[1].str, "avail_state")) {
if (argc < 3) {
return cmd_usage(name);
}
bool step = false;
int index = 2;
if (!strcmp(argv[2].str, "step")) {
step = true;
index++;
}
MemoryWatchdog::PressureLevel state =
static_cast<MemoryWatchdog::PressureLevel>(argv[index++].u);
if (state >= MemoryWatchdog::PressureLevel::kNumLevels) {
printf("Invalid memstate %u. Specify a value between 0 and %u.\n", state,
MemoryWatchdog::PressureLevel::kNumLevels - 1u);
return cmd_usage(name);
}
uint64_t pages_to_alloc, pages_to_free = 0;
list_node list = LIST_INITIAL_VALUE(list);
if (step) {
uint8_t s = MemoryWatchdog::PressureLevel::kNumLevels - 1;
while (true) {
// In case we are racing with someone freeing pages we will leak in a loop until we are
// sure we have hit the required memory availability state.
uint64_t pages_allocated = 0;
while ((pages_to_alloc = GetMemoryWatchdog().DebugNumBytesTillPressureLevel(
MemoryWatchdog::PressureLevel(s)) /
PAGE_SIZE) > 0) {
if (pmm_alloc_pages(pages_to_alloc, 0, &list) == ZX_OK) {
printf("Leaked %lu pages\n", pages_to_alloc);
pages_allocated += pages_to_alloc;
}
}
pages_to_free += pages_allocated;
if (s == state) {
break;
}
s--;
if (pages_allocated) {
printf("Sleeping for 1 second...\n");
Thread::Current::SleepRelative(ZX_SEC(1));
}
}
} else {
// In case we are racing with someone freeing pages we will leak in a loop until we are
// sure we have hit the required memory availability state.
while ((pages_to_alloc =
GetMemoryWatchdog().DebugNumBytesTillPressureLevel(state) / PAGE_SIZE) > 0) {
if (pmm_alloc_pages(pages_to_alloc, 0, &list) == ZX_OK) {
printf("Leaked %lu pages\n", pages_to_alloc);
pages_to_free += pages_to_alloc;
}
}
}
if (pages_to_free > 0) {
uint64_t nsecs = 10;
if (argc > index) {
nsecs = argv[index].u;
}
printf("Sleeping for %lu seconds...\n", nsecs);
Thread::Current::SleepRelative(ZX_SEC(nsecs));
pmm_free(&list);
printf("Freed %lu pages\n", pages_to_free);
}
} else {
printf("unknown command\n");
return cmd_usage(name);
}
return ZX_OK;
}
STATIC_COMMAND_START
STATIC_COMMAND_MASKED("mem", "memory commands", &cmd_mem, CMD_AVAIL_NORMAL)
STATIC_COMMAND_END(mem_cmd)