Path: blob/master/src/jdk.internal.jvmstat/linux/classes/sun/jvmstat/PlatformSupportImpl.java
41152 views
/*1* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.jvmstat;2627import java.io.*;28import java.util.*;29import java.util.regex.*;30import java.nio.file.Path;31import java.nio.file.Paths;32import java.nio.file.Files;33import java.nio.charset.*;3435/*36* Linux specific implementation of the PlatformSupport routines37* providing process ID and temp directory support for host and38* cgroup container processes.39*/40public class PlatformSupportImpl extends PlatformSupport {41private static final String containerTmpPath = "/root" + getTemporaryDirectory();42private static final String pidPatternStr = "^[0-9]+$";4344/*45* Return the temporary directories that the VM uses for the attach46* and perf data files. This function returns the traditional47* /tmp directory in addition to paths within the /proc file system48* allowing access to container tmp directories such as /proc/{pid}/root/tmp.49*50* It is important that this directory is well-known and the51* same for all VM instances. It cannot be affected by configuration52* variables such as java.io.tmpdir.53*54* Implementation Details:55*56* Java processes that run in docker containers are typically running57* under cgroups with separate pid namespaces which means that pids58* within the container are different that the pid which is visible59* from the host. The container pids typically start with 1 and60* increase. The java process running in the container will use these61* pids when creating the hsperfdata files. In order to locate java62* processes that are running in containers, we take advantage of63* the Linux proc file system which maps the containers tmp directory64* to the hosts under /proc/{hostpid}/root/tmp. We use the /proc status65* file /proc/{hostpid}/status to determine the containers pid and66* then access the hsperfdata file. The status file contains an67* entry "NSPid:" which shows the mapping from the hostpid to the68* containers pid.69*70* Example:71*72* NSPid: 24345 1173*74* In this example process 24345 is visible from the host,75* is running under the PID namespace and has a container specific76* pid of 11.77*78* The search for Java processes is done by first looking in the79* traditional /tmp for host process hsperfdata files and then80* the search will container in every /proc/{pid}/root/tmp directory.81* There are of course added complications to this search that82* need to be taken into account.83*84* 1. duplication of tmp directories85*86* /proc/{hostpid}/root/tmp directories exist for many processes87* that are running on a Linux kernel that has cgroups enabled even88* if they are not running in a container. To avoid this duplication,89* we compare the inode of the /proc tmp directories to /tmp and90* skip these duplicated directories.91*92* 2. Containerized processes without PID namespaces being enabled.93*94* If a container is running a Java process without namespaces being95* enabled, an hsperfdata file will only be located at96* /proc/{hostpid}/root/tmp/{hostpid}. This is handled by97* checking the last component in the path for both the hostpid98* and potential namespacepids (if one exists).99*/100public List<String> getTemporaryDirectories(int pid) {101FilenameFilter pidFilter;102Matcher pidMatcher;103Pattern pidPattern = Pattern.compile(pidPatternStr);104long tmpInode = 0;105106File procdir = new File("/proc");107108if (pid != 0) {109pidPattern = Pattern.compile(Integer.toString(pid));110}111else {112pidPattern = Pattern.compile(pidPatternStr);113}114pidMatcher = pidPattern.matcher("");115116// Add the default temporary directory first117List<String> v = new ArrayList<>();118v.add(getTemporaryDirectory());119120try {121File f = new File(getTemporaryDirectory());122tmpInode = (Long)Files.getAttribute(f.toPath(), "unix:ino");123}124catch (IOException e) {}125126pidFilter = new FilenameFilter() {127public boolean accept(File dir, String name) {128if (!dir.isDirectory())129return false;130pidMatcher.reset(name);131return pidMatcher.matches();132}133};134135File[] dirs = procdir.listFiles(pidFilter);136137// Add all unique /proc/{pid}/root/tmp dirs that are not mapped to /tmp138for (File dir : dirs) {139String containerTmpDir = dir.getAbsolutePath() + containerTmpPath;140File containerFile = new File(containerTmpDir);141142try {143long procInode = (Long)Files.getAttribute(containerFile.toPath(), "unix:ino");144if (containerFile.exists() && containerFile.isDirectory() &&145containerFile.canRead() && procInode != tmpInode) {146v.add(containerTmpDir);147}148}149catch (IOException e) {}150}151152return v;153}154155156/*157* Extract either the host PID or the NameSpace PID158* from a file path.159*160* File path should be in 1 of these 2 forms:161*162* /proc/{pid}/root/tmp/hsperfdata_{user}/{nspid}163* or164* /tmp/hsperfdata_{user}/{pid}165*166* In either case we want to return {pid} and NOT {nspid}167*168* This function filters out host pids which do not have169* associated hsperfdata files. This is due to the fact that170* getTemporaryDirectories will return /proc/{pid}/root/tmp171* paths for all container processes whether they are java172* processes or not causing duplicate matches.173*/174public int getLocalVmId(File file) throws NumberFormatException {175String p = file.getAbsolutePath();176String s[] = p.split("\\/");177178// Determine if this file is from a container179if (s.length == 7 && s[1].equals("proc")) {180int hostpid = Integer.parseInt(s[2]);181int nspid = Integer.parseInt(s[6]);182if (nspid == hostpid || nspid == getNamespaceVmId(hostpid)) {183return hostpid;184}185else {186return -1;187}188}189else {190return Integer.parseInt(file.getName());191}192}193194195/*196* Return the inner most namespaced PID if there is one,197* otherwise return the original PID.198*/199public int getNamespaceVmId(int pid) {200// Assuming a real procfs sits beneath, reading this doesn't block201// nor will it consume a lot of memory.202Path statusPath = Paths.get("/proc", Integer.toString(pid), "status");203if (Files.notExists(statusPath)) {204return pid; // Likely a bad pid, but this is properly handled later.205}206207try {208for (String line : Files.readAllLines(statusPath, StandardCharsets.UTF_8)) {209String[] parts = line.split(":");210if (parts.length == 2 && parts[0].trim().equals("NSpid")) {211parts = parts[1].trim().split("\\s+");212// The last entry represents the PID the JVM "thinks" it is.213// Even in non-namespaced pids these entries should be214// valid. You could refer to it as the inner most pid.215int ns_pid = Integer.parseInt(parts[parts.length - 1]);216return ns_pid;217}218}219// Old kernels may not have NSpid field (i.e. 3.10).220// Fallback to original pid in the event we cannot deduce.221return pid;222} catch (NumberFormatException | IOException x) {223return pid;224}225}226}227228229