diff --git a/src/java.base/linux/classes/jdk/internal/platform/CgroupInfo.java b/src/java.base/linux/classes/jdk/internal/platform/CgroupInfo.java index 60b7f3fc3427d..e06705e22c790 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/CgroupInfo.java +++ b/src/java.base/linux/classes/jdk/internal/platform/CgroupInfo.java @@ -107,7 +107,7 @@ public void setCgroupPath(String cgroupPath) { * */ static CgroupInfo fromCgroupsLine(String line) { - String[] tokens = line.split("\\s+"); + String[] tokens = line.split("\t"); if (tokens.length != 4) { return null; } diff --git a/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemController.java b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemController.java index 38be8628fb48e..791b0391b5e8b 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemController.java +++ b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemController.java @@ -26,15 +26,12 @@ package jdk.internal.platform; import java.io.IOException; -import java.io.UncheckedIOException; import java.math.BigInteger; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.function.Function; -import java.util.stream.Stream; /** * Cgroup version agnostic controller logic @@ -161,16 +158,18 @@ public static double getDoubleValue(CgroupSubsystemController controller, String public static long getLongEntry(CgroupSubsystemController controller, String param, String entryname, long defaultRetval) { if (controller == null) return defaultRetval; - try (Stream lines = CgroupUtil.readFilePrivileged(Paths.get(controller.path(), param))) { - - Optional result = lines.map(line -> line.split(" ")) - .filter(line -> (line.length == 2 && - line[0].equals(entryname))) - .map(line -> line[1]) - .findFirst(); + try { + long result = defaultRetval; + for (String line : CgroupUtil.readAllLinesPrivileged(Paths.get(controller.path(), param))) { + String[] tokens = line.split(" "); + if (tokens.length == 2 && tokens[0].equals(entryname)) { + result = Long.parseLong(tokens[1]); + break; + } + } - return result.isPresent() ? Long.parseLong(result.get()) : defaultRetval; - } catch (UncheckedIOException | IOException e) { + return result; + } catch (IOException e) { return defaultRetval; } } diff --git a/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemFactory.java b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemFactory.java index 0a6d9958d11e0..337c693ec9412 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemFactory.java +++ b/src/java.base/linux/classes/jdk/internal/platform/CgroupSubsystemFactory.java @@ -26,7 +26,6 @@ package jdk.internal.platform; import java.io.IOException; -import java.io.UncheckedIOException; import java.lang.System.Logger; import java.lang.System.Logger.Level; import java.nio.file.Path; @@ -38,9 +37,6 @@ import java.util.Optional; import java.util.Objects; import java.util.function.Consumer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Stream; import jdk.internal.platform.cgroupv1.CgroupV1Subsystem; import jdk.internal.platform.cgroupv2.CgroupV2Subsystem; @@ -54,36 +50,11 @@ public class CgroupSubsystemFactory { private static final String MEMORY_CTRL = "memory"; private static final String PIDS_CTRL = "pids"; - /* - * From https://www.kernel.org/doc/Documentation/filesystems/proc.txt - * - * 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue - * (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) - * - * (1) mount ID: unique identifier of the mount (may be reused after umount) - * (2) parent ID: ID of parent (or of self for the top of the mount tree) - * (3) major:minor: value of st_dev for files on filesystem - * (4) root: root of the mount within the filesystem - * (5) mount point: mount point relative to the process's root - * (6) mount options: per mount options - * (7) optional fields: zero or more fields of the form "tag[:value]" - * (8) separator: marks the end of the optional fields - * (9) filesystem type: name of filesystem of the form "type[.subtype]" - * (10) mount source: filesystem specific information or "none" - * (11) super options: per super block options - */ - private static final Pattern MOUNTINFO_PATTERN = Pattern.compile( - "^[^\\s]+\\s+[^\\s]+\\s+[^\\s]+\\s+" + // (1), (2), (3) - "([^\\s]+)\\s+([^\\s]+)\\s+" + // (4), (5) - group 1, 2: root, mount point - "[^-]+-\\s+" + // (6), (7), (8) - "([^\\s]+)\\s+" + // (9) - group 3: filesystem type - ".*$"); // (10), (11) - static CgroupMetrics create() { Optional optResult = null; try { optResult = determineType("/proc/self/mountinfo", "/proc/cgroups", "/proc/self/cgroup"); - } catch (IOException | UncheckedIOException e) { + } catch (IOException e) { return null; } return create(optResult); @@ -104,8 +75,7 @@ public static CgroupMetrics create(Optional optResult) { // not ready to deal with that on a per-controller basis. Return no metrics // in that case if (result.isAnyCgroupV1Controllers() && result.isAnyCgroupV2Controllers()) { - Logger logger = System.getLogger("jdk.internal.platform"); - logger.log(Level.DEBUG, "Mixed cgroupv1 and cgroupv2 not supported. Metrics disabled."); + warn("Mixed cgroupv1 and cgroupv2 not supported. Metrics disabled."); return null; } @@ -121,6 +91,11 @@ public static CgroupMetrics create(Optional optResult) { } } + private static void warn(String msg) { + Logger logger = System.getLogger("jdk.internal.platform"); + logger.log(Level.DEBUG, msg); + } + /* * Determine the type of the cgroup system (v1 - legacy or hybrid - or, v2 - unified) * based on three files: @@ -196,16 +171,15 @@ public static Optional determineType(String mountInfo, // See: // setCgroupV1Path() for the action run for cgroups v1 systems // setCgroupV2Path() for the action run for cgroups v2 systems - try (Stream selfCgroupLines = - CgroupUtil.readFilePrivileged(Paths.get(selfCgroup))) { - Consumer action = (tokens -> setCgroupV1Path(infos, tokens)); - if (isCgroupsV2) { - action = (tokens -> setCgroupV2Path(infos, tokens)); - } + Consumer action = (tokens -> setCgroupV1Path(infos, tokens)); + if (isCgroupsV2) { + action = (tokens -> setCgroupV2Path(infos, tokens)); + } + for (String line : CgroupUtil.readAllLinesPrivileged(Paths.get(selfCgroup))) { // The limit value of 3 is because /proc/self/cgroup contains three // colon-separated tokens per line. The last token, cgroup path, might // contain a ':'. - selfCgroupLines.map(line -> line.split(":", 3)).forEach(action); + action.accept(line.split(":", 3)); } CgroupTypeResult result = new CgroupTypeResult(isCgroupsV2, @@ -281,8 +255,8 @@ private static void setCgroupV1Path(Map infos, /** * Amends cgroup infos with mount path and mount root. The passed in * 'mntInfoLine' represents a single line in, for example, - * /proc/self/mountinfo. Each line is matched with MOUNTINFO_PATTERN - * (see above), so as to extract the relevant tokens from the line. + * /proc/self/mountinfo. Each line is parsed with {@link MountInfo#parse}, + * so as to extract the relevant tokens from the line. * * Host example cgroups v1: * @@ -303,13 +277,13 @@ private static void setCgroupV1Path(Map infos, private static boolean amendCgroupInfos(String mntInfoLine, Map infos, boolean isCgroupsV2) { - Matcher lineMatcher = MOUNTINFO_PATTERN.matcher(mntInfoLine.trim()); + MountInfo mountInfo = MountInfo.parse(mntInfoLine); boolean cgroupv1ControllerFound = false; boolean cgroupv2ControllerFound = false; - if (lineMatcher.matches()) { - String mountRoot = lineMatcher.group(1); - String mountPath = lineMatcher.group(2); - String fsType = lineMatcher.group(3); + if (mountInfo != null) { + String mountRoot = mountInfo.mountRoot; + String mountPath = mountInfo.mountPath; + String fsType = mountInfo.fsType; if (fsType.equals("cgroup")) { Path p = Paths.get(mountPath); String[] controllerNames = p.getFileName().toString().split(","); @@ -345,6 +319,59 @@ private static boolean amendCgroupInfos(String mntInfoLine, return cgroupv1ControllerFound || cgroupv2ControllerFound; } + private record MountInfo(String mountRoot, String mountPath, String fsType) { + /* + * From https://www.kernel.org/doc/Documentation/filesystems/proc.txt + * + * 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue + * (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) + * + * (1) mount ID: unique identifier of the mount (may be reused after umount) + * (2) parent ID: ID of parent (or of self for the top of the mount tree) + * (3) major:minor: value of st_dev for files on filesystem + * (4) root: root of the mount within the filesystem + * (5) mount point: mount point relative to the process's root + * (6) mount options: per mount options + * (7) optional fields: zero or more fields of the form "tag[:value]" + * (8) separator: marks the end of the optional fields + * (9) filesystem type: name of filesystem of the form "type[.subtype]" + * (10) mount source: filesystem specific information or "none" + * (11) super options: per super block options + */ + static MountInfo parse(String line) { + String mountRoot = null; + String mountPath = null; + + int separatorOrdinal = -1; + // loop over space-separated tokens + for (int tOrdinal = 1, tStart = 0, tEnd = line.indexOf(' '); tEnd != -1; tOrdinal++, tStart = tEnd + 1, tEnd = line.indexOf(' ', tStart)) { + if (tStart == tEnd) { + break; // unexpected empty token + } + switch (tOrdinal) { + case 1, 2, 3, 6 -> {} // skip token + case 4 -> mountRoot = line.substring(tStart, tEnd); // root token + case 5 -> mountPath = line.substring(tStart, tEnd); // mount point token + default -> { + assert tOrdinal >= 7; + if (separatorOrdinal == -1) { + // check if we found a separator token + if (tEnd - tStart == 1 && line.charAt(tStart) == '-') { + separatorOrdinal = tOrdinal; + } + continue; // skip token + } + if (tOrdinal == separatorOrdinal + 1) { // filesystem type token + String fsType = line.substring(tStart, tEnd); + return new MountInfo(mountRoot, mountPath, fsType); + } + } + } + } + return null; // parsing failed + } + } + private static void setMountPoints(CgroupInfo info, String mountPath, String mountRoot) { if (info.getMountPoint() != null) { // On some systems duplicate controllers get mounted in addition to diff --git a/src/java.base/linux/classes/jdk/internal/platform/CgroupUtil.java b/src/java.base/linux/classes/jdk/internal/platform/CgroupUtil.java index dbe8a85b2b2f5..46056a5fb58ab 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/CgroupUtil.java +++ b/src/java.base/linux/classes/jdk/internal/platform/CgroupUtil.java @@ -26,32 +26,19 @@ package jdk.internal.platform; import java.io.BufferedReader; +import java.io.FileReader; import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; import java.util.List; -import java.util.stream.Stream; public final class CgroupUtil { - @SuppressWarnings("removal") - public static Stream readFilePrivileged(Path path) throws IOException { - try { - PrivilegedExceptionAction> pea = () -> Files.lines(path); - return AccessController.doPrivileged(pea); - } catch (PrivilegedActionException e) { - unwrapIOExceptionAndRethrow(e); - throw new InternalError(e.getCause()); - } catch (UncheckedIOException e) { - throw e.getCause(); - } - } - static void unwrapIOExceptionAndRethrow(PrivilegedActionException pae) throws IOException { Throwable x = pae.getCause(); if (x instanceof IOException) @@ -64,7 +51,7 @@ static void unwrapIOExceptionAndRethrow(PrivilegedActionException pae) throws IO static String readStringValue(CgroupSubsystemController controller, String param) throws IOException { PrivilegedExceptionAction pea = () -> - Files.newBufferedReader(Paths.get(controller.path(), param)); + new BufferedReader(new FileReader(Paths.get(controller.path(), param).toString(), StandardCharsets.UTF_8)); try (@SuppressWarnings("removal") BufferedReader bufferedReader = AccessController.doPrivileged(pea)) { String line = bufferedReader.readLine(); @@ -72,21 +59,26 @@ static String readStringValue(CgroupSubsystemController controller, String param } catch (PrivilegedActionException e) { unwrapIOExceptionAndRethrow(e); throw new InternalError(e.getCause()); - } catch (UncheckedIOException e) { - throw e.getCause(); } } @SuppressWarnings("removal") public static List readAllLinesPrivileged(Path path) throws IOException { try { - PrivilegedExceptionAction> pea = () -> Files.readAllLines(path); + PrivilegedExceptionAction> pea = () -> { + try (BufferedReader bufferedReader = new BufferedReader(new FileReader(path.toString(), StandardCharsets.UTF_8))) { + String line; + List lines = new ArrayList<>(); + while ((line = bufferedReader.readLine()) != null) { + lines.add(line); + } + return lines; + } + }; return AccessController.doPrivileged(pea); } catch (PrivilegedActionException e) { unwrapIOExceptionAndRethrow(e); throw new InternalError(e.getCause()); - } catch (UncheckedIOException e) { - throw e.getCause(); } } } diff --git a/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1SubsystemController.java b/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1SubsystemController.java index 051b4da5f78d8..21495bb58cfb4 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1SubsystemController.java +++ b/src/java.base/linux/classes/jdk/internal/platform/cgroupv1/CgroupV1SubsystemController.java @@ -107,7 +107,7 @@ public static long getLongValueMatchingLine(CgroupSubsystemController controller } public static long convertHierachicalLimitLine(String line) { - String[] tokens = line.split("\\s"); + String[] tokens = line.split(" "); if (tokens.length == 2) { String strVal = tokens[1]; return CgroupV1SubsystemController.convertStringToLong(strVal); diff --git a/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java index ddb4d8e271832..88b45dfd02cf9 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java +++ b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java @@ -26,11 +26,9 @@ package jdk.internal.platform.cgroupv2; import java.io.IOException; -import java.io.UncheckedIOException; import java.nio.file.Paths; import java.util.concurrent.TimeUnit; import java.util.function.Function; -import java.util.stream.Collectors; import jdk.internal.platform.CgroupInfo; import jdk.internal.platform.CgroupSubsystem; @@ -143,7 +141,7 @@ private long getFromCpuMax(int tokenIdx) { return CgroupSubsystem.LONG_RETVAL_UNLIMITED; } // $MAX $PERIOD - String[] tokens = cpuMaxRaw.split("\\s+"); + String[] tokens = cpuMaxRaw.split(" "); if (tokens.length != 2) { return CgroupSubsystem.LONG_RETVAL_UNLIMITED; } @@ -329,10 +327,12 @@ public long getBlkIOServiced() { private long sumTokensIOStat(Function mapFunc) { try { - return CgroupUtil.readFilePrivileged(Paths.get(unified.path(), "io.stat")) - .map(mapFunc) - .collect(Collectors.summingLong(e -> e)); - } catch (UncheckedIOException | IOException e) { + long sum = 0L; + for (String line : CgroupUtil.readAllLinesPrivileged(Paths.get(unified.path(), "io.stat"))) { + sum += mapFunc.apply(line); + } + return sum; + } catch (IOException e) { return CgroupSubsystem.LONG_RETVAL_UNLIMITED; } } @@ -359,7 +359,7 @@ private static Long ioStatLineToLong(String line, String[] matchNames) { if (line == null || EMPTY_STR.equals(line)) { return Long.valueOf(0); } - String[] tokens = line.split("\\s+"); + String[] tokens = line.split(" "); long retval = 0; for (String t: tokens) { String[] valKeys = t.split("=");