001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.hbase;
020
021import java.util.ArrayList;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.concurrent.ConcurrentHashMap;
027import org.apache.hadoop.hbase.ResourceChecker.Phase;
028import org.apache.hadoop.hbase.util.JVM;
029import org.junit.runner.notification.RunListener;
030
031/**
032 * Listen to the test progress and check the usage of:
033 * <ul>
034 * <li>threads</li>
035 * <li>open file descriptor</li>
036 * <li>max open file descriptor</li>
037 * </ul>
038 * <p>
039 * When surefire forkMode=once/always/perthread, this code is executed on the forked process.
040 */
041public class ResourceCheckerJUnitListener extends RunListener {
042  private Map<String, ResourceChecker> rcs = new ConcurrentHashMap<>();
043
044  static class ThreadResourceAnalyzer extends ResourceChecker.ResourceAnalyzer {
045    private static Set<String> initialThreadNames = new HashSet<>();
046    private static List<String> stringsToLog = null;
047
048    @Override
049    public int getVal(Phase phase) {
050      Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
051      if (phase == Phase.INITIAL) {
052        stringsToLog = null;
053        for (Thread t : stackTraces.keySet()) {
054          initialThreadNames.add(t.getName());
055        }
056      } else if (phase == Phase.END) {
057        if (stackTraces.size() > initialThreadNames.size()) {
058          stringsToLog = new ArrayList<>();
059          for (Thread t : stackTraces.keySet()) {
060            if (!initialThreadNames.contains(t.getName())) {
061              stringsToLog.add("\nPotentially hanging thread: " + t.getName() + "\n");
062              StackTraceElement[] stackElements = stackTraces.get(t);
063              for (StackTraceElement ele : stackElements) {
064                stringsToLog.add("\t" + ele + "\n");
065              }
066            }
067          }
068        }
069      }
070      return stackTraces.size();
071    }
072
073    @Override
074    public int getMax() {
075      return 500;
076    }
077    
078    @Override
079    public List<String> getStringsToLog() {
080      return stringsToLog;
081    }
082  }
083
084
085  static class OpenFileDescriptorResourceAnalyzer extends ResourceChecker.ResourceAnalyzer {
086    @Override
087    public int getVal(Phase phase) {
088      if (!JVM.isUnix()) {
089        return 0;
090      }
091      JVM jvm = new JVM();
092      return (int) jvm.getOpenFileDescriptorCount();
093    }
094
095    @Override
096    public int getMax() {
097      return 1024;
098    }
099  }
100
101  static class MaxFileDescriptorResourceAnalyzer extends ResourceChecker.ResourceAnalyzer {
102    @Override
103    public int getVal(Phase phase) {
104      if (!JVM.isUnix()) {
105        return 0;
106      }
107      JVM jvm = new JVM();
108      return (int) jvm.getMaxFileDescriptorCount();
109    }
110  }
111
112  static class SystemLoadAverageResourceAnalyzer extends ResourceChecker.ResourceAnalyzer {
113    @Override
114    public int getVal(Phase phase) {
115      if (!JVM.isUnix()) {
116        return 0;
117      }
118      return (int) (new JVM().getSystemLoadAverage() * 100);
119    }
120  }
121
122  static class ProcessCountResourceAnalyzer extends ResourceChecker.ResourceAnalyzer {
123    @Override
124    public int getVal(Phase phase) {
125      if (!JVM.isUnix()) {
126        return 0;
127      }
128      return new JVM().getNumberOfRunningProcess();
129    }
130  }
131
132  static class AvailableMemoryMBResourceAnalyzer extends ResourceChecker.ResourceAnalyzer {
133    @Override
134    public int getVal(Phase phase) {
135      if (!JVM.isUnix()) {
136        return 0;
137      }
138      return (int) (new JVM().getFreeMemory() / (1024L * 1024L));
139    }
140  }
141
142
143  /**
144   * To be implemented by sub classes if they want to add specific ResourceAnalyzer.
145   */
146  protected void addResourceAnalyzer(ResourceChecker rc) {
147  }
148
149
150  private void start(String testName) {
151    ResourceChecker rc = new ResourceChecker(testName);
152    rc.addResourceAnalyzer(new ThreadResourceAnalyzer());
153    rc.addResourceAnalyzer(new OpenFileDescriptorResourceAnalyzer());
154    rc.addResourceAnalyzer(new MaxFileDescriptorResourceAnalyzer());
155    rc.addResourceAnalyzer(new SystemLoadAverageResourceAnalyzer());
156    rc.addResourceAnalyzer(new ProcessCountResourceAnalyzer());
157    rc.addResourceAnalyzer(new AvailableMemoryMBResourceAnalyzer());
158
159    addResourceAnalyzer(rc);
160
161    rcs.put(testName, rc);
162
163    rc.start();
164  }
165
166  private void end(String testName) {
167    ResourceChecker rc = rcs.remove(testName);
168    assert rc != null;
169    rc.end();
170  }
171
172  /**
173   * Get the test name from the JUnit Description
174   *
175   * @return the string for the short test name
176   */
177  private String descriptionToShortTestName(
178      org.junit.runner.Description description) {
179    final int toRemove = "org.apache.hadoop.hbase.".length();
180    return description.getTestClass().getName().substring(toRemove) +
181        "#" + description.getMethodName();
182  }
183
184  @Override
185  public void testStarted(org.junit.runner.Description description) throws java.lang.Exception {
186    start(descriptionToShortTestName(description));
187  }
188
189  @Override
190  public void testFinished(org.junit.runner.Description description) throws java.lang.Exception {
191    end(descriptionToShortTestName(description));
192  }
193}