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