1 | /* |
2 | Copyright (c) 2015 MariaDB Corporation Ab |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of the GNU General Public License as published by |
6 | the Free Software Foundation; version 2 of the License. |
7 | |
8 | This program is distributed in the hope that it will be useful, |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | GNU General Public License for more details. |
12 | |
13 | You should have received a copy of the GNU General Public License |
14 | along with this program; if not, write to the Free Software |
15 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ |
16 | |
17 | /* |
18 | |
19 | == ANALYZE-stmt classes == |
20 | |
21 | This file contains classes for supporting "ANALYZE statement" feature. These are |
22 | a set of data structures that can be used to store the data about how the |
23 | statement executed. |
24 | |
25 | There are two kinds of data collection: |
26 | |
27 | 1. Various counters. We assume that incrementing counters has very low |
28 | overhead. Because of that, execution code increments counters unconditionally |
29 | (even when not running "ANALYZE $statement" commands. You run regular SELECT/ |
30 | UPDATE/DELETE/etc and the counters are incremented). |
31 | |
32 | As a free bonus, this lets us print detailed information into the slow query |
33 | log, should the query be slow. |
34 | |
35 | 2. Timing data. Measuring the time it took to run parts of query has noticeable |
36 | overhead. Because of that, we measure the time only when running "ANALYZE |
37 | $stmt"). |
38 | |
39 | */ |
40 | |
41 | /* |
42 | A class for tracking time it takes to do a certain action |
43 | */ |
44 | class Exec_time_tracker |
45 | { |
46 | protected: |
47 | ulonglong count; |
48 | ulonglong cycles; |
49 | ulonglong last_start; |
50 | |
51 | void cycles_stop_tracking() |
52 | { |
53 | ulonglong end= my_timer_cycles(); |
54 | cycles += end - last_start; |
55 | if (unlikely(end < last_start)) |
56 | cycles += ULONGLONG_MAX; |
57 | } |
58 | public: |
59 | Exec_time_tracker() : count(0), cycles(0) {} |
60 | |
61 | // interface for collecting time |
62 | void start_tracking() |
63 | { |
64 | last_start= my_timer_cycles(); |
65 | } |
66 | |
67 | void stop_tracking() |
68 | { |
69 | count++; |
70 | cycles_stop_tracking(); |
71 | } |
72 | |
73 | // interface for getting the time |
74 | ulonglong get_loops() const { return count; } |
75 | double get_time_ms() const |
76 | { |
77 | // convert 'cycles' to milliseconds. |
78 | return 1000 * ((double)cycles) / sys_timer_info.cycles.frequency; |
79 | } |
80 | }; |
81 | |
82 | |
83 | /* |
84 | A class for counting certain actions (in all queries), and optionally |
85 | collecting the timings (in ANALYZE queries). |
86 | */ |
87 | |
88 | class Time_and_counter_tracker: public Exec_time_tracker |
89 | { |
90 | public: |
91 | const bool timed; |
92 | |
93 | Time_and_counter_tracker(bool timed_arg) : timed(timed_arg) |
94 | {} |
95 | |
96 | /* Loops are counted in both ANALYZE and regular queries, as this is cheap */ |
97 | void incr_loops() { count++; } |
98 | |
99 | /* |
100 | Unlike Exec_time_tracker::stop_tracking, we don't increase loops. |
101 | */ |
102 | void stop_tracking() |
103 | { |
104 | cycles_stop_tracking(); |
105 | } |
106 | }; |
107 | |
108 | #define ANALYZE_START_TRACKING(tracker) \ |
109 | { \ |
110 | (tracker)->incr_loops(); \ |
111 | if (unlikely((tracker)->timed)) \ |
112 | { (tracker)->start_tracking(); } \ |
113 | } |
114 | |
115 | #define ANALYZE_STOP_TRACKING(tracker) \ |
116 | if (unlikely((tracker)->timed)) \ |
117 | { (tracker)->stop_tracking(); } |
118 | |
119 | /* |
120 | A class for collecting read statistics. |
121 | |
122 | The idea is that we run several scans. Each scans gets rows, and then filters |
123 | some of them out. We count scans, rows, and rows left after filtering. |
124 | |
125 | (note: at the moment, the class is not actually tied to a physical table. |
126 | It can be used to track reading from files, buffers, etc). |
127 | */ |
128 | |
129 | class Table_access_tracker |
130 | { |
131 | public: |
132 | Table_access_tracker() : |
133 | r_scans(0), r_rows(0), /*r_rows_after_table_cond(0),*/ |
134 | r_rows_after_where(0) |
135 | {} |
136 | |
137 | ha_rows r_scans; /* How many scans were ran on this join_tab */ |
138 | ha_rows r_rows; /* How many rows we've got after that */ |
139 | ha_rows r_rows_after_where; /* Rows after applying attached part of WHERE */ |
140 | |
141 | bool has_scans() { return (r_scans != 0); } |
142 | ha_rows get_loops() { return r_scans; } |
143 | double get_avg_rows() |
144 | { |
145 | return r_scans ? ((double)r_rows / r_scans): 0; |
146 | } |
147 | |
148 | double get_filtered_after_where() |
149 | { |
150 | double r_filtered; |
151 | if (r_rows > 0) |
152 | r_filtered= (double)r_rows_after_where / r_rows; |
153 | else |
154 | r_filtered= 1.0; |
155 | |
156 | return r_filtered; |
157 | } |
158 | |
159 | inline void on_scan_init() { r_scans++; } |
160 | inline void on_record_read() { r_rows++; } |
161 | inline void on_record_after_where() { r_rows_after_where++; } |
162 | }; |
163 | |
164 | |
165 | class Json_writer; |
166 | |
167 | /* |
168 | This stores the data about how filesort executed. |
169 | |
170 | A few things from here (e.g. r_used_pq, r_limit) belong to the query plan, |
171 | however, these parameters are calculated right during the execution so we |
172 | can't easily put them into the query plan. |
173 | |
174 | The class is designed to handle multiple invocations of filesort(). |
175 | */ |
176 | |
177 | class Filesort_tracker : public Sql_alloc |
178 | { |
179 | public: |
180 | Filesort_tracker(bool do_timing) : |
181 | time_tracker(do_timing), r_limit(0), r_used_pq(0), |
182 | r_examined_rows(0), r_sorted_rows(0), r_output_rows(0), |
183 | sort_passes(0), |
184 | sort_buffer_size(0) |
185 | {} |
186 | |
187 | /* Functions that filesort uses to report various things about its execution */ |
188 | |
189 | inline void report_use(ha_rows r_limit_arg) |
190 | { |
191 | if (!time_tracker.get_loops()) |
192 | r_limit= r_limit_arg; |
193 | else |
194 | r_limit= (r_limit != r_limit_arg)? 0: r_limit_arg; |
195 | |
196 | ANALYZE_START_TRACKING(&time_tracker); |
197 | } |
198 | inline void incr_pq_used() { r_used_pq++; } |
199 | |
200 | inline void report_row_numbers(ha_rows examined_rows, |
201 | ha_rows sorted_rows, |
202 | ha_rows returned_rows) |
203 | { |
204 | r_examined_rows += examined_rows; |
205 | r_sorted_rows += sorted_rows; |
206 | r_output_rows += returned_rows; |
207 | } |
208 | |
209 | inline void report_merge_passes_at_start(ulong passes) |
210 | { |
211 | sort_passes -= passes; |
212 | } |
213 | inline void report_merge_passes_at_end(ulong passes) |
214 | { |
215 | ANALYZE_STOP_TRACKING(&time_tracker); |
216 | sort_passes += passes; |
217 | } |
218 | |
219 | inline void report_sort_buffer_size(size_t bufsize) |
220 | { |
221 | if (sort_buffer_size) |
222 | sort_buffer_size= ulonglong(-1); // multiple buffers of different sizes |
223 | else |
224 | sort_buffer_size= bufsize; |
225 | } |
226 | |
227 | /* Functions to get the statistics */ |
228 | void print_json_members(Json_writer *writer); |
229 | |
230 | ulonglong get_r_loops() const { return time_tracker.get_loops(); } |
231 | double get_avg_examined_rows() |
232 | { |
233 | return ((double)r_examined_rows) / get_r_loops(); |
234 | } |
235 | double get_avg_returned_rows() |
236 | { |
237 | return ((double)r_output_rows) / get_r_loops(); |
238 | } |
239 | double get_r_filtered() |
240 | { |
241 | if (r_examined_rows > 0) |
242 | return ((double)r_sorted_rows / r_examined_rows); |
243 | else |
244 | return 1.0; |
245 | } |
246 | private: |
247 | Time_and_counter_tracker time_tracker; |
248 | |
249 | //ulonglong r_loops; /* How many times filesort was invoked */ |
250 | /* |
251 | LIMIT is typically a constant. There is never "LIMIT 0". |
252 | HA_POS_ERROR means we never had a limit |
253 | 0 means different values of LIMIT were used in |
254 | different filesort invocations |
255 | other value means the same LIMIT value was used every time. |
256 | */ |
257 | ulonglong r_limit; |
258 | ulonglong r_used_pq; /* How many times PQ was used */ |
259 | |
260 | /* How many rows were examined (before checking the select->cond) */ |
261 | ulonglong r_examined_rows; |
262 | |
263 | /* |
264 | How many rows were put into sorting (this is examined_rows minus rows that |
265 | didn't pass the WHERE condition) |
266 | */ |
267 | ulonglong r_sorted_rows; |
268 | |
269 | /* |
270 | How many rows were returned. This is equal to r_sorted_rows, unless there |
271 | was a LIMIT N clause in which case filesort would not have returned more |
272 | than N rows. |
273 | */ |
274 | ulonglong r_output_rows; |
275 | |
276 | /* How many sorts in total (divide by r_count to get the average) */ |
277 | ulonglong sort_passes; |
278 | |
279 | /* |
280 | 0 - means not used (or not known |
281 | (ulonglong)-1 - multiple |
282 | other - value |
283 | */ |
284 | ulonglong sort_buffer_size; |
285 | }; |
286 | |
287 | |