1 /* Copyright (c) 2014 Percona LLC and/or its affiliates. All rights reserved.
2 
3    This program is free software; you can redistribute it and/or
4    modify it under the terms of the GNU General Public License
5    as published by the Free Software Foundation; version 2 of
6    the License.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11    GNU General Public License for more details.
12 
13    You should have received a copy of the GNU General Public License
14    along with this program; if not, write to the Free Software
15    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA */
16 
17 #include <mysql/plugin.h>
18 #include <mysql/plugin_audit.h>
19 #include <my_global.h>
20 #include <my_sys.h>
21 #include <my_list.h>
22 #include <my_pthread.h>
23 #include <typelib.h>
24 #include <limits.h>
25 #include <string.h>
26 
27 static volatile ulonglong starttime= 0;
28 static volatile ulonglong concurrency= 0;
29 static volatile ulonglong busystart= 0;
30 static volatile ulonglong busytime= 0;
31 static volatile ulonglong totaltime= 0;
32 static volatile ulonglong queries= 0;
33 
34 #ifdef HAVE_PSI_INTERFACE
35 PSI_mutex_key key_thd_list_mutex;
36 #endif
37 mysql_mutex_t thd_list_mutex;
38 
39 LIST *thd_list_root= NULL;
40 
41 typedef struct sm_thd_data_struct {
42   ulonglong start;
43   ulonglong duration;
44   ulonglong queries;
45   LIST *backref;
46 } sm_thd_data_t;
47 
48 typedef enum { CTL_ON= 0, CTL_OFF= 1, CTL_RESET= 2 } sm_ctl_t;
49 static const char* sm_ctl_names[]= { "ON", "OFF", "RESET", NullS };
50 static TYPELIB sm_ctl_typelib= {
51   array_elements(sm_ctl_names) - 1,
52   "",
53   sm_ctl_names,
54   NULL
55 };
56 
57 static
58 void sm_ctl_update(MYSQL_THD thd, struct st_mysql_sys_var *var,
59                    void *var_ptr, const void *save);
60 
61 static ulong sm_ctl= CTL_OFF;
62 
63 static
64 MYSQL_THDVAR_ULONGLONG(thd_data,
65   PLUGIN_VAR_READONLY | PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
66   "scalability metrics data", NULL, NULL, 0, 0, ULONGLONG_MAX, 0);
67 
68 
69 static MYSQL_SYSVAR_ENUM(
70   control,                           /* name */
71   sm_ctl,                            /* var */
72   PLUGIN_VAR_RQCMDARG,
73   "Control the scalability metrics. Use this to turn ON/OFF or RESET metrics.",
74   NULL,                              /* check func. */
75   sm_ctl_update,                     /* update func. */
76   CTL_OFF,                           /* default */
77   &sm_ctl_typelib                    /* typelib */
78 );
79 
80 static
sm_clock_time_get()81 ulonglong sm_clock_time_get()
82 {
83 #if (defined HAVE_CLOCK_GETTIME)
84   struct timespec ts;
85 #ifdef CLOCK_MONOTONIC_RAW
86   clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
87 #else
88   clock_gettime(CLOCK_MONOTONIC, &ts);
89 #endif
90   return((ulonglong) ts.tv_sec * 1000000000 + ts.tv_nsec);
91 #else
92   /* since output values measured in microseconds anyway,
93   100 nanoseconds precision should be enough here */
94   return(my_getsystime() * 100);
95 #endif
96 }
97 
98 
99 /* Get duration in microseconds */
100 static
sm_clock_time_duration(ulonglong beg,ulonglong end)101 ulonglong sm_clock_time_duration(ulonglong beg, ulonglong end)
102 {
103   return((end - beg) / 1000);
104 }
105 
106 static
sm_thd_data_get(MYSQL_THD thd)107 sm_thd_data_t *sm_thd_data_get(MYSQL_THD thd)
108 {
109   sm_thd_data_t *thd_data = (sm_thd_data_t *) (intptr) THDVAR(thd, thd_data);
110   if (unlikely(thd_data == NULL))
111   {
112     thd_data= my_malloc(sizeof(sm_thd_data_t),
113                         MYF(MY_FAE | MY_WME | MY_ZEROFILL));
114     mysql_mutex_lock(&thd_list_mutex);
115     thd_data->backref= list_push(thd_list_root, thd_data);
116     mysql_mutex_unlock(&thd_list_mutex);
117     THDVAR(thd, thd_data)= (ulonglong) (intptr) thd_data;
118   }
119   return thd_data;
120 }
121 
122 
123 static
sm_thd_data_release(MYSQL_THD thd)124 void sm_thd_data_release(MYSQL_THD thd)
125 {
126   sm_thd_data_t *thd_data = (sm_thd_data_t *) (intptr) THDVAR(thd, thd_data);
127   if (likely(thd_data != NULL && thd_data->backref != NULL))
128   {
129     (void) __sync_add_and_fetch(&queries, thd_data->queries);
130     (void) __sync_add_and_fetch(&totaltime, thd_data->duration);
131     mysql_mutex_lock(&thd_list_mutex);
132     thd_list_root= list_delete(thd_list_root, thd_data->backref);
133     mysql_mutex_unlock(&thd_list_mutex);
134     my_free(thd_data->backref);
135     my_free(thd_data);
136     THDVAR(thd, thd_data)= 0;
137   }
138 }
139 
140 static
sm_reset_one(void * data,void * argument)141 int sm_reset_one(void *data, void *argument __attribute__((unused)))
142 {
143   sm_thd_data_t *thd_data= (sm_thd_data_t *) data;
144   thd_data->queries= 0;
145   thd_data->duration= 0;
146   return(0);
147 }
148 
149 static
sm_reset()150 void sm_reset()
151 {
152   starttime= sm_clock_time_get();
153   busytime= totaltime= queries= 0;
154   mysql_mutex_lock(&thd_list_mutex);
155   list_walk(thd_list_root, sm_reset_one, NULL);
156   mysql_mutex_unlock(&thd_list_mutex);
157 }
158 
159 
160 static
sm_ctl_update(MYSQL_THD thd,struct st_mysql_sys_var * var,void * var_ptr,const void * save)161 void sm_ctl_update(MYSQL_THD thd __attribute__((unused)),
162                    struct st_mysql_sys_var *var __attribute__((unused)),
163                    void *var_ptr __attribute__((unused)),
164                    const void *save) {
165   ulong new_val= *((sm_ctl_t*) save);
166 
167   if (new_val != sm_ctl)
168     sm_reset();
169 
170   if (new_val != CTL_RESET)
171   {
172     sm_ctl= new_val;
173 
174     if (new_val == CTL_OFF)
175     {
176       mysql_mutex_lock(&thd_list_mutex);
177       list_free(thd_list_root, TRUE);
178       thd_list_root= NULL;
179       mysql_mutex_unlock(&thd_list_mutex);
180     }
181   }
182 
183 }
184 
185 
186 static
sm_plugin_init(void * arg)187 int sm_plugin_init(void *arg __attribute__((unused)))
188 {
189   mysql_mutex_init(key_thd_list_mutex, &thd_list_mutex, MY_MUTEX_INIT_FAST);
190 
191   sm_reset();
192 
193   return(0);
194 }
195 
196 
197 static
sm_plugin_deinit(void * arg)198 int sm_plugin_deinit(void *arg __attribute__((unused)))
199 {
200   list_free(thd_list_root, TRUE);
201   thd_list_root= NULL;
202 
203   mysql_mutex_destroy(&thd_list_mutex);
204 
205   return(0);
206 }
207 
208 static
sm_query_started(MYSQL_THD thd,const char * query)209 void sm_query_started(MYSQL_THD thd,
210                       const char* query __attribute__((unused))) {
211 
212   sm_thd_data_t *thd_data= sm_thd_data_get(thd);
213 
214   if (__sync_bool_compare_and_swap(&concurrency, 0, 1))
215   {
216     thd_data->start= sm_clock_time_get();
217     busystart= thd_data->start;
218   }
219   else
220   {
221     thd_data->start= sm_clock_time_get();
222     (void) __sync_add_and_fetch(&concurrency, 1);
223   }
224 }
225 
226 static
sm_query_finished(MYSQL_THD thd,const char * query)227 void sm_query_finished(MYSQL_THD thd,
228                        const char* query __attribute__((unused))) {
229 
230   sm_thd_data_t *thd_data= sm_thd_data_get(thd);
231   ulonglong end, save_busystart;
232 
233   if (thd_data->start != 0)
234   {
235     save_busystart= busystart;
236     if (__sync_sub_and_fetch(&concurrency, 1) == 0)
237     {
238       end= sm_clock_time_get();
239       (void) __sync_add_and_fetch(&busytime,
240                            sm_clock_time_duration(save_busystart, end));
241     }
242     else
243     {
244       end= sm_clock_time_get();
245     }
246 
247     thd_data->duration+= sm_clock_time_duration(thd_data->start, end);
248     thd_data->queries++;
249   }
250 }
251 
252 static
sm_query_failed(MYSQL_THD thd,const char * query,int err)253 void sm_query_failed(MYSQL_THD thd,
254                      const char* query,
255                      int err __attribute__((unused))) {
256 
257   /* currently there is no difference between success and failure */
258 
259   sm_query_finished(thd, query);
260 
261 }
262 
263 
264 static
sm_elapsedtime(MYSQL_THD thd,struct st_mysql_show_var * var,char * buff)265 int sm_elapsedtime(MYSQL_THD thd __attribute__((unused)),
266                    struct st_mysql_show_var* var,
267                    char *buff)
268 {
269   *((ulonglong*)buff)= (sm_ctl == CTL_ON) ?
270                       sm_clock_time_duration(starttime, sm_clock_time_get()) :
271                       0;
272   var->type= SHOW_LONGLONG;
273   var->value= buff;
274   return(0);
275 }
276 
277 
278 static
sm_sum_queries(void * data,void * argument)279 int sm_sum_queries(void *data, void *argument)
280 {
281   sm_thd_data_t *thd_data= (sm_thd_data_t *) data;
282   *((ulonglong *) argument)+= thd_data->queries;
283   return(0);
284 }
285 
286 
287 static
sm_queries(MYSQL_THD thd,struct st_mysql_show_var * var,char * buff)288 int sm_queries(MYSQL_THD thd __attribute__((unused)),
289                struct st_mysql_show_var* var,
290                char *buff)
291 {
292   ulonglong sum_queries= 0;
293 
294   if (sm_ctl == CTL_ON)
295   {
296     mysql_mutex_lock(&thd_list_mutex);
297     list_walk(thd_list_root, sm_sum_queries, (unsigned char *) &sum_queries);
298     mysql_mutex_unlock(&thd_list_mutex);
299   }
300   *((ulonglong *) buff)= queries + sum_queries;
301   var->type= SHOW_LONGLONG;
302   var->value= buff;
303   return(0);
304 }
305 
306 
307 static
sm_sum_totaltime(void * data,void * argument)308 int sm_sum_totaltime(void *data, void *argument)
309 {
310   sm_thd_data_t *thd_data= (sm_thd_data_t *) data;
311   *((ulonglong *) argument)+= thd_data->duration;
312   return(0);
313 }
314 
315 
316 static
sm_totaltime(MYSQL_THD thd,struct st_mysql_show_var * var,char * buff)317 int sm_totaltime(MYSQL_THD thd __attribute__((unused)),
318                struct st_mysql_show_var* var,
319                char *buff)
320 {
321   ulonglong sum_totaltime= 0;
322 
323   if (sm_ctl == CTL_ON)
324   {
325     mysql_mutex_lock(&thd_list_mutex);
326     list_walk(thd_list_root, sm_sum_totaltime,
327               (unsigned char *) &sum_totaltime);
328     mysql_mutex_unlock(&thd_list_mutex);
329   }
330   *((ulonglong *) buff)= totaltime + sum_totaltime;
331   var->type= SHOW_LONGLONG;
332   var->value= buff;
333   return(0);
334 }
335 
336 
sm_notify(MYSQL_THD thd,unsigned int event_class,const void * event)337 static void sm_notify(MYSQL_THD thd, unsigned int event_class,
338                       const void *event)
339 {
340 
341   if (event_class == MYSQL_AUDIT_GENERAL_CLASS)
342   {
343     const struct mysql_event_general *event_general=
344       (const struct mysql_event_general *) event;
345 
346     if (sm_ctl != CTL_ON)
347     {
348       return;
349     }
350 
351     if (event_general->general_command &&
352         event_general->event_subclass == MYSQL_AUDIT_GENERAL_LOG &&
353         strcmp(event_general->general_command, "Query") == 0)
354     {
355       sm_query_started(thd, event_general->general_query);
356     }
357     else if (event_general->general_command &&
358         event_general->event_subclass == MYSQL_AUDIT_GENERAL_LOG &&
359         strcmp(event_general->general_command, "Execute") == 0)
360     {
361       sm_query_started(thd, event_general->general_query);
362     }
363     else if (event_general->general_query &&
364         event_general->event_subclass == MYSQL_AUDIT_GENERAL_RESULT)
365     {
366       sm_query_finished(thd, event_general->general_query);
367     }
368     else if (event_general->general_query &&
369         event_general->event_subclass == MYSQL_AUDIT_GENERAL_ERROR)
370     {
371       sm_query_failed(thd, event_general->general_query,
372                                 event_general->general_error_code);
373     }
374 
375   }
376   else if (event_class == MYSQL_AUDIT_CONNECTION_CLASS)
377   {
378     const struct mysql_event_connection *event_connection=
379       (const struct mysql_event_connection *) event;
380     switch (event_connection->event_subclass)
381     {
382     case MYSQL_AUDIT_CONNECTION_CONNECT:
383       sm_thd_data_get(thd);
384       break;
385     case MYSQL_AUDIT_CONNECTION_DISCONNECT:
386       sm_thd_data_release(thd);
387       break;
388     default:
389       break;
390     }
391   }
392 }
393 
394 /*
395  * Plugin system vars
396  */
397 static struct st_mysql_sys_var* scalability_metrics_system_variables[] =
398 {
399   MYSQL_SYSVAR(thd_data),
400   MYSQL_SYSVAR(control),
401   NULL
402 };
403 
404 /*
405   Plugin type-specific descriptor
406 */
407 static struct st_mysql_audit scalability_metrics_descriptor=
408 {
409   MYSQL_AUDIT_INTERFACE_VERSION,                    /* interface version    */
410   NULL,                                             /* release_thd function */
411   sm_notify,                                        /* notify function      */
412   { MYSQL_AUDIT_GENERAL_CLASSMASK |
413     MYSQL_AUDIT_CONNECTION_CLASSMASK }              /* class mask           */
414 };
415 
416 /*
417   Plugin status variables for SHOW STATUS
418 */
419 
420 static struct st_mysql_show_var simple_status[]=
421 {
422   { "scalability_metrics_elapsedtime", (char *) &sm_elapsedtime, SHOW_FUNC },
423   { "scalability_metrics_queries", (char *) &sm_queries, SHOW_FUNC },
424   { "scalability_metrics_concurrency", (char *) &concurrency, SHOW_LONGLONG },
425   { "scalability_metrics_totaltime", (char *) &sm_totaltime, SHOW_FUNC },
426   { "scalability_metrics_busytime", (char *) &busytime, SHOW_LONGLONG },
427   { 0, 0, 0}
428 };
429 
430 
431 /*
432   Plugin library descriptor
433 */
434 
mysql_declare_plugin(scalability_metrics)435 mysql_declare_plugin(scalability_metrics)
436 {
437   MYSQL_AUDIT_PLUGIN,                     /* type                            */
438   &scalability_metrics_descriptor,        /* descriptor                      */
439   "scalability_metrics",                  /* name                            */
440   "Percona LLC and/or its affiliates",    /* author                          */
441   "Scalability metrics",                  /* description                     */
442   PLUGIN_LICENSE_GPL,
443   sm_plugin_init,                         /* init function (when loaded)     */
444   sm_plugin_deinit,                       /* deinit function (when unloaded) */
445   0x0001,                                 /* version                         */
446   simple_status,                          /* status variables                */
447   scalability_metrics_system_variables,   /* system variables                */
448   NULL,
449   0,
450 }
451 mysql_declare_plugin_end;
452 
453