[Commits] Rev 2950: WL#12 - MariaDB User Feedback (a.k.a. Phone Home) plugin in http://bazaar.launchpad.net/~maria-captains/maria/5.1/

serg at askmonty.org serg at askmonty.org
Thu Sep 30 17:24:41 EEST 2010


At http://bazaar.launchpad.net/~maria-captains/maria/5.1/

------------------------------------------------------------
revno: 2950
revision-id: sergii at pisem.net-20100930142431-4aw4satz0041zt40
parent: sergii at pisem.net-20100929201605-tabk3wi9qdl1n05j
committer: Sergei Golubchik <sergii at pisem.net>
branch nick: 5.1
timestamp: Thu 2010-09-30 16:24:31 +0200
message:
  WL#12 - MariaDB User Feedback (a.k.a. Phone Home) plugin
-------------- next part --------------
=== added directory 'plugin/feedback'
=== added file 'plugin/feedback/CMakeLists.txt'
--- a/plugin/feedback/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ b/plugin/feedback/CMakeLists.txt	2010-09-30 14:24:31 +0000
@@ -0,0 +1,9 @@
+INCLUDE("${PROJECT_SOURCE_DIR}/storage/mysql_storage_engine.cmake")
+
+INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/sql ${CMAKE_SOURCE_DIR}/regex
+                    ${CMAKE_SOURCE_DIR}/extra/yassl/include)
+
+SET(FEEDBACK_SOURCES feedback.cc sender_thread.cc
+    url_base.cc url_http.cc utils.cc)
+
+MYSQL_PLUGIN(FEEDBACK)

=== added file 'plugin/feedback/Makefile.am'
--- a/plugin/feedback/Makefile.am	1970-01-01 00:00:00 +0000
+++ b/plugin/feedback/Makefile.am	2010-09-30 14:24:31 +0000
@@ -0,0 +1,19 @@
+pkgplugindir =          $(pkglibdir)/plugin
+INCLUDES =              -I$(top_srcdir)/include -I$(top_builddir)/include \
+                        -I$(top_srcdir)/regex -I$(top_srcdir)/sql
+
+EXTRA_LTLIBRARIES =     feedback.la
+pkgplugin_LTLIBRARIES = @plugin_feedback_shared_target@
+feedback_la_LDFLAGS =   -module -rpath $(pkgplugindir)
+feedback_la_CXXFLAGS =  -shared -DMYSQL_DYNAMIC_PLUGIN
+feedback_la_SOURCES =   feedback.cc utils.cc url_base.cc url_http.cc \
+                        sender_thread.cc
+
+EXTRA_LIBRARIES =       libfeedback.a
+noinst_LIBRARIES =      @plugin_feedback_static_target@
+libfeedback_a_SOURCES=  feedback.cc utils.cc url_base.cc url_http.cc \
+                        sender_thread.cc
+
+noinst_HEADERS =        feedback.h
+EXTRA_DIST =            CMakeLists.txt plug.in
+

