001// Copyright 2005 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.hivemind.management.mbeans; 016 017import java.util.ArrayList; 018import java.util.HashMap; 019import java.util.Iterator; 020import java.util.List; 021import java.util.Map; 022import java.util.Set; 023 024import javax.management.AttributeNotFoundException; 025import javax.management.MBeanAttributeInfo; 026import javax.management.MBeanException; 027import javax.management.ReflectionException; 028 029import org.apache.hivemind.management.impl.PerformanceCollector; 030import org.apache.hivemind.service.MethodSignature; 031 032/** 033 * MBean that holds and calculates the performance data for service method calls intercepted by the 034 * {@link org.apache.hivemind.management.impl.PerformanceMonitorFactory performanceMonitor} 035 * interceptor. Creates for each intercepted method 5 MBean attributes: Number of Calls, Minimum, 036 * maximum, average and last execution time 037 * 038 * @author Achim Huegen 039 * @since 1.1 040 */ 041public class PerformanceMonitorMBean extends AbstractDynamicMBean implements PerformanceCollector 042{ 043 protected static final String DATA_TYPE_MAXIMUM_TIME = "Maximum time"; 044 045 protected static final String DATA_TYPE_MINIMUM_TIME = "Minimum time"; 046 047 protected static final String DATA_TYPE_LAST_TIME = "Last time"; 048 049 protected static final String DATA_TYPE_AVERAGE_TIME = "Average time"; 050 051 protected static final String DATA_TYPE_COUNT = "Count"; 052 053 private Set _methods; 054 055 private Map _countersByMethodSignature = new HashMap(); 056 057 private Map _countersByMethodId = new HashMap(); 058 059 private MBeanAttributeInfo[] _mBeanAttributeInfos; 060 061 private Map _mBeanAttributeNameToCounterMap = new HashMap(); 062 063 /** 064 * Creates a new instance 065 * 066 * @param methods 067 * Set with instances of {@link org.apache.hivemind.service.MethodSignature}. 068 * Contains the methods for that calls can be counted by this MBean 069 */ 070 public PerformanceMonitorMBean(Set methods) 071 { 072 _methods = methods; 073 initCounters(); 074 } 075 076 /** 077 * Builds two maps for accessing the counters by method signature and method id 078 */ 079 protected void initCounters() 080 { 081 List mBeanAttributeInfoList = new ArrayList(); 082 for (Iterator methodIterator = _methods.iterator(); methodIterator.hasNext();) 083 { 084 MethodSignature method = (MethodSignature) methodIterator.next(); 085 Counter counter = new Counter(); 086 _countersByMethodSignature.put(method, counter); 087 _countersByMethodId.put(method.getUniqueId(), counter); 088 089 initAttributes(mBeanAttributeInfoList, counter, method); 090 } 091 _mBeanAttributeInfos = (MBeanAttributeInfo[]) mBeanAttributeInfoList 092 .toArray(new MBeanAttributeInfo[mBeanAttributeInfoList.size()]); 093 } 094 095 /** 096 * Creates for a intercepted method 5 MBean attributes: Number of Calls, Minimum, maximum, 097 * average and last execution time 098 */ 099 protected void initAttributes(List mBeanAttributeInfoList, Counter counter, MethodSignature method) 100 { 101 addAttribute( 102 mBeanAttributeInfoList, counter, 103 method, 104 Long.class, 105 DATA_TYPE_COUNT, 106 "Number of method calls for method " + method); 107 addAttribute( 108 mBeanAttributeInfoList, counter, 109 method, 110 Long.class, 111 DATA_TYPE_AVERAGE_TIME, 112 "Average execution time in ms of method " + method); 113 addAttribute( 114 mBeanAttributeInfoList, counter, 115 method, 116 Long.class, 117 DATA_TYPE_LAST_TIME, 118 "Last execution time in ms of method " + method); 119 addAttribute( 120 mBeanAttributeInfoList, counter, 121 method, 122 Long.class, 123 DATA_TYPE_MINIMUM_TIME, 124 "Minimum execution time in ms of method " + method); 125 addAttribute( 126 mBeanAttributeInfoList, counter, 127 method, 128 Long.class, 129 DATA_TYPE_MAXIMUM_TIME, 130 "Maximum execution time in ms of method " + method); 131 132 } 133 134 /** 135 * Creates a new MBean Attribute for a performance counter 136 */ 137 private void addAttribute(List mBeanAttributeInfoList, Counter counter, MethodSignature method, 138 Class attributeType, String performanceDataType, String description) 139 { 140 String attributeName = null; 141 MBeanAttributeInfo attributeInfo = null; 142 try 143 { 144 attributeName = buildAttributeName(method, performanceDataType); 145 attributeInfo = new MBeanAttributeInfo(attributeName, attributeType.getName(), description, 146 true, false, false); 147 } 148 catch (IllegalArgumentException e) 149 { 150 // Some jmx implementations (jboss 3.2.7) don't accept spaces and braces 151 // in attribute names. In this case a fallback is executed, that replaces 152 // invalid chars by underscores. 153 attributeName = buildAttributeNameDefensive(method, performanceDataType); 154 attributeInfo = new MBeanAttributeInfo(attributeName, attributeType.getName(), description, 155 true, false, false); 156 } 157 mBeanAttributeInfoList.add(attributeInfo); 158 AttributeToCounterLink atcLink = new AttributeToCounterLink(counter, performanceDataType); 159 _mBeanAttributeNameToCounterMap.put(attributeName, atcLink); 160 } 161 162 /** 163 * Replaces all chars in a string which are not valid in a java identifier with underscores 164 */ 165 private String makeValidJavaIdentifier(String attributeName) 166 { 167 StringBuffer result = new StringBuffer(); 168 for (int i = 0; i < attributeName.length(); i++) 169 { 170 char currentChar = attributeName.charAt(i); 171 if (Character.isJavaIdentifierPart(currentChar)) 172 result.append(currentChar); 173 else result.append('_'); 174 } 175 return result.toString(); 176 } 177 178 /** 179 * Builds the attribute name that holds the measurement data of type 180 * <code>performanceDataType</code> for the method. 181 */ 182 protected String buildAttributeName(MethodSignature method, String performanceDataType) 183 { 184 String attributeName = method.getUniqueId() + " : " + performanceDataType; 185 return attributeName; 186 } 187 188 /** 189 * Builds the attribute name that holds the measurement data of type. 190 * <code>performanceDataType</code> for the method. 191 * Some jmx implementations (jboss 3.2.7) don't accept spaces and braces in attribute names. 192 * Unlike {@link #buildAttributeName(MethodSignature, String)} this method doesn't 193 * use chars that are not accepted by {@link Character#isJavaIdentifierPart(char)}. 194 */ 195 protected String buildAttributeNameDefensive(MethodSignature method, String performanceDataType) 196 { 197 String attributeName = method.getUniqueId() + "$[" + performanceDataType; 198 return makeValidJavaIdentifier(attributeName); 199 } 200 201 /** 202 * @see PerformanceCollector#addMeasurement(MethodSignature, long) 203 */ 204 public void addMeasurement(MethodSignature method, long executionTime) 205 { 206 Counter counter = (Counter) _countersByMethodSignature.get(method); 207 counter.addMeasurement(executionTime); 208 } 209 210 protected MBeanAttributeInfo[] createMBeanAttributeInfo() 211 { 212 return _mBeanAttributeInfos; 213 } 214 215 /** 216 * @see AbstractDynamicMBean#getAttribute(java.lang.String) 217 */ 218 public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, 219 ReflectionException 220 { 221 // Split the attribute to get method id and performance data type separately 222 AttributeToCounterLink atcLink = (AttributeToCounterLink) _mBeanAttributeNameToCounterMap.get(attribute); 223 if (atcLink == null) 224 throw new AttributeNotFoundException("Attribute '" + attribute + "' not found"); 225 226 String type = atcLink.type; 227 Counter counter = atcLink.counter; 228 if (type.equals(DATA_TYPE_COUNT)) 229 return new Long(counter.count); 230 else if (type.equals(DATA_TYPE_AVERAGE_TIME)) 231 return new Long(counter.average); 232 else if (type.equals(DATA_TYPE_LAST_TIME)) 233 return new Long(counter.last); 234 else if (type.equals(DATA_TYPE_MINIMUM_TIME)) 235 return new Long(counter.min); 236 else if (type.equals(DATA_TYPE_MAXIMUM_TIME)) 237 return new Long(counter.max); 238 else 239 throw new IllegalArgumentException("Unknown performance data type"); 240 } 241 242} 243 244/** 245 * Class that holds and calculates the performance data for a single method 246 */ 247 248class Counter 249{ 250 long count = 0; 251 252 long last = 0; 253 254 long average = 0; 255 256 long max = 0; 257 258 long min = 0; 259 260 public String toString() 261 { 262 return "" + count; 263 } 264 265 /** 266 * Should be synchronized, but this could slow things really down 267 * 268 * @param executionTime 269 */ 270 public void addMeasurement(long executionTime) 271 { 272 count++; 273 last = executionTime; 274 // not an exact value without a complete history and stored as long 275 average = (average * (count - 1) + executionTime) / count; 276 if (executionTime < min || min == 0) 277 min = executionTime; 278 if (executionTime > max || max == 0) 279 max = executionTime; 280 } 281} 282 283class AttributeToCounterLink 284{ 285 Counter counter; 286 287 String type; 288 289 public AttributeToCounterLink(Counter counter, String type) 290 { 291 this.counter = counter; 292 this.type = type; 293 } 294}