1 | /* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab |
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 02111-1301 USA */ |
15 | |
16 | #include "feedback.h" |
17 | |
18 | /* MySQL functions/variables not declared in mysql_priv.h */ |
19 | int fill_variables(THD *thd, TABLE_LIST *tables, COND *cond); |
20 | int fill_status(THD *thd, TABLE_LIST *tables, COND *cond); |
21 | extern ST_SCHEMA_TABLE schema_tables[]; |
22 | |
23 | namespace feedback { |
24 | |
25 | #ifndef DBUG_OFF |
26 | ulong debug_startup_interval, debug_first_interval, debug_interval; |
27 | #endif |
28 | |
29 | char server_uid_buf[SERVER_UID_SIZE+1]; ///< server uid will be written here |
30 | |
31 | /* backing store for system variables */ |
32 | static char *server_uid= server_uid_buf, *url, *http_proxy; |
33 | char *user_info; |
34 | ulong send_timeout, send_retry_wait; |
35 | |
36 | /** |
37 | these three are used to communicate the shutdown signal to the |
38 | background thread |
39 | */ |
40 | mysql_mutex_t sleep_mutex; |
41 | mysql_cond_t sleep_condition; |
42 | volatile bool shutdown_plugin; |
43 | static pthread_t sender_thread; |
44 | |
45 | #ifdef HAVE_PSI_INTERFACE |
46 | static PSI_mutex_key key_sleep_mutex; |
47 | static PSI_mutex_info mutex_list[]= |
48 | {{ &key_sleep_mutex, "sleep_mutex" , PSI_FLAG_GLOBAL}}; |
49 | |
50 | static PSI_cond_key key_sleep_cond; |
51 | static PSI_cond_info cond_list[]= |
52 | {{ &key_sleep_cond, "sleep_condition" , PSI_FLAG_GLOBAL}}; |
53 | |
54 | static PSI_thread_key key_sender_thread; |
55 | static PSI_thread_info thread_list[] = |
56 | {{&key_sender_thread, "sender_thread" , 0}}; |
57 | #endif |
58 | |
59 | Url **urls; ///< list of urls to send the report to |
60 | uint url_count; |
61 | |
62 | ST_SCHEMA_TABLE *i_s_feedback; ///< table descriptor for our I_S table |
63 | |
64 | /* |
65 | the column names *must* match column names in GLOBAL_VARIABLES and |
66 | GLOBAL_STATUS tables otherwise condition pushdown below will not work |
67 | */ |
68 | static ST_FIELD_INFO feedback_fields[] = |
69 | { |
70 | {"VARIABLE_NAME" , 255, MYSQL_TYPE_STRING, 0, 0, 0, 0}, |
71 | {"VARIABLE_VALUE" , 1024, MYSQL_TYPE_STRING, 0, 0, 0, 0}, |
72 | {0, 0, MYSQL_TYPE_NULL, 0, 0, 0, 0} |
73 | }; |
74 | |
75 | static COND * const OOM= (COND*)1; |
76 | |
77 | /** |
78 | Generate the COND tree for the condition pushdown |
79 | |
80 | This function takes a list of strings and generates an Item tree |
81 | corresponding to the following expression: |
82 | |
83 | field LIKE str1 OR field LIKE str2 OR field LIKE str3 OR ... |
84 | |
85 | where 'field' is the first field in the table - VARIABLE_NAME field - |
86 | and str1, str2... are strings from the list. |
87 | |
88 | This condition is used to filter the selected rows, emulating |
89 | |
90 | SELECT * FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE ... |
91 | */ |
92 | static COND* make_cond(THD *thd, TABLE_LIST *tables, LEX_STRING *filter) |
93 | { |
94 | Item_cond_or *res= NULL; |
95 | Name_resolution_context nrc; |
96 | const char *db= tables->db.str, *table= tables->alias.str; |
97 | LEX_CSTRING *field= &tables->table->field[0]->field_name; |
98 | CHARSET_INFO *cs= &my_charset_latin1; |
99 | |
100 | if (!filter->str) |
101 | return 0; |
102 | |
103 | nrc.init(); |
104 | nrc.resolve_in_table_list_only(tables); |
105 | |
106 | res= new (thd->mem_root) Item_cond_or(thd); |
107 | if (!res) |
108 | return OOM; |
109 | |
110 | for (; filter->str; filter++) |
111 | { |
112 | Item_field *fld= new (thd->mem_root) Item_field(thd, &nrc, db, table, |
113 | field); |
114 | Item_string *pattern= new (thd->mem_root) Item_string(thd, filter->str, |
115 | (uint) filter->length, cs); |
116 | Item_string *escape= new (thd->mem_root) Item_string(thd, "\\" , 1, cs); |
117 | |
118 | if (!fld || !pattern || !escape) |
119 | return OOM; |
120 | |
121 | Item_func_like *like= new (thd->mem_root) Item_func_like(thd, fld, pattern, |
122 | escape, 0); |
123 | |
124 | if (!like) |
125 | return OOM; |
126 | |
127 | res->add(like, thd->mem_root); |
128 | } |
129 | |
130 | if (res->fix_fields(thd, (Item**)&res)) |
131 | return OOM; |
132 | |
133 | return res; |
134 | } |
135 | |
136 | /** |
137 | System variables that we want to see in the feedback report |
138 | */ |
139 | static LEX_STRING vars_filter[]= { |
140 | {C_STRING_WITH_LEN("auto\\_increment%" )}, |
141 | {C_STRING_WITH_LEN("binlog\\_format" )}, |
142 | {C_STRING_WITH_LEN("character\\_set\\_%" )}, |
143 | {C_STRING_WITH_LEN("collation%" )}, |
144 | {C_STRING_WITH_LEN("engine\\_condition\\_pushdown" )}, |
145 | {C_STRING_WITH_LEN("event\\_scheduler" )}, |
146 | {C_STRING_WITH_LEN("feedback\\_%" )}, |
147 | {C_STRING_WITH_LEN("ft\\_m%" )}, |
148 | {C_STRING_WITH_LEN("have\\_%" )}, |
149 | {C_STRING_WITH_LEN("%\\_size" )}, |
150 | {C_STRING_WITH_LEN("innodb_f%" )}, |
151 | {C_STRING_WITH_LEN("%\\_length%" )}, |
152 | {C_STRING_WITH_LEN("%\\_timeout" )}, |
153 | {C_STRING_WITH_LEN("large\\_%" )}, |
154 | {C_STRING_WITH_LEN("lc_time_names" )}, |
155 | {C_STRING_WITH_LEN("log" )}, |
156 | {C_STRING_WITH_LEN("log_bin" )}, |
157 | {C_STRING_WITH_LEN("log_output" )}, |
158 | {C_STRING_WITH_LEN("log_slow_queries" )}, |
159 | {C_STRING_WITH_LEN("log_slow_time" )}, |
160 | {C_STRING_WITH_LEN("lower_case%" )}, |
161 | {C_STRING_WITH_LEN("max_allowed_packet" )}, |
162 | {C_STRING_WITH_LEN("max_connections" )}, |
163 | {C_STRING_WITH_LEN("max_prepared_stmt_count" )}, |
164 | {C_STRING_WITH_LEN("max_sp_recursion_depth" )}, |
165 | {C_STRING_WITH_LEN("max_user_connections" )}, |
166 | {C_STRING_WITH_LEN("max_write_lock_count" )}, |
167 | {C_STRING_WITH_LEN("myisam_recover_options" )}, |
168 | {C_STRING_WITH_LEN("myisam_repair_threads" )}, |
169 | {C_STRING_WITH_LEN("myisam_stats_method" )}, |
170 | {C_STRING_WITH_LEN("myisam_use_mmap" )}, |
171 | {C_STRING_WITH_LEN("net\\_%" )}, |
172 | {C_STRING_WITH_LEN("new" )}, |
173 | {C_STRING_WITH_LEN("old%" )}, |
174 | {C_STRING_WITH_LEN("optimizer%" )}, |
175 | {C_STRING_WITH_LEN("profiling" )}, |
176 | {C_STRING_WITH_LEN("query_cache%" )}, |
177 | {C_STRING_WITH_LEN("secure_auth" )}, |
178 | {C_STRING_WITH_LEN("slow_launch_time" )}, |
179 | {C_STRING_WITH_LEN("sql%" )}, |
180 | {C_STRING_WITH_LEN("storage_engine" )}, |
181 | {C_STRING_WITH_LEN("sync_binlog" )}, |
182 | {C_STRING_WITH_LEN("table_definition_cache" )}, |
183 | {C_STRING_WITH_LEN("table_open_cache" )}, |
184 | {C_STRING_WITH_LEN("thread_handling" )}, |
185 | {C_STRING_WITH_LEN("time_zone" )}, |
186 | {C_STRING_WITH_LEN("timed_mutexes" )}, |
187 | {C_STRING_WITH_LEN("version%" )}, |
188 | {0, 0} |
189 | }; |
190 | |
191 | /** |
192 | Status variables that we want to see in the feedback report |
193 | |
194 | (empty list = no WHERE condition) |
195 | */ |
196 | static LEX_STRING status_filter[]= {{0, 0}}; |
197 | |
198 | /** |
199 | Fill our I_S table with data |
200 | |
201 | This function works by invoking fill_variables() and |
202 | fill_status() of the corresponding I_S tables - to have |
203 | their data UNION-ed in the same target table. |
204 | After that it invokes our own fill_* functions |
205 | from the utils.cc - to get the data that aren't available in the |
206 | I_S.GLOBAL_VARIABLES and I_S.GLOBAL_STATUS. |
207 | */ |
208 | int fill_feedback(THD *thd, TABLE_LIST *tables, COND *unused) |
209 | { |
210 | int res; |
211 | COND *cond; |
212 | |
213 | tables->schema_table= schema_tables + SCH_GLOBAL_VARIABLES; |
214 | cond= make_cond(thd, tables, vars_filter); |
215 | res= (cond == OOM) ? 1 : fill_variables(thd, tables, cond); |
216 | |
217 | tables->schema_table= schema_tables + SCH_GLOBAL_STATUS; |
218 | if (!res) |
219 | { |
220 | cond= make_cond(thd, tables, status_filter); |
221 | res= (cond == OOM) ? 1 : fill_status(thd, tables, cond); |
222 | } |
223 | |
224 | tables->schema_table= i_s_feedback; |
225 | res= res || fill_plugin_version(thd, tables) |
226 | || fill_misc_data(thd, tables) |
227 | || fill_linux_info(thd, tables) |
228 | || fill_collation_statistics(thd, tables); |
229 | |
230 | return res; |
231 | } |
232 | |
233 | /** |
234 | plugin initialization function |
235 | */ |
236 | static int init(void *p) |
237 | { |
238 | i_s_feedback= (ST_SCHEMA_TABLE*) p; |
239 | /* initialize the I_S descriptor structure */ |
240 | i_s_feedback->fields_info= feedback_fields; ///< field descriptor |
241 | i_s_feedback->fill_table= fill_feedback; ///< how to fill the I_S table |
242 | i_s_feedback->idx_field1 = 0; ///< virtual index on the 1st col |
243 | |
244 | #ifdef HAVE_PSI_INTERFACE |
245 | #define PSI_register(X) \ |
246 | if(PSI_server) PSI_server->register_ ## X("feedback", X ## _list, array_elements(X ## _list)) |
247 | #else |
248 | #define PSI_register(X) /* no-op */ |
249 | #endif |
250 | |
251 | PSI_register(mutex); |
252 | PSI_register(cond); |
253 | PSI_register(thread); |
254 | |
255 | if (calculate_server_uid(server_uid_buf)) |
256 | return 1; |
257 | |
258 | prepare_linux_info(); |
259 | |
260 | #ifndef DBUG_OFF |
261 | if (startup_interval != debug_startup_interval || |
262 | first_interval != debug_first_interval || |
263 | interval != debug_interval) |
264 | { |
265 | startup_interval= debug_startup_interval; |
266 | first_interval= debug_first_interval; |
267 | interval= debug_interval; |
268 | user_info= const_cast<char*>("mysql-test" ); |
269 | } |
270 | #endif |
271 | |
272 | url_count= 0; |
273 | if (*url) |
274 | { |
275 | // now we split url on spaces and store them in Url objects |
276 | int slot; |
277 | char *s, *e; |
278 | |
279 | for (s= url, url_count= 1; *s; s++) |
280 | if (*s == ' ') |
281 | url_count++; |
282 | |
283 | urls= (Url **)my_malloc(url_count*sizeof(Url*), MYF(MY_WME)); |
284 | if (!urls) |
285 | return 1; |
286 | |
287 | for (s= url, e = url+1, slot= 0; e[-1]; e++) |
288 | if (*e == 0 || *e == ' ') |
289 | { |
290 | if (e > s && (urls[slot]= Url::create(s, e - s))) |
291 | { |
292 | if (urls[slot]->set_proxy(http_proxy, |
293 | http_proxy ? strlen(http_proxy) : 0)) |
294 | sql_print_error("feedback plugin: invalid proxy '%s'" , |
295 | http_proxy ? http_proxy : "" ); |
296 | slot++; |
297 | } |
298 | else |
299 | { |
300 | if (e > s) |
301 | sql_print_error("feedback plugin: invalid url '%.*s'" , (int)(e-s), s); |
302 | url_count--; |
303 | } |
304 | s= e + 1; |
305 | } |
306 | |
307 | // create a background thread to handle urls, if any |
308 | if (url_count) |
309 | { |
310 | mysql_mutex_init(0, &sleep_mutex, 0); |
311 | mysql_cond_init(0, &sleep_condition, 0); |
312 | shutdown_plugin= false; |
313 | |
314 | pthread_attr_t attr; |
315 | pthread_attr_init(&attr); |
316 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); |
317 | if (pthread_create(&sender_thread, &attr, background_thread, 0) != 0) |
318 | { |
319 | sql_print_error("feedback plugin: failed to start a background thread" ); |
320 | return 1; |
321 | } |
322 | } |
323 | else |
324 | my_free(urls); |
325 | } |
326 | |
327 | return 0; |
328 | } |
329 | |
330 | /** |
331 | plugin deinitialization function |
332 | */ |
333 | static int free(void *p) |
334 | { |
335 | if (url_count) |
336 | { |
337 | mysql_mutex_lock(&sleep_mutex); |
338 | shutdown_plugin= true; |
339 | mysql_cond_signal(&sleep_condition); |
340 | mysql_mutex_unlock(&sleep_mutex); |
341 | pthread_join(sender_thread, NULL); |
342 | |
343 | mysql_mutex_destroy(&sleep_mutex); |
344 | mysql_cond_destroy(&sleep_condition); |
345 | |
346 | for (uint i= 0; i < url_count; i++) |
347 | delete urls[i]; |
348 | my_free(urls); |
349 | } |
350 | return 0; |
351 | } |
352 | |
353 | #ifdef HAVE_OPENSSL |
354 | #define DEFAULT_PROTO "https://" |
355 | #else |
356 | #define DEFAULT_PROTO "http://" |
357 | #endif |
358 | |
359 | static MYSQL_SYSVAR_STR(server_uid, server_uid, |
360 | PLUGIN_VAR_READONLY | PLUGIN_VAR_NOCMDOPT, |
361 | "Automatically calculated server unique id hash." , NULL, NULL, 0); |
362 | static MYSQL_SYSVAR_STR(user_info, user_info, |
363 | PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG, |
364 | "User specified string that will be included in the feedback report." , |
365 | NULL, NULL, "" ); |
366 | static MYSQL_SYSVAR_STR(url, url, PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG, |
367 | "Space separated URLs to send the feedback report to." , NULL, NULL, |
368 | DEFAULT_PROTO "mariadb.org/feedback_plugin/post" ); |
369 | static MYSQL_SYSVAR_ULONG(send_timeout, send_timeout, PLUGIN_VAR_RQCMDARG, |
370 | "Timeout (in seconds) for the sending the report." , |
371 | NULL, NULL, 60, 1, 60*60*24, 1); |
372 | static MYSQL_SYSVAR_ULONG(send_retry_wait, send_retry_wait, PLUGIN_VAR_RQCMDARG, |
373 | "Wait this many seconds before retrying a failed send." , |
374 | NULL, NULL, 60, 1, 60*60*24, 1); |
375 | static MYSQL_SYSVAR_STR(http_proxy, http_proxy, |
376 | PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG, |
377 | "Proxy server host:port." , NULL, NULL,0); |
378 | |
379 | #ifndef DBUG_OFF |
380 | static MYSQL_SYSVAR_ULONG(debug_startup_interval, debug_startup_interval, |
381 | PLUGIN_VAR_RQCMDARG, "for debugging only" , |
382 | NULL, NULL, startup_interval, 1, INT_MAX32, 1); |
383 | static MYSQL_SYSVAR_ULONG(debug_first_interval, debug_first_interval, |
384 | PLUGIN_VAR_RQCMDARG, "for debugging only" , |
385 | NULL, NULL, first_interval, 1, INT_MAX32, 1); |
386 | static MYSQL_SYSVAR_ULONG(debug_interval, debug_interval, |
387 | PLUGIN_VAR_RQCMDARG, "for debugging only" , |
388 | NULL, NULL, interval, 1, INT_MAX32, 1); |
389 | #endif |
390 | |
391 | static struct st_mysql_sys_var* settings[] = { |
392 | MYSQL_SYSVAR(server_uid), |
393 | MYSQL_SYSVAR(user_info), |
394 | MYSQL_SYSVAR(url), |
395 | MYSQL_SYSVAR(send_timeout), |
396 | MYSQL_SYSVAR(send_retry_wait), |
397 | MYSQL_SYSVAR(http_proxy), |
398 | #ifndef DBUG_OFF |
399 | MYSQL_SYSVAR(debug_startup_interval), |
400 | MYSQL_SYSVAR(debug_first_interval), |
401 | MYSQL_SYSVAR(debug_interval), |
402 | #endif |
403 | NULL |
404 | }; |
405 | |
406 | |
407 | static struct st_mysql_information_schema feedback = |
408 | { MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION }; |
409 | |
410 | } // namespace feedback |
411 | |
412 | mysql_declare_plugin(feedback) |
413 | { |
414 | MYSQL_INFORMATION_SCHEMA_PLUGIN, |
415 | &feedback::feedback, |
416 | "FEEDBACK" , |
417 | "Sergei Golubchik" , |
418 | "MariaDB User Feedback Plugin" , |
419 | PLUGIN_LICENSE_GPL, |
420 | feedback::init, |
421 | feedback::free, |
422 | 0x0101, |
423 | NULL, |
424 | feedback::settings, |
425 | NULL, |
426 | 0 |
427 | } |
428 | mysql_declare_plugin_end; |
429 | #ifdef MARIA_PLUGIN_INTERFACE_VERSION |
430 | maria_declare_plugin(feedback) |
431 | { |
432 | MYSQL_INFORMATION_SCHEMA_PLUGIN, |
433 | &feedback::feedback, |
434 | "FEEDBACK" , |
435 | "Sergei Golubchik" , |
436 | "MariaDB User Feedback Plugin" , |
437 | PLUGIN_LICENSE_GPL, |
438 | feedback::init, |
439 | feedback::free, |
440 | 0x0101, |
441 | NULL, |
442 | feedback::settings, |
443 | "1.1" , |
444 | MariaDB_PLUGIN_MATURITY_STABLE |
445 | } |
446 | maria_declare_plugin_end; |
447 | #endif |
448 | |