=== added file 'plugin/feedback/feedback.cc'
--- a/plugin/feedback/feedback.cc	1970-01-01 00:00:00 +0000
+++ b/plugin/feedback/feedback.cc	2010-09-30 14:24:31 +0000
@@ -0,0 +1,334 @@
+/* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 {
+
+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, *user_info, *url;
+ulong send_timeout, send_retry_wait;
+ulonglong send_mode;
+
+/**
+  these three are used to communicate the shutdown signal to the
+  background thread
+*/
+pthread_mutex_t sleep_mutex;
+pthread_cond_t sleep_condition;
+volatile bool shutdown_plugin;
+
+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[] =
+{
+  {"VARIABLE_NAME",   255, MYSQL_TYPE_STRING, 0, 0, 0, 0},
+  {"VARIABLE_VALUE", 1024, MYSQL_TYPE_STRING, 0, 0, 0, 0},
+  {0, 0, MYSQL_TYPE_NULL, 0, 0, 0, 0}
+};
+
+/**
+  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;
+  Name_resolution_context nrc;
+  const char *db= tables->db, *table= tables->alias,
+             *field= tables->table->field[0]->field_name;
+
+  nrc.init();
+  nrc.resolve_in_table_list_only(tables);
+
+  for (; filter->str; filter++)
+  {
+    if (!res)
+      res= new Item_cond_or();
+
+    res->add(new
+               Item_func_like(
+                 new Item_field(&nrc, db, table, field),
+                 new Item_string(filter->str, filter->length, &my_charset_latin1),
+                 new Item_string("\\", 1, &my_charset_latin1), 0
+                 )
+               );
+  }
+
+  if (res)
+    res->fix_fields(thd, (Item**)&res);
+
+  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("%\\_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%")},
+  {C_STRING_WITH_LEN("slow_launch_time")},
+  {C_STRING_WITH_LEN("sql%")},
+  {C_STRING_WITH_LEN("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("timed_mutexes")},
+  {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 *cond)
+{
+  int res= 0;
+
+  tables->schema_table= schema_tables + SCH_GLOBAL_VARIABLES;
+  res= fill_variables(thd, tables, make_cond(thd, tables, vars_filter));
+
+  tables->schema_table= schema_tables + SCH_GLOBAL_STATUS;
+  res= res || fill_status(thd, tables, make_cond(thd, tables, status_filter));
+
+  tables->schema_table= i_s_feedback;
+  res= res || fill_plugin_version(thd, tables)
+           || fill_misc_data(thd, tables);
+
+  return res;
+}
+
+static pthread_t sender_thread;
+
+/**
+   plugin initialization function
+*/
+static int init(void *p)
+{
+  i_s_feedback= (ST_SCHEMA_TABLE*) p;
+  i_s_feedback->fields_info= feedback_fields;
+  i_s_feedback->fill_table= fill_feedback;
+  i_s_feedback->idx_field1 = 0;
+
+  if (calculate_server_uid(server_uid_buf))
+    return 1;
+
+  if (*url && send_mode)
+  {
+    // 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(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 ((urls[slot]= Url::create(s, e - s)))
+          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)
+    {
+      pthread_mutex_init(&sleep_mutex, 0);
+      pthread_cond_init(&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, MYF(0));
+  }
+  else
+    url_count= 0;
+
+  return 0;
+}
+
+/**
+   plugin deinitialization function
+*/
+static int free(void *p)
+{
+  if (url_count)
+  {
+    shutdown_plugin= true;
+    pthread_cond_signal(&sleep_condition);
+    pthread_join(sender_thread, NULL);
+
+    pthread_mutex_destroy(&sleep_mutex);
+    pthread_cond_destroy(&sleep_condition);
+
+    for (uint i= 0; i < url_count; i++)
+      delete urls[i];
+    my_free(urls, MYF(0));
+  }
+  return 0;
+}
+
+static const char *sending_modes[]= { "PERIODIC", "STARTUP" };
+TYPELIB sending_mode_lib= { 2, "", sending_modes, 0};
+
+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, 0);
+static MYSQL_SYSVAR_STR(url, url, PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG,
+       "Space separated URLs to send the feedback report to.", NULL, NULL,
+       "https://mariadb.org/feedback_reports");
+static MYSQL_SYSVAR_SET(send_mode, send_mode,
+       PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG,
+       "When to send the feedback report. PERIODIC, STARTUP, or both.",
+       NULL, NULL, 0, &sending_mode_lib);
+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 struct st_mysql_sys_var* settings[] = {
+  MYSQL_SYSVAR(server_uid),
+  MYSQL_SYSVAR(user_info),
+  MYSQL_SYSVAR(url),
+  MYSQL_SYSVAR(send_mode),
+  MYSQL_SYSVAR(send_timeout),
+  MYSQL_SYSVAR(send_retry_wait),
+  NULL
+};
+
+
+static struct st_mysql_information_schema feedback =
+{ MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION };
+
+} // namespace feedback
+
+mysql_declare_plugin(feedback)
+{
+  MYSQL_INFORMATION_SCHEMA_PLUGIN,
+  &feedback::feedback,
+  "FEEDBACK",
+  "Sergei Golubchik",
+  "MariaDB User Feedback Plugin",
+  PLUGIN_LICENSE_GPL,
+  feedback::init,
+  feedback::free,
+  0x0100,
+  NULL,
+  feedback::settings,
+  NULL
+}
+mysql_declare_plugin_end;
+

