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}