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}