1 /* Copyright (C) 2014 Percona and Sergey Vojtovich
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; version 2 of the License.
6 
7    This program is distributed in the hope that it will be useful,
8    but WITHOUT ANY WARRANTY; without even the implied warranty of
9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10    GNU General Public License for more details.
11 
12    You should have received a copy of the GNU General Public License
13    along with this program; if not, write to the Free Software
14    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,  USA */
15 
16 #ifndef MYSQL_SERVER
17 #define MYSQL_SERVER
18 #endif
19 #include <sql_class.h>
20 #include <table.h>
21 #include <sql_show.h>
22 #include <mysql/plugin_audit.h>
23 #include <sp_instr.h>
24 #include <sql_parse.h>
25 #include "query_response_time.h"
26 
27 
28 ulong opt_query_response_time_range_base= QRT_DEFAULT_BASE;
29 static my_bool opt_query_response_time_stats= FALSE;
30 static my_bool opt_query_response_time_flush= FALSE;
31 
32 class qrt_atomic_flag
33 {
34   public:
qrt_atomic_flag(bool initial_value)35     explicit qrt_atomic_flag(bool initial_value):
36       value_(initial_value ? 1 : 0)
37     {
38       my_atomic_rwlock_init(&lock_);
39     }
~qrt_atomic_flag()40     ~qrt_atomic_flag()
41     {
42       my_atomic_rwlock_destroy(&lock_);
43     }
set()44     void set()
45     {
46       my_atomic_rwlock_wrlock(&lock_);
47       my_atomic_store32(&value_, 1);
48       my_atomic_rwlock_wrunlock(&lock_);
49     }
clear()50     void clear()
51     {
52       my_atomic_rwlock_wrlock(&lock_);
53       my_atomic_store32(&value_, 0);
54       my_atomic_rwlock_wrunlock(&lock_);
55     }
is_set() const56     bool is_set() const
57     {
58       my_atomic_rwlock_rdlock(&lock_);
59       int32 res= my_atomic_load32(const_cast<volatile int32*>(&value_));
60       my_atomic_rwlock_rdunlock(&lock_);
61       return res != 0;
62     }
63 
64   private:
65     qrt_atomic_flag(const qrt_atomic_flag&);
66     qrt_atomic_flag& operator = (const qrt_atomic_flag&);
67 
68     mutable my_atomic_rwlock_t lock_;
69     volatile int32 value_;
70 };
71 static qrt_atomic_flag qrt_vars_initialized(false);
72 
query_response_time_flush_update(MYSQL_THD thd,struct st_mysql_sys_var * var,void * tgt,const void * save)73 static void query_response_time_flush_update(
74               MYSQL_THD thd __attribute__((unused)),
75               struct st_mysql_sys_var *var __attribute__((unused)),
76               void *tgt __attribute__((unused)),
77               const void *save __attribute__((unused)))
78 {
79   query_response_time_flush();
80 }
81 
82 
83 static MYSQL_SYSVAR_ULONG(range_base, opt_query_response_time_range_base,
84        PLUGIN_VAR_RQCMDARG,
85        "Select base of log for query_response_time ranges."
86        "WARNING: change of this variable take effect only after next "
87        "FLUSH QUERY_RESPONSE_TIME execution.",
88        NULL, NULL, QRT_DEFAULT_BASE, 2, QRT_MAXIMUM_BASE, 1);
89 static MYSQL_SYSVAR_BOOL(stats, opt_query_response_time_stats,
90        PLUGIN_VAR_OPCMDARG,
91        "Enable and disable collection of query times.",
92        NULL, NULL, FALSE);
93 static MYSQL_SYSVAR_BOOL(flush, opt_query_response_time_flush,
94        PLUGIN_VAR_NOCMDOPT,
95        "Update of this variable flushes statistics and re-reads "
96        "query_response_time_range_base.",
97        NULL, query_response_time_flush_update, FALSE);
98 #ifndef DBUG_OFF
99 static MYSQL_THDVAR_ULONGLONG(exec_time_debug, PLUGIN_VAR_NOCMDOPT,
100        "Pretend queries take this many microseconds. When 0 (the default) use "
101        "the actual execution time. Used only for debugging.",
102        NULL, NULL, 0, 0, LONG_TIMEOUT, 1);
103 #endif
104 
105 enum session_stat
106 {
107   session_stat_global,
108   session_stat_on,
109   session_stat_off
110 };
111 
112 static const char *session_stat_names[]= {"GLOBAL", "ON", "OFF", NullS};
113 static TYPELIB session_stat_typelib= { array_elements(session_stat_names) - 1,
114                                        "", session_stat_names, NULL};
115 
116 static MYSQL_THDVAR_ENUM(session_stats, PLUGIN_VAR_RQCMDARG,
117        "Controls query response time statistics collection for the current "
118        "session: ON - enable, OFF - disable, GLOBAL - use "
119        "query_response_time_stats value", NULL, NULL,
120        session_stat_global, &session_stat_typelib);
121 
122 static struct st_mysql_sys_var *query_response_time_info_vars[]=
123 {
124   MYSQL_SYSVAR(range_base),
125   MYSQL_SYSVAR(stats),
126   MYSQL_SYSVAR(flush),
127 #ifndef DBUG_OFF
128   MYSQL_SYSVAR(exec_time_debug),
129 #endif
130   MYSQL_SYSVAR(session_stats),
131   NULL
132 };
133 
134 
135 ST_FIELD_INFO query_response_time_fields_info[] =
136 {
137   { "TIME",
138     QRT_TIME_STRING_LENGTH,
139     MYSQL_TYPE_STRING,
140     0,
141     0,
142     "",
143     SKIP_OPEN_TABLE },
144   { "COUNT",
145     MY_INT32_NUM_DECIMAL_DIGITS,
146     MYSQL_TYPE_LONG,
147     0,
148     MY_I_S_UNSIGNED,
149     "",
150     SKIP_OPEN_TABLE },
151   { "TOTAL",
152     QRT_TIME_STRING_LENGTH,
153     MYSQL_TYPE_STRING,
154     0,
155     0,
156     "",
157     SKIP_OPEN_TABLE },
158   { 0, 0, MYSQL_TYPE_NULL, 0, 0, 0, 0 }
159 };
160 
161 
query_response_time_info_init(void * p)162 static int query_response_time_info_init(void *p)
163 {
164   ST_SCHEMA_TABLE *i_s_query_response_time= (ST_SCHEMA_TABLE *) p;
165   i_s_query_response_time->fields_info= query_response_time_fields_info;
166   if (!my_strcasecmp(system_charset_info, i_s_query_response_time->table_name,
167                      "QUERY_RESPONSE_TIME"))
168     i_s_query_response_time->fill_table= query_response_time_fill;
169   else if (!my_strcasecmp(system_charset_info,
170                           i_s_query_response_time->table_name,
171                           "QUERY_RESPONSE_TIME_READ"))
172     i_s_query_response_time->fill_table= query_response_time_fill_ro;
173   else if (!my_strcasecmp(system_charset_info,
174                           i_s_query_response_time->table_name,
175                           "QUERY_RESPONSE_TIME_WRITE"))
176     i_s_query_response_time->fill_table= query_response_time_fill_rw;
177   else
178     DBUG_ASSERT(0);
179   query_response_time_init();
180   return 0;
181 }
182 
query_response_time_info_init_main(void * p)183 static int query_response_time_info_init_main(void *p)
184 {
185   int res= query_response_time_info_init(p);
186   qrt_vars_initialized.set();
187   return res;
188 }
189 
query_response_time_info_deinit(void * arg)190 static int query_response_time_info_deinit(void *arg __attribute__((unused)))
191 {
192   opt_query_response_time_stats= FALSE;
193   query_response_time_free();
194   return 0;
195 }
196 
query_response_time_info_deinit_main(void * arg)197 static int query_response_time_info_deinit_main(void *arg)
198 {
199   qrt_vars_initialized.clear();
200   return query_response_time_info_deinit(arg);
201 }
202 
203 static struct st_mysql_information_schema query_response_time_info_descriptor=
204 { MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION };
205 
query_response_time_should_log(MYSQL_THD thd)206 static bool query_response_time_should_log(MYSQL_THD thd)
207 {
208   const enum session_stat session_stat_val= qrt_vars_initialized.is_set() ?
209     static_cast<session_stat>(THDVAR(thd, session_stats)) :
210     session_stat_off;
211   return (session_stat_val == session_stat_on)
212     || (session_stat_val == session_stat_global
213         && opt_query_response_time_stats);
214 }
215 
query_response_time_audit_notify(MYSQL_THD thd,unsigned int event_class,const void * event)216 static void query_response_time_audit_notify(MYSQL_THD thd,
217                                              unsigned int event_class,
218                                              const void *event)
219 {
220   const struct mysql_event_general *event_general=
221     (const struct mysql_event_general *) event;
222   DBUG_ASSERT(event_class == MYSQL_AUDIT_GENERAL_CLASS);
223   if (event_general->event_subclass == MYSQL_AUDIT_GENERAL_STATUS &&
224       query_response_time_should_log(thd))
225   {
226     /*
227      Get sql command id of currently executed statement
228      inside of stored function or procedure. If the command is "PREPARE"
229      don't get the statement inside of "PREPARE". If the statement
230      is not inside of stored function or procedure get sql command id
231      of the statement itself.
232     */
233     enum_sql_command sql_command=
234       (
235         thd->lex->sql_command != SQLCOM_PREPARE &&
236         thd->sp_runtime_ctx &&
237         thd->stmt_arena &&
238         ((sp_lex_instr *)thd->stmt_arena)->get_command() >= 0
239       ) ?
240       (enum_sql_command)((sp_lex_instr *)thd->stmt_arena)->get_command() :
241       thd->lex->sql_command;
242     if (sql_command == SQLCOM_EXECUTE)
243     {
244       const LEX_STRING *name=
245         (
246           thd->sp_runtime_ctx &&
247           thd->stmt_arena &&
248           ((sp_lex_instr *)thd->stmt_arena)->get_prepared_stmt_name()
249         )                                                               ?
250         /* If we are inside of SP */
251         ((sp_lex_instr *)thd->stmt_arena)->get_prepared_stmt_name()     :
252         /* otherwise */
253         &thd->lex->prepared_stmt_name;
254       Statement *stmt=
255         (Statement *)thd->stmt_map.find_by_name(name);
256       /* In case of EXECUTE <non-existing-PS>, keep SQLCOM_EXECUTE as the
257       command. */
258       if (likely(stmt && stmt->lex))
259         sql_command= stmt->lex->sql_command;
260     }
261     QUERY_TYPE query_type=
262       (sql_command_flags[sql_command] & CF_CHANGES_DATA) ? WRITE : READ;
263 #ifndef DBUG_OFF
264     if (THDVAR(thd, exec_time_debug)) {
265       ulonglong t = THDVAR(thd, exec_time_debug);
266       if ((thd->lex->sql_command == SQLCOM_SET_OPTION) ||
267           (thd->sp_runtime_ctx && thd->lex->spname && thd->stmt_arena &&
268               ((sp_lex_instr *)thd->stmt_arena)->get_command() ==
269               SQLCOM_SET_OPTION )) {
270           t = 0;
271       }
272       query_response_time_collect(query_type, t);
273     }
274     else
275 #endif
276       query_response_time_collect(query_type,
277                                   thd->utime_after_query -
278                                   thd->utime_after_lock);
279   }
280 }
281 
282 
283 static struct st_mysql_audit query_response_time_audit_descriptor=
284 {
285   MYSQL_AUDIT_INTERFACE_VERSION, NULL, query_response_time_audit_notify,
286   { (unsigned long) MYSQL_AUDIT_GENERAL_CLASSMASK }
287 };
288 
289 
mysql_declare_plugin(query_response_time)290 mysql_declare_plugin(query_response_time)
291 {
292   MYSQL_INFORMATION_SCHEMA_PLUGIN,
293   &query_response_time_info_descriptor,
294   "QUERY_RESPONSE_TIME",
295   "Percona and Sergey Vojtovich",
296   "Query Response Time Distribution INFORMATION_SCHEMA Plugin",
297   PLUGIN_LICENSE_GPL,
298   query_response_time_info_init_main,
299   query_response_time_info_deinit_main,
300   0x0100,
301   NULL,
302   query_response_time_info_vars,
303   (void *)"1.0",
304   0,
305 },
306 {
307   MYSQL_INFORMATION_SCHEMA_PLUGIN,
308   &query_response_time_info_descriptor,
309   "QUERY_RESPONSE_TIME_READ",
310   "Percona and Sergey Vojtovich",
311   "Query Response Time Distribution INFORMATION_SCHEMA Plugin",
312   PLUGIN_LICENSE_GPL,
313   query_response_time_info_init,
314   query_response_time_info_deinit,
315   0x0100,
316   NULL,
317   NULL,
318   (void *)"1.0",
319   0,
320 },
321 {
322   MYSQL_INFORMATION_SCHEMA_PLUGIN,
323   &query_response_time_info_descriptor,
324   "QUERY_RESPONSE_TIME_WRITE",
325   "Percona and Sergey Vojtovich",
326   "Query Response Time Distribution INFORMATION_SCHEMA Plugin",
327   PLUGIN_LICENSE_GPL,
328   query_response_time_info_init,
329   query_response_time_info_deinit,
330   0x0100,
331   NULL,
332   NULL,
333   (void *)"1.0",
334   0,
335 },
336 {
337   MYSQL_AUDIT_PLUGIN,
338   &query_response_time_audit_descriptor,
339   "QUERY_RESPONSE_TIME_AUDIT",
340   "Percona and Sergey Vojtovich",
341   "Query Response Time Distribution Audit Plugin",
342   PLUGIN_LICENSE_GPL,
343   NULL,
344   NULL,
345   0x0100,
346   NULL,
347   NULL,
348   (void *)"1.0",
349   0,
350 }
351 mysql_declare_plugin_end;
352