=== added file 'plugin/feedback/feedback.h'
--- a/plugin/feedback/feedback.h	1970-01-01 00:00:00 +0000
+++ b/plugin/feedback/feedback.h	2010-09-30 14:24:31 +0000
@@ -0,0 +1,70 @@
+/* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
+
+#define MYSQL_SERVER
+#include <mysql_priv.h>
+
+namespace feedback {
+
+/** flags for the send_mode value */
+static const ulonglong PERIODIC= 1;
+static const ulonglong STARTUP=  2; ///< this includes shutdown
+
+int fill_feedback(THD *thd, TABLE_LIST *tables, COND *cond);
+int fill_plugin_version(THD *thd, TABLE_LIST *tables);
+int fill_misc_data(THD *thd, TABLE_LIST *tables);
+
+static const int SERVER_UID_SIZE= 29;
+extern char server_uid_buf[SERVER_UID_SIZE+1];
+int calculate_server_uid(char *);
+
+extern ST_SCHEMA_TABLE *i_s_feedback;
+
+extern ulong send_timeout, send_retry_wait;
+extern ulonglong send_mode;
+
+pthread_handler_t background_thread(void *arg);
+
+/**
+  The class for storing urls to send report data to.
+
+  Constructors are private, the object should be created with create() method.
+  send() method does the actual sending.
+*/
+class Url {
+  protected:
+  Url(LEX_STRING &url_arg) : full_url(url_arg) {}
+  const LEX_STRING full_url;
+
+  public:
+  virtual ~Url() { my_free(full_url.str, MYF(0)); }
+
+  const char *url()   { return full_url.str; }
+  size_t url_length() { return full_url.length; }
+  virtual int send(const char* data, size_t data_length) =  0;
+
+  static Url* create(const char *url, size_t url_length);
+};
+
+extern Url **urls;
+extern uint url_count;
+
+/* these are used to communicate with the background thread */
+extern pthread_mutex_t sleep_mutex;
+extern pthread_cond_t sleep_condition;
+extern volatile bool shutdown_plugin;
+
+} // namespace feedback
+

=== added file 'plugin/feedback/plug.in'
--- a/plugin/feedback/plug.in	1970-01-01 00:00:00 +0000
+++ b/plugin/feedback/plug.in	2010-09-30 14:24:31 +0000
@@ -0,0 +1,16 @@
+MYSQL_PLUGIN(feedback,[MariaDB User Feedback Plugin],
+        [MariaDB User Feedback Plugin], [default,max,max-no-ndb])
+
+dnl Although it's not exactly obvious, top-level CMakeLists.txt parses plug.in
+dnl files, in particular looking for what the library name should be. It uses
+dnl regexp that matches MYSQL_PLUGIN_DYNAMIC or MYSQL_PLUGIN_STATIC, followed
+dnl by an open parenthesys, and the plugin name. Having engine name enclosed in
+dnl square brackets below causes this regexp to fail and as a result feedback
+dnl plugin will not be considered for dynamic builds on Windows.
+dnl Unfortunately, feedback cannot be built dynamically on Windows, because it
+dnl needs to access server internals that aren't designed for plugin use and
+dnl aren't marked with MYSQL_PLUGIN_IMPORT.
+MYSQL_PLUGIN_DYNAMIC([feedback], [feedback.la])
+MYSQL_PLUGIN_STATIC(feedback, [libfeedback.a])
+
+AC_CHECK_HEADERS_ONCE([netdb.h])

