Skip to content

Hibernate statistics page

January 4, 2010

Hi !

We were some time ago at a presentation about hibernate (at a Karlsruhe JUG event). The presenter, Michael Plöd showed us a nice Hibernate statistics page. As you can bet, we went for reusing it :)

However, in doing so, some issues appeared, and in the end we thought the updated version could be useful for others as well.

It mainly consists in an adaptation to Wicket 1.4 (generics).

In details, the changes are :
- use of ReloadableDetachableModel, needed AFAIK by Hibernate statistics (they have transient fields in their objects, and as such I was getting NPE after refreshing the page),
- page completely “genericfied” : compact code, no more cast or compiler warning (the page was written for wicket 1.3 I presume),
- minor display issues fixed : date showed as a formatted date, executionMinTime’s default value (Long.MAX_VALUE) taken in account (and replace by zero),
- html closer from the “standards” : use of h1, thead and the like (which we use in our template, so…),
- in order to make the component injection independent, there is now a setter for the entity manager provider (consequently the actual page construction is made in “onBeforeRender”),
- the code is now in a panel, so it can be easily put in any application specific page.

Whatever, the code, quite long long, is following.

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;

import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.stat.CollectionStatistics;
import org.hibernate.stat.EntityStatistics;
import org.hibernate.stat.QueryStatistics;
import org.hibernate.stat.SecondLevelCacheStatistics;
import org.hibernate.stat.Statistics;

public class HibernateStatisticsPanel extends Panel
{
    private static final String DATE_FORMAT = "hh'h'mm dd.yy.MM";

    private EntityManager entityManager;

