mariadb/plugin/feedback/feedback.cc
Sergei Golubchik 8bc3241016 feedback plugin: abort sending the report on server shutdown
network timeouts might be rather large and feedback plugin
waits forever for the sender thread to exit.

an alternative could've been to use GNU-specific pthread_timedjoin_np(),
where _np mean "not portable".
2024-04-13 16:28:13 +02:00

434 lines
13 KiB
C++

/* 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->init();
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<char*>("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);
for (uint i= 0; i < url_count; i++)
urls[i]->abort();
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;