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
21This file contains classes for supporting "ANALYZE statement" feature. These are
22a set of data structures that can be used to store the data about how the
23statement executed.
24
25There are two kinds of data collection:
26
271. Various counters. We assume that incrementing counters has very low
28overhead. Because of that, execution code increments counters unconditionally
29(even when not running "ANALYZE $statement" commands. You run regular SELECT/
30UPDATE/DELETE/etc and the counters are incremented).
31
32As a free bonus, this lets us print detailed information into the slow query
33log, should the query be slow.
34
352. Timing data. Measuring the time it took to run parts of query has noticeable
36overhead. 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*/
44class Exec_time_tracker
45{
46protected:
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 }
58public:
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
88class Time_and_counter_tracker: public Exec_time_tracker
89{
90public:
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
129class Table_access_tracker
130{
131public:
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
165class 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
177class Filesort_tracker : public Sql_alloc
178{
179public:
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 }
246private:
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