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 02110-1335 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 
cycles_stop_tracking()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:
Exec_time_tracker()59   Exec_time_tracker() : count(0), cycles(0) {}
60 
61   // interface for collecting time
start_tracking()62   void start_tracking()
63   {
64     last_start= my_timer_cycles();
65   }
66 
stop_tracking()67   void stop_tracking()
68   {
69     count++;
70     cycles_stop_tracking();
71   }
72 
73   // interface for getting the time
get_loops()74   ulonglong get_loops() const { return count; }
get_time_ms()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 
Time_and_counter_tracker(bool timed_arg)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 */
incr_loops()97   void incr_loops() { count++; }
98 
99   /*
100     Unlike Exec_time_tracker::stop_tracking, we don't increase loops.
101   */
stop_tracking()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:
Table_access_tracker()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 
has_scans()141   bool has_scans() { return (r_scans != 0); }
get_loops()142   ha_rows get_loops() { return r_scans; }
get_avg_rows()143   double get_avg_rows()
144   {
145     return r_scans ? ((double)r_rows / r_scans): 0;
146   }
147 
get_filtered_after_where()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 
on_scan_init()159   inline void on_scan_init() { r_scans++; }
on_record_read()160   inline void on_record_read() { r_rows++; }
on_record_after_where()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:
Filesort_tracker(bool do_timing)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 
report_use(ha_rows r_limit_arg)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   }
incr_pq_used()198   inline void incr_pq_used() { r_used_pq++; }
199 
report_row_numbers(ha_rows examined_rows,ha_rows sorted_rows,ha_rows returned_rows)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 
report_merge_passes_at_start(ulong passes)209   inline void report_merge_passes_at_start(ulong passes)
210   {
211     sort_passes -= passes;
212   }
report_merge_passes_at_end(ulong passes)213   inline void report_merge_passes_at_end(ulong passes)
214   {
215     ANALYZE_STOP_TRACKING(&time_tracker);
216     sort_passes += passes;
217   }
218 
report_sort_buffer_size(size_t bufsize)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 
get_r_loops()230   ulonglong get_r_loops() const { return time_tracker.get_loops(); }
get_avg_examined_rows()231   double get_avg_examined_rows()
232   {
233     return ((double)r_examined_rows) / get_r_loops();
234   }
get_avg_returned_rows()235   double get_avg_returned_rows()
236   {
237     return ((double)r_output_rows) / get_r_loops();
238   }
get_r_filtered()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