/* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ #include "feedback.h" /* MySQL functions/variables not declared in mysql_priv.h */ int fill_variables(THD *thd, TABLE_LIST *tables, COND *cond); int fill_status(THD *thd, TABLE_LIST *tables, COND *cond); extern ST_SCHEMA_TABLE schema_tables[]; namespace feedback { #ifndef DBUG_OFF ulong debug_startup_interval, debug_first_interval, debug_interval; #endif char server_uid_buf[SERVER_UID_SIZE+1]; ///< server uid will be written here /* backing store for system variables */ static char *server_uid= server_uid_buf, *url, *http_proxy; char *user_info; ulong send_timeout, send_retry_wait; /** these three are used to communicate the shutdown signal to the background thread */ mysql_mutex_t sleep_mutex; mysql_cond_t sleep_condition; volatile bool shutdown_plugin; static pthread_t sender_thread; #ifdef HAVE_PSI_INTERFACE static PSI_mutex_key key_sleep_mutex; static PSI_mutex_info mutex_list[]= {{ &key_sleep_mutex, "sleep_mutex", PSI_FLAG_GLOBAL}}; static PSI_cond_key key_sleep_cond; static PSI_cond_info cond_list[]= {{ &key_sleep_cond, "sleep_condition", PSI_FLAG_GLOBAL}}; static PSI_thread_key key_sender_thread; static PSI_thread_info thread_list[] = {{&key_sender_thread, "sender_thread", 0}}; #endif Url **urls; ///< list of urls to send the report to uint url_count; ST_SCHEMA_TABLE *i_s_feedback; ///< table descriptor for our I_S table /* the column names *must* match column names in GLOBAL_VARIABLES and GLOBAL_STATUS tables otherwise condition pushdown below will not work */ static ST_FIELD_INFO feedback_fields[] = { Show::Column("VARIABLE_NAME", Show::Varchar(255), NOT_NULL), Show::Column("VARIABLE_VALUE", Show::Varchar(1024), NOT_NULL), Show::CEnd() }; static COND * const OOM= (COND*)1; /** Generate the COND tree for the condition pushdown This function takes a list of strings and generates an Item tree corresponding to the following expression: field LIKE str1 OR field LIKE str2 OR field LIKE str3 OR ... where 'field' is the first field in the table - VARIABLE_NAME field - and str1, str2... are strings from the list. This condition is used to filter the selected rows, emulating SELECT * FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE ... */ static COND* make_cond(THD *thd, TABLE_LIST *tables, LEX_STRING *filter) { Item_cond_or *res= NULL; /* A reference to this context will be stored in Item_field */ Name_resolution_context *nrc= new (thd->mem_root) Name_resolution_context; LEX_CSTRING &db= tables->db; LEX_CSTRING &table= tables->alias; LEX_CSTRING &field= tables->table->field[0]->field_name; CHARSET_INFO *cs= &my_charset_latin1; if (!filter->str || !nrc) return 0; nrc->resolve_in_table_list_only(tables); nrc->select_lex= tables->select_lex; res= new (thd->mem_root) Item_cond_or(thd); if (!res) return OOM; for (; filter->str; filter++) { Item_field *fld= new (thd->mem_root) Item_field(thd, nrc, db, table, field); Item_string *pattern= new (thd->mem_root) Item_string(thd, filter->str, (uint) filter->length, cs); Item_string *escape= new (thd->mem_root) Item_string(thd, "\\", 1, cs); if (!fld || !pattern || !escape) return OOM; Item_func_like *like= new (thd->mem_root) Item_func_like(thd, fld, pattern, escape, 0); if (!like) return OOM; res->add(like, thd->mem_root); } if (res->fix_fields(thd, (Item**)&res)) return OOM; return res; } /** System variables that we want to see in the feedback report */ static LEX_STRING vars_filter[]= { {C_STRING_WITH_LEN("auto\\_increment%")}, {C_STRING_WITH_LEN("binlog\\_format")}, {C_STRING_WITH_LEN("character\\_set\\_%")}, {C_STRING_WITH_LEN("collation%")}, {C_STRING_WITH_LEN("engine\\_condition\\_pushdown")}, {C_STRING_WITH_LEN("event\\_scheduler")}, {C_STRING_WITH_LEN("feedback\\_%")}, {C_STRING_WITH_LEN("ft\\_m%")}, {C_STRING_WITH_LEN("have\\_%")}, {C_STRING_WITH_LEN("%\\_size")}, {C_STRING_WITH_LEN("innodb_f%")}, {C_STRING_WITH_LEN("%\\_length%")}, {C_STRING_WITH_LEN("%\\_timeout")}, {C_STRING_WITH_LEN("large\\_%")}, {C_STRING_WITH_LEN("lc_time_names")}, {C_STRING_WITH_LEN("log")}, {C_STRING_WITH_LEN("log_bin")}, {C_STRING_WITH_LEN("log_output")}, {C_STRING_WITH_LEN("log_slow_queries")}, {C_STRING_WITH_LEN("log_slow_time")}, {C_STRING_WITH_LEN("lower_case%")}, {C_STRING_WITH_LEN("max_allowed_packet")}, {C_STRING_WITH_LEN("max_connections")}, {C_STRING_WITH_LEN("max_prepared_stmt_count")}, {C_STRING_WITH_LEN("max_sp_recursion_depth")}, {C_STRING_WITH_LEN("max_user_connections")}, {C_STRING_WITH_LEN("max_write_lock_count")}, {C_STRING_WITH_LEN("myisam_recover_options")}, {C_STRING_WITH_LEN("myisam_repair_threads")}, {C_STRING_WITH_LEN("myisam_stats_method")}, {C_STRING_WITH_LEN("myisam_use_mmap")}, {C_STRING_WITH_LEN("net\\_%")}, {C_STRING_WITH_LEN("new")}, {C_STRING_WITH_LEN("old%")}, {C_STRING_WITH_LEN("optimizer%")}, {C_STRING_WITH_LEN("profiling")}, {C_STRING_WITH_LEN("query_cache%")}, {C_STRING_WITH_LEN("secure_auth")}, {C_STRING_WITH_LEN("slow_launch_time")}, {C_STRING_WITH_LEN("sql%")}, {C_STRING_WITH_LEN("default_storage_engine")}, {C_STRING_WITH_LEN("sync_binlog")}, {C_STRING_WITH_LEN("table_definition_cache")}, {C_STRING_WITH_LEN("table_open_cache")}, {C_STRING_WITH_LEN("thread_handling")}, {C_STRING_WITH_LEN("time_zone")}, {C_STRING_WITH_LEN("version%")}, {0, 0} }; /** Status variables that we want to see in the feedback report (empty list = no WHERE condition) */ static LEX_STRING status_filter[]= {{0, 0}}; /** Fill our I_S table with data This function works by invoking fill_variables() and fill_status() of the corresponding I_S tables - to have their data UNION-ed in the same target table. After that it invokes our own fill_* functions from the utils.cc - to get the data that aren't available in the I_S.GLOBAL_VARIABLES and I_S.GLOBAL_STATUS. */ int fill_feedback(THD *thd, TABLE_LIST *tables, COND *unused) { int res; COND *cond; tables->schema_table= schema_tables + SCH_GLOBAL_VARIABLES; cond= make_cond(thd, tables, vars_filter); res= (cond == OOM) ? 1 : fill_variables(thd, tables, cond); tables->schema_table= schema_tables + SCH_GLOBAL_STATUS; if (!res) { cond= make_cond(thd, tables, status_filter); res= (cond == OOM) ? 1 : fill_status(thd, tables, cond); } tables->schema_table= i_s_feedback; res= res || fill_plugin_version(thd, tables) || fill_misc_data(thd, tables) || fill_linux_info(thd, tables) || fill_collation_statistics(thd, tables); return res; } /** plugin initialization function */ static int init(void *p) { i_s_feedback= (ST_SCHEMA_TABLE*) p; /* initialize the I_S descriptor structure */ i_s_feedback->fields_info= feedback_fields; ///< field descriptor i_s_feedback->fill_table= fill_feedback; ///< how to fill the I_S table i_s_feedback->idx_field1 = 0; ///< virtual index on the 1st col #ifdef HAVE_PSI_INTERFACE #define PSI_register(X) \ if(PSI_server) PSI_server->register_ ## X("feedback", X ## _list, array_elements(X ## _list)) #else #define PSI_register(X) /* no-op */ #endif PSI_register(mutex); PSI_register(cond); PSI_register(thread); if (calculate_server_uid(server_uid_buf)) return 1; prepare_linux_info(); #ifndef DBUG_OFF if (startup_interval != debug_startup_interval || first_interval != debug_first_interval || interval != debug_interval) { startup_interval= debug_startup_interval; first_interval= debug_first_interval; interval= debug_interval; user_info= const_cast("mysql-test"); } #endif url_count= 0; if (*url) { // now we split url on spaces and store them in Url objects int slot; char *s, *e; for (s= url, url_count= 1; *s; s++) if (*s == ' ') url_count++; urls= (Url **)my_malloc(PSI_INSTRUMENT_ME, url_count*sizeof(Url*), MYF(MY_WME)); if (!urls) return 1; for (s= url, e = url+1, slot= 0; e[-1]; e++) if (*e == 0 || *e == ' ') { if (e > s && (urls[slot]= Url::create(s, e - s))) { if (urls[slot]->set_proxy(http_proxy, http_proxy ? strlen(http_proxy) : 0)) sql_print_error("feedback plugin: invalid proxy '%s'", http_proxy ? http_proxy : ""); slot++; } else { if (e > s) sql_print_error("feedback plugin: invalid url '%.*s'", (int)(e-s), s); url_count--; } s= e + 1; } // create a background thread to handle urls, if any if (url_count) { mysql_mutex_init(0, &sleep_mutex, 0); mysql_cond_init(0, &sleep_condition, 0); shutdown_plugin= false; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); if (pthread_create(&sender_thread, &attr, background_thread, 0) != 0) { sql_print_error("feedback plugin: failed to start a background thread"); return 1; } } else my_free(urls); } return 0; } /** plugin deinitialization function */ static int free(void *p) { if (url_count) { mysql_mutex_lock(&sleep_mutex); shutdown_plugin= true; mysql_cond_signal(&sleep_condition); mysql_mutex_unlock(&sleep_mutex); pthread_join(sender_thread, NULL); mysql_mutex_destroy(&sleep_mutex); mysql_cond_destroy(&sleep_condition); for (uint i= 0; i < url_count; i++) delete urls[i]; my_free(urls); } return 0; } #ifdef HAVE_OPENSSL #define DEFAULT_PROTO "https://" #else #define DEFAULT_PROTO "http://" #endif static MYSQL_SYSVAR_STR(server_uid, server_uid, PLUGIN_VAR_READONLY | PLUGIN_VAR_NOCMDOPT, "Automatically calculated server unique id hash.", NULL, NULL, 0); static MYSQL_SYSVAR_STR(user_info, user_info, PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG, "User specified string that will be included in the feedback report.", NULL, NULL, ""); static MYSQL_SYSVAR_STR(url, url, PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG, "Space separated URLs to send the feedback report to.", NULL, NULL, DEFAULT_PROTO "feedback.mariadb.org/rest/v1/post"); static MYSQL_SYSVAR_ULONG(send_timeout, send_timeout, PLUGIN_VAR_RQCMDARG, "Timeout (in seconds) for the sending the report.", NULL, NULL, 60, 1, 60*60*24, 1); static MYSQL_SYSVAR_ULONG(send_retry_wait, send_retry_wait, PLUGIN_VAR_RQCMDARG, "Wait this many seconds before retrying a failed send.", NULL, NULL, 60, 1, 60*60*24, 1); static MYSQL_SYSVAR_STR(http_proxy, http_proxy, PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG, "Proxy server host:port.", NULL, NULL,0); #ifndef DBUG_OFF static MYSQL_SYSVAR_ULONG(debug_startup_interval, debug_startup_interval, PLUGIN_VAR_RQCMDARG, "for debugging only", NULL, NULL, startup_interval, 1, INT_MAX32, 1); static MYSQL_SYSVAR_ULONG(debug_first_interval, debug_first_interval, PLUGIN_VAR_RQCMDARG, "for debugging only", NULL, NULL, first_interval, 1, INT_MAX32, 1); static MYSQL_SYSVAR_ULONG(debug_interval, debug_interval, PLUGIN_VAR_RQCMDARG, "for debugging only", NULL, NULL, interval, 1, INT_MAX32, 1); #endif static struct st_mysql_sys_var* settings[] = { MYSQL_SYSVAR(server_uid), MYSQL_SYSVAR(user_info), MYSQL_SYSVAR(url), MYSQL_SYSVAR(send_timeout), MYSQL_SYSVAR(send_retry_wait), MYSQL_SYSVAR(http_proxy), #ifndef DBUG_OFF MYSQL_SYSVAR(debug_startup_interval), MYSQL_SYSVAR(debug_first_interval), MYSQL_SYSVAR(debug_interval), #endif NULL }; static struct st_mysql_information_schema feedback = { MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION }; } // namespace feedback maria_declare_plugin(feedback) { MYSQL_INFORMATION_SCHEMA_PLUGIN, &feedback::feedback, "FEEDBACK", "Sergei Golubchik", "MariaDB User Feedback Plugin", PLUGIN_LICENSE_GPL, feedback::init, feedback::free, 0x0101, NULL, feedback::settings, "1.1", MariaDB_PLUGIN_MATURITY_STABLE } maria_declare_plugin_end;