=== added file 'plugin/feedback/sender_thread.cc'
--- a/plugin/feedback/sender_thread.cc	1970-01-01 00:00:00 +0000
+++ b/plugin/feedback/sender_thread.cc	2010-09-30 14:24:31 +0000
@@ -0,0 +1,275 @@
+/* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
+
+#include "feedback.h"
+#include <time.h>
+
+namespace feedback {
+
+static THD *thd= 0;                ///< background thread thd
+static my_thread_id thd_thread_id; ///< its thread_id
+
+const time_t interval= 60*60*24*7; ///< interval is in seconds (one week)
+
+/**
+  reads the rows from a table and puts them, concatenated, in a String
+
+  @note
+  1. only supports two column tables - no less, no more.
+  2. it emulates mysql -e "select * from..." and thus it separates
+     columns with \t and starts the output with column names.
+*/
+static int table_to_string(TABLE *table, String *result)
+{
+  bool res;
+  char buff[MAX_FIELD_WIDTH];
+  String str(buff, sizeof(buff), system_charset_info);
+
+  res= table->file->ha_rnd_init(1);
+
+  dbug_tmp_use_all_columns(table, table->read_set);
+
+  while(!res && !table->file->rnd_next(table->record[0]))
+  {
+    table->field[0]->val_str(&str);
+    res = res || result->append(str) || result->append('\t');
+    table->field[1]->val_str(&str);
+    res = res || result->append(str) || result->append('\n');
+  }
+
+  res = res || result->append('\n');
+
+  /*
+    Note, "|=" and not "||" - because we want to call ha_rnd_end()
+    even if res is already 1.
+  */
+  res |= table->file->ha_rnd_end();
+
+  return res;
+}
+
+/**
+  Initialize the THD and TABLE_LIST
+
+  The structures must be sufficiently initialized for create_tmp_table()
+  and fill_feedback() to work.
+*/
+static int prepare_for_fill(TABLE_LIST *tables)
+{
+  /*
+    Add our thd to the list, for it to be visible in SHOW PROCESSLIST.
+    But don't generate thread_id every time - use the saved value
+    (every increment of global thread_id counts as a new connection
+    in SHOW STATUS and we want to avoid skewing the statistics)
+  */
+  pthread_mutex_lock(&LOCK_thread_count);
+  thd->thread_id= thd->variables.pseudo_thread_id= thd_thread_id;
+  thread_count++;
+  threads.append(thd);
+  pthread_mutex_unlock(&LOCK_thread_count);
+  thd->thread_stack= (char*) &thd;
+  if (thd->store_globals())
+    return 1;
+
+  thd->mysys_var->current_cond= &sleep_condition;
+  thd->mysys_var->current_mutex= &sleep_mutex;
+  thd->proc_info="feedback";
+  thd->command=COM_SLEEP;
+  thd->version=refresh_version;
+  thd->system_thread= SYSTEM_THREAD_EVENT_WORKER; // whatever
+  thd->set_time();
+  thd->init_for_queries();
+  thd->real_id= pthread_self();
+  thd->db= NULL;
+  thd->db_length= 0;
+  thd->security_ctx->host_or_ip= "";
+  thd->security_ctx->db_access= DB_ACLS;
+  thd->security_ctx->master_access= ~NO_ACCESS;
+  bzero((char*) &thd->net, sizeof(thd->net));
+  lex_start(thd);
+  mysql_init_select(thd->lex);
+
+  tables->init_one_table(INFORMATION_SCHEMA_NAME.str,
+                         i_s_feedback->table_name, TL_READ);
+  tables->schema_table= i_s_feedback;
+  tables->table= i_s_feedback->create_table(thd, tables);
+  if (!tables->table)
+    return 1;
+
+  tables->table->pos_in_table_list= tables;
+
+  return 0;
+}
+
+/**
+  Try to detect if this thread is going down
+
+  which can happen for different reasons:
+  * plugin is being unloaded
+  * mysqld server is being shut down
+  * the thread is being killed
+
+*/
+static bool going_down()
+{
+  return shutdown_plugin || shutdown_in_progress || (thd && thd->killed);
+}
+
+/**
+  just like sleep, but waits on a condition and checks "plugin shutdown" status
+*/
+static int delay(int sec)
+{
+  struct timespec abstime;
+  int ret= 0;
+
+  set_timespec(abstime, sec);
+
+  pthread_mutex_lock(&sleep_mutex);
+  while (!going_down() && ret != ETIMEDOUT)
+    ret= pthread_cond_timedwait(&sleep_condition, &sleep_mutex, &abstime);
+  pthread_mutex_unlock(&sleep_mutex);
+
+  return going_down();
+}
+
+/**
+  create a feedback report and send it to all specified urls
+
+  If "when" argument is not null, only it and the server uid are sent.
+  Otherwise a full report is generated.
+*/
+static void send_report(const char *when)
+{
+  TABLE_LIST tables;
+  String str;
+  int i, last_todo;
+  Url **todo= (Url**)alloca(url_count*sizeof(Url*));
+
+  /*
+    on startup and shutdown the server may not be completely
+    initialized, and full report won't work.
+    We send a short status notice only.
+  */
+  if (when)
+  {
+    str.length(0);
+    str.append(STRING_WITH_LEN("FEEDBACK_SERVER_UID"));
+    str.append('\t');
+    str.append(server_uid_buf);
+    str.append('\n');
+    str.append(STRING_WITH_LEN("FEEDBACK_WHEN"));
+    str.append('\t');
+    str.append(when);
+    str.append('\n');
+    str.append('\n');
+  }
+  else
+  {
+    /*
+      otherwise, prepare the THD and TABLE_LIST,
+      create and fill the temporary table with data just like
+      SELECT * FROM IFROEMATION_SCHEMA.feedback is doing,
+      read and concatenate table data into a String.
+    */
+    if (!(thd= new THD()))
+      return;
+
+    if (prepare_for_fill(&tables))
+      goto ret;
+
+    if (fill_feedback(thd, &tables, NULL))
+      goto ret;
+
+    if (table_to_string(tables.table, &str))
+      goto ret;
+
+    free_tmp_table(thd, tables.table);
+  }
+
+  /*
+    Try to send the report on every url from the list, remove url on success,
+    keep failed in the list. Repeat until the list is empty.
+  */
+  memcpy(todo, urls, url_count*sizeof(Url*));
+  last_todo= url_count - 1;
+  do
+  {
+    for (i= 0; i <= last_todo;)
+    {
+      Url *url= todo[i];
+
+      if (thd) // for nicer SHOW PROCESSLIST
+        thd->set_query(const_cast<char*>(url->url()), url->url_length());
+
+      if (url->send(str.c_ptr(), str.length()))
+        i++;
+      else
+        todo[i]= todo[last_todo--];
+    }
+    if (last_todo < 0)
+      break;
+  } while (delay(send_retry_wait) == 0); // wait a little bit before retrying
+
+ret:
+  if (thd)
+  {
+    /*
+      clean up, free the thd.
+      reset all thread local status variables to minimize
+      the effect of the background thread on SHOW STATUS.
+    */
+    pthread_mutex_lock(&LOCK_thread_count);
+    bzero(&thd->status_var, sizeof(thd->status_var));
+    thread_count--;
+    thd->killed= THD::KILL_CONNECTION;
+    pthread_cond_broadcast(&COND_thread_count);
+    pthread_mutex_unlock(&LOCK_thread_count);
+    delete thd;
+    thd= 0;
+  }
+}
+
+/**
+  background sending thread
+*/
+pthread_handler_t background_thread(void *arg __attribute__((unused)))
+{
+  if (my_thread_init())
+    return 0;
+
+  pthread_mutex_lock(&LOCK_thread_count);
+  thd_thread_id= thread_id++;
+  pthread_mutex_unlock(&LOCK_thread_count);
+
+  if (send_mode & STARTUP)
+    send_report("startup");
+
+  while (delay(interval) == 0)
+  {
+    if (send_mode & PERIODIC)
+      send_report(NULL);
+  }
+
+  if (send_mode & STARTUP)
+    send_report("shutdown");
+
+  my_thread_end();
+  pthread_exit(0);
+  return 0;
+}
+
+} // namespace feedback
+

