feedback.cc 12.9 KB
Newer Older
Sergei Golubchik's avatar
Sergei Golubchik committed
1 2 3 4 5 6 7 8 9 10 11 12 13
/* 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
Michal Schorm's avatar
Michal Schorm committed
14
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
Sergei Golubchik's avatar
Sergei Golubchik committed
15 16 17 18 19 20 21 22 23 24

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

Sergei Golubchik's avatar
Sergei Golubchik committed
25 26 27 28
#ifndef DBUG_OFF
ulong debug_startup_interval, debug_first_interval, debug_interval;
#endif

Sergei Golubchik's avatar
Sergei Golubchik committed
29 30 31
char server_uid_buf[SERVER_UID_SIZE+1]; ///< server uid will be written here

/* backing store for system variables */
32
static char *server_uid= server_uid_buf, *url, *http_proxy;
Sergei Golubchik's avatar
Sergei Golubchik committed
33 34 35 36 37 38 39
char *user_info;
ulong send_timeout, send_retry_wait;

/**
  these three are used to communicate the shutdown signal to the
  background thread
*/
Sergei Golubchik's avatar
Sergei Golubchik committed
40 41
mysql_mutex_t sleep_mutex;
mysql_cond_t sleep_condition;
Sergei Golubchik's avatar
Sergei Golubchik committed
42 43 44
volatile bool shutdown_plugin;
static pthread_t sender_thread;

45
#ifdef HAVE_PSI_INTERFACE
Sergei Golubchik's avatar
Sergei Golubchik committed
46 47 48 49 50 51 52 53 54 55 56
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}};
57
#endif
Sergei Golubchik's avatar
Sergei Golubchik committed
58

Sergei Golubchik's avatar
Sergei Golubchik committed
59 60 61 62 63 64 65 66 67 68 69
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[] =
{
70 71 72
  Show::Column("VARIABLE_NAME",  Show::Varchar(255),  NOT_NULL),
  Show::Column("VARIABLE_VALUE", Show::Varchar(1024), NOT_NULL),
  Show::CEnd()
Sergei Golubchik's avatar
Sergei Golubchik committed
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
};

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;
95 96
  /* A reference to this context will be stored in Item_field */
  Name_resolution_context *nrc= new (thd->mem_root) Name_resolution_context;
97 98 99
  LEX_CSTRING &db= tables->db;
  LEX_CSTRING &table= tables->alias;
  LEX_CSTRING &field= tables->table->field[0]->field_name;
Sergei Golubchik's avatar
Sergei Golubchik committed
100 101
  CHARSET_INFO *cs= &my_charset_latin1;

102
  if (!filter->str || !nrc)
Sergei Golubchik's avatar
Sergei Golubchik committed
103 104
    return 0;

105 106
  nrc->resolve_in_table_list_only(tables);
  nrc->select_lex= tables->select_lex;
Sergei Golubchik's avatar
Sergei Golubchik committed
107

Monty's avatar
Monty committed
108
  res= new (thd->mem_root) Item_cond_or(thd);
Sergei Golubchik's avatar
Sergei Golubchik committed
109 110 111 112 113
  if (!res)
    return OOM;

  for (; filter->str; filter++)
  {
114
    Item_field  *fld= new (thd->mem_root) Item_field(thd, nrc, db, table,
Monty's avatar
Monty committed
115 116
                                                     field);
    Item_string *pattern= new (thd->mem_root) Item_string(thd, filter->str,
117
                                                          (uint) filter->length, cs);
Monty's avatar
Monty committed
118
    Item_string *escape= new (thd->mem_root) Item_string(thd, "\\", 1, cs);
Sergei Golubchik's avatar
Sergei Golubchik committed
119 120 121 122

    if (!fld || !pattern || !escape)
      return OOM;

Monty's avatar
Monty committed
123 124
    Item_func_like *like= new (thd->mem_root) Item_func_like(thd, fld, pattern,
                                                             escape, 0);
Sergei Golubchik's avatar
Sergei Golubchik committed
125 126 127 128

    if (!like)
      return OOM;

129
    res->add(like, thd->mem_root);
Sergei Golubchik's avatar
Sergei Golubchik committed
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
  }

  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")},
152
  {C_STRING_WITH_LEN("innodb_f%")},
Sergei Golubchik's avatar
Sergei Golubchik committed
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
  {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%")},
182
  {C_STRING_WITH_LEN("default_storage_engine")},
Sergei Golubchik's avatar
Sergei Golubchik committed
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
  {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)
228 229
           || fill_linux_info(thd, tables)
           || fill_collation_statistics(thd, tables);
Sergei Golubchik's avatar
Sergei Golubchik committed
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244

  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

245
#ifdef HAVE_PSI_INTERFACE
Sergei Golubchik's avatar
Sergei Golubchik committed
246
#define PSI_register(X) \
247
  if(PSI_server) PSI_server->register_ ## X("feedback", X ## _list, array_elements(X ## _list))
248 249 250
#else
#define PSI_register(X) /* no-op */
#endif
Sergei Golubchik's avatar
Sergei Golubchik committed
251 252 253 254 255

  PSI_register(mutex);
  PSI_register(cond);
  PSI_register(thread);

Sergei Golubchik's avatar
Sergei Golubchik committed
256 257 258 259 260
  if (calculate_server_uid(server_uid_buf))
    return 1;

  prepare_linux_info();

Sergei Golubchik's avatar
Sergei Golubchik committed
261 262 263 264 265 266 267 268
#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;
269
    user_info= const_cast<char*>("mysql-test");
Sergei Golubchik's avatar
Sergei Golubchik committed
270 271 272
  }
