/* * Copyright (c) 2018, 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 * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */package sun.jvmstat;import java.io.*;import java.util.*;import java.util.regex.*;import java.nio.file.Path;import java.nio.file.Paths;import java.nio.file.Files;import java.nio.charset.*;/* * Linux specific implementation of the PlatformSupport routines * providing process ID and temp directory support for host and * cgroup container processes. */public class PlatformSupportImpl extends PlatformSupport { private static final String containerTmpPath = "/root" + getTemporaryDirectory(); private static final String pidPatternStr = "^[0-9]+$"; /* * Return the temporary directories that the VM uses for the attach * and perf data files. This function returns the traditional * /tmp directory in addition to paths within the /proc file system * allowing access to container tmp directories such as /proc/{pid}/root/tmp. * * It is important that this directory is well-known and the * same for all VM instances. It cannot be affected by configuration * variables such as java.io.tmpdir. * * Implementation Details: * * Java processes that run in docker containers are typically running * under cgroups with separate pid namespaces which means that pids * within the container are different that the pid which is visible * from the host. The container pids typically start with 1 and * increase. The java process running in the container will use these * pids when creating the hsperfdata files. In order to locate java * processes that are running in containers, we take advantage of * the Linux proc file system which maps the containers tmp directory * to the hosts under /proc/{hostpid}/root/tmp. We use the /proc status * file /proc/{hostpid}/status to determine the containers pid and * then access the hsperfdata file. The status file contains an * entry "NSPid:" which shows the mapping from the hostpid to the * containers pid. * * Example: * * NSPid: 24345 11 * * In this example process 24345 is visible from the host, * is running under the PID namespace and has a container specific * pid of 11. * * The search for Java processes is done by first looking in the * traditional /tmp for host process hsperfdata files and then * the search will container in every /proc/{pid}/root/tmp directory. * There are of course added complications to this search that * need to be taken into account. * * 1. duplication of tmp directories * * /proc/{hostpid}/root/tmp directories exist for many processes * that are running on a Linux kernel that has cgroups enabled even * if they are not running in a container. To avoid this duplication, * we compare the inode of the /proc tmp directories to /tmp and * skip these duplicated directories. * * 2. Containerized processes without PID namespaces being enabled. * * If a container is running a Java process without namespaces being * enabled, an hsperfdata file will only be located at * /proc/{hostpid}/root/tmp/{hostpid}. This is handled by * checking the last component in the path for both the hostpid * and potential namespacepids (if one exists). */ public List<String> getTemporaryDirectories(int pid) { FilenameFilter pidFilter; Matcher pidMatcher; Pattern pidPattern = Pattern.compile(pidPatternStr); long tmpInode = 0; File procdir = new File("/proc"); if (pid != 0) { pidPattern = Pattern.compile(Integer.toString(pid)); } else { pidPattern = Pattern.compile(pidPatternStr); } pidMatcher = pidPattern.matcher(""); // Add the default temporary directory first List<String> v = new ArrayList<>(); v.add(getTemporaryDirectory()); try { File f = new File(getTemporaryDirectory()); tmpInode = (Long)Files.getAttribute(f.toPath(), "unix:ino"); } catch (IOException e) {} pidFilter = new FilenameFilter() { public boolean accept(File dir, String name) { if (!dir.isDirectory()) return false; pidMatcher.reset(name); return pidMatcher.matches(); } }; File[] dirs = procdir.listFiles(pidFilter); // Add all unique /proc/{pid}/root/tmp dirs that are not mapped to /tmp for (File dir : dirs) { String containerTmpDir = dir.getAbsolutePath() + containerTmpPath; File containerFile = new File(containerTmpDir); try { long procInode = (Long)Files.getAttribute(containerFile.toPath(), "unix:ino"); if (containerFile.exists() && containerFile.isDirectory() && containerFile.canRead() && procInode != tmpInode) { v.add(containerTmpDir); } } catch (IOException e) {} } return v; } /* * Extract either the host PID or the NameSpace PID * from a file path. * * File path should be in 1 of these 2 forms: * * /proc/{pid}/root/tmp/hsperfdata_{user}/{nspid} * or * /tmp/hsperfdata_{user}/{pid} * * In either case we want to return {pid} and NOT {nspid} * * This function filters out host pids which do not have * associated hsperfdata files. This is due to the fact that * getTemporaryDirectories will return /proc/{pid}/root/tmp * paths for all container processes whether they are java * processes or not causing duplicate matches. */ public int getLocalVmId(File file) throws NumberFormatException { String p = file.getAbsolutePath(); String s[] = p.split("\\/"); // Determine if this file is from a container if (s.length == 7 && s[1].equals("proc")) { int hostpid = Integer.parseInt(s[2]); int nspid = Integer.parseInt(s[6]); if (nspid == hostpid || nspid == getNamespaceVmId(hostpid)) { return hostpid; } else { return -1; } } else { return Integer.parseInt(file.getName()); } } /* * Return the inner most namespaced PID if there is one, * otherwise return the original PID. */ public int getNamespaceVmId(int pid) { // Assuming a real procfs sits beneath, reading this doesn't block // nor will it consume a lot of memory. Path statusPath = Paths.get("/proc", Integer.toString(pid), "status"); if (Files.notExists(statusPath)) { return pid; // Likely a bad pid, but this is properly handled later. } try { for (String line : Files.readAllLines(statusPath, StandardCharsets.UTF_8)) { String[] parts = line.split(":"); if (parts.length == 2 && parts[0].trim().equals("NSpid")) { parts = parts[1].trim().split("\\s+"); // The last entry represents the PID the JVM "thinks" it is. // Even in non-namespaced pids these entries should be // valid. You could refer to it as the inner most pid. int ns_pid = Integer.parseInt(parts[parts.length - 1]); return ns_pid; } } // Old kernels may not have NSpid field (i.e. 3.10). // Fallback to original pid in the event we cannot deduce. return pid; } catch (NumberFormatException | IOException x) { return pid; } }}