/** * Return a string that describes the statistics for the top n entries, sorted by descending * order of total time spent dealing with the query. This will always include the totals entry * in the first position. * * @param n the desired number of entries to describe * @return a string describing the statistics for the top n entries */ public synchronized String toStringTop(int n) { StringBuilder out = new StringBuilder(); List<Entry> list = entries(); if (list.isEmpty()) return "<no queries executed>"; Collections.sort(list, TOTAL_TIME_DESCENDING); int maxCountLength = COUNT_FORMAT.format(list.get(0).numQueries).length(); double totalDuration = list.get(0).queryTime; for (Entry entry : list.subList(0, Math.min(n, list.size()))) out.append(entry.toString(maxCountLength, totalDuration)).append('\n'); return out.toString(); }
public synchronized String toString(int maxCountLength, double totalDuration) { String formattedCount = String.format("%" + maxCountLength + "s", COUNT_FORMAT.format(numQueries)); return FULL_ENTRY_FORMAT.format( new Object[] { query == null ? "TOTALS" : query, formattedCount, (queriesPrepared - queriesCompiled) / (double) queriesPrepared, queryTime, queryCompilationTime, queryPreparationTime, queryRunTime, queryTime * 1000 / numQueries, queriesCompiled == 0 ? 0 : queryCompilationTime * 1000 / queriesCompiled, queriesPrepared == 0 ? 0 : queryPreparationTime * 1000 / queriesPrepared, queriesRun == 0 ? 0 : queryRunTime * 1000 / queriesRun, queryTime / totalDuration }); }
public static class Statistics { private static final NumberFormat COUNT_FORMAT = NumberFormat.getIntegerInstance(); private static final MessageFormat FULL_ENTRY_FORMAT = new MessageFormat( "{1} uses in {3,number,0.000}s ({11,number,percent}, {7,number,0.00}ms avg) [" + "{4,number,0.000}s compiling ({8,number,0.00}ms avg, {2,number,percent} cache hits), " + "{5,number,0.000}s preparing ({9,number,0.00}ms avg), {6,number,0.000}s executing ({10,number,0.00}ms avg)" + "]: {0}"); private static final MessageFormat STAND_ALONE_ENTRY_FORMAT = new MessageFormat( "{1,number,integer} uses in {3,number,0.000}s ({7,number,0.00}ms avg) [" + "{4,number,0.000}s compiling ({8,number,0.00}ms avg, {2,number,percent} cache hits), " + "{5,number,0.000}s preparing ({9,number,0.00}ms avg), {6,number,0.000}s executing ({10,number,0.00}ms avg)" + "]: {0}"); private static final Comparator<Entry> TOTAL_TIME_DESCENDING = new Comparator<Entry>() { public int compare(Entry e1, Entry e2) { return e1.queryTime == e2.queryTime ? 0 : (e1.queryTime > e2.queryTime ? -1 : 1); } }; private final Map<String, Entry> entries = new HashMap<String, Entry>(); void update(String query, long t1, long t2, long t3, long t4, long t5) { long tQuery = t5 - t1, tPreparation = t2 > 0 ? t2 - t1 : -1, tCompilation = t3 > 0 ? t3 - t2 : -1, tExecution = t4 > 0 ? t4 - (t3 > 0 ? t3 : t2) : -1; get(null).update(tQuery, tPreparation, tCompilation, tExecution); get(query).update(tQuery, tPreparation, tCompilation, tExecution); } synchronized Entry get(String query) { Entry entry = entries.get(query); if (entry == null) entries.put(query, entry = new Entry(query)); return entry; } /** * Get a list of all statistics entries for which data has been gathered. The list is a copy and * can be further manipulating without affecting the service. * * @return a list of all statistics entries */ public synchronized List<Entry> entries() { return new ArrayList<Entry>(entries.values()); } /** * Get the entry that aggregates statistics over all the queries. * * @return the totals entry */ public Entry totals() { return get(null); } /** Reset all gathered statistics back to zero. */ public synchronized void reset() { entries.clear(); } /** * Return a string that describes the statistics gathered for all the entries. * * @return a string describing the statistics gathered so far */ public synchronized String toString() { return toStringTop(entries.size()); } /** * Return a string that describes the statistics for the top n entries, sorted by descending * order of total time spent dealing with the query. This will always include the totals entry * in the first position. * * @param n the desired number of entries to describe * @return a string describing the statistics for the top n entries */ public synchronized String toStringTop(int n) { StringBuilder out = new StringBuilder(); List<Entry> list = entries(); if (list.isEmpty()) return "<no queries executed>"; Collections.sort(list, TOTAL_TIME_DESCENDING); int maxCountLength = COUNT_FORMAT.format(list.get(0).numQueries).length(); double totalDuration = list.get(0).queryTime; for (Entry entry : list.subList(0, Math.min(n, list.size()))) out.append(entry.toString(maxCountLength, totalDuration)).append('\n'); return out.toString(); } /** * Performance counters for a single query. The fields are public for convenience (and to avoid * a forest of accessors) but should be considered as read-only. * * @author <a href="mailto:[email protected]">Piotr Kaminski</a> */ public static class Entry { /** * The query string (after pre-substitution) that this entry is about. If <code>null</code> * then this is the totals entry. */ public final String query; // These are simple counters public long numQueries, queriesPrepared, queriesCompiled, queriesRun; // All times are in seconds. public double queryTime, queryPreparationTime, queryCompilationTime, queryRunTime; Entry(String query) { this.query = query; } synchronized void update(long tQuery, long tPreparation, long tCompilation, long tRun) { numQueries++; queryTime += tQuery / 1000.0; if (tPreparation >= 0) { queriesPrepared++; queryPreparationTime += tPreparation / 1000.0; } if (tCompilation >= 0) { queriesCompiled++; queryCompilationTime += tCompilation / 1000.0; } if (tRun >= 0) { queryRunTime += tRun / 1000.0; queriesRun++; } } public synchronized String toString(int maxCountLength, double totalDuration) { String formattedCount = String.format("%" + maxCountLength + "s", COUNT_FORMAT.format(numQueries)); return FULL_ENTRY_FORMAT.format( new Object[] { query == null ? "TOTALS" : query, formattedCount, (queriesPrepared - queriesCompiled) / (double) queriesPrepared, queryTime, queryCompilationTime, queryPreparationTime, queryRunTime, queryTime * 1000 / numQueries, queriesCompiled == 0 ? 0 : queryCompilationTime * 1000 / queriesCompiled, queriesPrepared == 0 ? 0 : queryPreparationTime * 1000 / queriesPrepared, queriesRun == 0 ? 0 : queryRunTime * 1000 / queriesRun, queryTime / totalDuration }); } @Override public synchronized String toString() { return STAND_ALONE_ENTRY_FORMAT.format( new Object[] { query == null ? "TOTALS" : query, numQueries, (queriesPrepared - queriesCompiled) / (double) queriesPrepared, queryTime, queryCompilationTime, queryPreparationTime, queryRunTime, queryTime * 1000 / numQueries, queriesCompiled == 0 ? 0 : queryCompilationTime * 1000 / queriesCompiled, queriesPrepared == 0 ? 0 : queryPreparationTime * 1000 / queriesPrepared, queriesRun == 0 ? 0 : queryRunTime * 1000 / queriesRun }); } } }