=== added file 'plugin/feedback/url_base.cc'
--- a/plugin/feedback/url_base.cc	1970-01-01 00:00:00 +0000
+++ b/plugin/feedback/url_base.cc	2010-09-30 14:24:31 +0000
@@ -0,0 +1,51 @@
+/* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
+
+#include "feedback.h"
+
+namespace feedback {
+
+Url* http_create(const char *url, size_t url_length);
+
+/**
+  creates an Url object out of an url, if possible.
+
+  This is done by invoking corresponding creator functions
+  of the derived classes, until the first not NULL result.
+*/
+Url* Url::create(const char *url, size_t url_length)
+{
+  url= my_strndup(url, url_length, MYF(MY_WME));
+  
+  if (!url)
+    return NULL;
+
+  Url *self= http_create(url, url_length);
+
+  /*
+    here we can add
+
+    if (!self) self= smtp_create(url, url_length);
+    if (!self) self= tftp_create(url, url_length);
+    etc
+  */
+
+  if (!self)
+    my_free(const_cast<char*>(url), MYF(0));
+
+  return self;
+}
+
+} // namespace feedback

=== added file 'plugin/feedback/url_http.cc'
--- a/plugin/feedback/url_http.cc	1970-01-01 00:00:00 +0000
+++ b/plugin/feedback/url_http.cc	2010-09-30 14:24:31 +0000
@@ -0,0 +1,305 @@
+/* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
+
+#include "feedback.h"
+
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#ifdef _WIN32
+#include <ws2tcpip.h>
+#define addrinfo ADDRINFOA
+#endif
+
+namespace feedback {
+
+static const char   *http= "http://";
+static const size_t  http_len= 7;
+static const char   *https= "https://";
+static const size_t  https_len= 8;
+
+static const uint FOR_READING= 0;
+static const uint FOR_WRITING= 1;
+
+#ifdef MARIADB_BASE_VERSION
+#define ssl_connect(A,B,C,D) sslconnect(A,B,C,D)
+#else
+#define ssl_connect(A,B,C,D) sslconnect(A,B,C)
+#endif
+
+/**
+  implementation of the Url class that sends the data via HTTP POST request.
+
+  Both http:// and https:// protocols are supported.
+*/
+class Url_http: public Url {
+  protected:
+  const LEX_STRING host, port, path;
+  bool ssl;
+
+  Url_http(LEX_STRING &url_arg, LEX_STRING &host_arg,
+          LEX_STRING &port_arg, LEX_STRING &path_arg, bool ssl_arg) :
+    Url(url_arg), host(host_arg), port(port_arg), path(path_arg), ssl(ssl_arg)
+    {}
+  ~Url_http()
+  {
+    my_free(host.str, MYF(0));
+    my_free(port.str, MYF(0));
+    my_free(path.str, MYF(0));
+  }
+
+  public:
+  int send(const char* data, size_t data_length);
+
+  friend Url* http_create(const char *url, size_t url_length);
+};
+
+/**
+  create a Url_http object out of the url, if possible.
+
+  @note
+  Arbitrary limitations here.
+
+  The url must be http[s]://hostname[:port]/path
+  No username:password@ or ?script=parameters are supported.
+
+  But it's ok. This is not a generic purpose www browser - it only needs to be
+  good enough to POST the data to mariadb.org.
+*/
+Url* http_create(const char *url, size_t url_length)
+{
+  const char *s;
+  LEX_STRING full_url= {const_cast<char*>(url), url_length};
+  LEX_STRING host, port, path;
+  bool ssl= false;
+
+  if (!is_prefix(url, http))
+  {
+#ifdef HAVE_OPENSSL
+    if (is_prefix(url, https))
+      ssl= true;
+    else
+#endif
+      return NULL;
+  }
+
+  s= url + (ssl ? https_len : http_len);
+
+  for (url= s; *s && *s != ':' && *s != '/'; s++);
+  host.str= const_cast<char*>(url);
+  host.length= s-url;
+
+  if (*s == ':')
+  {
+    for (url= ++s; *s && *s >= '0' && *s <= '9'; s++);
+    port.str= const_cast<char*>(url);
+    port.length= s-url;
+  }
+  else
+  {
+    if (ssl)
+    {
+      port.str= const_cast<char*>("443");
+      port.length=3;
+    }
+    else
+    {
+      port.str= const_cast<char*>("80");
+      port.length=2;
+    }
+  }
+
+  if (*s == 0)
+  {
+    path.str= const_cast<char*>("/");
+    path.length= 1;
+  }
+  else
+  {
+    path.str= const_cast<char*>(s);
+    path.length= strlen(s);
+  }
+  if (!host.length || !port.length || path.str[0] != '/')
+    return NULL;
+
+  host.str= my_strndup(host.str, host.length, MYF(MY_WME));
+  port.str= my_strndup(port.str, port.length, MYF(MY_WME));
+  path.str= my_strndup(path.str, path.length, MYF(MY_WME));
+
+  if (!host.str || !port.str || !path.str)
+  {
+    my_free(host.str, MYF(MY_ALLOW_ZERO_PTR));
+    my_free(port.str, MYF(MY_ALLOW_ZERO_PTR));
+    my_free(path.str, MYF(MY_ALLOW_ZERO_PTR));
+    return NULL;
+  }
+
+  return new Url_http(full_url, host, port, path, ssl);
+}
+
+/* do the vio_write and check that all data were sent ok */
+#define write_check(VIO, DATA, LEN)             \
+  (vio_write((VIO), (DATA), (LEN)) != (LEN))
+
+int Url_http::send(const char* data, size_t data_length)
+{
+  my_socket fd;
+  char buf[1024];
+  uint len;
+
+  addrinfo *addrs, *addr, filter= {0, AF_UNSPEC, SOCK_STREAM, 6, 0, 0, 0, 0};
+  int res= getaddrinfo(host.str, port.str, &filter, &addrs);
+
+  if (res)
+  {
+    sql_print_error("feedback plugin: getaddrinfo() failed for url '%s': %s",
+                    full_url.str, gai_strerror(res));
+    return 1;
+  }
+
+  for (addr = addrs; addr != NULL; addr = addr->ai_next)
+  {
+    fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
+    if (fd == INVALID_SOCKET)
+      continue;
+
+    if (connect(fd, addr->ai_addr, addr->ai_addrlen) == 0)
+      break;
+
+    closesocket(fd);
+  }
+  if (fd == INVALID_SOCKET)
+  {
+    sql_print_error("feedback plugin: could not connect for url '%s'",
+                    full_url.str);
+    return 1;
+  }
+
+  freeaddrinfo(addrs);
+
+  Vio *vio= vio_new(fd, VIO_TYPE_TCPIP, 0);
+  if (!vio)
+  {
+    sql_print_error("feedback plugin: vio_new failed for url '%s'",
+                    full_url.str);
+    closesocket(fd);
+    return 1;
+  }
+
+#ifdef HAVE_OPENSSL
+  struct st_VioSSLFd *ssl_fd;
+  if (ssl)
+  {
+    buf[0]= 0;
+    if (!(ssl_fd= new_VioSSLConnectorFd(0, 0, 0, 0, 0)) ||
+        ssl_connect(ssl_fd, vio, send_timeout, buf))
+    {
+      sql_print_error("feedback plugin: ssl failed for url '%s' %s",
+                      full_url.str, buf);
+      if (ssl_fd)
+        free_vio_ssl_acceptor_fd(ssl_fd);
+      closesocket(fd);
+      vio_delete(vio);
+      return 1;
+    }
+  }
+#endif
+
+  static const LEX_STRING boundary= 
+  { C_STRING_WITH_LEN("----------------------------ba4f3696b39f") };
+  static const LEX_STRING header=
+  { C_STRING_WITH_LEN("\r\n"
+      "Content-Disposition: form-data; name=\"data\"; filename=\"-\"\r\n"
+      "Content-Type: application/octet-stream\r\n\r\n")
+  };
+
+  len= my_snprintf(buf, sizeof(buf),
+                   "POST %s HTTP/1.0\r\n"
+                   "User-Agent: MariaDB User Feedback Plugin\r\n"
+                   "Host: %s:%s\r\n"
+                   "Accept: */*\r\n"
+                   "Content-Length: %u\r\n"
+                   "Content-Type: multipart/form-data; boundary=%s\r\n"
+                   "\r\n",
+                   path.str, host.str, port.str,
+                   (uint)(2*boundary.length + header.length + data_length + 4),
+                   boundary.str + 2);
+
+  vio_timeout(vio, FOR_READING, send_timeout);
+  vio_timeout(vio, FOR_WRITING, send_timeout);
+  res = write_check(vio, (uchar*)buf, len)
+     || write_check(vio, (uchar*)boundary.str, boundary.length)
+     || write_check(vio, (uchar*)header.str, header.length)
+     || write_check(vio, (uchar*)data, data_length)
+     || write_check(vio, (uchar*)boundary.str, boundary.length)
+     || write_check(vio, (uchar*)"--\r\n", 4);
+
+  if (res)
+    sql_print_error("feedback plugin: failed to send report to '%s'",
+                    full_url.str);
+  else
+  {
+    sql_print_information("feedback plugin: report to '%s' was sent",
+                          full_url.str);
+
+    /*
+      if the data were send successfully, read the reply.
+      Extract the first string between <h1>...</h1> tags
+      and put it as a server reply into the error log.
+    */
+    len= vio_read(vio, (uchar*)buf, sizeof(buf)-1);
+    if (len && len < sizeof(buf))
+    {
+      char *from;
+
+      buf[len+1]= 0; // safety
+
+      if ((from= strstr(buf, "<h1>")))
+      {
+        from+= 4;
+        char *to= strstr(from, "</h1>");
+        if (to)
+          *to= 0;
+        else
+          from= NULL;
+      }
+      if (from)
+        sql_print_information("feedback plugin: server replied '%s'", from);
+      else
+        sql_print_warning("feedback plugin: failed to parse server reply");
+    }
+    else
+    {
+      res= 1;
+      sql_print_error("feedback plugin: failed to read server reply");
+    }
+  }
+
+  vio_delete(vio);
+
+#ifdef HAVE_OPENSSL
+  if (ssl)
+  {
+    SSL_CTX_free(ssl_fd->ssl_context);
+    my_free(ssl_fd, MYF(0));
+  }
+#endif
+
+  return res;
+}
+
+} // namespace feedback
+

