1 /* Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved.
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, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software Foundation,
21 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
22
23 /**
24 @file storage/perfschema/pfs_digest.h
25 Statement Digest data structures (implementation).
26 */
27
28 /*
29 This code needs extra visibility in the lexer structures
30 */
31
32 #include "my_global.h"
33 #include "my_sys.h"
34 #include "pfs_instr.h"
35 #include "pfs_digest.h"
36 #include "pfs_global.h"
37 #include "table_helper.h"
38 #include "sql_lex.h"
39 #include "sql_get_diagnostics.h"
40 #include "sql_string.h"
41 #include <string.h>
42
43 size_t digest_max= 0;
44 ulong digest_lost= 0;
45
46 /** EVENTS_STATEMENTS_HISTORY_LONG circular buffer. */
47 PFS_statements_digest_stat *statements_digest_stat_array= NULL;
48 static unsigned char *statements_digest_token_array= NULL;
49 /** Consumer flag for table EVENTS_STATEMENTS_SUMMARY_BY_DIGEST. */
50 bool flag_statements_digest= true;
51 /**
52 Current index in Stat array where new record is to be inserted.
53 index 0 is reserved for "all else" case when entire array is full.
54 */
55 volatile uint32 PFS_ALIGNED digest_monotonic_index;
56 bool digest_full= false;
57
58 LF_HASH digest_hash;
59 static bool digest_hash_inited= false;
60
61 /**
62 Initialize table EVENTS_STATEMENTS_SUMMARY_BY_DIGEST.
63 @param param performance schema sizing
64 */
init_digest(const PFS_global_param * param)65 int init_digest(const PFS_global_param *param)
66 {
67 /*
68 Allocate memory for statements_digest_stat_array based on
69 performance_schema_digests_size values
70 */
71 digest_max= param->m_digest_sizing;
72 digest_lost= 0;
73 PFS_atomic::store_u32(& digest_monotonic_index, 1);
74 digest_full= false;
75
76 if (digest_max == 0)
77 return 0;
78
79 statements_digest_stat_array=
80 PFS_MALLOC_ARRAY(digest_max,
81 sizeof(PFS_statements_digest_stat),
82 PFS_statements_digest_stat,
83 MYF(MY_ZEROFILL));
84
85 if (unlikely(statements_digest_stat_array == NULL))
86 {
87 cleanup_digest();
88 return 1;
89 }
90
91 if (pfs_max_digest_length > 0)
92 {
93 /* Size of each digest array. */
94 size_t digest_memory_size= pfs_max_digest_length * sizeof(unsigned char);
95
96 statements_digest_token_array=
97 PFS_MALLOC_ARRAY(digest_max,
98 digest_memory_size,
99 unsigned char,
100 MYF(MY_ZEROFILL));
101
102 if (unlikely(statements_digest_token_array == NULL))
103 {
104 cleanup_digest();
105 return 1;
106 }
107 }
108
109 for (size_t index= 0; index < digest_max; index++)
110 {
111 statements_digest_stat_array[index].reset_data(statements_digest_token_array
112 + index * pfs_max_digest_length, pfs_max_digest_length);
113 }
114
115 /* Set record[0] as allocated. */
116 statements_digest_stat_array[0].m_lock.set_allocated();
117
118 return 0;
119 }
120
121 /** Cleanup table EVENTS_STATEMENTS_SUMMARY_BY_DIGEST. */
cleanup_digest(void)122 void cleanup_digest(void)
123 {
124 /* Free memory allocated to statements_digest_stat_array. */
125 pfs_free(statements_digest_stat_array);
126 pfs_free(statements_digest_token_array);
127 statements_digest_stat_array= NULL;
128 statements_digest_token_array= NULL;
129 }
130
131 C_MODE_START
digest_hash_get_key(const uchar * entry,size_t * length,my_bool)132 static uchar *digest_hash_get_key(const uchar *entry, size_t *length,
133 my_bool)
134 {
135 const PFS_statements_digest_stat * const *typed_entry;
136 const PFS_statements_digest_stat *digest;
137 const void *result;
138 typed_entry= reinterpret_cast<const PFS_statements_digest_stat*const*>(entry);
139 DBUG_ASSERT(typed_entry != NULL);
140 digest= *typed_entry;
141 DBUG_ASSERT(digest != NULL);
142 *length= sizeof (PFS_digest_key);
143 result= & digest->m_digest_key;
144 return const_cast<uchar*> (reinterpret_cast<const uchar*> (result));
145 }
146 C_MODE_END
147
148
149 /**
150 Initialize the digest hash.
151 @return 0 on success
152 */
init_digest_hash(void)153 int init_digest_hash(void)
154 {
155 if ((! digest_hash_inited) && (digest_max > 0))
156 {
157 lf_hash_init(&digest_hash, sizeof(PFS_statements_digest_stat*),
158 LF_HASH_UNIQUE, 0, 0, digest_hash_get_key,
159 &my_charset_bin);
160 digest_hash.size= (int32)digest_max;
161 digest_hash_inited= true;
162 }
163 return 0;
164 }
165
cleanup_digest_hash(void)166 void cleanup_digest_hash(void)
167 {
168 if (digest_hash_inited)
169 {
170 lf_hash_destroy(&digest_hash);
171 digest_hash_inited= false;
172 }
173 }
174
get_digest_hash_pins(PFS_thread * thread)175 static LF_PINS* get_digest_hash_pins(PFS_thread *thread)
176 {
177 if (unlikely(thread->m_digest_hash_pins == NULL))
178 {
179 if (!digest_hash_inited)
180 return NULL;
181 thread->m_digest_hash_pins= lf_hash_get_pins(&digest_hash);
182 }
183 return thread->m_digest_hash_pins;
184 }
185
186 PFS_statement_stat*
find_or_create_digest(PFS_thread * thread,const sql_digest_storage * digest_storage,const char * schema_name,uint schema_name_length)187 find_or_create_digest(PFS_thread *thread,
188 const sql_digest_storage *digest_storage,
189 const char *schema_name,
190 uint schema_name_length)
191 {
192 DBUG_ASSERT(digest_storage != NULL);
193
194 if (statements_digest_stat_array == NULL)
195 return NULL;
196
197 if (digest_storage->m_byte_count <= 0)
198 return NULL;
199
200 LF_PINS *pins= get_digest_hash_pins(thread);
201 if (unlikely(pins == NULL))
202 return NULL;
203
204 /*
205 Note: the LF_HASH key is a block of memory,
206 make sure to clean unused bytes,
207 so that memcmp() can compare keys.
208 */
209 PFS_digest_key hash_key;
210 memset(& hash_key, 0, sizeof(hash_key));
211 /* Compute MD5 Hash of the tokens received. */
212 compute_digest_md5(digest_storage, hash_key.m_md5);
213 memcpy((void*)& digest_storage->m_md5, &hash_key.m_md5, MD5_HASH_SIZE);
214 /* Add the current schema to the key */
215 hash_key.m_schema_name_length= schema_name_length;
216 if (schema_name_length > 0)
217 memcpy(hash_key.m_schema_name, schema_name, schema_name_length);
218
219 int res;
220 uint retry_count= 0;
221 const uint retry_max= 3;
222 size_t safe_index;
223 size_t attempts= 0;
224 PFS_statements_digest_stat **entry;
225 PFS_statements_digest_stat *pfs= NULL;
226
227 ulonglong now= my_micro_time();
228
229 search:
230
231 /* Lookup LF_HASH using this new key. */
232 entry= reinterpret_cast<PFS_statements_digest_stat**>
233 (lf_hash_search(&digest_hash, pins,
234 &hash_key, sizeof(PFS_digest_key)));
235
236 if (entry && (entry != MY_ERRPTR))
237 {
238 /* If digest already exists, update stats and return. */
239 pfs= *entry;
240 pfs->m_last_seen= now;
241 lf_hash_search_unpin(pins);
242 return & pfs->m_stat;
243 }
244
245 lf_hash_search_unpin(pins);
246
247 if (digest_full)
248 {
249 /* digest_stat array is full. Add stat at index 0 and return. */
250 pfs= &statements_digest_stat_array[0];
251 digest_lost++;
252
253 if (pfs->m_first_seen == 0)
254 pfs->m_first_seen= now;
255 pfs->m_last_seen= now;
256 return & pfs->m_stat;
257 }
258
259 while (++attempts <= digest_max)
260 {
261 safe_index= PFS_atomic::add_u32(& digest_monotonic_index, 1) % digest_max;
262 if (safe_index == 0)
263 {
264 /* Record [0] is reserved. */
265 continue;
266 }
267
268 /* Add a new record in digest stat array. */
269 DBUG_ASSERT(safe_index < digest_max);
270 pfs= &statements_digest_stat_array[safe_index];
271
272 if (pfs->m_lock.is_free())
273 {
274 if (pfs->m_lock.free_to_dirty())
275 {
276 /* Copy digest hash/LF Hash search key. */
277 memcpy(& pfs->m_digest_key, &hash_key, sizeof(PFS_digest_key));
278
279 /*
280 Copy digest storage to statement_digest_stat_array so that it could be
281 used later to generate digest text.
282 */
283 pfs->m_digest_storage.copy(digest_storage);
284
285 pfs->m_first_seen= now;
286 pfs->m_last_seen= now;
287
288 res= lf_hash_insert(&digest_hash, pins, &pfs);
289 if (likely(res == 0))
290 {
291 pfs->m_lock.dirty_to_allocated();
292 return & pfs->m_stat;
293 }
294
295 pfs->m_lock.dirty_to_free();
296
297 if (res > 0)
298 {
299 /* Duplicate insert by another thread */
300 if (++retry_count > retry_max)
301 {
302 /* Avoid infinite loops */
303 digest_lost++;
304 return NULL;
305 }
306 goto search;
307 }
308
309 /* OOM in lf_hash_insert */
310 digest_lost++;
311 return NULL;
312 }
313 }
314 }
315
316 /* The digest array is now full. */
317 digest_full= true;
318 pfs= &statements_digest_stat_array[0];
319
320 if (pfs->m_first_seen == 0)
321 pfs->m_first_seen= now;
322 pfs->m_last_seen= now;
323 return & pfs->m_stat;
324 }
325
purge_digest(PFS_thread * thread,PFS_digest_key * hash_key)326 void purge_digest(PFS_thread* thread, PFS_digest_key *hash_key)
327 {
328 LF_PINS *pins= get_digest_hash_pins(thread);
329 if (unlikely(pins == NULL))
330 return;
331
332 PFS_statements_digest_stat **entry;
333
334 /* Lookup LF_HASH using this new key. */
335 entry= reinterpret_cast<PFS_statements_digest_stat**>
336 (lf_hash_search(&digest_hash, pins,
337 hash_key, sizeof(PFS_digest_key)));
338
339 if (entry && (entry != MY_ERRPTR))
340 {
341 lf_hash_delete(&digest_hash, pins,
342 hash_key, sizeof(PFS_digest_key));
343 }
344 lf_hash_search_unpin(pins);
345 return;
346 }
347
reset_data(unsigned char * token_array,uint length)348 void PFS_statements_digest_stat::reset_data(unsigned char *token_array, uint length)
349 {
350 m_lock.set_dirty();
351 m_digest_storage.reset(token_array, length);
352 m_stat.reset();
353 m_first_seen= 0;
354 m_last_seen= 0;
355 m_lock.dirty_to_free();
356 }
357
reset_index(PFS_thread * thread)358 void PFS_statements_digest_stat::reset_index(PFS_thread *thread)
359 {
360 /* Only remove entries that exists in the HASH index. */
361 if (m_digest_storage.m_byte_count > 0)
362 {
363 purge_digest(thread, & m_digest_key);
364 }
365 }
366
reset_esms_by_digest()367 void reset_esms_by_digest()
368 {
369 if (statements_digest_stat_array == NULL)
370 return;
371
372 PFS_thread *thread= PFS_thread::get_current_thread();
373 if (unlikely(thread == NULL))
374 return;
375
376 /* Reset statements_digest_stat_array. */
377 for (size_t index= 0; index < digest_max; index++)
378 {
379 statements_digest_stat_array[index].reset_index(thread);
380 statements_digest_stat_array[index].reset_data(statements_digest_token_array + index * pfs_max_digest_length, pfs_max_digest_length);
381 }
382
383 /* Mark record[0] as allocated again. */
384 statements_digest_stat_array[0].m_lock.set_allocated();
385
386 /*
387 Reset index which indicates where the next calculated digest information
388 to be inserted in statements_digest_stat_array.
389 */
390 PFS_atomic::store_u32(& digest_monotonic_index, 1);
391 digest_full= false;
392 }
393
394