src/jdk.internal.jvmstat/linux/classes/sun/jvmstat/PlatformSupportImpl.java
changeset 48801 099f18e10557
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.internal.jvmstat/linux/classes/sun/jvmstat/PlatformSupportImpl.java	Mon Jan 22 15:05:03 2018 -0500
@@ -0,0 +1,227 @@
+/*
+ * 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;
+        }
+    }
+}