/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.openjpa.kernel; import java.io.PrintStream; import java.io.Serializable; import java.util.Collections; import java.util.Date; import java.util.Map; import java.util.Set; import org.apache.openjpa.lib.util.ReferenceMap; import org.apache.openjpa.lib.util.concurrent.ConcurrentReferenceHashMap; /** * Records query execution statistics. * * Statistics can be reset. * * Gathers both accumulated statistics since start as well as statistics since * last reset. * * @since 1.3.0 * * @author Pinaki Poddar * */ public interface QueryStatistics<T> extends Serializable { /** * Gets all the identifier keys for the cached queries. */ public Set<T> keys(); /** * Record that the given query has been executed. */ void recordExecution(T query); /** * Record that the given query has been evicted. */ void recordEviction(T query); /** * Gets number of total query execution since last reset. */ public long getExecutionCount(); /** * Gets number of total query execution since start. */ public long getTotalExecutionCount(); /** * Gets number of executions for the given query since last reset. */ public long getExecutionCount(T query); /** * Gets number of executions for the given query since start. */ public long getTotalExecutionCount(T query); /** * Gets number of total query execution that are cached since last reset. */ public long getHitCount(); /** * Gets number of total query execution that are cached since start. */ public long getTotalHitCount(); /** * Gets number of executions for the given query that are cached since * last reset. */ public long getHitCount(T query); /** * Gets number of executions for the given query that are cached since * start. */ public long getTotalHitCount(T query); /** * Gets number of total query evictions since last reset. */ public long getEvictionCount(); /** * Gets number of total query evictions since start. */ public long getTotalEvictionCount(); /** * Gets the time of last reset. */ public Date since(); /** * Gets the time of start. */ public Date start(); /** * Clears all statistics accumulated since last reset. */ public void reset(); /** * Clears all statistics accumulated since start. */ public void clear(); /** * Dumps on the given output stream. */ public void dump(PrintStream out); /** * A default implementation. * * Maintains statistics for only a fixed number of queries. * Statistical counts are approximate and not exact (to keep thread synchorization overhead low). * */ public static class Default<T> implements QueryStatistics<T> { private static final int FIXED_SIZE = 1000; private static final float LOAD_FACTOR = 0.75f; private static final int CONCURRENCY = 16; private static final int ARRAY_SIZE = 3; private static final int READ = 0; private static final int HIT = 1; private static final int EVICT = 2; private long[] astat = new long[ARRAY_SIZE]; private long[] stat = new long[ARRAY_SIZE]; private Map<T, long[]> stats; private Map<T, long[]> astats; private Date start = new Date(); private Date since = start; public Default() { initializeMaps(); } private void initializeMaps() { ConcurrentReferenceHashMap statsMap = new ConcurrentReferenceHashMap(ReferenceMap.HARD, ReferenceMap.HARD, CONCURRENCY, LOAD_FACTOR); statsMap.setMaxSize(FIXED_SIZE); stats = statsMap; ConcurrentReferenceHashMap aStatsMap = new ConcurrentReferenceHashMap(ReferenceMap.HARD, ReferenceMap.HARD, CONCURRENCY, LOAD_FACTOR); aStatsMap.setMaxSize(FIXED_SIZE); astats = aStatsMap; } public Set<T> keys() { return stats.keySet(); } public long getExecutionCount() { return stat[READ]; } public long getTotalExecutionCount() { return astat[READ]; } public long getExecutionCount(T query) { return getCount(stats, query, READ); } public long getTotalExecutionCount(T query) { return getCount(astats, query, READ); } public long getHitCount() { return stat[HIT]; } public long getTotalHitCount() { return astat[HIT]; } public long getHitCount(T query) { return getCount(stats, query, HIT); } public long getTotalHitCount(T query) { return getCount(astats, query, HIT); } private long getCount(Map<T, long[]> target, T query, int i) { long[] row = target.get(query); return (row == null) ? 0 : row[i]; } public Date since() { return since; } public Date start() { return start; } public synchronized void reset() { stat = new long[ARRAY_SIZE]; stats.clear(); since = new Date(); } public synchronized void clear() { astat = new long[ARRAY_SIZE]; stat = new long[ARRAY_SIZE]; initializeMaps(); start = new Date(); since = start; } private void addSample(T query, int index) { stat[index]++; astat[index]++; addSample(stats, query, index); addSample(astats, query, index); } private void addSample(Map<T, long[]> target, T query, int i) { long[] row = target.get(query); if (row == null) { row = new long[ARRAY_SIZE]; } row[i]++; target.put(query, row); } public void recordExecution(T query) { if (query == null) return; boolean cached = astats.containsKey(query); addSample(query, READ); if (cached) addSample(query, HIT); } public void recordEviction(T query) { if (query == null) { return; } addSample(query, EVICT); } public void dump(PrintStream out) { String header = "Query Statistics starting from " + start; out.print(header); if (since == start) { out.println(); out.println("Total Query Execution: " + toString(astat)); out.println("\tTotal \t\tQuery"); } else { out.println(" last reset on " + since); out.println("Total Query Execution since start " + toString(astat) + " since reset " + toString(stat)); out.println("\tSince Start \tSince Reset \t\tQuery"); } int i = 0; for (T key : stats.keySet()) { i++; long[] arow = astats.get(key); if (since == start) { out.println(i + ". \t" + toString(arow) + " \t" + key); } else { long[] row = stats.get(key); out.println(i + ". \t" + toString(arow) + " \t" + toString(row) + " \t\t" + key); } } } long pct(long per, long cent) { if (cent <= 0) return 0; return (100*per)/cent; } String toString(long[] row) { return row[READ] + ":" + row[HIT] + "(" + pct(row[HIT], row[READ]) + "%)"; } public long getEvictionCount() { return stat[EVICT]; } public long getTotalEvictionCount() { return astat[EVICT]; } } /** * A do-nothing implementation. * * @author Pinaki Poddar * * @param <T> */ public static class None<T> implements QueryStatistics<T> { private Date start = new Date(); private Date since = start; public void clear() { } public void dump(PrintStream out) { } public long getExecutionCount() { return 0; } public long getExecutionCount(T query) { return 0; } public long getHitCount() { return 0; } public long getHitCount(T query) { return 0; } public long getTotalExecutionCount() { return 0; } public long getTotalExecutionCount(T query) { return 0; } public long getTotalHitCount() { return 0; } public long getTotalHitCount(T query) { return 0; } public long getEvictionCount() { return 0; } public long getTotalEvictionCount() { return 0; } public Set<T> keys() { return Collections.emptySet(); } public void recordExecution(T query) { } public void reset() { start = new Date(); since = start; } public Date since() { return since; } public Date start() { return start; } public void recordEviction(T query) { } } }