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}