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.devutils.inspector;
018
019import org.apache.wicket.Component;
020import org.apache.wicket.MarkupContainer;
021import org.apache.wicket.Page;
022import org.apache.wicket.application.IComponentInstantiationListener;
023import org.apache.wicket.behavior.Behavior;
024import org.apache.wicket.devutils.debugbar.DebugBar;
025import org.apache.wicket.markup.html.debug.PageView;
026import org.apache.wicket.util.lang.Classes;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030/**
031 * A listener that adds a special {@link Behavior} that measures the time needed by a component to
032 * render itself. {@link MarkupContainer}'s render includes the time for rendering its children so
033 * the time accumulates.
034 * <p>
035 * To enable this listener use the following in YourApplication.init():
036 * 
037 * <pre>
038 * getComponentInstantiationListeners().add(new RenderPerformanceListener());
039 * </pre>
040 * 
041 * </p>
042 */
043public class RenderPerformanceListener implements IComponentInstantiationListener
044{
045        private static final Logger log = LoggerFactory.getLogger(RenderPerformanceListener.class);
046
047        @Override
048        public void onInstantiation(final Component component)
049        {
050                if (accepts(component))
051                {
052                        component.add(new RenderMeasuringBehavior());
053                }
054        }
055
056        /**
057         * Filters which components' render performance should be measured.
058         * 
059         * @param component
060         *            the component that is instantiated
061         * @return {@code true} if render time should be measured the for this component, {@code false}
062         *         - otherwise
063         */
064        protected boolean accepts(final Component component)
065        {
066                return component.isAuto() == false;
067        }
068
069        /**
070         * A {@link Behavior} that sets the current time in the meta data of the component before its
071         * render starts and use it to calculate how long it took. The collected data is logged as debug
072         * and also can be read from the meta data with key {@link PageView#RENDER_KEY}.
073         * {@link DebugBar}'s inspector panel visualizes it.
074         */
075        private static class RenderMeasuringBehavior extends Behavior
076        {
077                private static final long serialVersionUID = 1L;
078
079                @Override
080                public void beforeRender(final Component component)
081                {
082                        super.beforeRender(component);
083                        if (component.isAuto() == false)
084                        {
085                                Long now = System.currentTimeMillis();
086                                component.setMetaData(PageView.RENDER_KEY, now);
087                        }
088                }
089
090                @Override
091                public void afterRender(final Component component)
092                {
093                        super.afterRender(component);
094                        Long renderEnd = System.currentTimeMillis();
095                        Long renderStart = component.getMetaData(PageView.RENDER_KEY);
096                        if (renderStart != null && component.isAuto() == false)
097                        {
098                                Long duration = renderEnd - renderStart;
099                                component.setMetaData(PageView.RENDER_KEY, duration);
100
101                                if (log.isDebugEnabled())
102                                {
103                                        String componentPath = (component instanceof Page) ? Classes.simpleName(component.getClass())
104                                                        + " page" : component.getPageRelativePath();
105                                        log.debug("rendered '{}' for {}ms", componentPath, duration);
106                                }
107                        }
108                }
109        }
110}