#endif

Sergei Golubchik's avatar
Sergei Golubchik committed
273 274 275 276 277 278 279 280 281 282 283
  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++;

284
    urls= (Url **)my_malloc(PSI_INSTRUMENT_ME, url_count*sizeof(Url*), MYF(MY_WME));
Sergei Golubchik's avatar
Sergei Golubchik committed
285 286 287 288 289 290 291
    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)))
292 293 294 295 296
        {
          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 : "");
Sergei Golubchik's avatar
Sergei Golubchik committed
297
          slot++;
298
        }
Sergei Golubchik's avatar
Sergei Golubchik committed
299 300 301 302 303 304 305 306 307 308 309 310
        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)
    {
Sergei Golubchik's avatar
Sergei Golubchik committed
311 312
      mysql_mutex_init(0, &sleep_mutex, 0);
      mysql_cond_init(0, &sleep_condition, 0);
Sergei Golubchik's avatar
Sergei Golubchik committed
313 314 315 316 317 318 319 320 321 322 323 324
      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
Sergei Golubchik's avatar
Sergei Golubchik committed
325
      my_free(urls);
Sergei Golubchik's avatar
Sergei Golubchik committed
326 327 328 329 330 331 332 333 334 335 336 337
  }

  return 0;
}

/**
   plugin deinitialization function
*/
static int free(void *p)
{
  if (url_count)
  {
Sergei Golubchik's avatar
Sergei Golubchik committed
338
    mysql_mutex_lock(&sleep_mutex);
Sergei Golubchik's avatar
Sergei Golubchik committed
339
    shutdown_plugin= true;
Sergei Golubchik's avatar
Sergei Golubchik committed
340 341
    mysql_cond_signal(&sleep_condition);
    mysql_mutex_unlock(&sleep_mutex);
Sergei Golubchik's avatar
Sergei Golubchik committed
342 343
    pthread_join(sender_thread, NULL);

Sergei Golubchik's avatar
Sergei Golubchik committed
344 345
    mysql_mutex_destroy(&sleep_mutex);
    mysql_cond_destroy(&sleep_condition);
Sergei Golubchik's avatar
Sergei Golubchik committed
346 347 348

    for (uint i= 0; i < url_count; i++)
      delete urls[i];
Sergei Golubchik's avatar
Sergei Golubchik committed
349
    my_free(urls);
Sergei Golubchik's avatar
Sergei Golubchik committed
350 351 352 353
  }
  return 0;
}

354 355 356 357 358 359
#ifdef HAVE_OPENSSL
#define DEFAULT_PROTO "https://"
#else
#define DEFAULT_PROTO "http://"
#endif

Sergei Golubchik's avatar
Sergei Golubchik committed
360 361 362 363 364 365 366 367 368
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,
369
       DEFAULT_PROTO "mariadb.org/feedback_plugin/post");
Sergei Golubchik's avatar
Sergei Golubchik committed
370 371 372 373 374 375
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);
376 377 378 379
static MYSQL_SYSVAR_STR(http_proxy, http_proxy,
                        PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG,
       "Proxy server host:port.", NULL, NULL,0);

Sergei Golubchik's avatar
Sergei Golubchik committed
380 381 382 383 384 385 386 387 388 389 390 391
#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

Sergei Golubchik's avatar
Sergei Golubchik committed
392 393 394 395 396 397
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),
398
  MYSQL_SYSVAR(http_proxy),
Sergei Golubchik's avatar
Sergei Golubchik committed
399 400 401 402 403
#ifndef DBUG_OFF
  MYSQL_SYSVAR(debug_startup_interval),
  MYSQL_SYSVAR(debug_first_interval),
  MYSQL_SYSVAR(debug_interval),
#endif
Sergei Golubchik's avatar
Sergei Golubchik committed
404 405 406 407 408 409 410 411 412
  NULL
};


static struct st_mysql_information_schema feedback =
{ MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION };

} // namespace feedback

Sergei Golubchik's avatar
Sergei Golubchik committed
413 414 415 416 417 418 419 420 421 422
maria_declare_plugin(feedback)
{
  MYSQL_INFORMATION_SCHEMA_PLUGIN,
  &feedback::feedback,
  "FEEDBACK",
  "Sergei Golubchik",
  "MariaDB User Feedback Plugin",
  PLUGIN_LICENSE_GPL,
  feedback::init,
  feedback::free,
Sergei Golubchik's avatar
Sergei Golubchik committed
423
  0x0101,
Sergei Golubchik's avatar
Sergei Golubchik committed
424 425
  NULL,
  feedback::settings,
Sergei Golubchik's avatar
Sergei Golubchik committed
426
  "1.1",
427
  MariaDB_PLUGIN_MATURITY_STABLE
Sergei Golubchik's avatar
Sergei Golubchik committed
428
}
Sergei Golubchik's avatar
Sergei Golubchik committed
429
maria_declare_plugin_end;