From Software Development to Rock Music....

Freitag, 3. April 2009

Hibernate Statistics Wicket Page

I spent the last couple of days in Vienna and had two talks at the ejugdays. One of the talks was about Hibernate Tuning and during that talk I usually show a demo of a web application that has a very simple wicket page displaying the Hibernate's statistics. Once again I got asked by a few people if they could have the source code of that page. I decided to post the source code of the Wicket page to my blog.
As you might have already guessed: this code example is based on Apache Wicket, my favorite web framework. I'm sure that refactoring the page to JSF, Tapestry or whatever will not be too hard to achieve. As an entrance point I inject the Hibernate SessionFactory through the @SpringBean Annotation (part of Wicket's Spring support). If you don't use Spring you will have to code the SessionFactory's lookup. That shouldn't be too hard either.

HibernateStatisticsPage.java

package de.allschools.view.admin;

import java.util.ArrayList;
import java.util.List;

import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.spring.injection.annot.SpringBean;
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 HibernateStatisticsPage extends WebPage {
@SpringBean(name = "sessionFactory")
private SessionFactory sf;

public HibernateStatisticsPage(PageParameters parameters) {
super(parameters);

final WebMarkupContainer st = new WebMarkupContainer("stats");
final CompoundPropertyModel model = new CompoundPropertyModel(new LoadableDetachableModel() {
protected Object load() {
return sf.getStatistics();
}
});
st.setModel(model);
st.add(new Label("isStatisticsEnabled"));
st.add(new Label("startTime"));
st.add(new Label("sessionOpenCount"));
st.add(new Label("sessionCloseCount"));
st.add(new Label("flushCount"));
st.add(new Label("connectCount"));
st.add(new Label("prepareStatementCount"));
st.add(new Label("closeStatementCount"));
st.add(new Label("entityLoadCount"));
st.add(new Label("entityUpdateCount"));
st.add(new Label("entityInsertCount"));
st.add(new Label("entityDeleteCount"));
st.add(new Label("entityFetchCount"));
st.add(new Label("collectionLoadCount"));
st.add(new Label("collectionUpdateCount"));
st.add(new Label("collectionRemoveCount"));
st.add(new Label("collectionRecreateCount"));
st.add(new Label("collectionFetchCount"));
st.add(new Label("secondLevelCacheHitCount"));
st.add(new Label("secondLevelCacheMissCount"));
st.add(new Label("secondLevelCachePutCount"));
st.add(new Label("queryExecutionCount"));
st.add(new Label("queryExecutionMaxTime"));
st.add(new Label("queryExecutionMaxTimeQueryString"));
st.add(new Label("queryCacheHitCount"));
st.add(new Label("queryCacheMissCount"));
st.add(new Label("queryCachePutCount"));
st.add(new Label("commitedTransactionCount"));
st.add(new Label("transactionCount"));
st.add(new Label("optimisticFailureCount"));
add(st);
String[] entities = ((Statistics) model.getObject()).getEntityNames();
List entityNames = new ArrayList();
for (int i = 0; i < entities.length; i++) {
entityNames.add(entities[i]);
}
ListView entityStats = new ListView("entities", entityNames) {
protected void populateItem(ListItem item) {
String entityName = (String) item.getModelObject();
final EntityStatistics entityStat = ((Statistics) model.getObject()).getEntityStatistics(entityName);
item.add(new Label("deleteCount", new Model(entityStat.getDeleteCount())));
item.add(new Label("updateCount", new Model(entityStat.getUpdateCount())));
item.add(new Label("fetchCount", new Model(entityStat.getFetchCount())));
item.add(new Label("insertCount", new Model(entityStat.getInsertCount())));
item.add(new Label("loadCount", new Model(entityStat.getLoadCount())));
item.add(new Label("optimisticFailureCount", new Model(entityStat.getOptimisticFailureCount())));
item.add(new Label("entityName", new Model(entityName)));
}
};
add(entityStats);

String[] collections = ((Statistics) model.getObject()).getCollectionRoleNames();
List collectionNames = new ArrayList();
for (int i = 0; i < collections.length; i++) {
collectionNames.add(collections[i]);
}
ListView collectionStats = new ListView("collections", collectionNames) {
protected void populateItem(ListItem item) {
String collName = (String) item.getModelObject();
CollectionStatistics collectionStatistics = ((Statistics) model.getObject()).getCollectionStatistics(collName);
item.add(new Label("recreateCount", new Model(collectionStatistics.getRecreateCount())));
item.add(new Label("updateCount", new Model(collectionStatistics.getUpdateCount())));
item.add(new Label("fetchCount", new Model(collectionStatistics.getFetchCount())));
item.add(new Label("removeCount", new Model(collectionStatistics.getRemoveCount())));
item.add(new Label("loadCount", new Model(collectionStatistics.getLoadCount())));
item.add(new Label("collName", new Model(collName)));
}
};
add(collectionStats);

String[] queries = ((Statistics) model.getObject()).getQueries();
List queryNames = new ArrayList();
for (int i = 0; i < queries.length; i++) {
queryNames.add(queries[i]);
}
ListView queryStats = new ListView("queries", queryNames) {
protected void populateItem(ListItem item) {
String queryName = (String) item.getModelObject();
QueryStatistics queryStatistics = ((Statistics) model.getObject()).getQueryStatistics(queryName);
item.setModel(new CompoundPropertyModel(queryStatistics));
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"));
item.add(new Label("categoryName"));
}
};
add(queryStats);

String[] caches = ((Statistics) model.getObject()).getSecondLevelCacheRegionNames();
List cacheNames = new ArrayList();
for (int i = 0; i < caches.length; i++) {
cacheNames.add(caches[i]);
}
ListView cacheStats = new ListView("caches", cacheNames) {
protected void populateItem(ListItem item) {
String cacheName = (String) item.getModelObject();
SecondLevelCacheStatistics cacheStatistics = ((Statistics) model.getObject()).getSecondLevelCacheStatistics(cacheName);
item.setModel(new CompoundPropertyModel(cacheStatistics));
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);

add(new org.apache.wicket.markup.html.link.Link("switch_stats") {

@Override
public void onClick() {
sf.getStatistics().setStatisticsEnabled(!sf.getStatistics().isStatisticsEnabled());
sf.getStatistics().clear();
final CompoundPropertyModel model = new CompoundPropertyModel(new LoadableDetachableModel() {
protected Object load() {
return sf.getStatistics();
}
});
st.setModel(model);

}

});
}

}

HibernateStatisticsPage.html

<html>
<body>
<div id="header">Hibernate Statistics</div>
<a href="#" wicket:id="build_index">Create Search Index</a>
<a href="#" wicket:id="switch_stats">En- or Disable Statistics</a>
<div wicket:id="stats">
<table width="100%">
<tr><td>isStatisticsEnabled</td><td><span wicket:id="isStatisticsEnabled"/></td></tr>
<tr><td>startTime</td><td><span wicket:id="startTime"/></td></tr>
<tr><td>sessionOpenCount</td><td><span wicket:id="sessionOpenCount"/></td></tr>
<tr><td>sessionCloseCount</td><td><span wicket:id="sessionCloseCount"/></td></tr>
<tr><td>flushCount</td><td><span wicket:id="flushCount"/></td></tr>
<tr><td>connectCount</td><td><span wicket:id="connectCount"/></td></tr>
<tr><td>prepareStatementCount</td><td><span wicket:id="prepareStatementCount"/></td></tr>
<tr><td>closeStatementCount</td><td><span wicket:id="closeStatementCount"/></td></tr>
<tr><td>entityLoadCount</td><td><span wicket:id="entityLoadCount"/></td></tr>
<tr><td>entityUpdateCount</td><td><span wicket:id="entityUpdateCount"/></td></tr>
<tr><td>entityInsertCount</td><td><span wicket:id="entityInsertCount"/></td></tr>
<tr><td>entityDeleteCount</td><td><span wicket:id="entityDeleteCount"/></td></tr>
<tr><td>entityFetchCount</td><td><span wicket:id="entityFetchCount"/></td></tr>
<tr><td>collectionLoadCount</td><td><span wicket:id="collectionLoadCount"/></td></tr>
<tr><td>collectionUpdateCount</td><td><span wicket:id="collectionUpdateCount"/></td></tr>
<tr><td>collectionRemoveCount</td><td><span wicket:id="collectionRemoveCount"/></td></tr>
<tr><td>collectionRecreateCount</td><td><span wicket:id="collectionRecreateCount"/></td></tr>
<tr><td>collectionFetchCount</td><td><span wicket:id="collectionFetchCount"/></td></tr>
<tr><td>secondLevelCacheHitCount</td><td><span wicket:id="secondLevelCacheHitCount"/></td></tr>
<tr><td>secondLevelCacheMissCount</td><td><span wicket:id="secondLevelCacheMissCount"/></td></tr>
<tr><td>secondLevelCachePutCount</td><td><span wicket:id="secondLevelCachePutCount"/></td></tr>
<tr><td>queryExecutionCount</td><td><span wicket:id="queryExecutionCount"/></td></tr>
<tr><td>queryExecutionMaxTime</td><td><span wicket:id="queryExecutionMaxTime"/></td></tr>
<tr><td>queryExecutionMaxTimeQueryString</td><td><span wicket:id="queryExecutionMaxTimeQueryString"/></td></tr>
<tr><td>queryCacheHitCount</td><td><span wicket:id="queryCacheHitCount"/></td></tr>
<tr><td>queryCacheMissCount</td><td><span wicket:id="queryCacheMissCount"/></td></tr>
<tr><td>queryCachePutCount</td><td><span wicket:id="queryCachePutCount"/></td></tr>
<tr><td>commitedTransactionCount</td><td><span wicket:id="commitedTransactionCount"/></td></tr>
<tr><td>transactionCount</td><td><span wicket:id="transactionCount"/></td></tr>
<tr><td>optimisticFailureCount</td><td><span wicket:id="optimisticFailureCount"/></td></tr>
</table>
</div>
<br>
<div id="header">Entity Statistics</div>
<table witdh="100%" border ="1">
<tr>
<td>Entity</td>
<td>Load Count</td>
<td>Fetch Count</td>
<td>Insert Count</td>
<td>Delete Count</td>
<td>Update Count</td>
<td>Optimistic Failure Count</td>
</tr>
<tr wicket:id="entities">
<td><span wicket:id="entityName"/></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>
</table>
<br>
<div id="header">Collection Statistics</div>
<table witdh="100%" border ="1">
<tr>
<td>Collection</td>
<td>Load Count</td>
<td>Fetch Count</td>
<td>Recreate Count</td>
<td>Remove Count</td>
<td>Update Count</td>
</tr>
<tr wicket:id="collections">
<td><span wicket:id="collName"/></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>
</table>
<br>
<div id="header">Query Statistics</div>
<table witdh="100%" border ="1">
<tr>
<td>Query</td>
<td>Execution Count</td>
<td>Execution Row Count</td>
<td>Avg Time</td>
<td>Min Time</td>
<td>Max Time</td>
<td>Cache Hit Count</td>
<td>Cache Miss Count</td>
<td>Cache Put Count</td>
</tr>
<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>
</table>
<br>
<div id="header">Cache Statistics</div>
<table witdh="100%" border ="1">
<tr>
<td>Cache</td>
<td>Hit Count</td>
<td>Miss Count</td>
<td>Put Count</td>
<td>Elements in Memory</td>
<td>Elements on Disk</td>
<td>Size in Memory</td>
</tr>
<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>
</table>

</body>
</html>


Kommentare:

Martijn Dashorst hat gesagt…

Don't put the Statistics object directly in a CompoundPropertyModel, but put it in a LoadableDetachableModel that retrieves the stats on every load. This way you get 2 benefits: fresh stats on every request, *and* you won't serialize the statistics with your component tree.

so:

new CompoundPropertyModel(new LoadableDetachableModel() { protected Object load() { return sf.getStatistics(); } }

Michael Plöd hat gesagt…

Thanks Martijn!

You are absolutely right what you say! Thanks for your valuable input.

I will update the blog example accordingly.

markus hat gesagt…

accordingly to martijn hint you must also fix the onClick() method to avoid a serialization exception in wicket

Michael Plöd hat gesagt…

yes, you are right Markus. I adjusted that as well.

Gena Batalski hat gesagt…

Hi Michael,

thank you for your talk at the University of Karlsruhe this week!

I was inspired by this post and would post as a comment the JSF/Facelets (also optional Seam, who uses) version of statistics page.

But my version is about 8k and is not allowed as a comment. I could send it to you via mail, if you wish. My contact: gb at sivis.de

Thanks, Gena