|
1 /* |
|
2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package sun.jvmstat; |
|
27 |
|
28 import java.io.*; |
|
29 import java.util.*; |
|
30 import java.util.regex.*; |
|
31 import java.nio.file.Path; |
|
32 import java.nio.file.Paths; |
|
33 import java.nio.file.Files; |
|
34 import java.nio.charset.*; |
|
35 |
|
36 /* |
|
37 * Linux specific implementation of the PlatformSupport routines |
|
38 * providing process ID and temp directory support for host and |
|
39 * cgroup container processes. |
|
40 */ |
|
41 public class PlatformSupportImpl extends PlatformSupport { |
|
42 private static final String containerTmpPath = "/root" + getTemporaryDirectory(); |
|
43 private static final String pidPatternStr = "^[0-9]+$"; |
|
44 |
|
45 /* |
|
46 * Return the temporary directories that the VM uses for the attach |
|
47 * and perf data files. This function returns the traditional |
|
48 * /tmp directory in addition to paths within the /proc file system |
|
49 * allowing access to container tmp directories such as /proc/{pid}/root/tmp. |
|
50 * |
|
51 * It is important that this directory is well-known and the |
|
52 * same for all VM instances. It cannot be affected by configuration |
|
53 * variables such as java.io.tmpdir. |
|
54 * |
|
55 * Implementation Details: |
|
56 * |
|
57 * Java processes that run in docker containers are typically running |
|
58 * under cgroups with separate pid namespaces which means that pids |
|
59 * within the container are different that the pid which is visible |
|
60 * from the host. The container pids typically start with 1 and |
|
61 * increase. The java process running in the container will use these |
|
62 * pids when creating the hsperfdata files. In order to locate java |
|
63 * processes that are running in containers, we take advantage of |
|
64 * the Linux proc file system which maps the containers tmp directory |
|
65 * to the hosts under /proc/{hostpid}/root/tmp. We use the /proc status |
|
66 * file /proc/{hostpid}/status to determine the containers pid and |
|
67 * then access the hsperfdata file. The status file contains an |
|
68 * entry "NSPid:" which shows the mapping from the hostpid to the |
|
69 * containers pid. |
|
70 * |
|
71 * Example: |
|
72 * |
|
73 * NSPid: 24345 11 |
|
74 * |
|
75 * In this example process 24345 is visible from the host, |
|
76 * is running under the PID namespace and has a container specific |
|
77 * pid of 11. |
|
78 * |
|
79 * The search for Java processes is done by first looking in the |
|
80 * traditional /tmp for host process hsperfdata files and then |
|
81 * the search will container in every /proc/{pid}/root/tmp directory. |
|
82 * There are of course added complications to this search that |
|
83 * need to be taken into account. |
|
84 * |
|
85 * 1. duplication of tmp directories |
|
86 * |
|
87 * /proc/{hostpid}/root/tmp directories exist for many processes |
|
88 * that are running on a Linux kernel that has cgroups enabled even |
|
89 * if they are not running in a container. To avoid this duplication, |
|
90 * we compare the inode of the /proc tmp directories to /tmp and |
|
91 * skip these duplicated directories. |
|
92 * |
|
93 * 2. Containerized processes without PID namespaces being enabled. |
|
94 * |
|
95 * If a container is running a Java process without namespaces being |
|
96 * enabled, an hsperfdata file will only be located at |
|
97 * /proc/{hostpid}/root/tmp/{hostpid}. This is handled by |
|
98 * checking the last component in the path for both the hostpid |
|
99 * and potential namespacepids (if one exists). |
|
100 */ |
|
101 public List<String> getTemporaryDirectories(int pid) { |
|
102 FilenameFilter pidFilter; |
|
103 Matcher pidMatcher; |
|
104 Pattern pidPattern = Pattern.compile(pidPatternStr); |
|
105 long tmpInode = 0; |
|
106 |
|
107 File procdir = new File("/proc"); |
|
108 |
|
109 if (pid != 0) { |
|
110 pidPattern = Pattern.compile(Integer.toString(pid)); |
|
111 } |
|
112 else { |
|
113 pidPattern = Pattern.compile(pidPatternStr); |
|
114 } |
|
115 pidMatcher = pidPattern.matcher(""); |
|
116 |
|
117 // Add the default temporary directory first |
|
118 List<String> v = new ArrayList<>(); |
|
119 v.add(getTemporaryDirectory()); |
|
120 |
|
121 try { |
|
122 File f = new File(getTemporaryDirectory()); |
|
123 tmpInode = (Long)Files.getAttribute(f.toPath(), "unix:ino"); |
|
124 } |
|
125 catch (IOException e) {} |
|
126 |
|
127 pidFilter = new FilenameFilter() { |
|
128 public boolean accept(File dir, String name) { |
|
129 if (!dir.isDirectory()) |
|
130 return false; |
|
131 pidMatcher.reset(name); |
|
132 return pidMatcher.matches(); |
|
133 } |
|
134 }; |
|
135 |
|
136 File[] dirs = procdir.listFiles(pidFilter); |
|
137 |
|
138 // Add all unique /proc/{pid}/root/tmp dirs that are not mapped to /tmp |
|
139 for (File dir : dirs) { |
|
140 String containerTmpDir = dir.getAbsolutePath() + containerTmpPath; |
|
141 File containerFile = new File(containerTmpDir); |
|
142 |
|
143 try { |
|
144 long procInode = (Long)Files.getAttribute(containerFile.toPath(), "unix:ino"); |
|
145 if (containerFile.exists() && containerFile.isDirectory() && |
|
146 containerFile.canRead() && procInode != tmpInode) { |
|
147 v.add(containerTmpDir); |
|
148 } |
|
149 } |
|
150 catch (IOException e) {} |
|
151 } |
|
152 |
|
153 return v; |
|
154 } |
|
155 |
|
156 |
|
157 /* |
|
158 * Extract either the host PID or the NameSpace PID |
|
159 * from a file path. |
|
160 * |
|
161 * File path should be in 1 of these 2 forms: |
|
162 * |
|
163 * /proc/{pid}/root/tmp/hsperfdata_{user}/{nspid} |
|
164 * or |
|
165 * /tmp/hsperfdata_{user}/{pid} |
|
166 * |
|
167 * In either case we want to return {pid} and NOT {nspid} |
|
168 * |
|
169 * This function filters out host pids which do not have |
|
170 * associated hsperfdata files. This is due to the fact that |
|
171 * getTemporaryDirectories will return /proc/{pid}/root/tmp |
|
172 * paths for all container processes whether they are java |
|
173 * processes or not causing duplicate matches. |
|
174 */ |
|
175 public int getLocalVmId(File file) throws NumberFormatException { |
|
176 String p = file.getAbsolutePath(); |
|
177 String s[] = p.split("\\/"); |
|
178 |
|
179 // Determine if this file is from a container |
|
180 if (s.length == 7 && s[1].equals("proc")) { |
|
181 int hostpid = Integer.parseInt(s[2]); |
|
182 int nspid = Integer.parseInt(s[6]); |
|
183 if (nspid == hostpid || nspid == getNamespaceVmId(hostpid)) { |
|
184 return hostpid; |
|
185 } |
|
186 else { |
|
187 return -1; |
|
188 } |
|
189 } |
|
190 else { |
|
191 return Integer.parseInt(file.getName()); |
|
192 } |
|
193 } |
|
194 |
|
195 |
|
196 /* |
|
197 * Return the inner most namespaced PID if there is one, |
|
198 * otherwise return the original PID. |
|
199 */ |
|
200 public int getNamespaceVmId(int pid) { |
|
201 // Assuming a real procfs sits beneath, reading this doesn't block |
|
202 // nor will it consume a lot of memory. |
|
203 Path statusPath = Paths.get("/proc", Integer.toString(pid), "status"); |
|
204 if (Files.notExists(statusPath)) { |
|
205 return pid; // Likely a bad pid, but this is properly handled later. |
|
206 } |
|
207 |
|
208 try { |
|
209 for (String line : Files.readAllLines(statusPath, StandardCharsets.UTF_8)) { |
|
210 String[] parts = line.split(":"); |
|
211 if (parts.length == 2 && parts[0].trim().equals("NSpid")) { |
|
212 parts = parts[1].trim().split("\\s+"); |
|
213 // The last entry represents the PID the JVM "thinks" it is. |
|
214 // Even in non-namespaced pids these entries should be |
|
215 // valid. You could refer to it as the inner most pid. |
|
216 int ns_pid = Integer.parseInt(parts[parts.length - 1]); |
|
217 return ns_pid; |
|
218 } |
|
219 } |
|
220 // Old kernels may not have NSpid field (i.e. 3.10). |
|
221 // Fallback to original pid in the event we cannot deduce. |
|
222 return pid; |
|
223 } catch (NumberFormatException | IOException x) { |
|
224 return pid; |
|
225 } |
|
226 } |
|
227 } |