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