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
015 package org.apache.hivemind.management.mbeans;
016
017 import java.util.ArrayList;
018 import java.util.HashMap;
019 import java.util.Iterator;
020 import java.util.List;
021 import java.util.Map;
022 import java.util.Set;
023
024 import javax.management.AttributeNotFoundException;
025 import javax.management.MBeanAttributeInfo;
026 import javax.management.MBeanException;
027 import javax.management.ReflectionException;
028
029 import org.apache.hivemind.management.impl.PerformanceCollector;
030 import 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 */
041 public 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
248 class 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
283 class 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 }