1 /* Copyright (c) 2015, 2021, Oracle and/or its affiliates.
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
21   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
22 
23 /**
24   @file storage/perfschema/pfs_variable.cc
25   Performance schema system variable and status variable (implementation).
26 */
27 #include "my_global.h"
28 #include "pfs_variable.h"
29 #include "my_sys.h"
30 #include "debug_sync.h"
31 #include "pfs.h"
32 #include "pfs_global.h"
33 #include "pfs_visitor.h"
34 #include "sql_audit.h"                      // audit_global_variable_get
35 
36 /**
37   CLASS PFS_system_variable_cache
38 */
39 
40 /**
41   Build a sorted list of all system variables from the system variable hash.
42   Filter by scope. Must be called inside of LOCK_plugin_delete.
43 */
init_show_var_array(enum_var_type scope,bool strict)44 bool PFS_system_variable_cache::init_show_var_array(enum_var_type scope, bool strict)
45 {
46   assert(!m_initialized);
47   m_query_scope= scope;
48 
49   mysql_rwlock_rdlock(&LOCK_system_variables_hash);
50   DEBUG_SYNC(m_current_thd, "acquired_LOCK_system_variables_hash");
51 
52   /* Record the system variable hash version to detect subsequent changes. */
53   m_version= get_system_variable_hash_version();
54 
55   /* Build the SHOW_VAR array from the system variable hash. */
56   enumerate_sys_vars(m_current_thd, &m_show_var_array, true, m_query_scope, strict);
57 
58   mysql_rwlock_unlock(&LOCK_system_variables_hash);
59 
60   /* Increase cache size if necessary. */
61   m_cache.reserve(m_show_var_array.size());
62 
63   m_initialized= true;
64   return true;
65 }
66 
67 /**
68   Build an array of SHOW_VARs from the system variable hash.
69   Filter for SESSION scope.
70 */
do_initialize_session(void)71 bool PFS_system_variable_cache::do_initialize_session(void)
72 {
73   /* Block plugins from unloading. */
74   mysql_mutex_lock(&LOCK_plugin_delete);
75 
76   /* Build the array. */
77   bool ret= init_show_var_array(OPT_SESSION, true);
78 
79   mysql_mutex_unlock(&LOCK_plugin_delete);
80   return ret;
81 }
82 
83 /**
84   Match system variable scope to desired scope.
85 */
match_scope(int scope)86 bool PFS_system_variable_cache::match_scope(int scope)
87 {
88   switch (scope)
89   {
90     case sys_var::GLOBAL:
91       return m_query_scope == OPT_GLOBAL;
92       break;
93 
94     case sys_var::SESSION:
95       return (m_query_scope == OPT_GLOBAL || m_query_scope == OPT_SESSION);
96       break;
97 
98     case sys_var::ONLY_SESSION:
99       return m_query_scope == OPT_SESSION;
100       break;
101 
102     default:
103       return false;
104       break;
105   }
106   return false;
107 }
108 
109 /**
110   Build a GLOBAL system variable cache.
111 */
do_materialize_global(void)112 int PFS_system_variable_cache::do_materialize_global(void)
113 {
114   /* Block plugins from unloading. */
115   mysql_mutex_lock(&LOCK_plugin_delete);
116 
117   m_materialized= false;
118 
119   /*
120      Build array of SHOW_VARs from system variable hash. Do this within
121      LOCK_plugin_delete to ensure that the hash table remains unchanged
122      during materialization.
123    */
124   if (!m_external_init)
125     init_show_var_array(OPT_GLOBAL, true);
126 
127   /* Resolve the value for each SHOW_VAR in the array, add to cache. */
128   for (Show_var_array::iterator show_var= m_show_var_array.begin();
129        show_var->value && (show_var != m_show_var_array.end()); show_var++)
130   {
131     const char* name= show_var->name;
132     sys_var *value= (sys_var *)show_var->value;
133     assert(value);
134 
135     if ((m_query_scope == OPT_GLOBAL) &&
136         (!my_strcasecmp(system_charset_info, name, "sql_log_bin")))
137     {
138       /*
139         PLEASE READ:
140         http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-6.html
141 
142         SQL_LOG_BIN is:
143         - declared in sys_vars.cc as both GLOBAL and SESSION in 5.7
144         - impossible to SET with SET GLOBAL (raises an error)
145         - and yet can be read with @@global.sql_log_bin
146 
147         When show_compatibility_56 = ON,
148         - SHOW GLOBAL VARIABLES does expose a row for SQL_LOG_BIN
149         - INFORMATION_SCHEMA.GLOBAL_VARIABLES also does expose a row,
150         both are for backward compatibility of existing applications,
151         so that no application logic change is required.
152 
153         Now, with show_compatibility_56 = OFF (aka, in this code)
154         - SHOW GLOBAL VARIABLES does -- not -- expose a row for SQL_LOG_BIN
155         - PERFORMANCE_SCHEMA.GLOBAL_VARIABLES also does -- not -- expose a row
156         so that a clean interface is exposed to (upgraded and modified) applications.
157 
158         The assert below will fail once SQL_LOG_BIN really is defined
159         as SESSION_ONLY (in 5.8), so that this special case can be removed.
160       */
161       assert(value->scope() == sys_var::SESSION);
162       continue;
163     }
164 
165     /* Match the system variable scope to the target scope. */
166     if (match_scope(value->scope()))
167     {
168       /* Resolve value, convert to text, add to cache. */
169       System_variable system_var(m_current_thd, show_var, m_query_scope, false);
170       m_cache.push_back(system_var);
171     }
172   }
173 
174   m_materialized= true;
175   mysql_mutex_unlock(&LOCK_plugin_delete);
176   return 0;
177 }
178 
179 /**
180   Build a GLOBAL and SESSION system variable cache.
181 */
do_materialize_all(THD * unsafe_thd)182 int PFS_system_variable_cache::do_materialize_all(THD *unsafe_thd)
183 {
184   int ret= 1;
185 
186   m_unsafe_thd= unsafe_thd;
187   m_safe_thd= NULL;
188   m_materialized= false;
189   m_cache.clear();
190 
191   /* Block plugins from unloading. */
192   mysql_mutex_lock(&LOCK_plugin_delete);
193 
194   /*
195      Build array of SHOW_VARs from system variable hash. Do this within
196      LOCK_plugin_delete to ensure that the hash table remains unchanged
197      while this thread is materialized.
198    */
199   if (!m_external_init)
200     init_show_var_array(OPT_SESSION, false);
201 
202   /* Get and lock a validated THD from the thread manager. */
203   if ((m_safe_thd= get_THD(unsafe_thd)) != NULL)
204   {
205     DEBUG_SYNC(m_current_thd, "materialize_session_variable_array_THD_locked");
206     for (Show_var_array::iterator show_var= m_show_var_array.begin();
207          show_var->value && (show_var != m_show_var_array.end()); show_var++)
208     {
209       const char* name= show_var->name;
210       sys_var *value= (sys_var *)show_var->value;
211       assert(value);
212       bool ignore= false;
213 
214       if (value->scope() == sys_var::SESSION &&
215           (!my_strcasecmp(system_charset_info, name, "gtid_executed")))
216       {
217         /*
218          GTID_EXECUTED is:
219          - declared in sys_vars.cc as both GLOBAL and SESSION in 5.7
220          - can be read with @@session.gtid_executed
221 
222          When show_compatibility_56 = ON,
223          - SHOW SESSION VARIABLES does expose a row for GTID_EXECUTED
224          - INFORMATION_SCHEMA.SESSION_VARIABLES also does expose a row,
225          both are for backward compatibility of existing applications,
226          so that no application logic change is required.
227 
228          Now, with show_compatibility_56 = OFF (aka, in this code)
229          - SHOW SESSION VARIABLES does -- not -- expose a row for GTID_EXECUTED
230          - PERFORMANCE_SCHEMA.SESSION_VARIABLES also does -- not -- expose a row
231          so that a clean interface is exposed to (upgraded and modified)
232          applications.
233 
234          This special case needs be removed once @@SESSION.GTID_EXECUTED is
235          deprecated.
236         */
237         ignore= true;
238 
239       }
240       /* Resolve value, convert to text, add to cache. */
241       System_variable system_var(m_safe_thd, show_var, m_query_scope, ignore);
242       m_cache.push_back(system_var);
243     }
244 
245     /* Release lock taken in get_THD(). */
246     mysql_mutex_unlock(&m_safe_thd->LOCK_thd_data);
247 
248     m_materialized= true;
249     ret= 0;
250   }
251 
252   mysql_mutex_unlock(&LOCK_plugin_delete);
253   return ret;
254 }
255 
256 /**
257   Allocate and assign mem_root for system variable materialization.
258 */
set_mem_root(void)259 void PFS_system_variable_cache::set_mem_root(void)
260 {
261   if (m_mem_sysvar_ptr == NULL)
262   {
263     init_sql_alloc(PSI_INSTRUMENT_ME, &m_mem_sysvar, SYSVAR_MEMROOT_BLOCK_SIZE, 0);
264     m_mem_sysvar_ptr= &m_mem_sysvar;
265   }
266   m_mem_thd= my_thread_get_THR_MALLOC();  /* pointer to current THD mem_root */
267   m_mem_thd_save= *m_mem_thd;             /* restore later */
268   *m_mem_thd= &m_mem_sysvar;              /* use temporary mem_root */
269 }
270 
271 /**
272   Mark memory blocks in the temporary mem_root as free.
273   Restore THD::mem_root.
274 */
clear_mem_root(void)275 void PFS_system_variable_cache::clear_mem_root(void)
276 {
277   if (m_mem_sysvar_ptr)
278   {
279     free_root(&m_mem_sysvar, MYF(MY_MARK_BLOCKS_FREE));
280     *m_mem_thd= m_mem_thd_save;          /* restore original mem_root */
281     m_mem_thd= NULL;
282     m_mem_thd_save= NULL;
283   }
284 }
285 
286 /**
287   Free the temporary mem_root.
288   Restore THD::mem_root if necessary.
289 */
free_mem_root(void)290 void PFS_system_variable_cache::free_mem_root(void)
291 {
292   if (m_mem_sysvar_ptr)
293   {
294     free_root(&m_mem_sysvar, MYF(0));
295     m_mem_sysvar_ptr= NULL;
296     if (m_mem_thd && m_mem_thd_save)
297     {
298       *m_mem_thd= m_mem_thd_save;       /* restore original mem_root */
299       m_mem_thd= NULL;
300       m_mem_thd_save= NULL;
301     }
302   }
303 }
304 
305 /**
306   Build a SESSION system variable cache for a pfs_thread.
307   Requires that init_show_var_array() has already been called.
308   Return 0 for success.
309 */
do_materialize_session(PFS_thread * pfs_thread)310 int PFS_system_variable_cache::do_materialize_session(PFS_thread *pfs_thread)
311 {
312   int ret= 1;
313 
314   m_pfs_thread= pfs_thread;
315   m_materialized= false;
316   m_cache.clear();
317 
318   /* Block plugins from unloading. */
319   mysql_mutex_lock(&LOCK_plugin_delete);
320 
321   /* The SHOW_VAR array must be initialized externally. */
322   assert(m_initialized);
323 
324   /* Use a temporary mem_root to avoid depleting THD mem_root. */
325   if (m_use_mem_root)
326     set_mem_root();
327 
328   /* Get and lock a validated THD from the thread manager. */
329   if ((m_safe_thd= get_THD(pfs_thread)) != NULL)
330   {
331     for (Show_var_array::iterator show_var= m_show_var_array.begin();
332          show_var->value && (show_var != m_show_var_array.end()); show_var++)
333     {
334       sys_var *value= (sys_var *)show_var->value;
335 
336       /* Match the system variable scope to the target scope. */
337       if (match_scope(value->scope()))
338       {
339         const char* name= show_var->name;
340         bool ignore= false;
341 
342         if (value->scope() == sys_var::SESSION &&
343             (!my_strcasecmp(system_charset_info, name, "gtid_executed")))
344         {
345           /* Deprecated. See PFS_system_variable_cache::do_materialize_all. */
346           ignore= true;
347         }
348         /* Resolve value, convert to text, add to cache. */
349         System_variable system_var(m_safe_thd, show_var, m_query_scope, ignore);
350         m_cache.push_back(system_var);
351       }
352     }
353 
354     /* Release lock taken in get_THD(). */
355     mysql_mutex_unlock(&m_safe_thd->LOCK_thd_data);
356 
357     m_materialized= true;
358     ret= 0;
359   }
360 
361   /* Mark mem_root blocks as free. */
362   if (m_use_mem_root)
363     clear_mem_root();
364 
365   mysql_mutex_unlock(&LOCK_plugin_delete);
366   return ret;
367 }
368 
369 /**
370   Materialize a single system variable for a pfs_thread.
371   Requires that init_show_var_array() has already been called.
372   Return 0 for success.
373 */
do_materialize_session(PFS_thread * pfs_thread,uint index)374 int PFS_system_variable_cache::do_materialize_session(PFS_thread *pfs_thread, uint index)
375 {
376   int ret= 1;
377 
378   m_pfs_thread= pfs_thread;
379   m_materialized= false;
380   m_cache.clear();
381 
382   /* Block plugins from unloading. */
383   mysql_mutex_lock(&LOCK_plugin_delete);
384 
385   /* The SHOW_VAR array must be initialized externally. */
386   assert(m_initialized);
387 
388   /* Get and lock a validated THD from the thread manager. */
389   if ((m_safe_thd= get_THD(pfs_thread)) != NULL)
390   {
391     SHOW_VAR *show_var= &m_show_var_array.at(index);
392 
393     if (show_var && show_var->value &&
394         (show_var != m_show_var_array.end()))
395     {
396       sys_var *value= (sys_var *)show_var->value;
397 
398       /* Match the system variable scope to the target scope. */
399       if (match_scope(value->scope()))
400       {
401         const char* name= show_var->name;
402         bool ignore= false;
403 
404         if (value->scope() == sys_var::SESSION &&
405             (!my_strcasecmp(system_charset_info, name, "gtid_executed")))
406         {
407           /* Deprecated. See PFS_system_variable_cache::do_materialize_all. */
408           ignore= true;
409         }
410         /* Resolve value, convert to text, add to cache. */
411         System_variable system_var(m_safe_thd, show_var, m_query_scope, ignore);
412         m_cache.push_back(system_var);
413       }
414     }
415 
416     /* Release lock taken in get_THD(). */
417     mysql_mutex_unlock(&m_safe_thd->LOCK_thd_data);
418 
419     m_materialized= true;
420     ret= 0;
421   }
422 
423   mysql_mutex_unlock(&LOCK_plugin_delete);
424   return ret;
425 }
426 
427 /**
428   Build a SESSION system variable cache for a THD.
429 */
do_materialize_session(THD * unsafe_thd)430 int PFS_system_variable_cache::do_materialize_session(THD *unsafe_thd)
431 {
432   int ret= 1;
433 
434   m_unsafe_thd= unsafe_thd;
435   m_safe_thd= NULL;
436   m_materialized= false;
437   m_cache.clear();
438 
439   /* Block plugins from unloading. */
440   mysql_mutex_lock(&LOCK_plugin_delete);
441 
442   /*
443      Build array of SHOW_VARs from system variable hash. Do this within
444      LOCK_plugin_delete to ensure that the hash table remains unchanged
445      while this thread is materialized.
446    */
447   if (!m_external_init)
448     init_show_var_array(OPT_SESSION, true);
449 
450   /* Get and lock a validated THD from the thread manager. */
451   if ((m_safe_thd= get_THD(unsafe_thd)) != NULL)
452   {
453     for (Show_var_array::iterator show_var= m_show_var_array.begin();
454          show_var->value && (show_var != m_show_var_array.end()); show_var++)
455     {
456       sys_var *value = (sys_var *)show_var->value;
457 
458       /* Match the system variable scope to the target scope. */
459       if (match_scope(value->scope()))
460       {
461         const char* name= show_var->name;
462         bool ignore= false;
463 
464         if (value->scope() == sys_var::SESSION &&
465             (!my_strcasecmp(system_charset_info, name, "gtid_executed")))
466         {
467           /* Deprecated. See PFS_system_variable_cache::do_materialize_all. */
468           ignore= true;
469         }
470         /* Resolve value, convert to text, add to cache. */
471         System_variable system_var(m_safe_thd, show_var, m_query_scope, ignore);
472 
473         m_cache.push_back(system_var);
474       }
475     }
476 
477     /* Release lock taken in get_THD(). */
478     mysql_mutex_unlock(&m_safe_thd->LOCK_thd_data);
479 
480     m_materialized= true;
481     ret= 0;
482   }
483 
484   mysql_mutex_unlock(&LOCK_plugin_delete);
485   return ret;
486 }
487 
488 
489 /**
490   CLASS System_variable
491 */
492 
493 /**
494   Empty placeholder.
495 */
System_variable()496 System_variable::System_variable()
497   : m_name(NULL), m_name_length(0), m_value_length(0), m_type(SHOW_UNDEF), m_scope(0),
498     m_ignore(false), m_charset(NULL), m_initialized(false)
499 {
500   m_value_str[0]= '\0';
501 }
502 
503 /**
504   GLOBAL or SESSION system variable.
505 */
System_variable(THD * target_thd,const SHOW_VAR * show_var,enum_var_type query_scope,bool ignore)506 System_variable::System_variable(THD *target_thd, const SHOW_VAR *show_var,
507                                  enum_var_type query_scope, bool ignore)
508   : m_name(NULL), m_name_length(0), m_value_length(0), m_type(SHOW_UNDEF), m_scope(0),
509     m_ignore(ignore), m_charset(NULL), m_initialized(false)
510 {
511   init(target_thd, show_var, query_scope);
512 }
513 
514 /**
515   Get sys_var value from global or local source then convert to string.
516 */
init(THD * target_thd,const SHOW_VAR * show_var,enum_var_type query_scope)517 void System_variable::init(THD *target_thd, const SHOW_VAR *show_var,
518                            enum_var_type query_scope)
519 {
520   if (show_var == NULL || show_var->name == NULL)
521     return;
522 
523   enum_mysql_show_type show_var_type= show_var->type;
524   assert(show_var_type == SHOW_SYS);
525 
526   m_name= show_var->name;
527   m_name_length= strlen(m_name);
528 
529   /* Deprecated variables are ignored but must still be accounted for. */
530   if (m_ignore)
531   {
532     m_value_str[0]= '\0';
533     m_value_length= 0;
534     m_initialized= true;
535     return;
536   }
537 
538   THD *current_thread= current_thd;
539 
540   /* Block remote target thread from updating this system variable. */
541   if (target_thd != current_thread)
542     mysql_mutex_lock(&target_thd->LOCK_thd_sysvar);
543   /* Block system variable additions or deletions. */
544   mysql_mutex_lock(&LOCK_global_system_variables);
545 
546   sys_var *system_var= (sys_var *)show_var->value;
547   assert(system_var != NULL);
548   m_charset= system_var->charset(target_thd);
549   m_type= system_var->show_type();
550   m_scope= system_var->scope();
551 
552   /* Get the value of the system variable. */
553   const char *value;
554   value= get_one_variable_ext(current_thread, target_thd, show_var, query_scope, show_var_type,
555                               NULL, &m_charset, m_value_str, &m_value_length);
556 
557   m_value_length= MY_MIN(m_value_length, SHOW_VAR_FUNC_BUFF_SIZE);
558 
559   /* Returned value may reference a string other than m_value_str. */
560   if (value != m_value_str)
561     memcpy(m_value_str, value, m_value_length);
562   m_value_str[m_value_length]= 0;
563 
564   mysql_mutex_unlock(&LOCK_global_system_variables);
565   if (target_thd != current_thread)
566     mysql_mutex_unlock(&target_thd->LOCK_thd_sysvar);
567 
568 #ifndef EMBEDDED_LIBRARY
569   if (show_var_type != SHOW_FUNC && query_scope == OPT_GLOBAL &&
570       mysql_audit_notify(current_thread,
571                          AUDIT_EVENT(MYSQL_AUDIT_GLOBAL_VARIABLE_GET),
572                          m_name, value, m_value_length))
573     return;
574 #endif
575 
576   m_initialized= true;
577 }
578 
579 
580 /**
581   CLASS PFS_status_variable_cache
582 */
583 
584 PFS_status_variable_cache::
PFS_status_variable_cache(bool external_init)585 PFS_status_variable_cache(bool external_init) :
586                           PFS_variable_cache<Status_variable>(external_init),
587                           m_show_command(false), m_sum_client_status(NULL)
588 {
589   /* Determine if the originating query is a SHOW command. */
590   m_show_command= (m_current_thd->lex->sql_command == SQLCOM_SHOW_STATUS);
591 }
592 
593 /**
594   Build cache of SESSION status variables for a user.
595 */
materialize_user(PFS_user * pfs_user)596 int PFS_status_variable_cache::materialize_user(PFS_user *pfs_user)
597 {
598   if (!pfs_user)
599     return 1;
600 
601   if (is_materialized(pfs_user))
602     return 0;
603 
604   if (!pfs_user->m_lock.is_populated())
605     return 1;
606 
607   /* Set callback function. */
608   m_sum_client_status= sum_user_status;
609   return do_materialize_client((PFS_client *)pfs_user);
610 }
611 
612 /**
613   Build cache of SESSION status variables for a host.
614 */
materialize_host(PFS_host * pfs_host)615 int PFS_status_variable_cache::materialize_host(PFS_host *pfs_host)
616 {
617   if (!pfs_host)
618     return 1;
619 
620   if (is_materialized(pfs_host))
621     return 0;
622 
623   if (!pfs_host->m_lock.is_populated())
624     return 1;
625 
626   /* Set callback function. */
627   m_sum_client_status= sum_host_status;
628   return do_materialize_client((PFS_client *)pfs_host);
629 }
630 
631 /**
632   Build cache of SESSION status variables for an account.
633 */
materialize_account(PFS_account * pfs_account)634 int PFS_status_variable_cache::materialize_account(PFS_account *pfs_account)
635 {
636   if (!pfs_account)
637     return 1;
638 
639   if (is_materialized(pfs_account))
640     return 0;
641 
642   if (!pfs_account->m_lock.is_populated())
643     return 1;
644 
645   /* Set callback function. */
646   m_sum_client_status= sum_account_status;
647   return do_materialize_client((PFS_client *)pfs_account);
648 }
649 /**
650   Compare status variable scope to desired scope.
651   @param variable_scope         Scope of current status variable
652   @return TRUE if variable matches the query scope
653 */
match_scope(SHOW_SCOPE variable_scope,bool strict)654 bool PFS_status_variable_cache::match_scope(SHOW_SCOPE variable_scope, bool strict)
655 {
656   switch (variable_scope)
657   {
658     case SHOW_SCOPE_GLOBAL:
659       return (m_query_scope == OPT_GLOBAL) || (! strict && (m_query_scope == OPT_SESSION));
660       break;
661     case SHOW_SCOPE_SESSION:
662       /* Ignore session-only vars if aggregating by user, host or account. */
663       if (m_aggregate)
664         return false;
665       else
666         return (m_query_scope == OPT_SESSION);
667       break;
668     case SHOW_SCOPE_ALL:
669       return (m_query_scope == OPT_GLOBAL || m_query_scope == OPT_SESSION);
670       break;
671     case SHOW_SCOPE_UNDEF:
672     default:
673       return false;
674       break;
675   }
676   return false;
677 }
678 
679 /*
680   Exclude specific status variables from the query by name or prefix.
681   Return TRUE if variable should be filtered.
682 */
filter_by_name(const SHOW_VAR * show_var)683 bool PFS_status_variable_cache::filter_by_name(const SHOW_VAR *show_var)
684 {
685   assert(show_var);
686   assert(show_var->name);
687 
688   if (show_var->type == SHOW_ARRAY)
689   {
690     /* The SHOW_ARRAY name is the prefix for the variables in the subarray. */
691     const char *prefix= show_var->name;
692     /* Exclude COM counters if not a SHOW STATUS command. */
693     if (!my_strcasecmp(system_charset_info, prefix, "Com") && !m_show_command)
694       return true;
695   }
696   else
697   {
698     /*
699       Slave status resides in Performance Schema replication tables. Exclude
700       these slave status variables from the SHOW STATUS command and from the
701       status tables.
702       Assume null prefix to ensure that only server-defined slave status
703       variables are filtered.
704     */
705     const char *name= show_var->name;
706     if (!my_strcasecmp(system_charset_info, name, "Slave_running") ||
707         !my_strcasecmp(system_charset_info, name, "Slave_retried_transactions") ||
708         !my_strcasecmp(system_charset_info, name, "Slave_last_heartbeat") ||
709         !my_strcasecmp(system_charset_info, name, "Slave_received_heartbeats") ||
710         !my_strcasecmp(system_charset_info, name, "Slave_heartbeat_period"))
711     {
712       return true;
713     }
714   }
715 
716   return false;
717 }
718 
719 /**
720   Check that the variable type is aggregatable.
721 
722   @param variable_type         Status variable type
723   @return TRUE if variable type can be aggregated
724 */
can_aggregate(enum_mysql_show_type variable_type)725 bool PFS_status_variable_cache::can_aggregate(enum_mysql_show_type variable_type)
726 {
727   switch(variable_type)
728   {
729     /*
730       All server status counters that are totaled across threads are defined in
731       system_status_var as either SHOW_LONGLONG_STATUS or SHOW_LONG_STATUS.
732       These data types are not available to plugins.
733     */
734     case SHOW_LONGLONG_STATUS:
735     case SHOW_LONG_STATUS:
736       return true;
737       break;
738 
739     /* Server and plugin */
740     case SHOW_UNDEF:
741     case SHOW_BOOL:
742     case SHOW_CHAR:
743     case SHOW_CHAR_PTR:
744     case SHOW_ARRAY:
745     case SHOW_FUNC:
746     case SHOW_INT:
747     case SHOW_LONG:
748     case SHOW_LONGLONG:
749     case SHOW_DOUBLE:
750     /* Server only */
751     case SHOW_HAVE:
752     case SHOW_MY_BOOL:
753     case SHOW_SYS:
754     case SHOW_LEX_STRING:
755     case SHOW_KEY_CACHE_LONG:
756     case SHOW_KEY_CACHE_LONGLONG:
757     case SHOW_DOUBLE_STATUS:
758     case SHOW_HA_ROWS:
759     case SHOW_LONG_NOFLUSH:
760     case SHOW_SIGNED_INT:
761     case SHOW_SIGNED_LONG:
762     case SHOW_SIGNED_LONGLONG:
763     default:
764       return false;
765       break;
766   }
767 }
768 
769 /**
770   Check if a status variable should be excluded from the query.
771   Return TRUE if the variable should be excluded.
772 */
filter_show_var(const SHOW_VAR * show_var,bool strict)773 bool PFS_status_variable_cache::filter_show_var(const SHOW_VAR *show_var, bool strict)
774 {
775   /* Match the variable scope with the query scope. */
776   if (!match_scope(show_var->scope, strict))
777     return true;
778 
779   /* Exclude specific status variables by name or prefix. */
780   if (filter_by_name(show_var))
781     return true;
782 
783   /* For user, host or account, ignore variables having non-aggregatable types. */
784   if (m_aggregate && !can_aggregate(show_var->type))
785     return true;
786 
787   return false;
788 }
789 
790 
791 /**
792   Build an array of SHOW_VARs from the global status array. Expand nested
793   subarrays, filter unwanted variables.
794   NOTE: Must be done inside of LOCK_status to guard against plugin load/unload.
795 */
init_show_var_array(enum_var_type scope,bool strict)796 bool PFS_status_variable_cache::init_show_var_array(enum_var_type scope, bool strict)
797 {
798   assert(!m_initialized);
799 
800   /* Resize if necessary. */
801   m_show_var_array.reserve(all_status_vars.size()+1);
802 
803   m_query_scope= scope;
804 
805   for (Status_var_array::iterator show_var_iter= all_status_vars.begin();
806        show_var_iter != all_status_vars.end();
807        show_var_iter++)
808   {
809     SHOW_VAR show_var= *show_var_iter;
810 
811     /* Check if this status var should be excluded from the query. */
812     if (filter_show_var(&show_var, strict))
813       continue;
814 
815     if (show_var.type == SHOW_ARRAY)
816     {
817       /* Expand nested subarray. The name is used as a prefix. */
818       expand_show_var_array((SHOW_VAR *)show_var.value, show_var.name, strict);
819     }
820     else
821     {
822       show_var.name= make_show_var_name(NULL, show_var.name);
823       m_show_var_array.push_back(show_var);
824     }
825   }
826 
827   /* Last element is NULL. */
828   m_show_var_array.push_back(st_mysql_show_var());
829 
830   /* Get the latest version of all_status_vars. */
831   m_version= get_status_vars_version();
832 
833   /* Increase cache size if necessary. */
834   m_cache.reserve(m_show_var_array.size());
835 
836   m_initialized= true;
837   return true;
838 }
839 
840 /**
841   Expand a nested subarray of status variables, indicated by a type of SHOW_ARRAY.
842 */
expand_show_var_array(const SHOW_VAR * show_var_array,const char * prefix,bool strict)843 void PFS_status_variable_cache::expand_show_var_array(const SHOW_VAR *show_var_array, const char *prefix, bool strict)
844 {
845   for (const SHOW_VAR *show_var_ptr= show_var_array;
846        show_var_ptr && show_var_ptr->name;
847        show_var_ptr++)
848   {
849     SHOW_VAR show_var= *show_var_ptr;
850 
851     if (filter_show_var(&show_var, strict))
852       continue;
853 
854     if (show_var.type == SHOW_ARRAY)
855     {
856       char name_buf[SHOW_VAR_MAX_NAME_LEN];
857       show_var.name= make_show_var_name(prefix, show_var.name, name_buf, sizeof(name_buf));
858       /* Expand nested subarray. The name is used as a prefix. */
859       expand_show_var_array((SHOW_VAR *)show_var.value, show_var.name, strict);
860     }
861     else
862     {
863       /* Add the SHOW_VAR element. Make a local copy of the name string. */
864       show_var.name= make_show_var_name(prefix, show_var.name);
865       m_show_var_array.push_back(show_var);
866     }
867   }
868 }
869 
870 /**
871   Build the complete status variable name, with prefix. Return in buffer provided.
872 */
make_show_var_name(const char * prefix,const char * name,char * name_buf,size_t buf_len)873 char * PFS_status_variable_cache::make_show_var_name(const char* prefix, const char* name,
874                                                      char *name_buf, size_t buf_len)
875 {
876   assert(name_buf != NULL);
877   char *prefix_end= name_buf;
878 
879   if (prefix && *prefix)
880   {
881     /* Drop the prefix into the front of the name buffer. */
882     prefix_end= my_stpnmov(name_buf, prefix, buf_len-1);
883     *prefix_end++= '_';
884   }
885 
886   /* Restrict name length to remaining buffer size. */
887   size_t max_name_len= name_buf + buf_len - prefix_end;
888 
889   /* Load the name into the buffer after the prefix. */
890   my_stpnmov(prefix_end, name, max_name_len);
891   name_buf[buf_len-1]= 0;
892 
893   return (name_buf);
894 }
895 
896 /**
897   Make a copy of the name string prefixed with the subarray name if necessary.
898 */
make_show_var_name(const char * prefix,const char * name)899 char * PFS_status_variable_cache::make_show_var_name(const char* prefix, const char* name)
900 {
901   char name_buf[SHOW_VAR_MAX_NAME_LEN];
902   size_t buf_len= sizeof(name_buf);
903   make_show_var_name(prefix, name, name_buf, buf_len);
904   return m_current_thd->mem_strdup(name_buf); /* freed at statement end */
905 }
906 
907 /**
908   Build an internal SHOW_VAR array from the external status variable array.
909 */
do_initialize_session(void)910 bool PFS_status_variable_cache::do_initialize_session(void)
911 {
912   /* Acquire LOCK_status to guard against plugin load/unload. */
913   if (m_current_thd->fill_status_recursion_level++ == 0)
914     mysql_mutex_lock(&LOCK_status);
915 
916   bool ret= init_show_var_array(OPT_SESSION, true);
917 
918   if (m_current_thd->fill_status_recursion_level-- == 1)
919     mysql_mutex_unlock(&LOCK_status);
920 
921   return ret;
922 }
923 
924 /**
925   For the current THD, use initial_status_vars taken from before the query start.
926 */
set_status_vars(void)927 STATUS_VAR *PFS_status_variable_cache::set_status_vars(void)
928 {
929   STATUS_VAR *status_vars;
930   if (m_safe_thd == m_current_thd && m_current_thd->initial_status_var != NULL)
931     status_vars= m_current_thd->initial_status_var;
932   else
933     status_vars= &m_safe_thd->status_var;
934 
935   return status_vars;
936 }
937 
938 /**
939   Build cache for GLOBAL status variables using values totaled from all threads.
940 */
do_materialize_global(void)941 int PFS_status_variable_cache::do_materialize_global(void)
942 {
943   STATUS_VAR status_totals;
944 
945   m_materialized= false;
946   DEBUG_SYNC(m_current_thd, "before_materialize_global_status_array");
947 
948   /* Acquire LOCK_status to guard against plugin load/unload. */
949   if (m_current_thd->fill_status_recursion_level++ == 0)
950     mysql_mutex_lock(&LOCK_status);
951 
952   /*
953      Build array of SHOW_VARs from global status array. Do this within
954      LOCK_status to ensure that the array remains unchanged during
955      materialization.
956    */
957   if (!m_external_init)
958     init_show_var_array(OPT_GLOBAL, true);
959 
960   /*
961     Collect totals for all active threads. Start with global status vars as a
962     baseline.
963   */
964   PFS_connection_status_visitor visitor(&status_totals);
965   PFS_connection_iterator::visit_global(false, /* hosts */
966                                         false, /* users */
967                                         false, /* accounts */
968                                         false, /* threads */
969                                         true,  /* THDs */
970                                         &visitor);
971   /*
972     Build the status variable cache using the SHOW_VAR array as a reference.
973     Use the status totals collected from all threads.
974   */
975   manifest(m_current_thd, m_show_var_array.begin(), &status_totals, "", false, true);
976 
977   if (m_current_thd->fill_status_recursion_level-- == 1)
978     mysql_mutex_unlock(&LOCK_status);
979 
980   m_materialized= true;
981   DEBUG_SYNC(m_current_thd, "after_materialize_global_status_array");
982 
983   return 0;
984 }
985 
986 /**
987   Build GLOBAL and SESSION status variable cache using values for a non-instrumented thread.
988 */
do_materialize_all(THD * unsafe_thd)989 int PFS_status_variable_cache::do_materialize_all(THD* unsafe_thd)
990 {
991   int ret= 1;
992   assert(unsafe_thd != NULL);
993 
994   m_unsafe_thd= unsafe_thd;
995   m_materialized= false;
996   m_cache.clear();
997 
998   /* Avoid recursive acquisition of LOCK_status. */
999   if (m_current_thd->fill_status_recursion_level++ == 0)
1000     mysql_mutex_lock(&LOCK_status);
1001 
1002   /*
1003      Build array of SHOW_VARs from global status array. Do this within
1004      LOCK_status to ensure that the array remains unchanged while this
1005      thread is materialized.
1006    */
1007   if (!m_external_init)
1008     init_show_var_array(OPT_SESSION, false);
1009 
1010     /* Get and lock a validated THD from the thread manager. */
1011   if ((m_safe_thd= get_THD(unsafe_thd)) != NULL)
1012   {
1013     DEBUG_SYNC(m_current_thd, "materialize_session_status_array_THD_locked");
1014     /*
1015       Build the status variable cache using the SHOW_VAR array as a reference.
1016       Use the status values from the THD protected by the thread manager lock.
1017     */
1018     STATUS_VAR *status_vars= set_status_vars();
1019     manifest(m_safe_thd, m_show_var_array.begin(), status_vars, "", false, false);
1020 
1021     /* Release lock taken in get_THD(). */
1022     mysql_mutex_unlock(&m_safe_thd->LOCK_thd_data);
1023 
1024     m_materialized= true;
1025     ret= 0;
1026   }
1027 
1028   if (m_current_thd->fill_status_recursion_level-- == 1)
1029     mysql_mutex_unlock(&LOCK_status);
1030   return ret;
1031 }
1032 
1033 /**
1034   Build SESSION status variable cache using values for a non-instrumented thread.
1035 */
do_materialize_session(THD * unsafe_thd)1036 int PFS_status_variable_cache::do_materialize_session(THD* unsafe_thd)
1037 {
1038   int ret= 1;
1039   assert(unsafe_thd != NULL);
1040 
1041   m_unsafe_thd= unsafe_thd;
1042   m_materialized= false;
1043   m_cache.clear();
1044 
1045   /* Avoid recursive acquisition of LOCK_status. */
1046   if (m_current_thd->fill_status_recursion_level++ == 0)
1047     mysql_mutex_lock(&LOCK_status);
1048 
1049   /*
1050      Build array of SHOW_VARs from global status array. Do this within
1051      LOCK_status to ensure that the array remains unchanged while this
1052      thread is materialized.
1053    */
1054   if (!m_external_init)
1055     init_show_var_array(OPT_SESSION, true);
1056 
1057     /* Get and lock a validated THD from the thread manager. */
1058   if ((m_safe_thd= get_THD(unsafe_thd)) != NULL)
1059   {
1060     /*
1061       Build the status variable cache using the SHOW_VAR array as a reference.
1062       Use the status values from the THD protected by the thread manager lock.
1063     */
1064     STATUS_VAR *status_vars= set_status_vars();
1065     manifest(m_safe_thd, m_show_var_array.begin(), status_vars, "", false, true);
1066 
1067     /* Release lock taken in get_THD(). */
1068     mysql_mutex_unlock(&m_safe_thd->LOCK_thd_data);
1069 
1070     m_materialized= true;
1071     ret= 0;
1072   }
1073 
1074   if (m_current_thd->fill_status_recursion_level-- == 1)
1075     mysql_mutex_unlock(&LOCK_status);
1076   return ret;
1077 }
1078 
1079 /**
1080   Build SESSION status variable cache using values for a PFS_thread.
1081   NOTE: Requires that init_show_var_array() has already been called.
1082 */
do_materialize_session(PFS_thread * pfs_thread)1083 int PFS_status_variable_cache::do_materialize_session(PFS_thread *pfs_thread)
1084 {
1085   int ret= 1;
1086   assert(pfs_thread != NULL);
1087 
1088   m_pfs_thread= pfs_thread;
1089   m_materialized= false;
1090   m_cache.clear();
1091 
1092   /* Acquire LOCK_status to guard against plugin load/unload. */
1093   if (m_current_thd->fill_status_recursion_level++ == 0)
1094     mysql_mutex_lock(&LOCK_status);
1095 
1096   /* The SHOW_VAR array must be initialized externally. */
1097   assert(m_initialized);
1098 
1099     /* Get and lock a validated THD from the thread manager. */
1100   if ((m_safe_thd= get_THD(pfs_thread)) != NULL)
1101   {
1102     /*
1103       Build the status variable cache using the SHOW_VAR array as a reference.
1104       Use the status values from the THD protected by the thread manager lock.
1105     */
1106     STATUS_VAR *status_vars= set_status_vars();
1107     manifest(m_safe_thd, m_show_var_array.begin(), status_vars, "", false, true);
1108 
1109     /* Release lock taken in get_THD(). */
1110     mysql_mutex_unlock(&m_safe_thd->LOCK_thd_data);
1111 
1112     m_materialized= true;
1113     ret= 0;
1114   }
1115 
1116   if (m_current_thd->fill_status_recursion_level-- == 1)
1117     mysql_mutex_unlock(&LOCK_status);
1118   return ret;
1119 }
1120 
1121 /**
1122   Build cache of SESSION status variables using the status values provided.
1123   The cache is associated with a user, host or account, but not with any
1124   particular thread.
1125   NOTE: Requires that init_show_var_array() has already been called.
1126 */
do_materialize_client(PFS_client * pfs_client)1127 int PFS_status_variable_cache::do_materialize_client(PFS_client *pfs_client)
1128 {
1129   assert(pfs_client != NULL);
1130   STATUS_VAR status_totals;
1131 
1132   m_pfs_client= pfs_client;
1133   m_materialized= false;
1134   m_cache.clear();
1135 
1136   /* Acquire LOCK_status to guard against plugin load/unload. */
1137   if (m_current_thd->fill_status_recursion_level++ == 0)
1138     mysql_mutex_lock(&LOCK_status);
1139 
1140   /* The SHOW_VAR array must be initialized externally. */
1141   assert(m_initialized);
1142 
1143   /*
1144     Generate status totals from active threads and from totals aggregated
1145     from disconnected threads.
1146   */
1147   m_sum_client_status(pfs_client, &status_totals);
1148 
1149   /*
1150     Build the status variable cache using the SHOW_VAR array as a reference and
1151     the status totals collected from threads associated with this client.
1152   */
1153   manifest(m_current_thd, m_show_var_array.begin(), &status_totals, "", false, true);
1154 
1155   if (m_current_thd->fill_status_recursion_level-- == 1)
1156     mysql_mutex_unlock(&LOCK_status);
1157 
1158   m_materialized= true;
1159   return 0;
1160 }
1161 
1162 /*
1163   Build the status variable cache from the expanded and sorted SHOW_VAR array.
1164   Resolve status values using the STATUS_VAR struct provided.
1165 */
manifest(THD * thd,const SHOW_VAR * show_var_array,STATUS_VAR * status_vars,const char * prefix,bool nested_array,bool strict)1166 void PFS_status_variable_cache::manifest(THD *thd, const SHOW_VAR *show_var_array,
1167                                     STATUS_VAR *status_vars, const char *prefix,
1168                                     bool nested_array, bool strict)
1169 {
1170   for (const SHOW_VAR *show_var_iter= show_var_array;
1171        show_var_iter && show_var_iter->name;
1172        show_var_iter++)
1173   {
1174     // work buffer, must be aligned to handle long/longlong values
1175     my_aligned_storage<SHOW_VAR_FUNC_BUFF_SIZE+1, MY_ALIGNOF(longlong)>
1176       value_buf;
1177     SHOW_VAR show_var_tmp;
1178     const SHOW_VAR *show_var_ptr= show_var_iter;  /* preserve array pointer */
1179 
1180     /*
1181       If the value is a function reference, then execute the function and
1182       reevaluate the new SHOW_TYPE and value. Handle nested case where
1183       SHOW_FUNC resolves to another SHOW_FUNC.
1184     */
1185     if (show_var_ptr->type == SHOW_FUNC)
1186     {
1187       show_var_tmp= *show_var_ptr;
1188       /*
1189         Execute the function reference in show_var_tmp->value, which returns
1190         show_var_tmp with a new type and new value.
1191       */
1192       for (const SHOW_VAR *var= show_var_ptr; var->type == SHOW_FUNC; var= &show_var_tmp)
1193       {
1194         ((mysql_show_var_func)(var->value))(thd, &show_var_tmp, value_buf.data);
1195       }
1196       show_var_ptr= &show_var_tmp;
1197     }
1198 
1199     /*
1200       If we are expanding a SHOW_ARRAY, filter variables that were not prefiltered by
1201       init_show_var_array().
1202     */
1203     if (nested_array && filter_show_var(show_var_ptr, strict))
1204       continue;
1205 
1206     if (show_var_ptr->type == SHOW_ARRAY)
1207     {
1208       /*
1209         Status variables of type SHOW_ARRAY were expanded and filtered by
1210         init_show_var_array(), except where a SHOW_FUNC resolves into a
1211         SHOW_ARRAY, such as with InnoDB. Recurse to expand the subarray.
1212       */
1213       manifest(thd, (SHOW_VAR *)show_var_ptr->value, status_vars, show_var_ptr->name, true, strict);
1214     }
1215     else
1216     {
1217       /* Add the materialized status variable to the cache. */
1218       SHOW_VAR show_var= *show_var_ptr;
1219       /*
1220         For nested array expansions, make a copy of the variable name, just as
1221         done in init_show_var_array().
1222       */
1223       if (nested_array)
1224         show_var.name= make_show_var_name(prefix, show_var_ptr->name);
1225 
1226       /* Convert status value to string format. Add to the cache. */
1227       Status_variable status_var(&show_var, status_vars, m_query_scope);
1228       m_cache.push_back(status_var);
1229     }
1230   }
1231 }
1232 
1233 /**
1234   CLASS Status_variable
1235 */
Status_variable(const SHOW_VAR * show_var,STATUS_VAR * status_vars,enum_var_type query_scope)1236 Status_variable::Status_variable(const SHOW_VAR *show_var, STATUS_VAR *status_vars, enum_var_type query_scope)
1237   : m_name_length(0), m_value_length(0), m_type(SHOW_UNDEF),
1238     m_scope(SHOW_SCOPE_UNDEF), m_charset(NULL), m_initialized(false)
1239 {
1240   init(show_var, status_vars, query_scope);
1241 }
1242 
1243 /**
1244   Resolve status value, convert to string.
1245   show_var->value is an offset into status_vars.
1246   NOTE: Assumes LOCK_status is held.
1247 */
init(const SHOW_VAR * show_var,STATUS_VAR * status_vars,enum_var_type query_scope)1248 void Status_variable::init(const SHOW_VAR *show_var, STATUS_VAR *status_vars, enum_var_type query_scope)
1249 {
1250   if (show_var == NULL || show_var->name == NULL)
1251     return;
1252   m_name= show_var->name;
1253   m_name_length= strlen(m_name);
1254   m_type= show_var->type;
1255   m_scope= show_var->scope;
1256 
1257   /* Get the value of the status variable. */
1258   const char *value;
1259   value= get_one_variable(current_thd, show_var, query_scope, m_type,
1260                           status_vars, &m_charset, m_value_str, &m_value_length);
1261   m_value_length= MY_MIN(m_value_length, SHOW_VAR_FUNC_BUFF_SIZE);
1262 
1263   /* Returned value may reference a string other than m_value_str. */
1264   if (value != m_value_str)
1265     memcpy(m_value_str, value, m_value_length);
1266   m_value_str[m_value_length]= 0;
1267 
1268   m_initialized= true;
1269 }
1270 
1271 /*
1272   Get status totals for this user from active THDs and related accounts.
1273 */
sum_user_status(PFS_client * pfs_user,STATUS_VAR * status_totals)1274 void sum_user_status(PFS_client *pfs_user, STATUS_VAR *status_totals)
1275 {
1276   PFS_connection_status_visitor visitor(status_totals);
1277   PFS_connection_iterator::visit_user((PFS_user *)pfs_user,
1278                                                   true,  /* accounts */
1279                                                   false, /* threads */
1280                                                   true,  /* THDs */
1281                                                   &visitor);
1282 }
1283 
1284 /*
1285   Get status totals for this host from active THDs and related accounts.
1286 */
sum_host_status(PFS_client * pfs_host,STATUS_VAR * status_totals)1287 void sum_host_status(PFS_client *pfs_host, STATUS_VAR *status_totals)
1288 {
1289   PFS_connection_status_visitor visitor(status_totals);
1290   PFS_connection_iterator::visit_host((PFS_host *)pfs_host,
1291                                                   true,  /* accounts */
1292                                                   false, /* threads */
1293                                                   true,  /* THDs */
1294                                                   &visitor);
1295 }
1296 
1297 /*
1298   Get status totals for this account from active THDs and from totals aggregated
1299   from disconnectd threads.
1300 */
sum_account_status(PFS_client * pfs_account,STATUS_VAR * status_totals)1301 void sum_account_status(PFS_client *pfs_account, STATUS_VAR *status_totals)
1302 {
1303   PFS_connection_status_visitor visitor(status_totals);
1304   PFS_connection_iterator::visit_account((PFS_account *)pfs_account,
1305                                                         false,      /* threads */
1306                                                         true,       /* THDs */
1307                                                         &visitor);
1308 }
1309 
1310 /**
1311   Reset aggregated status counter stats for account, user and host.
1312   NOTE: Assumes LOCK_status is held.
1313 */
reset_pfs_status_stats()1314 void reset_pfs_status_stats()
1315 {
1316   reset_status_by_account();
1317   reset_status_by_user();
1318   reset_status_by_host();
1319 }
1320 
1321 /** @} */
1322