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
22namespace feedback {
23
24static THD *thd= 0; ///< background thread thd
25static my_thread_id thd_thread_id; ///< its thread_id
26
27static size_t needed_size= 20480;
28
29ulong startup_interval= 60*5; ///< in seconds (5 minutes)
30ulong first_interval= 60*60*24; ///< in seconds (one day)
31ulong 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*/
41static 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*/
84static 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*/
139static 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*/
147static 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*/
168static 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
249ret:
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*/
274pthread_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