    private static DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);

    public HibernateStatisticsPanel(final String id)
    {
        super(id);
    }

     @Override
    protected void onBeforeRender()
    {
        if (!hasBeenRendered())
        {
            if (entityManager == null)
            {
                throw new IllegalStateException("The entityManagerProvider must be set.");
            }
            final CompoundPropertyModel model = new CompoundPropertyModel(
                    new LoadableDetachableModel()
                    {
                         @Override
                        protected Statistics load()
                        {

                            return getSessionFactory(entityManager).getStatistics();
                        }
                    });
            setDefaultModel(model);

            add(new Label("isStatisticsEnabled", new LoadableDetachableModel()
            {

                 @Override
                protected String load()
                {
                    String result;
                    if (areStatsEnabled() == true)
                    {
                        result = "enabled" + " since " + dateFormat.format(new Date(model.getObject().getStartTime()))
                                + " (" + DATE_FORMAT + ")";
                    }
                    else
                    {
                        result = "disabled";
                    }
                    return result;
                }

            }));

            Link switchStats = new Link("switch_stats")
            {
                 @Override
                public void onClick()
                {
                    final SessionFactory sessionFactory = getSessionFactory(entityManager);
                    sessionFactory.getStatistics().setStatisticsEnabled(
                            !sessionFactory.getStatistics().isStatisticsEnabled());
                    sessionFactory.getStatistics().clear();
                    final CompoundPropertyModel model = new CompoundPropertyModel(
                            new LoadableDetachableModel()
                            {
                                 @Override
                                protected Statistics load()
                                {
                                    return sessionFactory.getStatistics();
                                }
                            });
                    setDefaultModel(model);
                }
            };
            switchStats.add(new Label("switchText", new LoadableDetachableModel()
            {

                 @Override
                protected String load()
                {
                    if (areStatsEnabled())
                    {
                        return "Click to disable stats";
                    }
                    return "Click to enable stats";
                }
            }));
            add(switchStats);

            add(new Label("sessionOpenCount"));
            add(new Label("sessionCloseCount"));
            add(new Label("flushCount"));
            add(new Label("connectCount"));
            add(new Label("prepareStatementCount"));
            add(new Label("closeStatementCount"));
            add(new Label("entityLoadCount"));
            add(new Label("entityUpdateCount"));
            add(new Label("entityInsertCount"));
            add(new Label("entityDeleteCount"));
            add(new Label("entityFetchCount"));
            add(new Label("collectionLoadCount"));
            add(new Label("collectionUpdateCount"));
            add(new Label("collectionRemoveCount"));
            add(new Label("collectionRecreateCount"));
            add(new Label("collectionFetchCount"));
            add(new Label("secondLevelCacheHitCount"));
            add(new Label("secondLevelCacheMissCount"));
            add(new Label("secondLevelCachePutCount"));
            add(new Label("queryExecutionCount"));
            add(new Label("queryExecutionMaxTime"));
            add(new Label("queryExecutionMaxTimeQueryString"));
            add(new Label("queryCacheHitCount"));
            add(new Label("queryCacheMissCount"));
            add(new Label("queryCachePutCount"));
            add(new Label("commitedTransactionCount"));
            add(new Label("transactionCount"));
            add(new Label("optimisticFailureCount"));

            ListView entityStats = new ListView("entities",
                    new LoadableDetachableModel< List  >()
                    {

                         @Override
                        protected List load()
                        {
                            String[] entities = model.getObject().getEntityNames();
                            List entityNames = new ArrayList();
                            for (int i = 0; i < entities.length; i++)
                            {
                                entityNames.add(model.getObject().getEntityStatistics(entities[i]));
                            }
                            return entityNames;
                        }
                    })
            {
                 @Override
                protected void populateItem(final ListItem item)
                {
                    item.setModel(new CompoundPropertyModel(item.getModelObject()));

                    item.add(new Label("deleteCount"));
                    item.add(new Label("updateCount"));
                    item.add(new Label("fetchCount"));
                    item.add(new Label("insertCount"));
                    item.add(new Label("loadCount"));
                    item.add(new Label("optimisticFailureCount"));
                    item.add(new Label("categoryName"));
                }
            };
            add(entityStats);

            ListView collectionStats = new ListView("collections",
                    new LoadableDetachableModel()
                    {

                         @Override
                        protected List load()
                        {
                            String[] collections = (model.getObject()).getCollectionRoleNames();
                            List collectionNames = new ArrayList();
                            for (int i = 0; i < collections.length; i++)
                            {
                                collectionNames.add(model.getObject().getCollectionStatistics(collections[i]));
                            }
                            return collectionNames;
                        }
                    })
            {
                 @Override
                protected void populateItem(final ListItem item)
                {
                    item.setModel(new CompoundPropertyModel(item.getModelObject()));

                    item.add(new Label("recreateCount"));
                    item.add(new Label("updateCount"));
                    item.add(new Label("fetchCount"));
                    item.add(new Label("removeCount"));
                    item.add(new Label("loadCount"));
                    item.add(new Label("categoryName"));
                }
            };
            add(collectionStats);

            ListView queryStats = new ListView("queries",
                    new LoadableDetachableModel< List  >()
                    {

                         @Override
                        protected List load()
                        {
                            String[] queries = (model.getObject()).getQueries();
                            List queryNames = new ArrayList();
                            for (int i = 0; i < queries.length; i++)
                            {
                                queryNames.add(model.getObject().getQueryStatistics(queries[i]));
                            }
                            return queryNames;
                        }
                    })
            {
                 @Override
                protected void populateItem(final ListItem item)
                {
                    item.setModel(new CompoundPropertyModel(item.getModelObject()));

                    item.add(new Label("cacheHitCount"));
                    item.add(new Label("cacheMissCount"));
                    item.add(new Label("cachePutCount"));
                    item.add(new Label("executionCount"));
                    item.add(new Label("executionRowCount"));
                    item.add(new Label("executionAvgTime"));
                    item.add(new Label("executionMaxTime"));
                    item.add(new Label("executionMinTime", new AbstractReadOnlyModel()
                    {

                         @Override
                        public String getObject()
                        {
                            // by default the hibernate stats. object put
                            // Long.MAX_VALUE to the executionMinTime, so we
                            // look for it and replace it by 0 where needed
                            long executionMinTime = item.getModelObject().getExecutionMinTime();
                            if (executionMinTime == Long.MAX_VALUE)
                            {
                                return "0";
                            }
                            return "" + executionMinTime;
                        }
                    }));
                    item.add(new Label("categoryName"));
                }
            };
            add(queryStats);

            ListView cacheStats = new ListView("caches",
                    new LoadableDetachableModel< List  >()
                    {
                         @Override
                        protected List load()
                        {
                            String[] caches = model.getObject().getSecondLevelCacheRegionNames();
                            List cacheNames = new ArrayList();
                            for (int i = 0; i < caches.length; i++)
                            {
                                cacheNames.add(model.getObject().getSecondLevelCacheStatistics(caches[i]));
                            }
                            return cacheNames;
                        }
                    })
            {
                 @Override
                protected void populateItem(final ListItem item)
                {
                    item.setModel(new CompoundPropertyModel(item.getModelObject()));

                    item.add(new Label("hitCount"));
                    item.add(new Label("missCount"));
                    item.add(new Label("putCount"));
                    item.add(new Label("elementCountInMemory"));
                    item.add(new Label("elementCountOnDisk"));
                    item.add(new Label("sizeInMemory"));
                    item.add(new Label("categoryName"));
                }
            };
            add(cacheStats);

        }
        super.onBeforeRender();
    }

    private boolean areStatsEnabled()
    {
        return ((Statistics) getDefaultModelObject()).isStatisticsEnabled();
    }

    public static SessionFactory getSessionFactory(final EntityManager entityManager)
    {
        Object delegate = entityManager.getDelegate();
        if (delegate instanceof Session)
        {
            Session session = (Session) delegate;

            return session.getSessionFactory();
        }
        return null;
    }

    public void setEntityManager(final EntityManager entityManager)
    {
        this.entityManager = entityManager;
    }

    public EntityManager getEntityManager()
    {
        return entityManager;
    }
}

