From 3d106cb091de6b6ef2a9bf483fb0f5c98c28263c Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Tue, 5 Mar 2024 15:34:27 +0000 Subject: [PATCH] 8325139: JFR SwapSpace event - add free swap space information on Linux when running in a container environment Reviewed-by: lucy, sgehwolf --- .../os/linux/cgroupSubsystem_linux.hpp | 1 + .../os/linux/cgroupV1Subsystem_linux.cpp | 16 ++++++- .../os/linux/cgroupV1Subsystem_linux.hpp | 1 + .../os/linux/cgroupV2Subsystem_linux.cpp | 10 +++++ .../os/linux/cgroupV2Subsystem_linux.hpp | 1 + src/hotspot/os/linux/osContainer_linux.cpp | 5 +++ src/hotspot/os/linux/osContainer_linux.hpp | 1 + src/hotspot/os/linux/os_linux.cpp | 43 ++++++++++++++----- .../containers/docker/TestJFREvents.java | 37 +++++++++++++++- 9 files changed, 103 insertions(+), 12 deletions(-) diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.hpp b/src/hotspot/os/linux/cgroupSubsystem_linux.hpp index 3cc2141f176..7943d2fe9de 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.hpp @@ -264,6 +264,7 @@ class CgroupSubsystem: public CHeapObj { virtual jlong pids_current() = 0; virtual jlong memory_usage_in_bytes() = 0; virtual jlong memory_and_swap_limit_in_bytes() = 0; + virtual jlong memory_and_swap_usage_in_bytes() = 0; virtual jlong memory_soft_limit_in_bytes() = 0; virtual jlong memory_max_usage_in_bytes() = 0; virtual jlong rss_usage_in_bytes() = 0; diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp index 37a1f476bde..b83da9099f8 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -168,6 +168,20 @@ jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() { return memory_swap; } +jlong CgroupV1Subsystem::memory_and_swap_usage_in_bytes() { + jlong memory_sw_limit = memory_and_swap_limit_in_bytes(); + jlong memory_limit = CgroupSubsystem::memory_limit_in_bytes(); + if (memory_sw_limit > 0 && memory_limit > 0) { + jlong delta_swap = memory_sw_limit - memory_limit; + if (delta_swap > 0) { + GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.memsw.usage_in_bytes", + "mem swap usage is: ", JULONG_FORMAT, JULONG_FORMAT, memory_swap_usage); + return (jlong)memory_swap_usage; + } + } + return memory_usage_in_bytes(); +} + jlong CgroupV1Subsystem::read_mem_swappiness() { GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.swappiness", "Swappiness is: ", JULONG_FORMAT, JULONG_FORMAT, swappiness); diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp index c7550136f48..3205973961a 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp @@ -76,6 +76,7 @@ class CgroupV1Subsystem: public CgroupSubsystem { public: jlong read_memory_limit_in_bytes(); jlong memory_and_swap_limit_in_bytes(); + jlong memory_and_swap_usage_in_bytes(); jlong memory_soft_limit_in_bytes(); jlong memory_usage_in_bytes(); jlong memory_max_usage_in_bytes(); diff --git a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp index 1c433be1c23..d7c6464caa0 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp @@ -180,6 +180,16 @@ jlong CgroupV2Subsystem::memory_and_swap_limit_in_bytes() { return swap_limit; } +jlong CgroupV2Subsystem::memory_and_swap_usage_in_bytes() { + jlong memory_usage = memory_usage_in_bytes(); + if (memory_usage >= 0) { + char* mem_swp_current_str = mem_swp_current_val(); + jlong swap_current = limit_from_str(mem_swp_current_str); + return memory_usage + (swap_current >= 0 ? swap_current : 0); + } + return memory_usage; // not supported or unlimited case +} + char* CgroupV2Subsystem::mem_swp_limit_val() { GET_CONTAINER_INFO_CPTR(cptr, _unified, "/memory.swap.max", "Memory and Swap Limit is: %s", "%1023s", mem_swp_limit_str, 1024); diff --git a/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp b/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp index b12b4ce6512..978c193c602 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp @@ -75,6 +75,7 @@ class CgroupV2Subsystem: public CgroupSubsystem { int cpu_period(); int cpu_shares(); jlong memory_and_swap_limit_in_bytes(); + jlong memory_and_swap_usage_in_bytes(); jlong memory_soft_limit_in_bytes(); jlong memory_usage_in_bytes(); jlong memory_max_usage_in_bytes(); diff --git a/src/hotspot/os/linux/osContainer_linux.cpp b/src/hotspot/os/linux/osContainer_linux.cpp index cd723228a75..fdb138c864f 100644 --- a/src/hotspot/os/linux/osContainer_linux.cpp +++ b/src/hotspot/os/linux/osContainer_linux.cpp @@ -77,6 +77,11 @@ jlong OSContainer::memory_and_swap_limit_in_bytes() { return cgroup_subsystem->memory_and_swap_limit_in_bytes(); } +jlong OSContainer::memory_and_swap_usage_in_bytes() { + assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); + return cgroup_subsystem->memory_and_swap_usage_in_bytes(); +} + jlong OSContainer::memory_soft_limit_in_bytes() { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); return cgroup_subsystem->memory_soft_limit_in_bytes(); diff --git a/src/hotspot/os/linux/osContainer_linux.hpp b/src/hotspot/os/linux/osContainer_linux.hpp index bb03ba3b005..fb837ec0bd6 100644 --- a/src/hotspot/os/linux/osContainer_linux.hpp +++ b/src/hotspot/os/linux/osContainer_linux.hpp @@ -52,6 +52,7 @@ class OSContainer: AllStatic { static jlong memory_limit_in_bytes(); static jlong memory_and_swap_limit_in_bytes(); + static jlong memory_and_swap_usage_in_bytes(); static jlong memory_soft_limit_in_bytes(); static jlong memory_usage_in_bytes(); static jlong memory_max_usage_in_bytes(); diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 119f98c6874..457bcc10ec1 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -304,18 +304,41 @@ jlong os::total_swap_space() { return (jlong)(si.totalswap * si.mem_unit); } -jlong os::free_swap_space() { - if (OSContainer::is_containerized()) { - // TODO add a good implementation +static jlong host_free_swap() { + struct sysinfo si; + int ret = sysinfo(&si); + if (ret != 0) { return -1; - } else { - struct sysinfo si; - int ret = sysinfo(&si); - if (ret != 0) { - return -1; - } - return (jlong)(si.freeswap * si.mem_unit); } + return (jlong)(si.freeswap * si.mem_unit); +} + +jlong os::free_swap_space() { + jlong host_free_swap_val = host_free_swap(); + if (OSContainer::is_containerized()) { + jlong mem_swap_limit = OSContainer::memory_and_swap_limit_in_bytes(); + jlong mem_limit = OSContainer::memory_limit_in_bytes(); + if (mem_swap_limit >= 0 && mem_limit >= 0) { + jlong delta_limit = mem_swap_limit - mem_limit; + if (delta_limit <= 0) { + return 0; + } + jlong mem_swap_usage = OSContainer::memory_and_swap_usage_in_bytes(); + jlong mem_usage = OSContainer::memory_usage_in_bytes(); + if (mem_swap_usage > 0 && mem_usage > 0) { + jlong delta_usage = mem_swap_usage - mem_usage; + if (delta_usage >= 0) { + jlong free_swap = delta_limit - delta_usage; + return free_swap >= 0 ? free_swap : 0; + } + } + } + // unlimited or not supported. Fall through to return host value + log_trace(os,container)("os::free_swap_space: container_swap_limit=" JLONG_FORMAT + " container_mem_limit=" JLONG_FORMAT " returning host value: " JLONG_FORMAT, + mem_swap_limit, mem_limit, host_free_swap_val); + } + return host_free_swap_val; } julong os::physical_memory() { diff --git a/test/hotspot/jtreg/containers/docker/TestJFREvents.java b/test/hotspot/jtreg/containers/docker/TestJFREvents.java index 534b580d1fa..19bbce1c547 100644 --- a/test/hotspot/jtreg/containers/docker/TestJFREvents.java +++ b/test/hotspot/jtreg/containers/docker/TestJFREvents.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -68,6 +68,10 @@ public class TestJFREvents { testMemory("500m", "" + 500*MB); testMemory("1g", "" + 1024*MB); + // see https://docs.docker.com/config/containers/resource_constraints/ + testSwapMemory("200m", "200m", "" + 0*MB, "" + 0*MB); + testSwapMemory("200m", "300m", "" + 100*MB, "" + 100*MB); + testProcessInfo(); testEnvironmentVariables(); @@ -211,6 +215,37 @@ public class TestJFREvents { } + private static void testSwapMemory(String memValueToSet, String swapValueToSet, String expectedTotalValue, String expectedFreeValue) throws Exception { + Common.logNewTestCase("Memory: --memory = " + memValueToSet + " --memory-swap = " + swapValueToSet); + OutputAnalyzer out = DockerTestUtils.dockerRunJava( + commonDockerOpts() + .addDockerOpts("--memory=" + memValueToSet) + .addDockerOpts("--memory-swap=" + swapValueToSet) + .addClassOptions("jdk.SwapSpace")); + out.shouldHaveExitValue(0) + .shouldContain("totalSize = " + expectedTotalValue) + .shouldContain("freeSize = "); + List ls = out.asLinesWithoutVMWarnings(); + for (String cur : ls) { + int idx = cur.indexOf("freeSize = "); + if (idx != -1) { + int startNbr = idx+11; + int endNbr = cur.indexOf(' ', startNbr); + if (endNbr == -1) endNbr = cur.length(); + String freeSizeStr = cur.substring(startNbr, endNbr); + long freeval = Long.parseLong(freeSizeStr); + long totalval = Long.parseLong(expectedTotalValue); + if (0 <= freeval && freeval <= totalval) { + System.out.println("Found freeSize value " + freeval + " is fine"); + } else { + System.out.println("Found freeSize value " + freeval + " is bad"); + throw new Exception("Found free size value is bad"); + } + } + } + } + + private static void testProcessInfo() throws Exception { Common.logNewTestCase("ProcessInfo"); DockerTestUtils.dockerRunJava(