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