Java tutorial
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.yarn.util; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.util.Shell; import org.apache.hadoop.util.Shell.ShellCommandExecutor; import org.apache.hadoop.util.StringUtils; /** * Works based on procfs and smaps. Works only on Linux. */ @InterfaceAudience.Private @InterfaceStability.Unstable public class SmapsBasedProcessTree extends ResourceCalculatorProcessTree { static final Log LOG = LogFactory.getLog(SmapsBasedProcessTree.class); private static final String PROCFS = "/proc/"; enum MEM_INFO { SIZE("Size"), RSS("Rss"), PSS("Pss"), SHARED_CLEAN("Shared_Clean"), SHARED_DIRTY( "Shared_Dirty"), PRIVATE_CLEAN("Private_Clean"), PRIVATE_DIRTY( "Private_Dirty"), REFERENCED("Referenced"), ANONYMOUS("Anonymous"), ANON_HUGE_PAGES( "AnonHugePages"), SWAP("swap"), KERNEL_PAGE_SIZE( "kernelPageSize"), MMU_PAGE_SIZE("mmuPageSize"), INVALID("invalid"); private String name; private MEM_INFO(String name) { this.name = name; } public static MEM_INFO getMemInfoByName(String name) { for (MEM_INFO info : MEM_INFO.values()) { if (info.name.trim().equals(name.trim())) { return info; } } return INVALID; } } public static final String SMAPS = "smaps"; private static final String KB = "kB"; private static final String READ_ONLY_WITH_SHARED_PERMISSION = "r--s"; private static final String READ_EXECUTE_WITH_SHARED_PERMISSION = "r-xs"; private static final Pattern PROCFS_STAT_FILE_FORMAT = Pattern .compile("^([0-9-]+)\\s([^\\s]+)\\s[^\\s]\\s([0-9-]+)\\s([0-9-]+)\\s([0-9-]+)\\s" + "([0-9-]+\\s){7}([0-9]+)\\s([0-9]+)\\s([0-9-]+\\s){7}([0-9]+)\\s([0-9]+)" + "(\\s[0-9-]+){15}"); private static final Pattern ADDRESS_PATTERN = Pattern .compile("([[a-f]|(0-9)]*)-([[a-f]|(0-9)]*)(\\s)*([rxwps\\-]*)"); private static final Pattern MEM_INFO_PATTERN = Pattern.compile("(^[A-Z].*):[\t ]+(.*)"); public static final String PROCFS_STAT_FILE = "stat"; public static final String PROCFS_CMDLINE_FILE = "cmdline"; public static final long PAGE_SIZE; public static final long JIFFY_LENGTH_IN_MILLIS; // in millisecond static { long jiffiesPerSecond = -1; long pageSize = -1; try { if (Shell.LINUX) { ShellCommandExecutor shellExecutorClk = new ShellCommandExecutor( new String[] { "getconf", "CLK_TCK" }); shellExecutorClk.execute(); jiffiesPerSecond = Long.parseLong(shellExecutorClk.getOutput().replace("\n", "")); ShellCommandExecutor shellExecutorPage = new ShellCommandExecutor( new String[] { "getconf", "PAGESIZE" }); shellExecutorPage.execute(); pageSize = Long.parseLong(shellExecutorPage.getOutput().replace("\n", "")); } } catch (IOException e) { LOG.error(StringUtils.stringifyException(e)); } finally { JIFFY_LENGTH_IN_MILLIS = jiffiesPerSecond != -1 ? Math.round(1000D / jiffiesPerSecond) : -1; PAGE_SIZE = pageSize; } } // to enable testing, using this variable which can be configured // to a test directory. private String procfsDir; static private String deadPid = "-1"; private String pid = deadPid; static private Pattern numberPattern = Pattern.compile("[1-9][0-9]*"); private Long cpuTime = 0L; protected Map<String, ProcessInfo> processTree = new HashMap<String, ProcessInfo>(); protected Map<String, ProcessMemInfo> processSMAPTree = new HashMap<String, ProcessMemInfo>(); public SmapsBasedProcessTree(String pid) { this(pid, PROCFS); } /** * Build a new process tree rooted at the pid. * * This method is provided mainly for testing purposes, where the root of the * proc file system can be adjusted. * * @param pid * root of the process tree * @param procfsDir * the root of a proc file system - only used for testing. */ public SmapsBasedProcessTree(String pid, String procfsDir) { super(pid); this.pid = getValidPID(pid); this.procfsDir = procfsDir; } /** * Checks if the ProcfsBasedProcessTree is available on this system. * * @return true if ProcfsBasedProcessTree is available. False otherwise. */ public static boolean isAvailable() { try { if (!Shell.LINUX) { LOG.info("ProcfsBasedProcessTree currently is supported only on " + "Linux."); return false; } } catch (SecurityException se) { LOG.warn("Failed to get Operating System name. " + se); return false; } return true; } /** * Update process-tree with latest state. If the root-process is not alive, * tree will be empty. * */ @Override public void updateProcessTree() { if (!pid.equals(deadPid)) { // Get the list of processes List<String> processList = getProcessList(); Map<String, ProcessInfo> allProcessInfo = new HashMap<String, ProcessInfo>(); // cache the processTree to get the age for processes Map<String, ProcessInfo> oldProcs = new HashMap<String, ProcessInfo>(processTree); processTree.clear(); ProcessInfo me = null; for (String proc : processList) { // Get information for each process ProcessInfo pInfo = new ProcessInfo(proc); if (constructProcessInfo(pInfo, procfsDir) != null) { allProcessInfo.put(proc, pInfo); if (proc.equals(this.pid)) { me = pInfo; // cache 'me' processTree.put(proc, pInfo); } } } if (me == null) { return; } // Add each process to its parent. for (Map.Entry<String, ProcessInfo> entry : allProcessInfo.entrySet()) { String pID = entry.getKey(); if (!pID.equals("1")) { ProcessInfo pInfo = entry.getValue(); ProcessInfo parentPInfo = allProcessInfo.get(pInfo.getPpid()); if (parentPInfo != null) { parentPInfo.addChild(pInfo); } } } // now start constructing the process-tree LinkedList<ProcessInfo> pInfoQueue = new LinkedList<ProcessInfo>(); pInfoQueue.addAll(me.getChildren()); while (!pInfoQueue.isEmpty()) { ProcessInfo pInfo = pInfoQueue.remove(); if (!processTree.containsKey(pInfo.getPid())) { processTree.put(pInfo.getPid(), pInfo); } pInfoQueue.addAll(pInfo.getChildren()); } // update age values and compute the number of jiffies since last update for (Map.Entry<String, ProcessInfo> procs : processTree.entrySet()) { ProcessInfo oldInfo = oldProcs.get(procs.getKey()); if (procs.getValue() != null) { procs.getValue().updateJiffy(oldInfo); if (oldInfo != null) { procs.getValue().updateAge(oldInfo); } } } if (LOG.isDebugEnabled()) { // Log.debug the ProcfsBasedProcessTree LOG.debug(this.toString()); } } // Update PSS related information for (ProcessInfo p : processTree.values()) { if (p != null) { // Get information for each process ProcessMemInfo memInfo = new ProcessMemInfo(p.getPid()); constructProcessSMAPInfo(memInfo, procfsDir); processSMAPTree.put(p.getPid(), memInfo); } } } /** * Verify that the given process id is same as its process group id. * * @return true if the process id matches else return false. */ @Override public boolean checkPidPgrpidForMatch() { return checkPidPgrpidForMatch(pid, PROCFS); } public static boolean checkPidPgrpidForMatch(String _pid, String procfs) { // Get information for this process ProcessInfo pInfo = new ProcessInfo(_pid); pInfo = constructProcessInfo(pInfo, procfs); // null if process group leader finished execution; issue no warning // make sure that pid and its pgrpId match if (pInfo == null) return true; String pgrpId = pInfo.getPgrpId().toString(); return pgrpId.equals(_pid); } private static final String PROCESSTREE_DUMP_FORMAT = "\t|- %s %s %d %d %s %d %d %d %d %s\n"; public List<String> getCurrentProcessIDs() { List<String> currentPIDs = new ArrayList<String>(); currentPIDs.addAll(processTree.keySet()); return currentPIDs; } /** * Get a dump of the process-tree. * * @return a string concatenating the dump of information of all the processes * in the process-tree */ @Override public String getProcessTreeDump() { StringBuilder ret = new StringBuilder(); // The header. ret.append(String.format("\t|- PID PPID PGRPID SESSID CMD_NAME " + "USER_MODE_TIME(MILLIS) SYSTEM_TIME(MILLIS) VMEM_USAGE(BYTES) " + "RSSMEM_USAGE(PAGES) FULL_CMD_LINE\n")); for (ProcessInfo p : processTree.values()) { if (p != null) { ret.append(String.format(PROCESSTREE_DUMP_FORMAT, p.getPid(), p.getPpid(), p.getPgrpId(), p.getSessionId(), p.getName(), p.getUtime(), p.getStime(), p.getVmem(), p.getRssmemPage(), p.getCmdLine(procfsDir))); } } return ret.toString(); } /** * Get the cumulative virtual memory used by all the processes in the * process-tree that are older than the passed in age. * * @param olderThanAge * processes above this age are included in the memory addition * @return cumulative virtual memory used by the process-tree in bytes, for * processes older than this age. */ @Override public long getCumulativeVmem(int olderThanAge) { long total = 0; for (ProcessInfo p : processTree.values()) { if ((p != null) && (p.getAge() > olderThanAge)) { total += p.getVmem(); } } return total; } /** * Get the cumulative resident set size (rss) memory used by all the processes * in the process-tree that are older than the passed in age. RSS is * calculated based on SMAP information. Skip regions with "r--" permission, * to get real RSS usage of the process. * * @param olderThanAge * processes above this age are included in the memory addition * @return cumulative rss memory used by the process-tree in bytes, for * processes older than this age. return 0 if it cannot be calculated */ @Override public long getCumulativeRssmem(int olderThanAge) { long total = 0; for (ProcessInfo p : processTree.values()) { if ((p != null) && (p.getAge() > olderThanAge)) { ProcessMemInfo procMemInfo = processSMAPTree.get(p.getPid()); if (procMemInfo != null) { for (MemoryMappingInfo info : procMemInfo.getModuleMemList()) { // Do not account for r--s or r-xs mappings if (info.getPermission().trim().equalsIgnoreCase(READ_ONLY_WITH_SHARED_PERMISSION) || info .getPermission().trim().equalsIgnoreCase(READ_EXECUTE_WITH_SHARED_PERMISSION)) { continue; } total += Math.min(info.sharedDirty, info.pss) + info.privateDirty + info.privateClean; if (LOG.isDebugEnabled()) { LOG.debug(" total(" + olderThanAge + "): PID : " + p.pid + ", SharedDirty : " + info.sharedDirty + ", PSS : " + info.pss + ", Private_Dirty : " + info.privateDirty + ", Private_Clean : " + info.privateClean + ", total : " + (total * 1024)); } } } if (LOG.isDebugEnabled()) { LOG.debug(procMemInfo.toString()); } } } total = (total * 1024); // convert to bytes return total; // size } /** * Get the CPU time in millisecond used by all the processes in the * process-tree since the process-tree created * * @return cumulative CPU time in millisecond since the process-tree created * return 0 if it cannot be calculated */ @Override public long getCumulativeCpuTime() { if (JIFFY_LENGTH_IN_MILLIS < 0) { return 0; } long incJiffies = 0; for (ProcessInfo p : processTree.values()) { if (p != null) { incJiffies += p.getDtime(); } } cpuTime += incJiffies * JIFFY_LENGTH_IN_MILLIS; return cpuTime; } private static String getValidPID(String pid) { if (pid == null) return deadPid; Matcher m = numberPattern.matcher(pid); if (m.matches()) return pid; return deadPid; } /** * Get the list of all processes in the system. */ private List<String> getProcessList() { String[] processDirs = (new File(procfsDir)).list(); List<String> processList = new ArrayList<String>(); for (String dir : processDirs) { Matcher m = numberPattern.matcher(dir); if (!m.matches()) continue; try { if ((new File(procfsDir, dir)).isDirectory()) { processList.add(dir); } } catch (SecurityException s) { // skip this process } } return processList; } /** * Construct the ProcessInfo using the process' PID and procfs rooted at the * specified directory and return the same. It is provided mainly to assist * testing purposes. * * Returns null on failing to read from procfs, * * @param pinfo * ProcessInfo that needs to be updated * @param procfsDir * root of the proc file system * @return updated ProcessInfo, null on errors. */ private static ProcessInfo constructProcessInfo(ProcessInfo pinfo, String procfsDir) { ProcessInfo ret = null; // Read "procfsDir/<pid>/stat" file - typically /proc/<pid>/stat BufferedReader in = null; FileReader fReader = null; try { File pidDir = new File(procfsDir, pinfo.getPid()); fReader = new FileReader(new File(pidDir, PROCFS_STAT_FILE)); in = new BufferedReader(fReader); } catch (FileNotFoundException f) { // The process vanished in the interim! return ret; } ret = pinfo; try { String str = in.readLine(); // only one line Matcher m = PROCFS_STAT_FILE_FORMAT.matcher(str); boolean mat = m.find(); if (mat) { // Set (name) (ppid) (pgrpId) (session) (utime) (stime) (vsize) (rss) pinfo.updateProcessInfo(m.group(2), m.group(3), Integer.parseInt(m.group(4)), Integer.parseInt(m.group(5)), Long.parseLong(m.group(7)), new BigInteger(m.group(8)), Long.parseLong(m.group(10)), Long.parseLong(m.group(11))); } else { LOG.warn("Unexpected: procfs stat file is not in the expected format" + " for process with pid " + pinfo.getPid()); ret = null; } } catch (IOException io) { LOG.warn("Error reading the stream " + io); ret = null; } finally { // Close the streams try { fReader.close(); try { in.close(); } catch (IOException i) { LOG.warn("Error closing the stream " + in); } } catch (IOException i) { LOG.warn("Error closing the stream " + fReader); } } return ret; } /** * Returns a string printing PIDs of process present in the * ProcfsBasedProcessTree. Output format : [pid pid ..] */ @Override public String toString() { StringBuffer pTree = new StringBuffer("[ "); for (String p : processTree.keySet()) { pTree.append(p); pTree.append(" "); } return pTree.substring(0, pTree.length()) + "]"; } /** * Update memory related information * * @param pinfo * @param procfsDir */ private static void constructProcessSMAPInfo(ProcessMemInfo pInfo, String procfsDir) { BufferedReader in = null; FileReader fReader = null; try { File pidDir = new File(procfsDir, pInfo.getPid()); File file = new File(pidDir, SMAPS); fReader = new FileReader(file); in = new BufferedReader(fReader); MemoryMappingInfo memoryMappingInfo = null; List<String> lines = IOUtils.readLines(new FileInputStream(file)); for (String line : lines) { line = line.trim(); try { Matcher address = ADDRESS_PATTERN.matcher(line); if (address.find()) { memoryMappingInfo = new MemoryMappingInfo(line); memoryMappingInfo.setPermission(address.group(4)); pInfo.getModuleMemList().add(memoryMappingInfo); continue; } Matcher memInfo = MEM_INFO_PATTERN.matcher(line); if (memInfo.find()) { String key = memInfo.group(1).trim(); String value = memInfo.group(2).replace(KB, "").trim(); if (LOG.isDebugEnabled()) { LOG.debug("MemInfo : " + key + " : Value : " + value); } memoryMappingInfo.updateModuleMemInfo(key, value); } } catch (Throwable t) { LOG.warn("Error parsing smaps line : " + line + "; " + t.getMessage()); } } } catch (FileNotFoundException f) { LOG.error(f.getMessage()); } catch (IOException e) { LOG.error(e.getMessage()); } catch (Throwable t) { LOG.error(t.getMessage()); } finally { IOUtils.closeQuietly(in); } } /** * Placeholder for process's SMAPS information */ static class ProcessMemInfo { private String pid; private List<MemoryMappingInfo> moduleMemList; public ProcessMemInfo(String pid) { this.pid = pid; this.moduleMemList = new LinkedList<MemoryMappingInfo>(); } public List<MemoryMappingInfo> getModuleMemList() { return moduleMemList; } public String getPid() { return pid; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("pid : " + pid); for (MemoryMappingInfo info : moduleMemList) { sb.append("\n"); sb.append(info.toString()); } return sb.toString(); } } /** * <pre> * Private Pages : Pages that were mapped only by the process * Shared Pages : Pages that were shared with other processes * * Clean Pages : Pages that have not been modified since they were mapped * Dirty Pages : Pages that have been modified since they were mapped * * Private RSS = Private Clean Pages + Private Dirty Pages * Shared RSS = Shared Clean Pages + Shared Dirty Pages * RSS = Private RSS + Shared RSS * PSS = The count of all pages mapped uniquely by the process, * plus a fraction of each shared page, said fraction to be * proportional to the number of processes which have mapped the page. * * </pre> */ static class MemoryMappingInfo { private int size; private int rss; private int pss; private int sharedClean; private int sharedDirty; private int privateClean; private int privateDirty; private int referenced; private String regionName; private String permission; public MemoryMappingInfo(String name) { this.regionName = name; } public String getName() { return regionName; } public void setPermission(String permission) { this.permission = permission; } public String getPermission() { return permission; } public int getSize() { return size; } public int getRss() { return rss; } public int getPss() { return pss; } public int getSharedClean() { return sharedClean; } public int getSharedDirty() { return sharedDirty; } public int getPrivateClean() { return privateClean; } public int getPrivateDirty() { return privateDirty; } public int getReferenced() { return referenced; } public void updateModuleMemInfo(String key, String value) { MEM_INFO info = MEM_INFO.getMemInfoByName(key); int val = 0; try { val = Integer.parseInt(value.trim()); } catch (NumberFormatException ne) { LOG.error("Error in parsing : " + info + " : value" + value.trim()); } if (info == null) { return; } if (LOG.isDebugEnabled()) { LOG.debug("updateModuleMemInfo : memInfo : " + info); } switch (info) { case SIZE: size = val; break; case RSS: rss = val; break; case PSS: pss = val; break; case SHARED_CLEAN: sharedClean = val; break; case SHARED_DIRTY: sharedDirty = val; break; case PRIVATE_CLEAN: privateClean = val; break; case PRIVATE_DIRTY: privateDirty = val; break; case REFERENCED: referenced = val; break; default: break; } } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("\t").append(this.getName()).append("\n"); sb.append("\t").append(this.regionName).append("\n"); sb.append("\t").append(MEM_INFO.SIZE + ":" + this.getSize()).append(" kB\n"); sb.append("\t").append(MEM_INFO.PSS + ":" + this.getPss()).append(" kB\n"); sb.append("\t").append(MEM_INFO.RSS + ":" + this.getRss()).append(" kB\n"); sb.append("\t").append(MEM_INFO.SHARED_CLEAN + ":" + this.getSharedClean()).append(" kB\n"); sb.append("\t").append(MEM_INFO.SHARED_DIRTY + ":" + this.getSharedDirty()).append(" kB\n"); sb.append("\t").append(MEM_INFO.PRIVATE_CLEAN + ":" + this.getPrivateClean()).append(" kB\n"); sb.append("\t").append(MEM_INFO.PRIVATE_DIRTY + ":" + this.getPrivateDirty()).append(" kB\n"); sb.append("\t").append(MEM_INFO.REFERENCED + ":" + this.getReferenced()).append(" kB\n"); sb.append("\t").append(MEM_INFO.PRIVATE_DIRTY + ":" + this.getPrivateDirty()).append(" kB\n"); sb.append("\t").append(MEM_INFO.PRIVATE_DIRTY + ":" + this.getPrivateDirty()).append(" kB\n"); return sb.toString(); } } /** * * Class containing information of a process. * */ private static class ProcessInfo { private String pid; // process-id private String name; // command name private Integer pgrpId; // process group-id private String ppid; // parent process-id private Integer sessionId; // session-id private Long vmem; // virtual memory usage private Long rssmemPage; // rss memory usage in # of pages private Long utime = 0L; // # of jiffies in user mode private final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE); private BigInteger stime = new BigInteger("0"); // # of jiffies in kernel // mode // how many times has this process been seen alive private int age; // # of jiffies used since last update: private Long dtime = 0L; // dtime = (utime + stime) - (utimeOld + stimeOld) // We need this to compute the cumulative CPU time // because the subprocess may finish earlier than root process private List<ProcessInfo> children = new ArrayList<ProcessInfo>(); // list // of // children public ProcessInfo(String pid) { this.pid = pid; // seeing this the first time. this.age = 1; } public String getPid() { return pid; } public String getName() { return name; } public Integer getPgrpId() { return pgrpId; } public String getPpid() { return ppid; } public Integer getSessionId() { return sessionId; } public Long getVmem() { return vmem; } public Long getUtime() { return utime; } public BigInteger getStime() { return stime; } public Long getDtime() { return dtime; } public Long getRssmemPage() { // get rss # of pages return rssmemPage; } public int getAge() { return age; } public void updateProcessInfo(String name, String ppid, Integer pgrpId, Integer sessionId, Long utime, BigInteger stime, Long vmem, Long rssmem) { this.name = name; this.ppid = ppid; this.pgrpId = pgrpId; this.sessionId = sessionId; this.utime = utime; this.stime = stime; this.vmem = vmem; this.rssmemPage = rssmem; } public void updateJiffy(ProcessInfo oldInfo) { if (oldInfo == null) { BigInteger sum = this.stime.add(BigInteger.valueOf(this.utime)); if (sum.compareTo(MAX_LONG) > 0) { this.dtime = 0L; LOG.warn("Sum of stime (" + this.stime + ") and utime (" + this.utime + ") is greater than " + Long.MAX_VALUE); } else { this.dtime = sum.longValue(); } return; } this.dtime = (this.utime - oldInfo.utime + this.stime.subtract(oldInfo.stime).longValue()); } public void updateAge(ProcessInfo oldInfo) { this.age = oldInfo.age + 1; } public boolean addChild(ProcessInfo p) { return children.add(p); } public List<ProcessInfo> getChildren() { return children; } public String getCmdLine(String procfsDir) { String ret = "N/A"; if (pid == null) { return ret; } BufferedReader in = null; FileReader fReader = null; try { fReader = new FileReader(new File(new File(procfsDir, pid.toString()), PROCFS_CMDLINE_FILE)); } catch (FileNotFoundException f) { // The process vanished in the interim! return ret; } in = new BufferedReader(fReader); try { ret = in.readLine(); // only one line if (ret == null) { ret = "N/A"; } else { ret = ret.replace('\0', ' '); // Replace each null char with a space if (ret.equals("")) { // The cmdline might be empty because the process is swapped out or // is a zombie. ret = "N/A"; } } } catch (IOException io) { LOG.warn("Error reading the stream " + io); ret = "N/A"; } finally { // Close the streams try { fReader.close(); try { in.close(); } catch (IOException i) { LOG.warn("Error closing the stream " + in); } } catch (IOException i) { LOG.warn("Error closing the stream " + fReader); } } return ret; } } }