--- old/src/hotspot/os/linux/perfMemory_linux.cpp 2018-01-11 13:32:12.442002981 -0500 +++ new/src/hotspot/os/linux/perfMemory_linux.cpp 2018-01-11 13:32:11.575952669 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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 @@ -147,13 +147,25 @@ // which is always a local file system and is sometimes a RAM based file // system. + // return the user specific temporary directory name. // +// If containerized process, get dirname of +// /proc/{vmid}/root/tmp/{PERFDATA_NAME_user} +// otherwise /tmp/{PERFDATA_NAME_user} +// // the caller is expected to free the allocated memory. // -static char* get_user_tmp_dir(const char* user) { +static char* get_user_tmp_dir(const char* user, int vmid, int nspid) { + char tmpdir[MAXPATHLEN]; + + if (nspid == -1) { + strncpy(tmpdir, os::get_temp_directory(), MAXPATHLEN); + } + else { + jio_snprintf(tmpdir, MAXPATHLEN, "/proc/%d/root%s", vmid, os::get_temp_directory()); + } - const char* tmpdir = os::get_temp_directory(); const char* perfdir = PERFDATA_NAME; size_t nbytes = strlen(tmpdir) + strlen(perfdir) + strlen(user) + 3; char* dirname = NEW_C_HEAP_ARRAY(char, nbytes, mtInternal); @@ -502,7 +514,10 @@ // // the caller is expected to free the allocated memory. // -static char* get_user_name_slow(int vmid, TRAPS) { +// If nspid != -1, look in /proc/{vmid}/root/tmp for directories +// containing nspid, otherwise just look for vmid in /tmp +// +static char* get_user_name_slow(int vmid, int nspid, TRAPS) { // short circuit the directory search if the process doesn't even exist. if (kill(vmid, 0) == OS_ERR) { @@ -518,8 +533,17 @@ // directory search char* oldest_user = NULL; time_t oldest_ctime = 0; - - const char* tmpdirname = os::get_temp_directory(); + char tmpdirname[MAXPATHLEN]; + int searchpid; + + if (nspid == -1) { + strncpy(tmpdirname, os::get_temp_directory(), MAXPATHLEN); + searchpid = vmid; + } + else { + jio_snprintf(tmpdirname, MAXPATHLEN, "/proc/%d/root%s", vmid, os::get_temp_directory()); + searchpid = nspid; + } // open the temp directory DIR* tmpdirp = os::opendir(tmpdirname); @@ -530,7 +554,7 @@ } // for each entry in the directory that matches the pattern hsperfdata_*, - // open the directory and check if the file for the given vmid exists. + // open the directory and check if the file for the given vmid or nspid exists. // The file with the expected name and the latest creation date is used // to determine the user name for the process id. // @@ -575,7 +599,7 @@ errno = 0; while ((udentry = os::readdir(subdirp, (struct dirent *)udbuf)) != NULL) { - if (filename_to_pid(udentry->d_name) == vmid) { + if (filename_to_pid(udentry->d_name) == searchpid) { struct stat statbuf; int result; @@ -626,10 +650,50 @@ return(oldest_user); } +// Determine if the vmid is the parent pid +// for a child in a PID namespace. +// return the namespace pid if so, otherwise -1 +static int get_namespace_pid(int vmid) { + char fname[64]; + int retpid = -1; + + snprintf(fname, sizeof(fname), "/proc/%d/status", vmid); + FILE *fp = fopen(fname, "r"); + int pid, nspid; + int ret; + if (fp) { + while (!feof(fp)) { + ret = fscanf(fp, "NSpid: %d %d", &pid, &nspid); + if (ret == 1) { + break; + } + if (ret == 2) { + retpid = nspid; + break; + } + for (;;) { + int ch = fgetc(fp); + if (ch == EOF || ch == (int)'\n') break; + } + } + } + fclose(fp); + return retpid; +} + // return the name of the user that owns the JVM indicated by the given vmid. // -static char* get_user_name(int vmid, TRAPS) { - return get_user_name_slow(vmid, THREAD); +static char* get_user_name(int vmid, int *nspid, TRAPS) { + char *result = get_user_name_slow(vmid, *nspid, THREAD); + + // If we are examining a container process without PID namespaces enabled + // we need to use /proc/{pid}/root/tmp to find hsperfdata files. + if (result == NULL) { + result = get_user_name_slow(vmid, vmid, THREAD); + // Enable nspid logic going forward + if (result != NULL) *nspid = vmid; + } + return result; } // return the file name of the backing store file for the named @@ -637,13 +701,15 @@ // // the caller is expected to free the allocated memory. // -static char* get_sharedmem_filename(const char* dirname, int vmid) { +static char* get_sharedmem_filename(const char* dirname, int vmid, int nspid) { + + int pid = (nspid == -1) ? vmid : nspid; // add 2 for the file separator and a null terminator. size_t nbytes = strlen(dirname) + UINT_CHARS + 2; char* name = NEW_C_HEAP_ARRAY(char, nbytes, mtInternal); - snprintf(name, nbytes, "%s/%d", dirname, vmid); + snprintf(name, nbytes, "%s/%d", dirname, pid); return name; } @@ -940,8 +1006,8 @@ if (user_name == NULL) return NULL; - char* dirname = get_user_tmp_dir(user_name); - char* filename = get_sharedmem_filename(dirname, vmid); + char* dirname = get_user_tmp_dir(user_name, vmid, -1); + char* filename = get_sharedmem_filename(dirname, vmid, -1); // get the short filename char* short_filename = strrchr(filename, '/'); @@ -1088,8 +1154,11 @@ "Illegal access mode"); } + // determine if vmid is for a containerized process + int nspid = get_namespace_pid(vmid); + if (user == NULL || strlen(user) == 0) { - luser = get_user_name(vmid, CHECK); + luser = get_user_name(vmid, &nspid, CHECK); } else { luser = user; @@ -1100,7 +1169,7 @@ "Could not map vmid to user Name"); } - char* dirname = get_user_tmp_dir(luser); + char* dirname = get_user_tmp_dir(luser, vmid, nspid); // since we don't follow symbolic links when creating the backing // store file, we don't follow them when attaching either. @@ -1114,7 +1183,7 @@ "Process not found"); } - char* filename = get_sharedmem_filename(dirname, vmid); + char* filename = get_sharedmem_filename(dirname, vmid, nspid); // copy heap memory to resource memory. the open_sharedmem_file // method below need to use the filename, but could throw an --- old/src/java.base/share/classes/jdk/internal/vm/VMSupport.java 2018-01-11 13:32:14.666132188 -0500 +++ new/src/java.base/share/classes/jdk/internal/vm/VMSupport.java 2018-01-11 13:32:13.798081760 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.File; import java.util.Properties; import java.util.Set; import java.util.jar.JarFile; @@ -107,4 +108,34 @@ * variables such as java.io.tmpdir. */ public static native String getVMTemporaryDirectory(); + + /* + * Return a list of the temporary directories that the VM uses for + * the attach and perf data files. + * + * This method returns the traditional host temp directory but also + * includes a list of temp directories used by containers. + * + * It is important that the returned directories are well-known and the + * same for all VM instances. It cannot be affected by configuration + * variables such as java.io.tmpdir. + */ + public static String[] getVMTemporaryDirectories(int vmid) { + return VMSupportImpl.getTemporaryDirectories(vmid); + } + + /* + * Extract the host PID from a file path. + */ + public static int getLocalVmId(File file) throws NumberFormatException { + return VMSupportImpl.getLocalVmId(file); + } + + /* + * Return the inner most namespaced PID if there is one, + * otherwise return the original PID. + */ + public static int getNamespaceVmId(int pid) { + return VMSupportImpl.getNamespaceVmId(pid); + } } --- old/src/java.base/share/classes/module-info.java 2018-01-11 13:32:16.916262905 -0500 +++ new/src/java.base/share/classes/module-info.java 2018-01-11 13:32:16.027211257 -0500 @@ -204,7 +204,8 @@ jdk.unsupported; exports jdk.internal.vm to jdk.management.agent, - jdk.internal.jvmstat; + jdk.internal.jvmstat, + jdk.attach; exports jdk.internal.vm.annotation to jdk.unsupported, jdk.internal.vm.ci, --- old/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java 2018-01-11 13:32:19.148392577 -0500 +++ new/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java 2018-01-11 13:32:18.269341510 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -36,6 +36,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.Files; +import jdk.internal.vm.VMSupport; /* * Linux implementation of HotSpotVirtualMachine @@ -68,7 +69,7 @@ } // Try to resolve to the "inner most" pid namespace - int ns_pid = getNamespacePid(pid); + int ns_pid = VMSupport.getNamespaceVmId(pid); // Find the socket file. If not found then we attempt to start the // attach mechanism in the target VM by sending it a QUIT signal. @@ -326,41 +327,6 @@ write(fd, b, 0, 1); } - - // Return the inner most namespaced PID if there is one, - // otherwise return the original PID. - private int getNamespacePid(int pid) throws AttachNotSupportedException, IOException { - // Assuming a real procfs sits beneath, reading this doesn't block - // nor will it consume a lot of memory. - String statusFile = "/proc/" + pid + "/status"; - File f = new File(statusFile); - if (!f.exists()) { - return pid; // Likely a bad pid, but this is properly handled later. - } - - Path statusPath = Paths.get(statusFile); - - 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) { - throw new AttachNotSupportedException("Unable to parse namespace"); - } - } - - //-- native methods static native void sendQuitToChildrenOf(int pid) throws IOException; --- old/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/protocol/local/LocalVmManager.java 2018-01-11 13:32:21.373521841 -0500 +++ new/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/protocol/local/LocalVmManager.java 2018-01-11 13:32:20.489470484 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 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 @@ -45,7 +45,7 @@ */ public class LocalVmManager { private String userName; // user name for monitored jvm - private File tmpdir; + private String[] tmpdirs; private Pattern userPattern; private Matcher userMatcher; private FilenameFilter userFilter; @@ -77,8 +77,9 @@ public LocalVmManager(String user) { this.userName = user; + if (userName == null) { - tmpdir = new File(PerfDataFile.getTempDirectory()); + tmpdirs = PerfDataFile.getTempDirectories(null, 0); userPattern = Pattern.compile(PerfDataFile.userDirNamePattern); userMatcher = userPattern.matcher(""); @@ -89,7 +90,7 @@ } }; } else { - tmpdir = new File(PerfDataFile.getTempDirectory(userName)); + tmpdirs = PerfDataFile.getTempDirectories(userName, 0); } filePattern = Pattern.compile(PerfDataFile.fileNamePattern); @@ -134,65 +135,72 @@ */ Set jvmSet = new HashSet(); - if (! tmpdir.isDirectory()) { - return jvmSet; - } + for (int t = 0; t < tmpdirs.length; t++) { + File tmpdir = new File(tmpdirs[t]); + if (! tmpdir.isDirectory()) { + continue; + } - if (userName == null) { - /* - * get a list of all of the user temporary directories and - * iterate over the list to find any files within those directories. - */ - File[] dirs = tmpdir.listFiles(userFilter); - - for (int i = 0 ; i < dirs.length; i ++) { - if (!dirs[i].isDirectory()) { - continue; + if (userName == null) { + /* + * get a list of all of the user temporary directories and + * iterate over the list to find any files within those directories. + */ + File[] dirs = tmpdir.listFiles(userFilter); + for (int i = 0 ; i < dirs.length; i ++) { + if (!dirs[i].isDirectory()) { + continue; + } + + // get a list of files from the directory + File[] files = dirs[i].listFiles(fileFilter); + if (files != null) { + for (int j = 0; j < files.length; j++) { + if (files[j].isFile() && files[j].canRead()) { + int vmid = PerfDataFile.getLocalVmId(files[j]); + if (vmid != -1) { + jvmSet.add(vmid); + } + } + } + } } + } else { + /* + * Check if the user directory can be accessed. Any of these + * conditions may have asynchronously changed between subsequent + * calls to this method. + */ - // get a list of files from the directory - File[] files = dirs[i].listFiles(fileFilter); + // get the list of files from the specified user directory + File[] files = tmpdir.listFiles(fileFilter); if (files != null) { for (int j = 0; j < files.length; j++) { if (files[j].isFile() && files[j].canRead()) { - jvmSet.add( - PerfDataFile.getLocalVmId(files[j])); + int vmid = PerfDataFile.getLocalVmId(files[j]); + if (vmid != -1) { + jvmSet.add(vmid); + } } } } } - } else { - /* - * Check if the user directory can be accessed. Any of these - * conditions may have asynchronously changed between subsequent - * calls to this method. - */ - - // get the list of files from the specified user directory - File[] files = tmpdir.listFiles(fileFilter); + // look for any 1.4.1 files + File[] files = tmpdir.listFiles(tmpFileFilter); if (files != null) { for (int j = 0; j < files.length; j++) { if (files[j].isFile() && files[j].canRead()) { - jvmSet.add( - PerfDataFile.getLocalVmId(files[j])); + int vmid = PerfDataFile.getLocalVmId(files[j]); + if (vmid != -1) { + jvmSet.add(vmid); + } } } } - } - // look for any 1.4.1 files - File[] files = tmpdir.listFiles(tmpFileFilter); - if (files != null) { - for (int j = 0; j < files.length; j++) { - if (files[j].isFile() && files[j].canRead()) { - jvmSet.add( - PerfDataFile.getLocalVmId(files[j])); - } - } } - return jvmSet; } } --- old/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/protocol/local/PerfDataFile.java 2018-01-11 13:32:23.919669810 -0500 +++ new/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/protocol/local/PerfDataFile.java 2018-01-11 13:32:23.038618587 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 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 @@ -94,7 +94,7 @@ * @return File - a File object to the backing store file for the named * shared memory region of the target JVM. * @see java.io.File - * @see #getTempDirectory() + * @see #getTempDirectories() */ public static File getFile(int lvmid) { if (lvmid == 0) { @@ -107,56 +107,66 @@ return null; } - /* - * iterate over all files in all directories in tmpDirName that - * match the file name patterns. - */ - File tmpDir = new File(tmpDirName); - String[] files = tmpDir.list(new FilenameFilter() { - public boolean accept(File dir, String name) { - if (!name.startsWith(dirNamePrefix)) { - return false; + String tmpDirs[] = getTempDirectories(null, lvmid); + File newest = null; + + for (int t = 0; t < tmpDirs.length; t++) { + /* + * iterate over all files in all directories in tmpDirName that + * match the file name patterns. + */ + File tmpDir = new File(tmpDirs[t]); + String[] files = tmpDir.list(new FilenameFilter() { + public boolean accept(File dir, String name) { + if (!name.startsWith(dirNamePrefix)) { + return false; + } + File candidate = new File(dir, name); + return ((candidate.isDirectory() || candidate.isFile()) + && candidate.canRead()); } - File candidate = new File(dir, name); - return ((candidate.isDirectory() || candidate.isFile()) - && candidate.canRead()); - } - }); + }); - long newestTime = 0; - File newest = null; + long newestTime = 0; - for (int i = 0; i < files.length; i++) { - File f = new File(tmpDirName + files[i]); - File candidate = null; - - if (f.exists() && f.isDirectory()) { - /* - * found a directory matching the name patterns. This - * is a 1.4.2 hsperfdata_ directory. Check for - * file named in that directory - */ - String name = Integer.toString(lvmid); - candidate = new File(f.getName(), name); - - } else if (f.exists() && f.isFile()) { - /* - * found a file matching the name patterns. This - * is a 1.4.1 hsperfdata_ file. - */ - candidate = f; + for (int i = 0; i < files.length; i++) { + File f = new File(tmpDirs[t] + files[i]); + File candidate = null; + + if (f.exists() && f.isDirectory()) { + /* + * found a directory matching the name patterns. This + * is a 1.4.2 hsperfdata_ directory. Check for + * file named in that directory + */ + String name = f.getAbsolutePath() + File.separator + + Integer.toString(lvmid); + candidate = new File(name); + // Try NameSpace Id if Host Id doesn't exist. + if (!candidate.exists()) { + name = f.getAbsolutePath() + File.separator + + Integer.toString(VMSupport.getNamespaceVmId(lvmid)); + candidate = new File(name); + } + } else if (f.exists() && f.isFile()) { + /* + * found a file matching the name patterns. This + * is a 1.4.1 hsperfdata_ file. + */ + candidate = f; - } else { - // unexpected - let conditional below filter this one out - candidate = f; - } + } else { + // unexpected - let conditional below filter this one out + candidate = f; + } - if (candidate.exists() && candidate.isFile() - && candidate.canRead()) { - long modTime = candidate.lastModified(); - if (modTime >= newestTime) { - newestTime = modTime; - newest = candidate; + if (candidate.exists() && candidate.isFile() + && candidate.canRead()) { + long modTime = candidate.lastModified(); + if (modTime >= newestTime) { + newestTime = modTime; + newest = candidate; + } } } } @@ -177,7 +187,7 @@ * @return File - a File object to the backing store file for the named * shared memory region of the target JVM. * @see java.io.File - * @see #getTempDirectory() + * @see #getTempDirectories() */ public static File getFile(String user, int lvmid) { if (lvmid == 0) { @@ -191,11 +201,22 @@ } // first try for 1.4.2 and later JVMs - String basename = getTempDirectory(user) + Integer.toString(lvmid); - File f = new File(basename); + String tmpDirs[] = getTempDirectories(user, lvmid); + String basename; + File f; - if (f.exists() && f.isFile() && f.canRead()) { - return f; + for (int t = 0; t < tmpDirs.length; t++) { + basename = tmpDirs[t] + Integer.toString(lvmid); + f = new File(basename); + if (f.exists() && f.isFile() && f.canRead()) { + return f; + } + // Try NameSpace Id if Host Id doesn't exist. + basename = tmpDirs[t] + Integer.toString(VMSupport.getNamespaceVmId(lvmid)); + f = new File(basename); + if (f.exists() && f.isFile() && f.canRead()) { + return f; + } } // No hit on 1.4.2 JVMs, try 1.4.1 files @@ -236,7 +257,7 @@ public static int getLocalVmId(File file) { try { // try 1.4.2 and later format first - return Integer.parseInt(file.getName()); + return(VMSupport.getLocalVmId(file)); } catch (NumberFormatException e) { } // now try the 1.4.1 format @@ -286,6 +307,28 @@ return tmpDirName + dirNamePrefix + user + File.separator; } + /** + * Return the names of the temporary directories being searched for + * HotSpot PerfData backing store files. + *

