/*
 * Copyright 2004-2019 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.
/** * 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 Long.signum(o1.lastUpdateTime - o2.lastUpdateTime); } }; private final HashMap<String, QueryEntry> map = new HashMap<>(); 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<>(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.
Params:
  • sqlStatement – the statement being executed
  • executionTimeNanos – the time in nanoseconds the query/update took to execute
  • rowCount – the query or update row count
/** * 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<>(map.values()); Collections.sort(list, QUERY_ENTRY_COMPARATOR); // Create a set of the oldest 1/3 of the entries HashSet<QueryEntry> oldestSet = new HashSet<>(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.
/** * The collected statistics for one query. */
public static final class QueryEntry {
The SQL statement.
/** * The SQL statement. */
public final String sqlStatement;
The number of times the statement was executed.
/** * 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.
/** * The last time the statistics for this entry were updated, * in milliseconds since 1970. */
public long lastUpdateTime;
The minimum execution time, in nanoseconds.
/** * The minimum execution time, in nanoseconds. */
public long executionTimeMinNanos;
The maximum execution time, in nanoseconds.
/** * The maximum execution time, in nanoseconds. */
public long executionTimeMaxNanos;
The total execution time.
/** * The total execution time. */
public long executionTimeCumulativeNanos;
The minimum number of rows.
/** * The minimum number of rows. */
public int rowCountMin;
The maximum number of rows.
/** * The maximum number of rows. */
public int rowCountMax;
The total number of rows.
/** * The total number of rows. */
public long rowCountCumulative;
The mean execution time.
/** * The mean execution time. */
public double executionTimeMeanNanos;
The mean number of rows.
/** * 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.
Params:
  • timeNanos – the execution time in nanos
  • rows – the number of rows
/** * 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); } } }