| 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 | |