/* * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, * and the EPL 1.0 (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.engine; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; /** * Maintains query statistics. */ public class QueryStatisticsData { private static final Comparator<QueryEntry> QUERY_ENTRY_COMPARATOR = new Comparator<QueryEntry>() { @Override public int compare(QueryEntry o1, QueryEntry o2) { return (int) Math.signum(o1.lastUpdateTime - o2.lastUpdateTime); } }; private final HashMap<String, QueryEntry> map = new HashMap<String, QueryEntry>(); private int maxQueryEntries; public QueryStatisticsData(int maxQueryEntries) { this.maxQueryEntries = maxQueryEntries; } public synchronized void setMaxQueryEntries(int maxQueryEntries) { this.maxQueryEntries = maxQueryEntries; } public synchronized List<QueryEntry> getQueries() { // return a copy of the map so we don't have to // worry about external synchronization ArrayList<QueryEntry> list = new ArrayList<QueryEntry>(); list.addAll(map.values()); // only return the newest 100 entries Collections.sort(list, QUERY_ENTRY_COMPARATOR); return list.subList(0, Math.min(list.size(), maxQueryEntries)); } /** * Update query statistics. * * @param sqlStatement the statement being executed * @param executionTimeNanos the time in nanoseconds the query/update took * to execute * @param rowCount the query or update row count */ public synchronized void update(String sqlStatement, long executionTimeNanos, int rowCount) { QueryEntry entry = map.get(sqlStatement); if (entry == null) { entry = new QueryEntry(sqlStatement); map.put(sqlStatement, entry); } entry.update(executionTimeNanos, rowCount); // Age-out the oldest entries if the map gets too big. // Test against 1.5 x max-size so we don't do this too often if (map.size() > maxQueryEntries * 1.5f) { // Sort the entries by age ArrayList<QueryEntry> list = new ArrayList<QueryEntry>(); list.addAll(map.values()); Collections.sort(list, QUERY_ENTRY_COMPARATOR); // Create a set of the oldest 1/3 of the entries HashSet<QueryEntry> oldestSet = new HashSet<QueryEntry>(list.subList(0, list.size() / 3)); // Loop over the map using the set and remove // the oldest 1/3 of the entries. for (Iterator<Entry<String, QueryEntry>> it = map.entrySet().iterator(); it.hasNext();) { Entry<String, QueryEntry> mapEntry = it.next(); if (oldestSet.contains(mapEntry.getValue())) { it.remove(); } } } } /** * The collected statistics for one query. */ public static final class QueryEntry { /** * The SQL statement. */ public final String sqlStatement; /** * The number of times the statement was executed. */ public int count; /** * The last time the statistics for this entry were updated, * in milliseconds since 1970. */ public long lastUpdateTime; /** * The minimum execution time, in nanoseconds. */ public long executionTimeMinNanos; /** * The maximum execution time, in nanoseconds. */ public long executionTimeMaxNanos; /** * The total execution time. */ public long executionTimeCumulativeNanos; /** * The minimum number of rows. */ public int rowCountMin; /** * The maximum number of rows. */ public int rowCountMax; /** * The total number of rows. */ public long rowCountCumulative; /** * The mean execution time. */ public double executionTimeMeanNanos; /** * The mean number of rows. */ public double rowCountMean; // Using Welford's method, see also // http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance // http://www.johndcook.com/standard_deviation.html private double executionTimeM2Nanos; private double rowCountM2; public QueryEntry(String sql) { this.sqlStatement = sql; } /** * Update the statistics entry. * * @param timeNanos the execution time in nanos * @param rows the number of rows */ void update(long timeNanos, int rows) { count++; executionTimeMinNanos = Math.min(timeNanos, executionTimeMinNanos); executionTimeMaxNanos = Math.max(timeNanos, executionTimeMaxNanos); rowCountMin = Math.min(rows, rowCountMin); rowCountMax = Math.max(rows, rowCountMax); double rowDelta = rows - rowCountMean; rowCountMean += rowDelta / count; rowCountM2 += rowDelta * (rows - rowCountMean); double timeDelta = timeNanos - executionTimeMeanNanos; executionTimeMeanNanos += timeDelta / count; executionTimeM2Nanos += timeDelta * (timeNanos - executionTimeMeanNanos); executionTimeCumulativeNanos += timeNanos; rowCountCumulative += rows; lastUpdateTime = System.currentTimeMillis(); } public double getExecutionTimeStandardDeviation() { // population standard deviation return Math.sqrt(executionTimeM2Nanos / count); } public double getRowCountStandardDeviation() { // population standard deviation return Math.sqrt(rowCountM2 / count); } } }