8217338: [Containers] Improve systemd slice memory limit support

Use hierachical memory limit in addition to memory_limits_in_bytes

Reviewed-by: bobv, dholmes
This commit is contained in:
Severin Gehwolf 2019-03-12 10:43:27 +01:00
parent b1ae2d0bf1
commit 73d7e8f86c
4 changed files with 207 additions and 26 deletions

View file

@ -122,7 +122,25 @@ class CgroupSubsystem: CHeapObj<mtInternal> {
char *subsystem_path() { return _path; } char *subsystem_path() { return _path; }
}; };
CgroupSubsystem* memory = NULL; class CgroupMemorySubsystem: CgroupSubsystem {
friend class OSContainer;
private:
/* Some container runtimes set limits via cgroup
* hierarchy. If set to true consider also memory.stat
* file if everything else seems unlimited */
bool _uses_mem_hierarchy;
public:
CgroupMemorySubsystem(char *root, char *mountpoint) : CgroupSubsystem::CgroupSubsystem(root, mountpoint) {
_uses_mem_hierarchy = false;
}
bool is_hierarchical() { return _uses_mem_hierarchy; }
void set_hierarchical(bool value) { _uses_mem_hierarchy = value; }
};
CgroupMemorySubsystem* memory = NULL;
CgroupSubsystem* cpuset = NULL; CgroupSubsystem* cpuset = NULL;
CgroupSubsystem* cpu = NULL; CgroupSubsystem* cpu = NULL;
CgroupSubsystem* cpuacct = NULL; CgroupSubsystem* cpuacct = NULL;
@ -131,21 +149,24 @@ typedef char * cptr;
PRAGMA_DIAG_PUSH PRAGMA_DIAG_PUSH
PRAGMA_FORMAT_NONLITERAL_IGNORED PRAGMA_FORMAT_NONLITERAL_IGNORED
template <typename T> int subsystem_file_contents(CgroupSubsystem* c, template <typename T> int subsystem_file_line_contents(CgroupSubsystem* c,
const char *filename, const char *filename,
const char *matchline,
const char *scan_fmt, const char *scan_fmt,
T returnval) { T returnval) {
FILE *fp = NULL; FILE *fp = NULL;
char *p; char *p;
char file[MAXPATHLEN+1]; char file[MAXPATHLEN+1];
char buf[MAXPATHLEN+1]; char buf[MAXPATHLEN+1];
char discard[MAXPATHLEN+1];
bool found_match = false;
if (c == NULL) { if (c == NULL) {
log_debug(os, container)("subsystem_file_contents: CgroupSubsytem* is NULL"); log_debug(os, container)("subsystem_file_line_contents: CgroupSubsytem* is NULL");
return OSCONTAINER_ERROR; return OSCONTAINER_ERROR;
} }
if (c->subsystem_path() == NULL) { if (c->subsystem_path() == NULL) {
log_debug(os, container)("subsystem_file_contents: subsystem path is NULL"); log_debug(os, container)("subsystem_file_line_contents: subsystem path is NULL");
return OSCONTAINER_ERROR; return OSCONTAINER_ERROR;
} }
@ -160,16 +181,32 @@ template <typename T> int subsystem_file_contents(CgroupSubsystem* c,
log_trace(os, container)("Path to %s is %s", filename, file); log_trace(os, container)("Path to %s is %s", filename, file);
fp = fopen(file, "r"); fp = fopen(file, "r");
if (fp != NULL) { if (fp != NULL) {
p = fgets(buf, MAXPATHLEN, fp); int err = 0;
if (p != NULL) { while ((p = fgets(buf, MAXPATHLEN, fp)) != NULL) {
int matched = sscanf(p, scan_fmt, returnval); found_match = false;
if (matched == 1) { if (matchline == NULL) {
// single-line file case
int matched = sscanf(p, scan_fmt, returnval);
found_match = (matched == 1);
} else {
// multi-line file case
if (strstr(p, matchline) != NULL) {
// discard matchline string prefix
int matched = sscanf(p, scan_fmt, discard, returnval);
found_match = (matched == 2);
} else {
continue; // substring not found
}
}
if (found_match) {
fclose(fp); fclose(fp);
return 0; return 0;
} else { } else {
err = 1;
log_debug(os, container)("Type %s not found in file %s", scan_fmt, file); log_debug(os, container)("Type %s not found in file %s", scan_fmt, file);
} }
} else { }
if (err == 0) {
log_debug(os, container)("Empty file %s", file); log_debug(os, container)("Empty file %s", file);
} }
} else { } else {
@ -186,10 +223,11 @@ PRAGMA_DIAG_POP
return_type variable; \ return_type variable; \
{ \ { \
int err; \ int err; \
err = subsystem_file_contents(subsystem, \ err = subsystem_file_line_contents(subsystem, \
filename, \ filename, \
scan_fmt, \ NULL, \
&variable); \ scan_fmt, \
&variable); \
if (err != 0) \ if (err != 0) \
return (return_type) OSCONTAINER_ERROR; \ return (return_type) OSCONTAINER_ERROR; \
\ \
@ -201,16 +239,33 @@ PRAGMA_DIAG_POP
char variable[bufsize]; \ char variable[bufsize]; \
{ \ { \
int err; \ int err; \
err = subsystem_file_contents(subsystem, \ err = subsystem_file_line_contents(subsystem, \
filename, \ filename, \
scan_fmt, \ NULL, \
variable); \ scan_fmt, \
variable); \
if (err != 0) \ if (err != 0) \
return (return_type) NULL; \ return (return_type) NULL; \
\ \
log_trace(os, container)(logstring, variable); \ log_trace(os, container)(logstring, variable); \
} }
#define GET_CONTAINER_INFO_LINE(return_type, subsystem, filename, \
matchline, logstring, scan_fmt, variable) \
return_type variable; \
{ \
int err; \
err = subsystem_file_line_contents(subsystem, \
filename, \
matchline, \
scan_fmt, \
&variable); \
if (err != 0) \
return (return_type) OSCONTAINER_ERROR; \
\
log_trace(os, container)(logstring, variable); \
}
/* init /* init
* *
* Initialize the container support and determine if * Initialize the container support and determine if
@ -266,7 +321,7 @@ void OSContainer::init() {
} }
while ((token = strsep(&cptr, ",")) != NULL) { while ((token = strsep(&cptr, ",")) != NULL) {
if (strcmp(token, "memory") == 0) { if (strcmp(token, "memory") == 0) {
memory = new CgroupSubsystem(tmproot, tmpmount); memory = new CgroupMemorySubsystem(tmproot, tmpmount);
} else if (strcmp(token, "cpuset") == 0) { } else if (strcmp(token, "cpuset") == 0) {
cpuset = new CgroupSubsystem(tmproot, tmpmount); cpuset = new CgroupSubsystem(tmproot, tmpmount);
} else if (strcmp(token, "cpu") == 0) { } else if (strcmp(token, "cpu") == 0) {
@ -344,6 +399,10 @@ void OSContainer::init() {
while ((token = strsep(&controllers, ",")) != NULL) { while ((token = strsep(&controllers, ",")) != NULL) {
if (strcmp(token, "memory") == 0) { if (strcmp(token, "memory") == 0) {
memory->set_subsystem_path(base); memory->set_subsystem_path(base);
jlong hierarchy = uses_mem_hierarchy();
if (hierarchy > 0) {
memory->set_hierarchical(true);
}
} else if (strcmp(token, "cpuset") == 0) { } else if (strcmp(token, "cpuset") == 0) {
cpuset->set_subsystem_path(base); cpuset->set_subsystem_path(base);
} else if (strcmp(token, "cpu") == 0) { } else if (strcmp(token, "cpu") == 0) {
@ -360,6 +419,7 @@ void OSContainer::init() {
// command line arguments have been processed. // command line arguments have been processed.
if ((mem_limit = memory_limit_in_bytes()) > 0) { if ((mem_limit = memory_limit_in_bytes()) > 0) {
os::Linux::set_physical_memory(mem_limit); os::Linux::set_physical_memory(mem_limit);
log_info(os, container)("Memory Limit is: " JLONG_FORMAT, mem_limit);
} }
_is_containerized = true; _is_containerized = true;
@ -374,6 +434,21 @@ const char * OSContainer::container_type() {
} }
} }
/* uses_mem_hierarchy
*
* Return whether or not hierarchical cgroup accounting is being
* done.
*
* return:
* A number > 0 if true, or
* OSCONTAINER_ERROR for not supported
*/
jlong OSContainer::uses_mem_hierarchy() {
GET_CONTAINER_INFO(jlong, memory, "/memory.use_hierarchy",
"Use Hierarchy is: " JLONG_FORMAT, JLONG_FORMAT, use_hierarchy);
return use_hierarchy;
}
/* memory_limit_in_bytes /* memory_limit_in_bytes
* *
@ -389,7 +464,18 @@ jlong OSContainer::memory_limit_in_bytes() {
"Memory Limit is: " JULONG_FORMAT, JULONG_FORMAT, memlimit); "Memory Limit is: " JULONG_FORMAT, JULONG_FORMAT, memlimit);
if (memlimit >= _unlimited_memory) { if (memlimit >= _unlimited_memory) {
log_trace(os, container)("Memory Limit is: Unlimited"); log_trace(os, container)("Non-Hierarchical Memory Limit is: Unlimited");
if (memory->is_hierarchical()) {
const char* matchline = "hierarchical_memory_limit";
char* format = "%s " JULONG_FORMAT;
GET_CONTAINER_INFO_LINE(julong, memory, "/memory.stat", matchline,
"Hierarchical Memory Limit is: " JULONG_FORMAT, format, hier_memlimit)
if (hier_memlimit >= _unlimited_memory) {
log_trace(os, container)("Hierarchical Memory Limit is: Unlimited");
} else {
return (jlong)hier_memlimit;
}
}
return (jlong)-1; return (jlong)-1;
} }
else { else {
@ -401,7 +487,18 @@ jlong OSContainer::memory_and_swap_limit_in_bytes() {
GET_CONTAINER_INFO(julong, memory, "/memory.memsw.limit_in_bytes", GET_CONTAINER_INFO(julong, memory, "/memory.memsw.limit_in_bytes",
"Memory and Swap Limit is: " JULONG_FORMAT, JULONG_FORMAT, memswlimit); "Memory and Swap Limit is: " JULONG_FORMAT, JULONG_FORMAT, memswlimit);
if (memswlimit >= _unlimited_memory) { if (memswlimit >= _unlimited_memory) {
log_trace(os, container)("Memory and Swap Limit is: Unlimited"); log_trace(os, container)("Non-Hierarchical Memory and Swap Limit is: Unlimited");
if (memory->is_hierarchical()) {
const char* matchline = "hierarchical_memsw_limit";
char* format = "%s " JULONG_FORMAT;
GET_CONTAINER_INFO_LINE(julong, memory, "/memory.stat", matchline,
"Hierarchical Memory and Swap Limit is : " JULONG_FORMAT, format, hier_memlimit)
if (hier_memlimit >= _unlimited_memory) {
log_trace(os, container)("Hierarchical Memory and Swap Limit is: Unlimited");
} else {
return (jlong)hier_memlimit;
}
}
return (jlong)-1; return (jlong)-1;
} else { } else {
return (jlong)memswlimit; return (jlong)memswlimit;

View file

@ -42,6 +42,7 @@ class OSContainer: AllStatic {
static inline bool is_containerized(); static inline bool is_containerized();
static const char * container_type(); static const char * container_type();
static jlong uses_mem_hierarchy();
static jlong memory_limit_in_bytes(); static jlong memory_limit_in_bytes();
static jlong memory_and_swap_limit_in_bytes(); static jlong memory_and_swap_limit_in_bytes();
static jlong memory_soft_limit_in_bytes(); static jlong memory_soft_limit_in_bytes();

View file

@ -25,15 +25,16 @@
package jdk.internal.platform.cgroupv1; package jdk.internal.platform.cgroupv1;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.stream.Stream; import java.util.stream.Stream;
import jdk.internal.platform.cgroupv1.SubSystem.MemorySubSystem;
public class Metrics implements jdk.internal.platform.Metrics { public class Metrics implements jdk.internal.platform.Metrics {
private SubSystem memory; private MemorySubSystem memory;
private SubSystem cpu; private SubSystem cpu;
private SubSystem cpuacct; private SubSystem cpuacct;
private SubSystem cpuset; private SubSystem cpuset;
@ -133,7 +134,7 @@ public class Metrics implements jdk.internal.platform.Metrics {
for (String subsystemName: subsystemNames) { for (String subsystemName: subsystemNames) {
switch (subsystemName) { switch (subsystemName) {
case "memory": case "memory":
metric.setMemorySubSystem(new SubSystem(mountentry[3], mountentry[4])); metric.setMemorySubSystem(new MemorySubSystem(mountentry[3], mountentry[4]));
break; break;
case "cpuset": case "cpuset":
metric.setCpuSetSubSystem(new SubSystem(mountentry[3], mountentry[4])); metric.setCpuSetSubSystem(new SubSystem(mountentry[3], mountentry[4]));
@ -195,6 +196,11 @@ public class Metrics implements jdk.internal.platform.Metrics {
if (subsystem != null) { if (subsystem != null) {
subsystem.setPath(base); subsystem.setPath(base);
if (subsystem instanceof MemorySubSystem) {
MemorySubSystem memorySubSystem = (MemorySubSystem)subsystem;
boolean isHierarchial = getHierarchical(memorySubSystem);
memorySubSystem.setHierarchical(isHierarchial);
}
metric.setActiveSubSystems(); metric.setActiveSubSystems();
} }
if (subsystem2 != null) { if (subsystem2 != null) {
@ -203,6 +209,11 @@ public class Metrics implements jdk.internal.platform.Metrics {
} }
private static boolean getHierarchical(MemorySubSystem subsystem) {
long hierarchical = SubSystem.getLongValue(subsystem, "memory.use_hierarchy");
return hierarchical > 0;
}
private void setActiveSubSystems() { private void setActiveSubSystems() {
activeSubSystems = true; activeSubSystems = true;
} }
@ -211,7 +222,7 @@ public class Metrics implements jdk.internal.platform.Metrics {
return activeSubSystems; return activeSubSystems;
} }
private void setMemorySubSystem(SubSystem memory) { private void setMemorySubSystem(MemorySubSystem memory) {
this.memory = memory; this.memory = memory;
} }
@ -366,9 +377,29 @@ public class Metrics implements jdk.internal.platform.Metrics {
public long getMemoryLimit() { public long getMemoryLimit() {
long retval = SubSystem.getLongValue(memory, "memory.limit_in_bytes"); long retval = SubSystem.getLongValue(memory, "memory.limit_in_bytes");
if (retval > unlimited_minimum) {
if (memory.isHierarchical()) {
// memory.limit_in_bytes returned unlimited, attempt
// hierarchical memory limit
String match = "hierarchical_memory_limit";
retval = SubSystem.getLongValueMatchingLine(memory,
"memory.stat",
match,
Metrics::convertHierachicalLimitLine);
}
}
return retval > unlimited_minimum ? -1L : retval; return retval > unlimited_minimum ? -1L : retval;
} }
public static long convertHierachicalLimitLine(String line) {
String[] tokens = line.split("\\s");
if (tokens.length == 2) {
String strVal = tokens[1];
return SubSystem.convertStringToLong(strVal);
}
return unlimited_minimum + 1; // unlimited
}
public long getMemoryMaxUsage() { public long getMemoryMaxUsage() {
return SubSystem.getLongValue(memory, "memory.max_usage_in_bytes"); return SubSystem.getLongValue(memory, "memory.max_usage_in_bytes");
} }
@ -417,6 +448,17 @@ public class Metrics implements jdk.internal.platform.Metrics {
public long getMemoryAndSwapLimit() { public long getMemoryAndSwapLimit() {
long retval = SubSystem.getLongValue(memory, "memory.memsw.limit_in_bytes"); long retval = SubSystem.getLongValue(memory, "memory.memsw.limit_in_bytes");
if (retval > unlimited_minimum) {
if (memory.isHierarchical()) {
// memory.memsw.limit_in_bytes returned unlimited, attempt
// hierarchical memory limit
String match = "hierarchical_memsw_limit";
retval = SubSystem.getLongValueMatchingLine(memory,
"memory.stat",
match,
Metrics::convertHierachicalLimitLine);
}
}
return retval > unlimited_minimum ? -1L : retval; return retval > unlimited_minimum ? -1L : retval;
} }

View file

@ -29,10 +29,11 @@ import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
public class SubSystem { public class SubSystem {
@ -99,10 +100,32 @@ public class SubSystem {
} }
public static long getLongValueMatchingLine(SubSystem subsystem,
String param,
String match,
Function<String, Long> conversion) {
long retval = Metrics.unlimited_minimum + 1; // default unlimited
try {
List<String> lines = Files.readAllLines(Paths.get(subsystem.path(), param));
for (String line: lines) {
if (line.contains(match)) {
retval = conversion.apply(line);
break;
}
}
} catch (IOException e) {
// Ignore. Default is unlimited.
}
return retval;
}
public static long getLongValue(SubSystem subsystem, String parm) { public static long getLongValue(SubSystem subsystem, String parm) {
String strval = getStringValue(subsystem, parm); String strval = getStringValue(subsystem, parm);
long retval = 0; return convertStringToLong(strval);
}
public static long convertStringToLong(String strval) {
long retval = 0;
if (strval == null) return 0L; if (strval == null) return 0L;
try { try {
@ -215,4 +238,22 @@ public class SubSystem {
return ints; return ints;
} }
public static class MemorySubSystem extends SubSystem {
private boolean hierarchical;
public MemorySubSystem(String root, String mountPoint) {
super(root, mountPoint);
}
boolean isHierarchical() {
return hierarchical;
}
void setHierarchical(boolean hierarchical) {
this.hierarchical = hierarchical;
}
}
} }