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