001/*
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase;
020
021import java.util.ArrayList;
022import java.util.List;
023
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027/**
028 * Utility class to check the resources:
029 *  - log them before and after each test method
030 *  - check them against a minimum or maximum
031 *  - check that they don't leak during the test
032 */
033public class ResourceChecker {
034  private static final Logger LOG = LoggerFactory.getLogger(ResourceChecker.class);
035  private String tagLine;
036
037  enum Phase {
038    INITIAL, INTERMEDIATE, END
039  }
040
041  /**
042   * Constructor
043   * @param tagLine The tagLine is added to the logs. Must not be null.
044   */
045  public ResourceChecker(final String tagLine) {
046    this.tagLine = tagLine;
047  }
048
049  /**
050   * Class to implement for each type of resource.
051   */
052  abstract static class ResourceAnalyzer {
053    /**
054     * Maximum we set for the resource. Will get a warning in logs
055     * if we go over this limit.
056     */
057    public int getMax() {
058      return Integer.MAX_VALUE;
059    }
060
061    /**
062     * Minimum we set for the resource. Will get a warning in logs
063     * if we go under this limit.
064     */
065    public int getMin() {
066      return Integer.MIN_VALUE;
067    }
068
069    /**
070     * Name of the resource analyzed. By default extracted from the class name, but
071     *  can be overridden by the subclasses.
072     */
073    public String getName() {
074      String className = this.getClass().getSimpleName();
075      final String extName = ResourceAnalyzer.class.getSimpleName();
076      if (className.endsWith(extName)) {
077        return className.substring(0, className.length() - extName.length());
078      } else {
079        return className;
080      }
081    }
082
083    /**
084     * The value for the resource.
085     * @param phase the {@link Phase} to get the value for
086     */
087    abstract public int getVal(Phase phase);
088    
089    /*
090     * Retrieves List of Strings which would be logged in logEndings()
091     */
092    public List<String> getStringsToLog() {
093      return null;
094    }
095  }
096
097  private List<ResourceAnalyzer> ras = new ArrayList<>();
098  private int[] initialValues;
099  private int[] endingValues;
100
101  private void fillInit() {
102    initialValues = new int[ras.size()];
103    fill(Phase.INITIAL, initialValues);
104  }
105
106  private void fillEndings() {
107    endingValues = new int[ras.size()];
108    fill(Phase.END, endingValues);
109  }
110
111  private void fill(Phase phase, int[] vals) {
112    int i = 0;
113    for (ResourceAnalyzer ra : ras) {
114      vals[i++] = ra.getVal(phase);
115    }
116  }
117
118  public void checkInit() {
119    check(initialValues);
120  }
121
122  private void checkEndings() {
123    check(endingValues);
124  }
125
126  private void check(int[] vals) {
127    int i = 0;
128    for (ResourceAnalyzer ra : ras) {
129      int cur = vals[i++];
130      if (cur < ra.getMin()) {
131        LOG.warn(ra.getName() + "=" + cur + " is inferior to " + ra.getMin());
132      }
133      if (cur > ra.getMax()) {
134        LOG.warn(ra.getName() + "=" + cur + " is superior to " + ra.getMax());
135      }
136    }
137  }
138
139  private void logInit() {
140    int i = 0;
141    StringBuilder sb = new StringBuilder();
142    for (ResourceAnalyzer ra : ras) {
143      int cur = initialValues[i++];
144
145      if (sb.length() > 0) {
146        sb.append(", ");
147      }
148
149      sb.append(ra.getName()).append("=").append(cur);
150    }
151    LOG.info("before: " + tagLine + " " + sb);
152  }
153
154  private void logEndings() {
155    assert initialValues.length == ras.size();
156    assert endingValues.length == ras.size();
157
158    int i = 0;
159    StringBuilder sb = new StringBuilder();
160    for (ResourceAnalyzer ra : ras) {
161      int curP = initialValues[i];
162      int curN = endingValues[i++];
163
164      if (sb.length() > 0) {
165        sb.append(", ");
166      }
167
168      sb.append(ra.getName()).append("=").append(curN).append(" (was ").append(curP).append(")");
169      if (curN > curP) {
170        List<String> strings = ra.getStringsToLog();
171        if (strings != null) {
172          for (String s : strings) {
173            sb.append(s);
174          }
175        }
176        sb.append(" - ").append(ra.getName()).append(" LEAK? -");
177      }
178    }
179    LOG.info("after: " + tagLine + " " + sb);
180  }
181
182  /**
183   * To be called as the beginning of a test method:
184   * - measure the resources
185   * - check vs. the limits.
186   * - logs them.
187   */
188  public void start() {
189    if (ras.isEmpty()) {
190      LOG.info("No resource analyzer");
191      return;
192    }
193    fillInit();
194    logInit();
195    checkInit();
196  }
197
198  /**
199   * To be called as the end of a test method:
200   * - measure the resources
201   * - check vs. the limits.
202   * - check vs. the initial state
203   * - logs them.
204   */
205  public void end() {
206    if (ras.isEmpty()) {
207      LOG.info("No resource analyzer");
208      return;
209    }
210    if (initialValues == null) {
211      LOG.warn("No initial values");
212      return;
213    }
214
215    fillEndings();
216    logEndings();
217    checkEndings();
218  }
219
220  /**
221   * Adds a resource analyzer to the resources checked.
222   */
223  public void addResourceAnalyzer(ResourceAnalyzer ra) {
224    ras.add(ra);
225  }
226}