Hibernate statistics page
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.
| Java | | copy | | ? |
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< Statistics > model = new CompoundPropertyModel< Statistics >( |
new LoadableDetachableModel< Statistics >() |
{ |
@Override |
protected Statistics load() |
{ |
return getSessionFactory(entityManager).getStatistics(); |
} |
}); |
setDefaultModel(model); |
add(new Label("isStatisticsEnabled", new LoadableDetachableModel< String >() |
{ |
@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< Void > switchStats = new Link< Void >("switch_stats") |
{ |
@Override |
public void onClick() |
{ |
final SessionFactory sessionFactory = getSessionFactory(entityManager); |
sessionFactory.getStatistics().setStatisticsEnabled( |
!sessionFactory.getStatistics().isStatisticsEnabled()); |
sessionFactory.getStatistics().clear(); |
final CompoundPropertyModel< Statistics > model = new CompoundPropertyModel< Statistics >( |
new LoadableDetachableModel< Statistics >() |
{ |
@Override |
protected Statistics load() |
{ |
return sessionFactory.getStatistics(); |
} |
}); |
setDefaultModel(model); |
} |
}; |
switchStats.add(new Label("switchText", new LoadableDetachableModel< String >() |
{ |
@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< EntityStatistics > entityStats = new ListView< EntityStatistics >("entities", |
new LoadableDetachableModel< List < EntityStatistics > >() |
{ |
@Override |
protected List< EntityStatistics > load() |
{ |
String[] entities = model.getObject().getEntityNames(); |
List< EntityStatistics > entityNames = new ArrayList< EntityStatistics >(); |
for (int i = 0; i < entities.length; i++) |
{ |
entityNames.add(model.getObject().getEntityStatistics(entities[i])); |
} |
return entityNames; |
} |
}) |
{ |
@Override |
protected void populateItem(final ListItem< EntityStatistics > item) |
{ |
item.setModel(new CompoundPropertyModel< EntityStatistics >(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< List >() |
{ |
@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< QueryStatistics > queryStats = new ListView< QueryStatistics >("queries", |
new LoadableDetachableModel< List < QueryStatistics > >() |
{ |
@Override |
protected List< QueryStatistics > load() |
{ |
String[] queries = (model.getObject()).getQueries(); |
List< QueryStatistics > queryNames = new ArrayList< QueryStatistics >(); |
for (int i = 0; i < queries.length; i++) |
{ |
queryNames.add(model.getObject().getQueryStatistics(queries[i])); |
} |
return queryNames; |
} |
}) |
{ |
@Override |
protected void populateItem(final ListItem< QueryStatistics > item) |
{ |
item.setModel(new CompoundPropertyModel< QueryStatistics >(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< String >() |
{ |
@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< SecondLevelCacheStatistics > cacheStats = new ListView< SecondLevelCacheStatistics >("caches", |
new LoadableDetachableModel< List < SecondLevelCacheStatistics > >() |
{ |
@Override |
protected List< SecondLevelCacheStatistics > load() |
{ |
String[] caches = model.getObject().getSecondLevelCacheRegionNames(); |
List< SecondLevelCacheStatistics > cacheNames = new ArrayList< SecondLevelCacheStatistics >(); |
for (int i = 0; i < caches.length; i++) |
{ |
cacheNames.add(model.getObject().getSecondLevelCacheStatistics(caches[i])); |
} |
return cacheNames; |
} |
}) |
{ |
@Override |
protected void populateItem(final ListItem< SecondLevelCacheStatistics > item) |
{ |
item.setModel(new CompoundPropertyModel< SecondLevelCacheStatistics >(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 :
| Java | | copy | | ? |
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


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 ?