/** * 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.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Random; import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.fs.FileContext; import org.apache.hadoop.fs.FileUtil; import org.apache.hadoop.fs.Path; import org.apache.hadoop.util.StringUtils; import org.apache.hadoop.util.Shell.ExitCodeException; import org.apache.hadoop.util.Shell.ShellCommandExecutor; import org.apache.hadoop.yarn.util.ProcfsBasedProcessTree; import org.junit.Assert; import org.junit.Before; import org.junit.Test; /** * A JUnit test to test ProcfsBasedProcessTree. */ public class TestProcfsBasedProcessTree { private static final Log LOG = LogFactory .getLog(TestProcfsBasedProcessTree.class); protected static File TEST_ROOT_DIR = new File("target", TestProcfsBasedProcessTree.class.getName() + "-localDir"); private ShellCommandExecutor shexec = null; private String pidFile, lowestDescendant; private String shellScript; private static final int N = 6; // Controls the RogueTask private class RogueTaskThread extends Thread { public void run() { try { Vector<String> args = new Vector<String>(); if(isSetsidAvailable()) { args.add("setsid"); } args.add("bash"); args.add("-c"); args.add(" echo $$ > " + pidFile + "; sh " + shellScript + " " + N + ";") ; shexec = new ShellCommandExecutor(args.toArray(new String[0])); shexec.execute(); } catch (ExitCodeException ee) { LOG.info("Shell Command exit with a non-zero exit code. This is" + " expected as we are killing the subprocesses of the" + " task intentionally. " + ee); } catch (IOException ioe) { LOG.info("Error executing shell command " + ioe); } finally { LOG.info("Exit code: " + shexec.getExitCode()); } } } private String getRogueTaskPID() { File f = new File(pidFile); while (!f.exists()) { try { Thread.sleep(500); } catch (InterruptedException ie) { break; } } // read from pidFile return getPidFromPidFile(pidFile); } @Before public void setup() throws IOException { FileContext.getLocalFSFileContext().delete( new Path(TEST_ROOT_DIR.getAbsolutePath()), true); } @Test public void testProcessTree() throws Exception { try { if (!ProcfsBasedProcessTree.isAvailable()) { System.out .println("ProcfsBasedProcessTree is not available on this system. Not testing"); return; } } catch (Exception e) { LOG.info(StringUtils.stringifyException(e)); return; } // create shell script Random rm = new Random(); File tempFile = new File(TEST_ROOT_DIR, getClass().getName() + "_shellScript_" + rm.nextInt() + ".sh"); tempFile.deleteOnExit(); shellScript = TEST_ROOT_DIR + File.separator + tempFile.getName(); // create pid file tempFile = new File(TEST_ROOT_DIR, getClass().getName() + "_pidFile_" + rm.nextInt() + ".pid"); tempFile.deleteOnExit(); pidFile = TEST_ROOT_DIR + File.separator + tempFile.getName(); lowestDescendant = TEST_ROOT_DIR + File.separator + "lowestDescendantPidFile"; // write to shell-script try { FileWriter fWriter = new FileWriter(shellScript); fWriter.write( "# rogue task\n" + "sleep 1\n" + "echo hello\n" + "if [ $1 -ne 0 ]\n" + "then\n" + " sh " + shellScript + " $(($1-1))\n" + "else\n" + " echo $$ > " + lowestDescendant + "\n" + " while true\n do\n" + " sleep 5\n" + " done\n" + "fi"); fWriter.close(); } catch (IOException ioe) { LOG.info("Error: " + ioe); return; } Thread t = new RogueTaskThread(); t.start(); String pid = getRogueTaskPID(); LOG.info("Root process pid: " + pid); ProcfsBasedProcessTree p = createProcessTree(pid); p = p.getProcessTree(); // initialize LOG.info("ProcessTree: " + p.toString()); File leaf = new File(lowestDescendant); //wait till lowest descendant process of Rougue Task starts execution while (!leaf.exists()) { try { Thread.sleep(500); } catch (InterruptedException ie) { break; } } p = p.getProcessTree(); // reconstruct LOG.info("ProcessTree: " + p.toString()); // Get the process-tree dump String processTreeDump = p.getProcessTreeDump(); // destroy the process and all its subprocesses destroyProcessTree(pid); if (isSetsidAvailable()) { // whole processtree should be gone Assert.assertFalse("Proceesses in process group live", isAnyProcessInTreeAlive(p)); } else {// process should be gone Assert.assertFalse("ProcessTree must have been gone", isAlive(pid)); } LOG.info("Process-tree dump follows: \n" + processTreeDump); Assert.assertTrue("Process-tree dump doesn't start with a proper header", processTreeDump.startsWith("\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 (int i = N; i >= 0; i--) { String cmdLineDump = "\\|- [0-9]+ [0-9]+ [0-9]+ [0-9]+ \\(sh\\)" + " [0-9]+ [0-9]+ [0-9]+ [0-9]+ sh " + shellScript + " " + i; Pattern pat = Pattern.compile(cmdLineDump); Matcher mat = pat.matcher(processTreeDump); Assert.assertTrue("Process-tree dump doesn't contain the cmdLineDump of " + i + "th process!", mat.find()); } // Not able to join thread sometimes when forking with large N. try { t.join(2000); LOG.info("RogueTaskThread successfully joined."); } catch (InterruptedException ie) { LOG.info("Interrupted while joining RogueTaskThread."); } // ProcessTree is gone now. Any further calls should be sane. p = p.getProcessTree(); Assert.assertFalse("ProcessTree must have been gone", isAlive(pid)); Assert.assertTrue("Cumulative vmem for the gone-process is " + p.getCumulativeVmem() + " . It should be zero.", p .getCumulativeVmem() == 0); Assert.assertTrue(p.toString().equals("[ ]")); } protected ProcfsBasedProcessTree createProcessTree(String pid) { return new ProcfsBasedProcessTree(pid, isSetsidAvailable()); } protected ProcfsBasedProcessTree createProcessTree(String pid, boolean setsidUsed, String procfsRootDir) { return new ProcfsBasedProcessTree(pid, setsidUsed, procfsRootDir); } protected void destroyProcessTree(String pid) throws IOException { sendSignal(pid, 9); } /** * Get PID from a pid-file. * * @param pidFileName * Name of the pid-file. * @return the PID string read from the pid-file. Returns null if the * pidFileName points to a non-existing file or if read fails from the * file. */ public static String getPidFromPidFile(String pidFileName) { BufferedReader pidFile = null; FileReader fReader = null; String pid = null; try { fReader = new FileReader(pidFileName); pidFile = new BufferedReader(fReader); } catch (FileNotFoundException f) { LOG.debug("PidFile doesn't exist : " + pidFileName); return pid; } try { pid = pidFile.readLine(); } catch (IOException i) { LOG.error("Failed to read from " + pidFileName); } finally { try { if (fReader != null) { fReader.close(); } try { if (pidFile != null) { pidFile.close(); } } catch (IOException i) { LOG.warn("Error closing the stream " + pidFile); } } catch (IOException i) { LOG.warn("Error closing the stream " + fReader); } } return pid; } public static class ProcessStatInfo { // sample stat in a single line : 3910 (gpm) S 1 3910 3910 0 -1 4194624 // 83 0 0 0 0 0 0 0 16 0 1 0 7852 2408448 88 4294967295 134512640 // 134590050 3220521392 3220520036 10975138 0 0 4096 134234626 // 4294967295 0 0 17 1 0 0 String pid; String name; String ppid; String pgrpId; String session; String vmem = "0"; String rssmemPage = "0"; String utime = "0"; String stime = "0"; public ProcessStatInfo(String[] statEntries) { pid = statEntries[0]; name = statEntries[1]; ppid = statEntries[2]; pgrpId = statEntries[3]; session = statEntries[4]; vmem = statEntries[5]; if (statEntries.length > 6) { rssmemPage = statEntries[6]; } if (statEntries.length > 7) { utime = statEntries[7]; stime = statEntries[8]; } } // construct a line that mimics the procfs stat file. // all unused numerical entries are set to 0. public String getStatLine() { return String.format("%s (%s) S %s %s %s 0 0 0" + " 0 0 0 0 %s %s 0 0 0 0 0 0 0 %s %s 0 0" + " 0 0 0 0 0 0 0 0" + " 0 0 0 0 0", pid, name, ppid, pgrpId, session, utime, stime, vmem, rssmemPage); } } /** * A basic test that creates a few process directories and writes * stat files. Verifies that the cpu time and memory is correctly * computed. * @throws IOException if there was a problem setting up the * fake procfs directories or files. */ @Test public void testCpuAndMemoryForProcessTree() throws IOException { // test processes String[] pids = { "100", "200", "300", "400" }; // create the fake procfs root directory. File procfsRootDir = new File(TEST_ROOT_DIR, "proc"); try { setupProcfsRootDir(procfsRootDir); setupPidDirs(procfsRootDir, pids); // create stat objects. // assuming processes 100, 200, 300 are in tree and 400 is not. ProcessStatInfo[] procInfos = new ProcessStatInfo[4]; procInfos[0] = new ProcessStatInfo(new String[] {"100", "proc1", "1", "100", "100", "100000", "100", "1000", "200"}); procInfos[1] = new ProcessStatInfo(new String[] {"200", "proc2", "100", "100", "100", "200000", "200", "2000", "400"}); procInfos[2] = new ProcessStatInfo(new String[] {"300", "proc3", "200", "100", "100", "300000", "300", "3000", "600"}); procInfos[3] = new ProcessStatInfo(new String[] {"400", "proc4", "1", "400", "400", "400000", "400", "4000", "800"}); writeStatFiles(procfsRootDir, pids, procInfos); // crank up the process tree class. ProcfsBasedProcessTree processTree = createProcessTree("100", true, procfsRootDir.getAbsolutePath()); // build the process tree. processTree.getProcessTree(); // verify cumulative memory Assert.assertEquals("Cumulative virtual memory does not match", 600000L, processTree.getCumulativeVmem()); // verify rss memory long cumuRssMem = ProcfsBasedProcessTree.PAGE_SIZE > 0 ? 600L * ProcfsBasedProcessTree.PAGE_SIZE : 0L; Assert.assertEquals("Cumulative rss memory does not match", cumuRssMem, processTree.getCumulativeRssmem()); // verify cumulative cpu time long cumuCpuTime = ProcfsBasedProcessTree.JIFFY_LENGTH_IN_MILLIS > 0 ? 7200L * ProcfsBasedProcessTree.JIFFY_LENGTH_IN_MILLIS : 0L; Assert.assertEquals("Cumulative cpu time does not match", cumuCpuTime, processTree.getCumulativeCpuTime()); // test the cpu time again to see if it cumulates procInfos[0] = new ProcessStatInfo(new String[] {"100", "proc1", "1", "100", "100", "100000", "100", "2000", "300"}); procInfos[1] = new ProcessStatInfo(new String[] {"200", "proc2", "100", "100", "100", "200000", "200", "3000", "500"}); writeStatFiles(procfsRootDir, pids, procInfos); // build the process tree. processTree.getProcessTree(); // verify cumulative cpu time again cumuCpuTime = ProcfsBasedProcessTree.JIFFY_LENGTH_IN_MILLIS > 0 ? 9400L * ProcfsBasedProcessTree.JIFFY_LENGTH_IN_MILLIS : 0L; Assert.assertEquals("Cumulative cpu time does not match", cumuCpuTime, processTree.getCumulativeCpuTime()); } finally { FileUtil.fullyDelete(procfsRootDir); } } /** * Tests that cumulative memory is computed only for * processes older than a given age. * @throws IOException if there was a problem setting up the * fake procfs directories or files. */ @Test public void testMemForOlderProcesses() throws IOException { // initial list of processes String[] pids = { "100", "200", "300", "400" }; // create the fake procfs root directory. File procfsRootDir = new File(TEST_ROOT_DIR, "proc"); try { setupProcfsRootDir(procfsRootDir); setupPidDirs(procfsRootDir, pids); // create stat objects. // assuming 100, 200 and 400 are in tree, 300 is not. ProcessStatInfo[] procInfos = new ProcessStatInfo[4]; procInfos[0] = new ProcessStatInfo(new String[] {"100", "proc1", "1", "100", "100", "100000", "100"}); procInfos[1] = new ProcessStatInfo(new String[] {"200", "proc2", "100", "100", "100", "200000", "200"}); procInfos[2] = new ProcessStatInfo(new String[] {"300", "proc3", "1", "300", "300", "300000", "300"}); procInfos[3] = new ProcessStatInfo(new String[] {"400", "proc4", "100", "100", "100", "400000", "400"}); writeStatFiles(procfsRootDir, pids, procInfos); // crank up the process tree class. ProcfsBasedProcessTree processTree = createProcessTree("100", true, procfsRootDir.getAbsolutePath()); // build the process tree. processTree.getProcessTree(); // verify cumulative memory Assert.assertEquals("Cumulative memory does not match", 700000L, processTree.getCumulativeVmem()); // write one more process as child of 100. String[] newPids = { "500" }; setupPidDirs(procfsRootDir, newPids); ProcessStatInfo[] newProcInfos = new ProcessStatInfo[1]; newProcInfos[0] = new ProcessStatInfo(new String[] {"500", "proc5", "100", "100", "100", "500000", "500"}); writeStatFiles(procfsRootDir, newPids, newProcInfos); // check memory includes the new process. processTree.getProcessTree(); Assert.assertEquals("Cumulative vmem does not include new process", 1200000L, processTree.getCumulativeVmem()); long cumuRssMem = ProcfsBasedProcessTree.PAGE_SIZE > 0 ? 1200L * ProcfsBasedProcessTree.PAGE_SIZE : 0L; Assert.assertEquals("Cumulative rssmem does not include new process", cumuRssMem, processTree.getCumulativeRssmem()); // however processes older than 1 iteration will retain the older value Assert.assertEquals("Cumulative vmem shouldn't have included new process", 700000L, processTree.getCumulativeVmem(1)); cumuRssMem = ProcfsBasedProcessTree.PAGE_SIZE > 0 ? 700L * ProcfsBasedProcessTree.PAGE_SIZE : 0L; Assert.assertEquals("Cumulative rssmem shouldn't have included new process", cumuRssMem, processTree.getCumulativeRssmem(1)); // one more process newPids = new String[]{ "600" }; setupPidDirs(procfsRootDir, newPids); newProcInfos = new ProcessStatInfo[1]; newProcInfos[0] = new ProcessStatInfo(new String[] {"600", "proc6", "100", "100", "100", "600000", "600"}); writeStatFiles(procfsRootDir, newPids, newProcInfos); // refresh process tree processTree.getProcessTree(); // processes older than 2 iterations should be same as before. Assert.assertEquals("Cumulative vmem shouldn't have included new processes", 700000L, processTree.getCumulativeVmem(2)); cumuRssMem = ProcfsBasedProcessTree.PAGE_SIZE > 0 ? 700L * ProcfsBasedProcessTree.PAGE_SIZE : 0L; Assert.assertEquals("Cumulative rssmem shouldn't have included new processes", cumuRssMem, processTree.getCumulativeRssmem(2)); // processes older than 1 iteration should not include new process, // but include process 500 Assert.assertEquals("Cumulative vmem shouldn't have included new processes", 1200000L, processTree.getCumulativeVmem(1)); cumuRssMem = ProcfsBasedProcessTree.PAGE_SIZE > 0 ? 1200L * ProcfsBasedProcessTree.PAGE_SIZE : 0L; Assert.assertEquals("Cumulative rssmem shouldn't have included new processes", cumuRssMem, processTree.getCumulativeRssmem(1)); // no processes older than 3 iterations, this should be 0 Assert.assertEquals("Getting non-zero vmem for processes older than 3 iterations", 0L, processTree.getCumulativeVmem(3)); Assert.assertEquals("Getting non-zero rssmem for processes older than 3 iterations", 0L, processTree.getCumulativeRssmem(3)); } finally { FileUtil.fullyDelete(procfsRootDir); } } /** * Verifies ProcfsBasedProcessTree.checkPidPgrpidForMatch() in case of * 'constructProcessInfo() returning null' by not writing stat file for the * mock process * @throws IOException if there was a problem setting up the * fake procfs directories or files. */ @Test public void testDestroyProcessTree() throws IOException { // test process String pid = "100"; // create the fake procfs root directory. File procfsRootDir = new File(TEST_ROOT_DIR, "proc"); try { setupProcfsRootDir(procfsRootDir); // crank up the process tree class. ProcfsBasedProcessTree processTree = createProcessTree(pid, true, procfsRootDir.getAbsolutePath()); // Let us not create stat file for pid 100. Assert.assertTrue(ProcfsBasedProcessTree.checkPidPgrpidForMatch( pid, procfsRootDir.getAbsolutePath())); } finally { FileUtil.fullyDelete(procfsRootDir); } } /** * Test the correctness of process-tree dump. * * @throws IOException */ @Test public void testProcessTreeDump() throws IOException { String[] pids = { "100", "200", "300", "400", "500", "600" }; File procfsRootDir = new File(TEST_ROOT_DIR, "proc"); try { setupProcfsRootDir(procfsRootDir); setupPidDirs(procfsRootDir, pids); int numProcesses = pids.length; // Processes 200, 300, 400 and 500 are descendants of 100. 600 is not. ProcessStatInfo[] procInfos = new ProcessStatInfo[numProcesses]; procInfos[0] = new ProcessStatInfo(new String[] { "100", "proc1", "1", "100", "100", "100000", "100", "1000", "200"}); procInfos[1] = new ProcessStatInfo(new String[] { "200", "proc2", "100", "100", "100", "200000", "200", "2000", "400"}); procInfos[2] = new ProcessStatInfo(new String[] { "300", "proc3", "200", "100", "100", "300000", "300", "3000", "600"}); procInfos[3] = new ProcessStatInfo(new String[] { "400", "proc4", "200", "100", "100", "400000", "400", "4000", "800"}); procInfos[4] = new ProcessStatInfo(new String[] { "500", "proc5", "400", "100", "100", "400000", "400", "4000", "800"}); procInfos[5] = new ProcessStatInfo(new String[] { "600", "proc6", "1", "1", "1", "400000", "400", "4000", "800"}); String[] cmdLines = new String[numProcesses]; cmdLines[0] = "proc1 arg1 arg2"; cmdLines[1] = "proc2 arg3 arg4"; cmdLines[2] = "proc3 arg5 arg6"; cmdLines[3] = "proc4 arg7 arg8"; cmdLines[4] = "proc5 arg9 arg10"; cmdLines[5] = "proc6 arg11 arg12"; writeStatFiles(procfsRootDir, pids, procInfos); writeCmdLineFiles(procfsRootDir, pids, cmdLines); ProcfsBasedProcessTree processTree = createProcessTree( "100", true, procfsRootDir.getAbsolutePath()); // build the process tree. processTree.getProcessTree(); // Get the process-tree dump String processTreeDump = processTree.getProcessTreeDump(); LOG.info("Process-tree dump follows: \n" + processTreeDump); Assert.assertTrue("Process-tree dump doesn't start with a proper header", processTreeDump.startsWith("\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 (int i = 0; i < 5; i++) { ProcessStatInfo p = procInfos[i]; Assert.assertTrue( "Process-tree dump doesn't contain the cmdLineDump of process " + p.pid, processTreeDump.contains("\t|- " + p.pid + " " + p.ppid + " " + p.pgrpId + " " + p.session + " (" + p.name + ") " + p.utime + " " + p.stime + " " + p.vmem + " " + p.rssmemPage + " " + cmdLines[i])); } // 600 should not be in the dump ProcessStatInfo p = procInfos[5]; Assert.assertFalse( "Process-tree dump shouldn't contain the cmdLineDump of process " + p.pid, processTreeDump.contains("\t|- " + p.pid + " " + p.ppid + " " + p.pgrpId + " " + p.session + " (" + p.name + ") " + p.utime + " " + p.stime + " " + p.vmem + " " + cmdLines[5])); } finally { FileUtil.fullyDelete(procfsRootDir); } } protected static boolean isSetsidAvailable() { ShellCommandExecutor shexec = null; boolean setsidSupported = true; try { String[] args = {"setsid", "bash", "-c", "echo $$"}; shexec = new ShellCommandExecutor(args); shexec.execute(); } catch (IOException ioe) { LOG.warn("setsid is not available on this machine. So not using it."); setsidSupported = false; } finally { // handle the exit code LOG.info("setsid exited with exit code " + shexec.getExitCode()); } return setsidSupported; } /** * Is the root-process alive? * Used only in tests. * @return true if the root-process is alive, false otherwise. */ private static boolean isAlive(String pid) { try { final String sigpid = isSetsidAvailable() ? "-" + pid : pid; try { sendSignal(sigpid, 0); } catch (ExitCodeException e) { return false; } return true; } catch (IOException ignored) { } return false; } private static void sendSignal(String pid, int signal) throws IOException { ShellCommandExecutor shexec = null; String[] arg = { "kill", "-" + signal, pid }; shexec = new ShellCommandExecutor(arg); shexec.execute(); } /** * Is any of the subprocesses in the process-tree alive? * Used only in tests. * @return true if any of the processes in the process-tree is * alive, false otherwise. */ private static boolean isAnyProcessInTreeAlive( ProcfsBasedProcessTree processTree) { for (String pId : processTree.getCurrentProcessIDs()) { if (isAlive(pId)) { return true; } } return false; } /** * Create a directory to mimic the procfs file system's root. * @param procfsRootDir root directory to create. * @throws IOException if could not delete the procfs root directory */ public static void setupProcfsRootDir(File procfsRootDir) throws IOException { // cleanup any existing process root dir. if (procfsRootDir.exists()) { Assert.assertTrue(FileUtil.fullyDelete(procfsRootDir)); } // create afresh Assert.assertTrue(procfsRootDir.mkdirs()); } /** * Create PID directories under the specified procfs root directory * @param procfsRootDir root directory of procfs file system * @param pids the PID directories to create. * @throws IOException If PID dirs could not be created */ public static void setupPidDirs(File procfsRootDir, String[] pids) throws IOException { for (String pid : pids) { File pidDir = new File(procfsRootDir, pid); pidDir.mkdir(); if (!pidDir.exists()) { throw new IOException ("couldn't make process directory under " + "fake procfs"); } else { LOG.info("created pid dir"); } } } /** * Write stat files under the specified pid directories with data * setup in the corresponding ProcessStatInfo objects * @param procfsRootDir root directory of procfs file system * @param pids the PID directories under which to create the stat file * @param procs corresponding ProcessStatInfo objects whose data should be * written to the stat files. * @throws IOException if stat files could not be written */ public static void writeStatFiles(File procfsRootDir, String[] pids, ProcessStatInfo[] procs) throws IOException { for (int i=0; i<pids.length; i++) { File statFile = new File(new File(procfsRootDir, pids[i]), ProcfsBasedProcessTree.PROCFS_STAT_FILE); BufferedWriter bw = null; try { FileWriter fw = new FileWriter(statFile); bw = new BufferedWriter(fw); bw.write(procs[i].getStatLine()); LOG.info("wrote stat file for " + pids[i] + " with contents: " + procs[i].getStatLine()); } finally { // not handling exception - will throw an error and fail the test. if (bw != null) { bw.close(); } } } } private static void writeCmdLineFiles(File procfsRootDir, String[] pids, String[] cmdLines) throws IOException { for (int i = 0; i < pids.length; i++) { File statFile = new File(new File(procfsRootDir, pids[i]), ProcfsBasedProcessTree.PROCFS_CMDLINE_FILE); BufferedWriter bw = null; try { bw = new BufferedWriter(new FileWriter(statFile)); bw.write(cmdLines[i]); LOG.info("wrote command-line file for " + pids[i] + " with contents: " + cmdLines[i]); } finally { // not handling exception - will throw an error and fail the test. if (bw != null) { bw.close(); } } } } }