| 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 | #include <sql_acl.h> |
| 18 | #include <sql_parse.h> |
| 19 | #include <sql_show.h> |
| 20 | #include <time.h> |
| 21 | |
| 22 | namespace feedback { |
| 23 | |
| 24 | static THD *thd= 0; ///< background thread thd |
| 25 | static my_thread_id thd_thread_id; ///< its thread_id |
| 26 | |
| 27 | static size_t needed_size= 20480; |
| 28 | |
| 29 | ulong startup_interval= 60*5; ///< in seconds (5 minutes) |
| 30 | ulong first_interval= 60*60*24; ///< in seconds (one day) |
| 31 | ulong interval= 60*60*24*7; ///< in seconds (one week) |
| 32 | |
| 33 | /** |
| 34 | reads the rows from a table and puts them, concatenated, in a String |
| 35 | |
| 36 | @note |
| 37 | 1. only supports two column tables - no less, no more. |
| 38 | 2. it emulates mysql -e "select * from..." and thus it separates |
| 39 | columns with \t and starts the output with column names. |
| 40 | */ |
| 41 | static int table_to_string(TABLE *table, String *result) |
| 42 | { |
| 43 | int res; |
| 44 | char buff1[MAX_FIELD_WIDTH], buff2[MAX_FIELD_WIDTH]; |
| 45 | String str1(buff1, sizeof(buff1), system_charset_info); |
| 46 | String str2(buff2, sizeof(buff2), system_charset_info); |
| 47 | |
| 48 | res= table->file->ha_rnd_init(1); |
| 49 | |
| 50 | dbug_tmp_use_all_columns(table, table->read_set); |
| 51 | |
| 52 | while(!res && !table->file->ha_rnd_next(table->record[0])) |
| 53 | { |
| 54 | table->field[0]->val_str(&str1); |
| 55 | table->field[1]->val_str(&str2); |
| 56 | if (result->reserve(str1.length() + str2.length() + 3)) |
| 57 | res= 1; |
| 58 | else |
| 59 | { |
| 60 | result->qs_append(str1.ptr(), str1.length()); |
| 61 | result->qs_append('\t'); |
| 62 | result->qs_append(str2.ptr(), str2.length()); |
| 63 | result->qs_append('\n'); |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | res = res || (int)result->append('\n'); |
| 68 | |
| 69 | /* |
| 70 | Note, "|=" and not "||" - because we want to call ha_rnd_end() |
| 71 | even if res is already 1. |
| 72 | */ |
| 73 | res |= table->file->ha_rnd_end(); |
| 74 | |
| 75 | return res; |
| 76 | } |
| 77 | |
| 78 | /** |
| 79 | Initialize the THD and TABLE_LIST |
| 80 | |
| 81 | The structures must be sufficiently initialized for create_tmp_table() |
| 82 | and fill_feedback() to work. |
| 83 | */ |
| 84 | static int prepare_for_fill(TABLE_LIST *tables) |
| 85 | { |
| 86 | /* |
| 87 | Add our thd to the list, for it to be visible in SHOW PROCESSLIST. |
| 88 | But don't generate thread_id every time - use the saved value |
| 89 | (every increment of global thread_id counts as a new connection |
| 90 | in SHOW STATUS and we want to avoid skewing the statistics) |
| 91 | */ |
| 92 | thd->variables.pseudo_thread_id= thd->thread_id; |
| 93 | mysql_mutex_lock(&LOCK_thread_count); |
| 94 | threads.append(thd); |
| 95 | mysql_mutex_unlock(&LOCK_thread_count); |
| 96 | thd->thread_stack= (char*) &tables; |
| 97 | if (thd->store_globals()) |
| 98 | return 1; |
| 99 | |
| 100 | thd->mysys_var->current_cond= &sleep_condition; |
| 101 | thd->mysys_var->current_mutex= &sleep_mutex; |
| 102 | thd->proc_info="feedback" ; |
| 103 | thd->set_command(COM_SLEEP); |
| 104 | thd->system_thread= SYSTEM_THREAD_EVENT_WORKER; // whatever |
| 105 | thd->set_time(); |
| 106 | thd->init_for_queries(); |
| 107 | thd->real_id= pthread_self(); |
| 108 | thd->db= null_clex_str; |
| 109 | thd->security_ctx->host_or_ip= "" ; |
| 110 | thd->security_ctx->db_access= DB_ACLS; |
| 111 | thd->security_ctx->master_access= ~NO_ACCESS; |
| 112 | bzero((char*) &thd->net, sizeof(thd->net)); |
| 113 | lex_start(thd); |
| 114 | mysql_init_select(thd->lex); |
| 115 | |
| 116 | LEX_CSTRING tbl_name= {i_s_feedback->table_name, strlen(i_s_feedback->table_name) }; |
| 117 | |
| 118 | tables->init_one_table(&INFORMATION_SCHEMA_NAME, &tbl_name, 0, TL_READ); |
| 119 | tables->schema_table= i_s_feedback; |
| 120 | tables->table= create_schema_table(thd, tables); |
| 121 | if (!tables->table) |
| 122 | return 1; |
| 123 | |
| 124 | tables->select_lex= thd->lex->current_select; |
| 125 | tables->table->pos_in_table_list= tables; |
| 126 | |
| 127 | return 0; |
| 128 | } |
| 129 | |
| 130 | /** |
| 131 | Try to detect if this thread is going down |
| 132 | |
| 133 | which can happen for different reasons: |
| 134 | * plugin is being unloaded |
| 135 | * mysqld server is being shut down |
| 136 | * the thread is being killed |
| 137 | |
| 138 | */ |
| 139 | static bool going_down() |
| 140 | { |
| 141 | return shutdown_plugin || shutdown_in_progress || (thd && thd->killed); |
| 142 | } |
| 143 | |
| 144 | /** |
| 145 | just like sleep, but waits on a condition and checks "plugin shutdown" status |
| 146 | */ |
| 147 | static int slept_ok(time_t sec) |
| 148 | { |
| 149 | struct timespec abstime; |
| 150 | int ret= 0; |
| 151 | |
| 152 | set_timespec(abstime, sec); |
| 153 | |
| 154 | mysql_mutex_lock(&sleep_mutex); |
| 155 | while (!going_down() && ret != ETIMEDOUT) |
| 156 | ret= mysql_cond_timedwait(&sleep_condition, &sleep_mutex, &abstime); |
| 157 | mysql_mutex_unlock(&sleep_mutex); |
| 158 | |
| 159 | return !going_down(); |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | create a feedback report and send it to all specified urls |
| 164 | |
| 165 | If "when" argument is not null, only it and the server uid are sent. |
| 166 | Otherwise a full report is generated. |
| 167 | */ |
| 168 | static void send_report(const char *when) |
| 169 | { |
| 170 | TABLE_LIST tables; |
| 171 | String str; |
| 172 | int i, last_todo; |
| 173 | Url **todo= (Url**)alloca(url_count*sizeof(Url*)); |
| 174 | |
| 175 | str.alloc(needed_size); // preallocate it to avoid many small mallocs |
| 176 | |
| 177 | /* |
| 178 | on startup and shutdown the server may not be completely |
| 179 | initialized, and full report won't work. |
| 180 | We send a short status notice only. |
| 181 | */ |
| 182 | if (when) |
| 183 | { |
| 184 | str.length(0); |
| 185 | str.append(STRING_WITH_LEN("FEEDBACK_SERVER_UID" )); |
| 186 | str.append('\t'); |
| 187 | str.append(server_uid_buf); |
| 188 | str.append('\n'); |
| 189 | str.append(STRING_WITH_LEN("FEEDBACK_WHEN" )); |
| 190 | str.append('\t'); |
| 191 | str.append(when); |
| 192 | str.append('\n'); |
| 193 | str.append(STRING_WITH_LEN("FEEDBACK_USER_INFO" )); |
| 194 | str.append('\t'); |
| 195 | str.append(user_info); |
| 196 | str.append('\n'); |
| 197 | str.append('\n'); |
| 198 | } |
| 199 | else |
| 200 | { |
| 201 | /* |
| 202 | otherwise, prepare the THD and TABLE_LIST, |
| 203 | create and fill the temporary table with data just like |
| 204 | SELECT * FROM INFORMATION_SCHEMA.FEEDBACK is doing, |
| 205 | read and concatenate table data into a String. |
| 206 | */ |
| 207 | if (!(thd= new THD(thd_thread_id))) |
| 208 | return; |
| 209 | |
| 210 | if (prepare_for_fill(&tables)) |
| 211 | goto ret; |
| 212 | |
| 213 | if (fill_feedback(thd, &tables, NULL)) |
| 214 | goto ret; |
| 215 | |
| 216 | if (table_to_string(tables.table, &str)) |
| 217 | goto ret; |
| 218 | |
| 219 | needed_size= (size_t)(str.length() * 1.1); |
| 220 | |
| 221 | free_tmp_table(thd, tables.table); |
| 222 | tables.table= 0; |
| 223 | } |
| 224 | |
| 225 | /* |
| 226 | Try to send the report on every url from the list, remove url on success, |
| 227 | keep failed in the list. Repeat until the list is empty. |
| 228 | */ |
| 229 | memcpy(todo, urls, url_count*sizeof(Url*)); |
| 230 | last_todo= url_count - 1; |
| 231 | do |
| 232 | { |
| 233 | for (i= 0; i <= last_todo;) |
| 234 | { |
| 235 | Url *url= todo[i]; |
| 236 | |
| 237 | if (thd) // for nicer SHOW PROCESSLIST |
| 238 | thd->set_query(const_cast<char*>(url->url()), (uint) url->url_length()); |
| 239 | |
| 240 | if (url->send(str.ptr(), str.length())) |
| 241 | i++; |
| 242 | else |
| 243 | todo[i]= todo[last_todo--]; |
| 244 | } |
| 245 | if (last_todo < 0) |
| 246 | break; |
| 247 | } while (slept_ok(send_retry_wait)); // wait a little bit before retrying |
| 248 | |
| 249 | ret: |
| 250 | if (thd) |
| 251 | { |
| 252 | if (tables.table) |
| 253 | free_tmp_table(thd, tables.table); |
| 254 | thd->cleanup_after_query(); |
| 255 | /* |
| 256 | clean up, free the thd. |
| 257 | reset all thread local status variables to minimize |
| 258 | the effect of the background thread on SHOW STATUS. |
| 259 | */ |
| 260 | mysql_mutex_lock(&LOCK_thread_count); |
| 261 | thd->set_status_var_init(); |
| 262 | thd->killed= KILL_CONNECTION; |
| 263 | thd->unlink(); |
| 264 | mysql_cond_broadcast(&COND_thread_count); |
| 265 | mysql_mutex_unlock(&LOCK_thread_count); |
| 266 | delete thd; |
| 267 | thd= 0; |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | /** |
| 272 | background sending thread |
| 273 | */ |
| 274 | pthread_handler_t background_thread(void *arg __attribute__((unused))) |
| 275 | { |
| 276 | if (my_thread_init()) |
| 277 | return 0; |
| 278 | |
| 279 | thd_thread_id= next_thread_id(); |
| 280 | |
| 281 | if (slept_ok(startup_interval)) |
| 282 | { |
| 283 | send_report("startup" ); |
| 284 | |
| 285 | if (slept_ok(first_interval)) |
| 286 | { |
| 287 | send_report(NULL); |
| 288 | |
| 289 | while(slept_ok(interval)) |
| 290 | send_report(NULL); |
| 291 | } |
| 292 | |
| 293 | send_report("shutdown" ); |
| 294 | } |
| 295 | |
| 296 | my_thread_end(); |
| 297 | pthread_exit(0); |
| 298 | return 0; |
| 299 | } |
| 300 | |
| 301 | } // namespace feedback |
| 302 | |
| 303 | |