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
042: implements PerformanceCollector {
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: _methods = methods;
072: initCounters();
073: }
074:
075: /**
076: * Builds two maps for accessing the counters by method signature and method id
077: */
078: protected void initCounters() {
079: List mBeanAttributeInfoList = new ArrayList();
080: for (Iterator methodIterator = _methods.iterator(); methodIterator
081: .hasNext();) {
082: MethodSignature method = (MethodSignature) methodIterator
083: .next();
084: Counter counter = new Counter();
085: _countersByMethodSignature.put(method, counter);
086: _countersByMethodId.put(method.getUniqueId(), counter);
087:
088: initAttributes(mBeanAttributeInfoList, counter, method);
089: }
090: _mBeanAttributeInfos = (MBeanAttributeInfo[]) mBeanAttributeInfoList
091: .toArray(new MBeanAttributeInfo[mBeanAttributeInfoList
092: .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,
100: Counter counter, MethodSignature method) {
101: addAttribute(mBeanAttributeInfoList, counter, method,
102: Long.class, DATA_TYPE_COUNT,
103: "Number of method calls for method " + method);
104: addAttribute(mBeanAttributeInfoList, counter, method,
105: Long.class, DATA_TYPE_AVERAGE_TIME,
106: "Average execution time in ms of method " + method);
107: addAttribute(mBeanAttributeInfoList, counter, method,
108: Long.class, DATA_TYPE_LAST_TIME,
109: "Last execution time in ms of method " + method);
110: addAttribute(mBeanAttributeInfoList, counter, method,
111: Long.class, DATA_TYPE_MINIMUM_TIME,
112: "Minimum execution time in ms of method " + method);
113: addAttribute(mBeanAttributeInfoList, counter, method,
114: Long.class, DATA_TYPE_MAXIMUM_TIME,
115: "Maximum execution time in ms of method " + method);
116:
117: }
118:
119: /**
120: * Creates a new MBean Attribute for a performance counter
121: */
122: private void addAttribute(List mBeanAttributeInfoList,
123: Counter counter, MethodSignature method,
124: Class attributeType, String performanceDataType,
125: String description) {
126: String attributeName = null;
127: MBeanAttributeInfo attributeInfo = null;
128: try {
129: attributeName = buildAttributeName(method,
130: performanceDataType);
131: attributeInfo = new MBeanAttributeInfo(attributeName,
132: attributeType.getName(), description, true, false,
133: false);
134: } catch (IllegalArgumentException e) {
135: // Some jmx implementations (jboss 3.2.7) don't accept spaces and braces
136: // in attribute names. In this case a fallback is executed, that replaces
137: // invalid chars by underscores.
138: attributeName = buildAttributeNameDefensive(method,
139: performanceDataType);
140: attributeInfo = new MBeanAttributeInfo(attributeName,
141: attributeType.getName(), description, true, false,
142: false);
143: }
144: mBeanAttributeInfoList.add(attributeInfo);
145: AttributeToCounterLink atcLink = new AttributeToCounterLink(
146: counter, performanceDataType);
147: _mBeanAttributeNameToCounterMap.put(attributeName, atcLink);
148: }
149:
150: /**
151: * Replaces all chars in a string which are not valid in a java identifier with underscores
152: */
153: private String makeValidJavaIdentifier(String attributeName) {
154: StringBuffer result = new StringBuffer();
155: for (int i = 0; i < attributeName.length(); i++) {
156: char currentChar = attributeName.charAt(i);
157: if (Character.isJavaIdentifierPart(currentChar))
158: result.append(currentChar);
159: else
160: result.append('_');
161: }
162: return result.toString();
163: }
164:
165: /**
166: * Builds the attribute name that holds the measurement data of type
167: * <code>performanceDataType</code> for the method.
168: */
169: protected String buildAttributeName(MethodSignature method,
170: String performanceDataType) {
171: String attributeName = method.getUniqueId() + " : "
172: + performanceDataType;
173: return attributeName;
174: }
175:
176: /**
177: * Builds the attribute name that holds the measurement data of type.
178: * <code>performanceDataType</code> for the method.
179: * Some jmx implementations (jboss 3.2.7) don't accept spaces and braces in attribute names.
180: * Unlike {@link #buildAttributeName(MethodSignature, String)} this method doesn't
181: * use chars that are not accepted by {@link Character#isJavaIdentifierPart(char)}.
182: */
183: protected String buildAttributeNameDefensive(
184: MethodSignature method, String performanceDataType) {
185: String attributeName = method.getUniqueId() + "$["
186: + performanceDataType;
187: return makeValidJavaIdentifier(attributeName);
188: }
189:
190: /**
191: * @see PerformanceCollector#addMeasurement(MethodSignature, long)
192: */
193: public void addMeasurement(MethodSignature method,
194: long executionTime) {
195: Counter counter = (Counter) _countersByMethodSignature
196: .get(method);
197: counter.addMeasurement(executionTime);
198: }
199:
200: protected MBeanAttributeInfo[] createMBeanAttributeInfo() {
201: return _mBeanAttributeInfos;
202: }
203:
204: /**
205: * @see AbstractDynamicMBean#getAttribute(java.lang.String)
206: */
207: public Object getAttribute(String attribute)
208: throws AttributeNotFoundException, MBeanException,
209: ReflectionException {
210: // Split the attribute to get method id and performance data type separately
211: AttributeToCounterLink atcLink = (AttributeToCounterLink) _mBeanAttributeNameToCounterMap
212: .get(attribute);
213: if (atcLink == null)
214: throw new AttributeNotFoundException("Attribute '"
215: + attribute + "' not found");
216:
217: String type = atcLink.type;
218: Counter counter = atcLink.counter;
219: if (type.equals(DATA_TYPE_COUNT))
220: return new Long(counter.count);
221: else if (type.equals(DATA_TYPE_AVERAGE_TIME))
222: return new Long(counter.average);
223: else if (type.equals(DATA_TYPE_LAST_TIME))
224: return new Long(counter.last);
225: else if (type.equals(DATA_TYPE_MINIMUM_TIME))
226: return new Long(counter.min);
227: else if (type.equals(DATA_TYPE_MAXIMUM_TIME))
228: return new Long(counter.max);
229: else
230: throw new IllegalArgumentException(
231: "Unknown performance data type");
232: }
233:
234: }
235:
236: /**
237: * Class that holds and calculates the performance data for a single method
238: */
239:
240: class Counter {
241: long count = 0;
242:
243: long last = 0;
244:
245: long average = 0;
246:
247: long max = 0;
248:
249: long min = 0;
250:
251: public String toString() {
252: return "" + count;
253: }
254:
255: /**
256: * Should be synchronized, but this could slow things really down
257: *
258: * @param executionTime
259: */
260: public void addMeasurement(long executionTime) {
261: count++;
262: last = executionTime;
263: // not an exact value without a complete history and stored as long
264: average = (average * (count - 1) + executionTime) / count;
265: if (executionTime < min || min == 0)
266: min = executionTime;
267: if (executionTime > max || max == 0)
268: max = executionTime;
269: }
270: }
271:
272: class AttributeToCounterLink {
273: Counter counter;
274:
275: String type;
276:
277: public AttributeToCounterLink(Counter counter, String type) {
278: this.counter = counter;
279: this.type = type;
280: }
281: }
|