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    }