+ * This method returns the traditional host temp directory but also + * includes a list of temp directories used by containers. + * + * @return String[] - A String array of temporary directories to search. + */ + public static String[] getTempDirectories(String userName, int vmid) { + String[] list = VMSupport.getVMTemporaryDirectories(vmid); + if (userName == null) { + return list; + } + + String[] nameList = new String[list.length]; + for (int i = 0; i < list.length; i++) { + nameList[i] = new String(list[i] + dirNamePrefix + userName + File.separator); + } + return nameList; + } + static { /* * For this to work, the target VM and this code need to use --- /dev/null 2017-12-22 23:59:38.986515101 -0500 +++ new/src/java.base/unix/classes/jdk/internal/vm/VMSupportImpl.java 2018-01-11 13:32:25.515762603 -0500 @@ -0,0 +1,250 @@ +/* + * 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 jdk.internal.vm; + +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.*; + +/* + * Operating System specific version of support class used by + * JVMTI and VM attach mechanism. + */ +public class VMSupportImpl { + public static final String pidPatternStr = "^[0-9]+$"; + public static final String containerTmpPath; + public static final String tmpDirName; + + static { + /* + * For this to work, the target VM and this code need to use + * the same directory. Instead of guessing which directory the + * VM is using, we will ask. + */ + String tmpdir = VMSupport.getVMTemporaryDirectory(); + + /* + * Assure that the string returned has a trailing File.separator + * character. This check was added because the Linux implementation + * changed such that the java.io.tmpdir string no longer terminates + * with a File.separator character. + */ + if (tmpdir.lastIndexOf(File.separator) != (tmpdir.length()-1)) { + tmpdir = tmpdir + File.separator; + } + tmpDirName = tmpdir; + containerTmpPath = "/root" + tmpDirName; + } + + /* + * 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/*/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 static 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 v = new ArrayList<>(); + v.add(tmpDirName); + + try { + File f = new File(tmpDirName); + 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 (int i = 0; i < dirs.length; i++) { + String containerTmpDir = dirs[i].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.toArray(new String[v.size()]); + } + + + /* + * 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 static 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 static int getNamespaceVmId(int pid) { + // Assuming a real procfs sits beneath, reading this doesn't block + // nor will it consume a lot of memory. + String statusFile = "/proc/" + pid + "/status"; + File f = new File(statusFile); + if (!f.exists()) { + return pid; // Likely a bad pid, but this is properly handled later. + } + + Path statusPath = Paths.get(statusFile); + + 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; + } + } +} --- /dev/null 2017-12-22 23:59:38.986515101 -0500 +++ new/src/java.base/windows/classes/jdk/internal/vm/VMSupportImpl.java 2018-01-11 13:32:27.755892840 -0500 @@ -0,0 +1,90 @@ +/* + * 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 jdk.internal.vm; + +import java.io.*; +import java.util.*; + +/* + * Operating System specific version of support class used by + * JVMTI and VM attach mechanism. + */ +public class VMSupportImpl { + public static final String tmpDirName; + + static { + /* + * For this to work, the target VM and this code need to use + * the same directory. Instead of guessing which directory the + * VM is using, we will ask. + */ + String tmpdir = VMSupport.getVMTemporaryDirectory(); + + /* + * Assure that the string returned has a trailing File.separator + * character. This check was added because the Linux implementation + * changed such that the java.io.tmpdir string no longer terminates + * with a File.separator character. + */ + if (tmpdir.lastIndexOf(File.separator) != (tmpdir.length()-1)) { + tmpdir = tmpdir + File.separator; + } + tmpDirName = tmpdir; + } + + /* + * Return a list of the temporary directories that the VM uses + * for the attach and perf data files. This function returns + * the traditional temp directory in addition to any paths + * accessible by the host which map to temp directories used + * by containers. The container functionality is only currently + * supported on Linux platforms. + * + * 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. + */ + public static String[] getTemporaryDirectories(int vmid) { + // Add the default temporary directory only + List v = new ArrayList<>(); + v.add(tmpDirName); + return v.toArray(new String[v.size()]); + } + + /* + * Extract the host PID from a file path. + */ + public static int getLocalVmId(File file) throws NumberFormatException { + return Integer.parseInt(file.getName()); + } + + /* + * Return the inner most namespaced PID if there is one, + * otherwise return the original PID. + */ + public static int getNamespaceVmId(int pid) { + return pid; + } +}