And the html page :

<html>
     <wicket:panel>
              <h1>Hibernate Statistics</h1>
               Statistics are <span wicket:id="isStatisticsEnabled"/>.<br /> <a href="#" wicket:id="switch_stats"><span wicket:id="switchText"></span></a>
     <br /><br />
     <h2>Overview</h2>
    <table class="whiteGrey">
        <tr><th>sessionOpenCount</th><td><span wicket:id="sessionOpenCount"/></td></tr>
        <tr><th>sessionCloseCount</th><td><span wicket:id="sessionCloseCount"/></td></tr>
        <tr><th>flushCount</th><td><span wicket:id="flushCount"/></td></tr>
        <tr><th>connectCount</th><td><span wicket:id="connectCount"/></td></tr>
        <tr><th>prepareStatementCount</th><td><span wicket:id="prepareStatementCount"/></td></tr>
        <tr><th>closeStatementCount</th><td><span wicket:id="closeStatementCount"/></td></tr>
        <tr><th>entityLoadCount</th><td><span wicket:id="entityLoadCount"/></td></tr>
        <tr><th>entityUpdateCount</th><td><span wicket:id="entityUpdateCount"/></td></tr>
        <tr><th>entityInsertCount</th><td><span wicket:id="entityInsertCount"/></td></tr>
        <tr><th>entityDeleteCount</th><td><span wicket:id="entityDeleteCount"/></td></tr>
        <tr><th>entityFetchCount</th><td><span wicket:id="entityFetchCount"/></td></tr>
        <tr><th>collectionLoadCount</th><td><span wicket:id="collectionLoadCount"/></td></tr>
        <tr><th>collectionUpdateCount</th><td><span wicket:id="collectionUpdateCount"/></td></tr>
        <tr><th>collectionRemoveCount</th><td><span wicket:id="collectionRemoveCount"/></td></tr>
        <tr><th>collectionRecreateCount</th><td><span wicket:id="collectionRecreateCount"/></td></tr>
        <tr><th>collectionFetchCount</th><td><span wicket:id="collectionFetchCount"/></td></tr>
        <tr><th>secondLevelCacheHitCount</th><td><span wicket:id="secondLevelCacheHitCount"/></td></tr>
        <tr><th>secondLevelCacheMissCount</th><td><span wicket:id="secondLevelCacheMissCount"/></td></tr>
        <tr><th>secondLevelCachePutCount</th><td><span wicket:id="secondLevelCachePutCount"/></td></tr>
        <tr><th>queryExecutionCount</th><td><span wicket:id="queryExecutionCount"/></td></tr>
        <tr><th>queryExecutionMaxTime</th><td><span wicket:id="queryExecutionMaxTime"/></td></tr>
        <tr><th>queryExecutionMaxTimeQueryString</th><td><span wicket:id="queryExecutionMaxTimeQueryString"/></td></tr>
        <tr><th>queryCacheHitCount</th><td><span wicket:id="queryCacheHitCount"/></td></tr>
        <tr><th>queryCacheMissCount</th><td><span wicket:id="queryCacheMissCount"/></td></tr>
        <tr><th>queryCachePutCount</th><td><span wicket:id="queryCachePutCount"/></td></tr>
        <tr><th>commitedTransactionCount</th><td><span wicket:id="commitedTransactionCount"/></td></tr>
        <tr><th>transactionCount</th><td><span wicket:id="transactionCount"/></td></tr>
        <tr><th>optimisticFailureCount</th><td><span wicket:id="optimisticFailureCount"/></td></tr>           
    </table>
    
    <h2>Entity Statistics</h2>
    <table class="whiteGrey">
        <thead>
             <tr>
                 <th>Entity</th>
                 <th>Load Count</th>
                 <th>Fetch Count</th>
                 <th>Insert Count</th>
                 <th>Delete Count</th>
                 <th>Update Count</th>
                 <th>Optimistic Failure Count</th>
               </tr>
        </thead>
          <tbody>
            <tr wicket:id="entities">
                <td><span wicket:id="categoryName"/></td>
                <td><span wicket:id="loadCount"/></td>
                <td><span wicket:id="fetchCount"/></td>
                <td><span wicket:id="insertCount"/></td>
                <td><span wicket:id="deleteCount"/></td>
                <td><span wicket:id="updateCount"/></td>
                <td><span wicket:id="optimisticFailureCount"/></td>
            </tr>
          </tbody>               
    </table>
    
    <h2>Collection Statistics</h2>
    <table class="whiteGrey">
        <thead>
             <tr>
                 <th>Collection</th>
                 <th>Load Count</th>
                 <th>Fetch Count</th>
                 <th>Recreate Count</th>
                 <th>Remove Count</th>
                 <th>Update Count</th>
               </tr>
        </thead>
          <tbody>
            <tr wicket:id="collections">
                <td><span wicket:id="categoryName"/></td>
                <td><span wicket:id="loadCount"/></td>
                <td><span wicket:id="fetchCount"/></td>
                <td><span wicket:id="recreateCount"/></td>
                <td><span wicket:id="removeCount"/></td>
                <td><span wicket:id="updateCount"/></td>
            </tr>
          </tbody>
    </table>
    
    <h2>Query Statistics</h2>
    <table class="whiteGrey">
        <thead>
             <tr>
                 <th>Query</th>
                 <th>Execution Count</th>
                 <th>Execution Row Count</th>
                 <th>Avg Time</th>
                 <th>Min Time</th>
                 <th>Max Time</th>
                 <th>Cache Hit Count</th>
                 <th>Cache Miss Count</th>
                 <th>Cache Put Count</th>
               </tr>
          </thead>
          <tbody>
            <tr wicket:id="queries">
                <td><span wicket:id="categoryName"/></td>
                <td><span wicket:id="executionCount"/></td>
                <td><span wicket:id="executionRowCount"/></td>
                <td><span wicket:id="executionAvgTime"/></td>
                <td><span wicket:id="executionMinTime"/></td>
                <td><span wicket:id="executionMaxTime"/></td>
                <td><span wicket:id="cacheHitCount"/></td>
                <td><span wicket:id="cacheMissCount"/></td>
                <td><span wicket:id="cachePutCount"/></td>
            </tr>
          </tbody>
    </table>
    
    <h2>Cache Statistics</h2>
    <table class="whiteGrey">
         <thead>
             <tr>
                 <th>Cache</th>
                 <th>Hit Count</th>
                 <th>Miss Count</th>
                 <th>Put Count</th>
                 <th>Elements in Memory</th>
                 <th>Elements on Disk</th>
                 <th>Size in Memory</th>
             </tr>
          </thead>
          <tbody>               
            <tr wicket:id="caches">
                <td><span wicket:id="categoryName"/></td>
                <td><span wicket:id="hitCount"/></td>
                <td><span wicket:id="missCount"/></td>
                <td><span wicket:id="putCount"/></td>
                <td><span wicket:id="elementCountInMemory"/></td>
                <td><span wicket:id="elementCountOnDisk"/></td>
                <td><span wicket:id="sizeInMemory"/></td>
            </tr>
          </tbody>
    </table>
     </wicket:panel>
</html>

The guicy one is as simple as :

import javax.persistence.EntityManager;

import org.apache.wicket.markup.html.panel.Panel;

import com.google.inject.Inject;

public class GuicyHibernateStatisticsPanel extends Panel
{

     @Inject
    private EntityManager entityManager;

    public GuicyHibernateStatisticsPanel(final String id)
    {
        super(id);
        HibernateStatisticsPanel statisticsPanel = new HibernateStatisticsPanel("hibernateStatisticsPanel");
        statisticsPanel.setEntityManager(entityManager);
        add(statisticsPanel);
    }

}

and :

<html>
     <body>
          <wicket:panel>
               <wicket:container wicket:id="hibernateStatisticsPanel" />
          </wicket:panel>
          
     </body>
</html>

Hope it helps :)
++

PS : I know the html code isn’t shown as best as it could, but I was fed up of trying to get wordpress to render the tags…

About these ads

From → professional

One Comment
  1. Uwe Schaefer permalink

    nice! one tiny thing came to my mind: as the EntityManager is a dependency, would it not be nice to make it part of construction ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: