1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.engine; |
7 | |
8 | import java.util.ArrayList; |
9 | import java.util.Collections; |
10 | import java.util.Comparator; |
11 | import java.util.HashMap; |
12 | import java.util.HashSet; |
13 | import java.util.Iterator; |
14 | import java.util.List; |
15 | import java.util.Map.Entry; |
16 | |
17 | /** |
18 | * Maintains query statistics. |
19 | */ |
20 | public class QueryStatisticsData { |
21 | |
22 | private static final int MAX_QUERY_ENTRIES = 100; |
23 | |
24 | private static final Comparator<QueryEntry> QUERY_ENTRY_COMPARATOR = |
25 | new Comparator<QueryEntry>() { |
26 | @Override |
27 | public int compare(QueryEntry o1, QueryEntry o2) { |
28 | return (int) Math.signum(o1.lastUpdateTime - o2.lastUpdateTime); |
29 | } |
30 | }; |
31 | |
32 | private final HashMap<String, QueryEntry> map = |
33 | new HashMap<String, QueryEntry>(); |
34 | |
35 | public synchronized List<QueryEntry> getQueries() { |
36 | // return a copy of the map so we don't have to |
37 | // worry about external synchronization |
38 | ArrayList<QueryEntry> list = new ArrayList<QueryEntry>(); |
39 | list.addAll(map.values()); |
40 | // only return the newest 100 entries |
41 | Collections.sort(list, QUERY_ENTRY_COMPARATOR); |
42 | return list.subList(0, Math.min(list.size(), MAX_QUERY_ENTRIES)); |
43 | } |
44 | |
45 | /** |
46 | * Update query statistics. |
47 | * |
48 | * @param sqlStatement the statement being executed |
49 | * @param executionTime the time in milliseconds the query/update took to |
50 | * execute |
51 | * @param rowCount the query or update row count |
52 | */ |
53 | public synchronized void update(String sqlStatement, long executionTime, |
54 | int rowCount) { |
55 | QueryEntry entry = map.get(sqlStatement); |
56 | if (entry == null) { |
57 | entry = new QueryEntry(); |
58 | entry.sqlStatement = sqlStatement; |
59 | map.put(sqlStatement, entry); |
60 | } |
61 | entry.update(executionTime, rowCount); |
62 | |
63 | // Age-out the oldest entries if the map gets too big. |
64 | // Test against 1.5 x max-size so we don't do this too often |
65 | if (map.size() > MAX_QUERY_ENTRIES * 1.5f) { |
66 | // Sort the entries by age |
67 | ArrayList<QueryEntry> list = new ArrayList<QueryEntry>(); |
68 | list.addAll(map.values()); |
69 | Collections.sort(list, QUERY_ENTRY_COMPARATOR); |
70 | // Create a set of the oldest 1/3 of the entries |
71 | HashSet<QueryEntry> oldestSet = |
72 | new HashSet<QueryEntry>(list.subList(0, list.size() / 3)); |
73 | // Loop over the map using the set and remove |
74 | // the oldest 1/3 of the entries. |
75 | for (Iterator<Entry<String, QueryEntry>> it = |
76 | map.entrySet().iterator(); it.hasNext();) { |
77 | Entry<String, QueryEntry> mapEntry = it.next(); |
78 | if (oldestSet.contains(mapEntry.getValue())) { |
79 | it.remove(); |
80 | } |
81 | } |
82 | } |
83 | } |
84 | |
85 | /** |
86 | * The collected statistics for one query. |
87 | */ |
88 | public static final class QueryEntry { |
89 | |
90 | /** |
91 | * The SQL statement. |
92 | */ |
93 | public String sqlStatement; |
94 | |
95 | /** |
96 | * The number of times the statement was executed. |
97 | */ |
98 | public int count; |
99 | |
100 | /** |
101 | * The last time the statistics for this entry were updated, |
102 | * in milliseconds since 1970. |
103 | */ |
104 | public long lastUpdateTime; |
105 | |
106 | /** |
107 | * The minimum execution time, in milliseconds. |
108 | */ |
109 | public long executionTimeMin; |
110 | |
111 | /** |
112 | * The maximum execution time, in milliseconds. |
113 | */ |
114 | public long executionTimeMax; |
115 | |
116 | /** |
117 | * The total execution time. |
118 | */ |
119 | public long executionTimeCumulative; |
120 | |
121 | /** |
122 | * The minimum number of rows. |
123 | */ |
124 | public int rowCountMin; |
125 | |
126 | /** |
127 | * The maximum number of rows. |
128 | */ |
129 | public int rowCountMax; |
130 | |
131 | /** |
132 | * The total number of rows. |
133 | */ |
134 | public long rowCountCumulative; |
135 | |
136 | /** |
137 | * The mean execution time. |
138 | */ |
139 | public double executionTimeMean; |
140 | |
141 | /** |
142 | * The mean number of rows. |
143 | */ |
144 | public double rowCountMean; |
145 | |
146 | // Using Welford's method, see also |
147 | // http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance |
148 | // http://www.johndcook.com/standard_deviation.html |
149 | |
150 | private double executionTimeM2; |
151 | private double rowCountM2; |
152 | |
153 | /** |
154 | * Update the statistics entry. |
155 | * |
156 | * @param time the execution time |
157 | * @param rows the number of rows |
158 | */ |
159 | void update(long time, int rows) { |
160 | count++; |
161 | executionTimeMin = Math.min(time, executionTimeMin); |
162 | executionTimeMax = Math.max(time, executionTimeMax); |
163 | rowCountMin = Math.min(rows, rowCountMin); |
164 | rowCountMax = Math.max(rows, rowCountMax); |
165 | |
166 | double delta = rows - rowCountMean; |
167 | rowCountMean += delta / count; |
168 | rowCountM2 += delta * (rows - rowCountMean); |
169 | |
170 | delta = time - executionTimeMean; |
171 | executionTimeMean += delta / count; |
172 | executionTimeM2 += delta * (time - executionTimeMean); |
173 | |
174 | executionTimeCumulative += time; |
175 | rowCountCumulative += rows; |
176 | lastUpdateTime = System.currentTimeMillis(); |
177 | |
178 | } |
179 | |
180 | public double getExecutionTimeStandardDeviation() { |
181 | // population standard deviation |
182 | return Math.sqrt(executionTimeM2 / count); |
183 | } |
184 | |
185 | public double getRowCountStandardDeviation() { |
186 | // population standard deviation |
187 | return Math.sqrt(rowCountM2 / count); |
188 | } |
189 | |
190 | } |
191 | |
192 | } |