001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.metrics;
018
019import org.apache.wicket.Application;
020import org.apache.wicket.MetaDataKey;
021import org.apache.wicket.WicketRuntimeException;
022import org.aspectj.lang.ProceedingJoinPoint;
023
024import com.codahale.metrics.Counter;
025import com.codahale.metrics.MetricRegistry;
026import com.codahale.metrics.Timer.Context;
027
028/**
029 * Base aspect provides access to the metric registry
030 * 
031 * @author Tobias Soloschenko
032 *
033 */
034public class WicketMetrics
035{
036
037        /**
038         * The name of the filter the metrics are going to collect of
039         */
040        private static String filterName;
041
042        private static final String APPLICATION_ERROR = "The application couldn't be resolved, please ensure to apply \"<aspect name=\"org.apache.wicket.metrics.aspects.WicketFilterInitAspect\" />\" to your aspects";
043
044        /** The key for metrics registry **/
045        public static final MetaDataKey<MetricRegistry> METRIC_REGISTRY = new MetaDataKey<>()
046        {
047                private static final long serialVersionUID = 1L;
048        };
049
050        /** The key for metrics registry **/
051        public static final MetaDataKey<WicketMetricsSettings> METRIC_SETTINGS = new MetaDataKey<>()
052        {
053                private static final long serialVersionUID = 1L;
054        };
055
056        /**
057         * Simply measure the time for a {@literal @}around
058         * 
059         * @param name
060         *            the name of the timer context
061         * @param joinPoint
062         *            the joinPoint to be proceed
063         * 
064         * @return the value of the join point
065         * @throws Throwable
066         *             if there is an exception while execution
067         * @see org.apache.wicket.metrics.WicketMetrics#measureTime(String, ProceedingJoinPoint,
068         *      boolean)
069         */
070        public Object measureTime(String name, ProceedingJoinPoint joinPoint) throws Throwable
071        {
072                return this.measureTime(name, joinPoint, true);
073        }
074
075        /**
076         * Simply measure the time for a {@literal @}around
077         * 
078         * @param name
079         *            the name of the timer context
080         * @param joinPoint
081         *            the joinPoint to be proceed
082         * @param renderClass
083         *            if the class name should be rendered behind the metric path
084         * 
085         * @return the value of the join point
086         * @throws Throwable
087         *             if there is an exception while execution
088         */
089        public Object measureTime(String name, ProceedingJoinPoint joinPoint, boolean renderClass)
090                throws Throwable
091        {
092                WicketMetricsSettings settings = getSettings();
093                MetricRegistry registry = getMetricRegistry();
094
095                if (settings.isEnabled())
096                {
097                        Context context = registry
098                                .timer(
099                                        settings.getPrefix() + name + (renderClass ? renderClassName(joinPoint) : ""))
100                                .time();
101                        try
102                        {
103                                return proceedSilent(joinPoint);
104                        }
105                        finally
106                        {
107                                stopQuietly(context);
108                        }
109                }
110                else
111                {
112                        return proceedSilent(joinPoint);
113                }
114        }
115
116        /**
117         * Stops the context quietly
118         * 
119         * @param context
120         *            the context to stop
121         */
122        public void stopQuietly(Context context)
123        {
124                if (context != null)
125                {
126                        context.stop();
127                }
128        }
129
130        /**
131         * 
132         * @author Tobias Soloschenko
133         *
134         */
135        public enum CounterOperation {
136                /**
137                 * Increments
138                 */
139                INC,
140                /**
141                 * Decrements
142                 */
143                DEC
144        }
145
146        /**
147         * Creates a count of the given arguments
148         * 
149         * @param name
150         *            the name of the meter to be marked
151         * @param joinPoint
152         *            the join point
153         * @param counterOperation
154         *            the operation
155         * @param value
156         *            the value to update the counter
157         * @return the result of the proceeded join point
158         * @throws Throwable
159         */
160        public Object count(String name, ProceedingJoinPoint joinPoint,
161                CounterOperation counterOperation, Long value) throws Throwable
162        {
163                WicketMetricsSettings settings = getSettings();
164                MetricRegistry registry = getMetricRegistry();
165
166                if (settings.isEnabled())
167                {
168                        Counter counter = registry
169                                .counter(settings.getPrefix() + name + renderClassName(joinPoint));
170                        if (counterOperation == CounterOperation.INC)
171                        {
172                                counter.inc(value);
173                        }
174                        else
175                        {
176                                counter.dec(value);
177                        }
178                }
179                return proceedSilent(joinPoint);
180        }
181
182
183        /**
184         * Marks the meter with the given name
185         * 
186         * @param name
187         *            the name of the meter to be marked
188         * @param joinPoint
189         *            the join point
190         * @return the result of the proceeded join point
191         * @throws Throwable
192         */
193        public Object mark(String name, ProceedingJoinPoint joinPoint) throws Throwable
194        {
195                WicketMetricsSettings settings = getSettings();
196                MetricRegistry registry = getMetricRegistry();
197
198                if (settings.isEnabled())
199                {
200                        registry.meter(settings.getPrefix() + name + renderClassName(joinPoint)).mark();
201                }
202                return proceedSilent(joinPoint);
203        }
204
205        /**
206         * Proceed the join point silent
207         * 
208         * @param joinPoint
209         *            the join point to proceed
210         * @return the result of the proceeded join point
211         * @throws Throwable
212         *             if the invocation throws an error
213         */
214        private Object proceedSilent(ProceedingJoinPoint joinPoint) throws Throwable
215        {
216                return joinPoint != null ? joinPoint.proceed() : null;
217        }
218
219        /**
220         * Renders the class name of the given join point
221         * 
222         * @param joinPoint
223         *            the join point to get the class of
224         * @return the class name representation
225         */
226        public String renderClassName(ProceedingJoinPoint joinPoint)
227        {
228                return joinPoint != null && joinPoint.getTarget() != null
229                        ? "/" + joinPoint.getTarget().getClass().getName().replace('.', '_') : "";
230        }
231
232        /**
233         * Gets the metric registry
234         * 
235         * @return the metric registry
236         */
237        public static synchronized MetricRegistry getMetricRegistry()
238        {
239                Application application = Application.get(getFilterName());
240                if (application == null)
241                {
242                        throw new WicketRuntimeException(APPLICATION_ERROR);
243                }
244                MetricRegistry metricRegistry = application.getMetaData(METRIC_REGISTRY);
245                if (metricRegistry == null)
246                {
247                        metricRegistry = new MetricRegistry();
248                        application.setMetaData(METRIC_REGISTRY, metricRegistry);
249                }
250                return metricRegistry;
251        }
252
253        /**
254         * Gets the wicket metrics settings
255         * 
256         * @return the wicket metrics settings
257         */
258        public static synchronized WicketMetricsSettings getSettings()
259        {
260                Application application = Application.get(getFilterName());
261                if (application == null)
262                {
263                        throw new WicketRuntimeException(APPLICATION_ERROR);
264                }
265                WicketMetricsSettings wicketMetricsSettings = application.getMetaData(METRIC_SETTINGS);
266                if (wicketMetricsSettings == null)
267                {
268                        wicketMetricsSettings = new WicketMetricsSettings();
269                        wicketMetricsSettings.setPrefix(getFilterName());
270                        application.setMetaData(METRIC_SETTINGS, wicketMetricsSettings);
271                }
272                return wicketMetricsSettings;
273        }
274
275        /**
276         * Gets the filter name the application should be resolved with
277         * 
278         * @return the filter name
279         */
280        public static String getFilterName()
281        {
282                return filterName;
283        }
284
285        /**
286         * Sets the filter name the application should be resolved with
287         * 
288         * @param filterName
289         *            the filter name
290         */
291        public static void setFilterName(String filterName)
292        {
293                WicketMetrics.filterName = filterName;
294        }
295}