source: proiecte/HadoopJUnit/hadoop-0.20.1/src/test/org/apache/hadoop/util/TestProcfsBasedProcessTree.java @ 120

Last change on this file since 120 was 120, checked in by (none), 14 years ago

Added the mail files for the Hadoop JUNit Project

  • Property svn:executable set to *
File size: 14.2 KB
Line 
1/**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19package org.apache.hadoop.util;
20
21import java.io.BufferedWriter;
22import java.io.File;
23import java.io.FileWriter;
24import java.io.IOException;
25import java.util.Random;
26
27import org.apache.commons.logging.Log;
28import org.apache.commons.logging.LogFactory;
29import org.apache.hadoop.fs.FileUtil;
30import org.apache.hadoop.fs.Path;
31import org.apache.hadoop.util.Shell.ExitCodeException;
32import org.apache.hadoop.util.Shell.ShellCommandExecutor;
33
34import junit.framework.TestCase;
35
36public class TestProcfsBasedProcessTree extends TestCase {
37
38  private static final Log LOG = LogFactory
39      .getLog(TestProcfsBasedProcessTree.class);
40  private static String TEST_ROOT_DIR = new Path(System.getProperty(
41      "test.build.data", "/tmp")).toString().replace(' ', '+');
42
43  private ShellCommandExecutor shexec = null;
44  private String pidFile;
45  private String shellScript;
46  private static final int N = 10; // Controls the RogueTask
47
48  private static final int memoryLimit = 15 * 1024 * 1024; // 15MB
49  private static final long PROCESSTREE_RECONSTRUCTION_INTERVAL =
50    ProcfsBasedProcessTree.DEFAULT_SLEEPTIME_BEFORE_SIGKILL; // msec
51
52  private class RogueTaskThread extends Thread {
53    public void run() {
54      try {
55        String args[] = { "bash", "-c",
56            "echo $$ > " + pidFile + "; sh " + shellScript + " " + N + ";" };
57        shexec = new ShellCommandExecutor(args);
58        shexec.execute();
59      } catch (ExitCodeException ee) {
60        LOG.info("Shell Command exit with a non-zero exit code. " + ee);
61      } catch (IOException ioe) {
62        LOG.info("Error executing shell command " + ioe);
63      } finally {
64        LOG.info("Exit code: " + shexec.getExitCode());
65      }
66    }
67  }
68
69  private String getRogueTaskPID() {
70    File f = new File(pidFile);
71    while (!f.exists()) {
72      try {
73        Thread.sleep(500);
74      } catch (InterruptedException ie) {
75        break;
76      }
77    }
78
79    // read from pidFile
80    return ProcfsBasedProcessTree.getPidFromPidFile(pidFile);
81  }
82
83  public void testProcessTree() {
84
85    try {
86      if (!ProcfsBasedProcessTree.isAvailable()) {
87        System.out
88            .println("ProcfsBasedProcessTree is not available on this system. Not testing");
89        return;
90      }
91    } catch (Exception e) {
92      LOG.info(StringUtils.stringifyException(e));
93      return;
94    }
95    // create shell script
96    Random rm = new Random();
97    File tempFile = new File(this.getName() + "_shellScript_" + rm.nextInt()
98        + ".sh");
99    tempFile.deleteOnExit();
100    shellScript = tempFile.getName();
101
102    // create pid file
103    tempFile = new File(this.getName() + "_pidFile_" + rm.nextInt() + ".pid");
104    tempFile.deleteOnExit();
105    pidFile = tempFile.getName();
106
107    // write to shell-script
108    try {
109      FileWriter fWriter = new FileWriter(shellScript);
110      fWriter.write(
111          "# rogue task\n" +
112          "sleep 10\n" +
113          "echo hello\n" +
114          "if [ $1 -ne 0 ]\n" +
115          "then\n" +
116          " sh " + shellScript + " $(($1-1))\n" +
117          "fi");
118      fWriter.close();
119    } catch (IOException ioe) {
120      LOG.info("Error: " + ioe);
121      return;
122    }
123
124    Thread t = new RogueTaskThread();
125    t.start();
126    String pid = getRogueTaskPID();
127    LOG.info("Root process pid: " + pid);
128    ProcfsBasedProcessTree p = new ProcfsBasedProcessTree(pid);
129    p = p.getProcessTree(); // initialize
130    try {
131      while (true) {
132        LOG.info("ProcessTree: " + p.toString());
133        long mem = p.getCumulativeVmem();
134        LOG.info("Memory usage: " + mem + "bytes.");
135        if (mem > memoryLimit) {
136          p.destroy();
137          break;
138        }
139        Thread.sleep(PROCESSTREE_RECONSTRUCTION_INTERVAL);
140        p = p.getProcessTree(); // reconstruct
141      }
142    } catch (InterruptedException ie) {
143      LOG.info("Interrupted.");
144    }
145
146    assertFalse("ProcessTree must have been gone", p.isAlive());
147
148    // Not able to join thread sometimes when forking with large N.
149    try {
150      t.join(2000);
151      LOG.info("RogueTaskThread successfully joined.");
152    } catch (InterruptedException ie) {
153      LOG.info("Interrupted while joining RogueTaskThread.");
154    }
155
156    // ProcessTree is gone now. Any further calls should be sane.
157    p = p.getProcessTree();
158    assertFalse("ProcessTree must have been gone", p.isAlive());
159    assertTrue("Cumulative vmem for the gone-process is "
160        + p.getCumulativeVmem() + " . It should be zero.", p
161        .getCumulativeVmem() == 0);
162    assertTrue(p.toString().equals("[ ]"));
163  }
164 
165  public static class ProcessStatInfo {
166    // sample stat in a single line : 3910 (gpm) S 1 3910 3910 0 -1 4194624
167    // 83 0 0 0 0 0 0 0 16 0 1 0 7852 2408448 88 4294967295 134512640
168    // 134590050 3220521392 3220520036 10975138 0 0 4096 134234626
169    // 4294967295 0 0 17 1 0 0
170    String pid;
171    String name;
172    String ppid;
173    String pgrpId;
174    String session;
175    String vmem;
176   
177    public ProcessStatInfo(String[] statEntries) {
178      pid = statEntries[0];
179      name = statEntries[1];
180      ppid = statEntries[2];
181      pgrpId = statEntries[3];
182      session = statEntries[4];
183      vmem = statEntries[5];
184    }
185   
186    // construct a line that mimics the procfs stat file.
187    // all unused numerical entries are set to 0.
188    public String getStatLine() {
189      return String.format("%s (%s) S %s %s %s 0 0 0" +
190                      " 0 0 0 0 0 0 0 0 0 0 0 0 0 %s 0 0 0" +
191                      " 0 0 0 0 0 0 0 0" +
192                      " 0 0 0 0 0", 
193                      pid, name, ppid, pgrpId, session, vmem);
194    }
195  }
196 
197  /**
198   * A basic test that creates a few process directories and writes
199   * stat files. Verifies that the virtual memory is correctly 
200   * computed.
201   * @throws IOException if there was a problem setting up the
202   *                      fake procfs directories or files.
203   */
204  public void testVirtualMemoryForProcessTree() throws IOException {
205
206    // test processes
207    String[] pids = { "100", "200", "300", "400" };
208    // create the fake procfs root directory.
209    File procfsRootDir = new File(TEST_ROOT_DIR, "proc");
210
211    try {
212      setupProcfsRootDir(procfsRootDir);
213      setupPidDirs(procfsRootDir, pids);
214     
215      // create stat objects.
216      // assuming processes 100, 200, 300 are in tree and 400 is not.
217      ProcessStatInfo[] procInfos = new ProcessStatInfo[4];
218      procInfos[0] = new ProcessStatInfo(new String[] 
219                                  {"100", "proc1", "1", "100", "100", "100000"});
220      procInfos[1] = new ProcessStatInfo(new String[] 
221                                  {"200", "proc2", "100", "100", "100", "200000"});
222      procInfos[2] = new ProcessStatInfo(new String[] 
223                                  {"300", "proc3", "200", "100", "100", "300000"});
224      procInfos[3] = new ProcessStatInfo(new String[] 
225                                  {"400", "proc4", "1", "400", "400", "400000"});
226     
227      writeStatFiles(procfsRootDir, pids, procInfos);
228     
229      // crank up the process tree class.
230      ProcfsBasedProcessTree processTree = 
231          new ProcfsBasedProcessTree("100", procfsRootDir.getAbsolutePath());
232      // build the process tree.
233      processTree.getProcessTree();
234     
235      // verify cumulative memory
236      assertEquals("Cumulative memory does not match", 
237              Long.parseLong("600000"), processTree.getCumulativeVmem());
238    } finally {
239      FileUtil.fullyDelete(procfsRootDir);
240    }
241  }
242 
243  /**
244   * Tests that cumulative memory is computed only for
245   * processes older than a given age.
246   * @throws IOException if there was a problem setting up the
247   *                      fake procfs directories or files.
248   */
249  public void testVMemForOlderProcesses() throws IOException {
250    // initial list of processes
251    String[] pids = { "100", "200", "300", "400" };
252    // create the fake procfs root directory.
253    File procfsRootDir = new File(TEST_ROOT_DIR, "proc");
254
255    try {
256      setupProcfsRootDir(procfsRootDir);
257      setupPidDirs(procfsRootDir, pids);
258     
259      // create stat objects.
260      // assuming 100, 200 and 400 are in tree, 300 is not.
261      ProcessStatInfo[] procInfos = new ProcessStatInfo[4];
262      procInfos[0] = new ProcessStatInfo(new String[] 
263                                  {"100", "proc1", "1", "100", "100", "100000"});
264      procInfos[1] = new ProcessStatInfo(new String[] 
265                                  {"200", "proc2", "100", "100", "100", "200000"});
266      procInfos[2] = new ProcessStatInfo(new String[] 
267                                  {"300", "proc3", "1", "300", "300", "300000"});
268      procInfos[3] = new ProcessStatInfo(new String[] 
269                                  {"400", "proc4", "100", "100", "100", "400000"});
270     
271      writeStatFiles(procfsRootDir, pids, procInfos);
272     
273      // crank up the process tree class.
274      ProcfsBasedProcessTree processTree = 
275          new ProcfsBasedProcessTree("100", procfsRootDir.getAbsolutePath());
276      // build the process tree.
277      processTree.getProcessTree();
278     
279      // verify cumulative memory
280      assertEquals("Cumulative memory does not match", 
281              Long.parseLong("700000"), processTree.getCumulativeVmem());
282     
283      // write one more process as child of 100.
284      String[] newPids = { "500" };
285      setupPidDirs(procfsRootDir, newPids);
286     
287      ProcessStatInfo[] newProcInfos = new ProcessStatInfo[1];
288      newProcInfos[0] = new ProcessStatInfo(new String[]
289                             {"500", "proc5", "100", "100", "100", "500000"});
290      writeStatFiles(procfsRootDir, newPids, newProcInfos);
291     
292      // check vmem includes the new process.
293      processTree.getProcessTree();
294      assertEquals("Cumulative memory does not include new process",
295              Long.parseLong("1200000"), processTree.getCumulativeVmem());
296     
297      // however processes older than 1 iteration will retain the older value
298      assertEquals("Cumulative memory shouldn't have included new process",
299              Long.parseLong("700000"), processTree.getCumulativeVmem(1));
300     
301      // one more process
302      newPids = new String[]{ "600" };
303      setupPidDirs(procfsRootDir, newPids);
304     
305      newProcInfos = new ProcessStatInfo[1];
306      newProcInfos[0] = new ProcessStatInfo(new String[]
307                                     {"600", "proc6", "100", "100", "100", "600000"});
308      writeStatFiles(procfsRootDir, newPids, newProcInfos);
309
310      // refresh process tree
311      processTree.getProcessTree();
312     
313      // processes older than 2 iterations should be same as before.
314      assertEquals("Cumulative memory shouldn't have included new processes",
315          Long.parseLong("700000"), processTree.getCumulativeVmem(2));
316     
317      // processes older than 1 iteration should not include new process,
318      // but include process 500
319      assertEquals("Cumulative memory shouldn't have included new processes",
320          Long.parseLong("1200000"), processTree.getCumulativeVmem(1));
321     
322      // no processes older than 3 iterations, this should be 0
323      assertEquals("Getting non-zero vmem for processes older than 3 iterations",
324                    0L, processTree.getCumulativeVmem(3));
325    } finally {
326      FileUtil.fullyDelete(procfsRootDir);
327    }
328  }
329
330  /**
331   * Create a directory to mimic the procfs file system's root.
332   * @param procfsRootDir root directory to create.
333   * @throws IOException if could not delete the procfs root directory
334   */
335  public static void setupProcfsRootDir(File procfsRootDir) 
336                                        throws IOException { 
337    // cleanup any existing process root dir.
338    if (procfsRootDir.exists()) {
339      assertTrue(FileUtil.fullyDelete(procfsRootDir)); 
340    }
341
342    // create afresh
343    assertTrue(procfsRootDir.mkdirs());
344  }
345
346  /**
347   * Create PID directories under the specified procfs root directory
348   * @param procfsRootDir root directory of procfs file system
349   * @param pids the PID directories to create.
350   * @throws IOException If PID dirs could not be created
351   */
352  public static void setupPidDirs(File procfsRootDir, String[] pids) 
353                      throws IOException {
354    for (String pid : pids) {
355      File pidDir = new File(procfsRootDir, pid);
356      pidDir.mkdir();
357      if (!pidDir.exists()) {
358        throw new IOException ("couldn't make process directory under " +
359            "fake procfs");
360      } else {
361        LOG.info("created pid dir");
362      }
363    }
364  }
365 
366  /**
367   * Write stat files under the specified pid directories with data
368   * setup in the corresponding ProcessStatInfo objects
369   * @param procfsRootDir root directory of procfs file system
370   * @param pids the PID directories under which to create the stat file
371   * @param procs corresponding ProcessStatInfo objects whose data should be
372   *              written to the stat files.
373   * @throws IOException if stat files could not be written
374   */
375  public static void writeStatFiles(File procfsRootDir, String[] pids, 
376                              ProcessStatInfo[] procs) throws IOException {
377    for (int i=0; i<pids.length; i++) {
378      File statFile = new File(new File(procfsRootDir, pids[i]), "stat");
379      BufferedWriter bw = null;
380      try {
381        FileWriter fw = new FileWriter(statFile);
382        bw = new BufferedWriter(fw);
383        bw.write(procs[i].getStatLine());
384        LOG.info("wrote stat file for " + pids[i] + 
385                  " with contents: " + procs[i].getStatLine());
386      } finally {
387        // not handling exception - will throw an error and fail the test.
388        if (bw != null) {
389          bw.close();
390        }
391      }
392    }
393  }
394}
Note: See TracBrowser for help on using the repository browser.