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