=== added file 'plugin/feedback/utils.cc'
--- a/plugin/feedback/utils.cc	1970-01-01 00:00:00 +0000
+++ b/plugin/feedback/utils.cc	2010-09-30 14:24:31 +0000
@@ -0,0 +1,134 @@
+/* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
+
+#include "feedback.h"
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <base64.h>
+#include <sha1.h>
+
+bool schema_table_store_record(THD *thd, TABLE *table);
+
+namespace feedback {
+
+/*
+  convenience macros for inserting rows into I_S table.
+*/
+#define INSERT2(NAME,LEN,VALUE)                       \
+  do {                                                \
+    table->field[0]->store(NAME, LEN, system_charset_info); \
+    table->field[1]->store VALUE;                     \
+    if (schema_table_store_record(thd, table))        \
+      return 1;                                       \
+  } while (0)
+
+#define INSERT1(NAME,VALUE)                           \
+  do {                                                \
+    table->field[0]->store(NAME, sizeof(NAME)-1, system_charset_info); \
+    table->field[1]->store VALUE;                     \
+    if (schema_table_store_record(thd, table))        \
+      return 1;                                       \
+  } while (0)
+
+static const bool UNSIGNED= true; ///< used below when inserting integers
+
+/**
+  callback for fill_plugin_version() - insert a plugin name and its version
+*/
+static my_bool show_plugins(THD *thd, plugin_ref plugin, void *arg)
+{
+  TABLE *table= (TABLE*) arg;
+
+  INSERT2(plugin_name(plugin)->str, plugin_name(plugin)->length,
+          (plugin_decl(plugin)->version, UNSIGNED));
+
+  return 0;
+}
+
+/**
+  inserts all plugins and their versions into I_S.FEEDBACK
+*/
+int fill_plugin_version(THD *thd, TABLE_LIST *tables)
+{
+  return plugin_foreach_with_mask(thd, show_plugins, MYSQL_ANY_PLUGIN,
+                                  ~PLUGIN_IS_FREED, tables->table);
+}
+
+#if defined(_SC_PAGE_SIZE) && !defined(_SC_PAGESIZE)
+#define _SC_PAGESIZE _SC_PAGE_SIZE
+#endif
+
+/**
+  return the amount of physical memory
+*/
+static ulonglong my_getphysmem()
+{
+  ulonglong pages= 0;
+#ifdef _SC_PHYS_PAGES
+  pages= sysconf(_SC_PHYS_PAGES);
+#else
+  return 0;
+#endif
+
+#ifdef _SC_PAGESIZE
+  return pages * sysconf(_SC_PAGESIZE);
+#else
+  return pages * my_getpagesize();
+#endif
+}
+
+/**
+  Adds varios bits of information to the I_S.FEEDBACK
+*/
+int fill_misc_data(THD *thd, TABLE_LIST *tables)
+{
+  TABLE *table= tables->table;
+
+#ifdef MY_ATOMIC_OK
+  INSERT1("Cpu_count", (my_getncpus(), UNSIGNED));
+#endif
+  INSERT1("Mem_total", (my_getphysmem(), UNSIGNED));
+
+  return 0;
+}
+
+/**
+  calculates the server unique identifier
+  
+  UID is a base64 encoded SHA1 hash of the MAC address of one of
+  the interfaces, and the tcp port that the server is listening on
+*/
+int calculate_server_uid(char *dest)
+{
+  uchar rawbuf[2 + 6];
+  uchar shabuf[SHA1_HASH_SIZE];
+  SHA1_CONTEXT ctx;
+
+  int2store(rawbuf, mysqld_port);
+  if (my_gethwaddr(rawbuf + 2))
+    return 1;
+
+  mysql_sha1_reset(&ctx);
+  mysql_sha1_input(&ctx, rawbuf, sizeof(rawbuf));
+  mysql_sha1_result(&ctx, shabuf);
+
+  assert(base64_needed_encoded_length(sizeof(shabuf)) <= SERVER_UID_SIZE);
+  base64_encode(shabuf, sizeof(shabuf), dest);
+
+  return 0;
+}
+
+} // namespace feedback



More information about the commits mailing list