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 */
19int fill_variables(THD *thd, TABLE_LIST *tables, COND *cond);
20int fill_status(THD *thd, TABLE_LIST *tables, COND *cond);
21extern ST_SCHEMA_TABLE schema_tables[];
22
23namespace feedback {
24
25#ifndef DBUG_OFF
26ulong debug_startup_interval, debug_first_interval, debug_interval;
27#endif
28
29char server_uid_buf[SERVER_UID_SIZE+1]; ///< server uid will be written here
30
31/* backing store for system variables */
32static char *server_uid= server_uid_buf, *url, *http_proxy;
33char *user_info;
34ulong send_timeout, send_retry_wait;
35
36/**
37 these three are used to communicate the shutdown signal to the
38 background thread
39*/
40mysql_mutex_t sleep_mutex;
41mysql_cond_t sleep_condition;
42volatile bool shutdown_plugin;
43static pthread_t sender_thread;
44
45#ifdef HAVE_PSI_INTERFACE
46static PSI_mutex_key key_sleep_mutex;
47static PSI_mutex_info mutex_list[]=
48{{ &key_sleep_mutex, "sleep_mutex", PSI_FLAG_GLOBAL}};
49
50static PSI_cond_key key_sleep_cond;
51static PSI_cond_info cond_list[]=
52{{ &key_sleep_cond, "sleep_condition", PSI_FLAG_GLOBAL}};
53
54static PSI_thread_key key_sender_thread;
55static PSI_thread_info thread_list[] =
56{{&key_sender_thread, "sender_thread", 0}};
57#endif
58
59Url **urls; ///< list of urls to send the report to
60uint url_count;
61
62ST_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*/
68static 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
75static 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*/
92static 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*/
139static 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*/
196static 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*/
208int 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*/
236static 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*/
333static 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
359static MYSQL_SYSVAR_STR(server_uid, server_uid,
360 PLUGIN_VAR_READONLY | PLUGIN_VAR_NOCMDOPT,
361 "Automatically calculated server unique id hash.", NULL, NULL, 0);
362static 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, "");
366static 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");
369static 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);
372static 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);
375static 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
380static 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);
383static 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);
386static 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
391static 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
407static struct st_mysql_information_schema feedback =
408{ MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION };
409
410} // namespace feedback
411
412mysql_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}
428mysql_declare_plugin_end;
429#ifdef MARIA_PLUGIN_INTERFACE_VERSION
430maria_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}
446maria_declare_plugin_end;
447#endif
448