1 /* Copyright (c) 2007, 2019, 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
21    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
22 
23 /**
24   @file
25 
26   Implement query profiling as as list of metaphorical fences, with one fence
27   per query, and each fencepost a change of thd->proc_info state (with a
28   snapshot of system statistics).  When asked, we can then iterate over the
29   fenceposts and calculate the distance between them, to inform the user what
30   happened during a particular query or thd->proc_info state.
31 
32   User variables that inform profiling behavior:
33   - "profiling", boolean, session only, "Are queries profiled?"
34   - "profiling_history_size", integer, session + global, "Num queries stored?"
35 */
36 
37 #include "sql/sql_profile.h"
38 
39 #include "my_config.h"
40 
41 #include <string.h>
42 #include <algorithm>
43 
44 #include "decimal.h"
45 #include "m_ctype.h"
46 #include "m_string.h"
47 #include "my_base.h"
48 #include "my_compiler.h"
49 #include "my_dbug.h"
50 #include "my_sqlcommand.h"
51 #include "my_sys.h"
52 #include "my_systime.h"
53 #include "sql/field.h"
54 #include "sql/item.h"
55 #include "sql/my_decimal.h"
56 #include "sql/protocol.h"
57 #include "sql/psi_memory_key.h"
58 #include "sql/query_options.h"
59 #include "sql/sql_class.h"  // THD
60 #include "sql/sql_error.h"
61 #include "sql/sql_lex.h"
62 #include "sql/sql_list.h"
63 #include "sql/sql_show.h"  // schema_table_store_record
64 #include "sql/system_variables.h"
65 #include "sql_string.h"
66 
67 using std::max;
68 using std::min;
69 
70 #define TIME_FLOAT_DIGITS 9
71 /** two vals encoded: (dec*100)+len */
72 #define TIME_I_S_DECIMAL_SIZE \
73   (TIME_FLOAT_DIGITS * 100) + (TIME_FLOAT_DIGITS - 3)
74 
75 static const size_t MAX_QUERY_LENGTH = 300;
76 #define MAX_QUERY_HISTORY 101U
77 
78 /**
79   Connects Information_Schema and Profiling.
80 */
fill_query_profile_statistics_info(THD * thd MY_ATTRIBUTE ((unused)),TABLE_LIST * tables MY_ATTRIBUTE ((unused)),Item *)81 int fill_query_profile_statistics_info(
82     THD *thd MY_ATTRIBUTE((unused)), TABLE_LIST *tables MY_ATTRIBUTE((unused)),
83     Item *) {
84 #if defined(ENABLED_PROFILING)
85   const char *old = thd->lex->sql_command == SQLCOM_SHOW_PROFILE
86                         ? "SHOW PROFILE"
87                         : "INFORMATION_SCHEMA.PROFILING";
88 
89   DBUG_ASSERT(thd->lex->sql_command != SQLCOM_SHOW_PROFILES);
90 
91   push_deprecated_warn(thd, old, "Performance Schema");
92   return (thd->profiling->fill_statistics_info(thd, tables));
93 #else
94   my_error(ER_FEATURE_DISABLED, MYF(0), "SHOW PROFILE", "enable-profiling");
95   return (1);
96 #endif
97 }
98 
99 ST_FIELD_INFO query_profile_statistics_info[] = {
100     /* name, length, type, value, maybe_null, old_name, open_method */
101     {"QUERY_ID", 20, MYSQL_TYPE_LONG, 0, false, "Query_id", 0},
102     {"SEQ", 20, MYSQL_TYPE_LONG, 0, false, "Seq", 0},
103     {"STATE", 30, MYSQL_TYPE_STRING, 0, false, "Status", 0},
104     {"DURATION", TIME_I_S_DECIMAL_SIZE, MYSQL_TYPE_DECIMAL, 0, false,
105      "Duration", 0},
106     {"CPU_USER", TIME_I_S_DECIMAL_SIZE, MYSQL_TYPE_DECIMAL, 0, true, "CPU_user",
107      0},
108     {"CPU_SYSTEM", TIME_I_S_DECIMAL_SIZE, MYSQL_TYPE_DECIMAL, 0, true,
109      "CPU_system", 0},
110     {"CONTEXT_VOLUNTARY", 20, MYSQL_TYPE_LONG, 0, true, "Context_voluntary", 0},
111     {"CONTEXT_INVOLUNTARY", 20, MYSQL_TYPE_LONG, 0, true, "Context_involuntary",
112      0},
113     {"BLOCK_OPS_IN", 20, MYSQL_TYPE_LONG, 0, true, "Block_ops_in", 0},
114     {"BLOCK_OPS_OUT", 20, MYSQL_TYPE_LONG, 0, true, "Block_ops_out", 0},
115     {"MESSAGES_SENT", 20, MYSQL_TYPE_LONG, 0, true, "Messages_sent", 0},
116     {"MESSAGES_RECEIVED", 20, MYSQL_TYPE_LONG, 0, true, "Messages_received", 0},
117     {"PAGE_FAULTS_MAJOR", 20, MYSQL_TYPE_LONG, 0, true, "Page_faults_major", 0},
118     {"PAGE_FAULTS_MINOR", 20, MYSQL_TYPE_LONG, 0, true, "Page_faults_minor", 0},
119     {"SWAPS", 20, MYSQL_TYPE_LONG, 0, true, "Swaps", 0},
120     {"SOURCE_FUNCTION", 30, MYSQL_TYPE_STRING, 0, true, "Source_function", 0},
121     {"SOURCE_FILE", 20, MYSQL_TYPE_STRING, 0, true, "Source_file", 0},
122     {"SOURCE_LINE", 20, MYSQL_TYPE_LONG, 0, true, "Source_line", 0},
123     {nullptr, 0, MYSQL_TYPE_STRING, 0, true, nullptr, 0}};
124 
make_profile_table_for_show(THD * thd,ST_SCHEMA_TABLE * schema_table)125 int make_profile_table_for_show(THD *thd, ST_SCHEMA_TABLE *schema_table) {
126   uint profile_options = thd->lex->profile_options;
127   uint fields_include_condition_truth_values[] = {
128       false,                                 /* Query_id */
129       false,                                 /* Seq */
130       true,                                  /* Status */
131       true,                                  /* Duration */
132       profile_options & PROFILE_CPU,         /* CPU_user */
133       profile_options & PROFILE_CPU,         /* CPU_system */
134       profile_options & PROFILE_CONTEXT,     /* Context_voluntary */
135       profile_options & PROFILE_CONTEXT,     /* Context_involuntary */
136       profile_options & PROFILE_BLOCK_IO,    /* Block_ops_in */
137       profile_options & PROFILE_BLOCK_IO,    /* Block_ops_out */
138       profile_options & PROFILE_IPC,         /* Messages_sent */
139       profile_options & PROFILE_IPC,         /* Messages_received */
140       profile_options & PROFILE_PAGE_FAULTS, /* Page_faults_major */
141       profile_options & PROFILE_PAGE_FAULTS, /* Page_faults_minor */
142       profile_options & PROFILE_SWAPS,       /* Swaps */
143       profile_options & PROFILE_SOURCE,      /* Source_function */
144       profile_options & PROFILE_SOURCE,      /* Source_file */
145       profile_options & PROFILE_SOURCE,      /* Source_line */
146   };
147 
148   ST_FIELD_INFO *field_info;
149   Name_resolution_context *context = &thd->lex->select_lex->context;
150   int i;
151 
152   for (i = 0; schema_table->fields_info[i].field_name != nullptr; i++) {
153     if (!fields_include_condition_truth_values[i]) continue;
154 
155     field_info = &schema_table->fields_info[i];
156     Item_field *field =
157         new Item_field(context, NullS, NullS, field_info->field_name);
158     if (field) {
159       field->item_name.copy(field_info->old_name);
160       if (add_item_to_list(thd, field)) return 1;
161     }
162   }
163   return 0;
164 }
165 
166 #if defined(ENABLED_PROFILING)
167 
168 #define RUSAGE_USEC(tv) ((tv).tv_sec * 1000 * 1000 + (tv).tv_usec)
169 #define RUSAGE_DIFF_USEC(tv1, tv2) (RUSAGE_USEC((tv1)) - RUSAGE_USEC((tv2)))
170 
171 #ifdef _WIN32
FileTimeToQuadWord(FILETIME * ft)172 static ULONGLONG FileTimeToQuadWord(FILETIME *ft) {
173   // Overlay FILETIME onto a ULONGLONG.
174   union {
175     ULONGLONG qwTime;
176     FILETIME ft;
177   } u;
178 
179   u.ft = *ft;
180   return u.qwTime;
181 }
182 
183 // Get time difference between to FILETIME objects in seconds.
GetTimeDiffInSeconds(FILETIME * a,FILETIME * b)184 static double GetTimeDiffInSeconds(FILETIME *a, FILETIME *b) {
185   return ((FileTimeToQuadWord(a) - FileTimeToQuadWord(b)) / 1e7);
186 }
187 #endif
188 
PROF_MEASUREMENT(QUERY_PROFILE * profile_arg,const char * status_arg)189 PROF_MEASUREMENT::PROF_MEASUREMENT(QUERY_PROFILE *profile_arg,
190                                    const char *status_arg)
191     : profile(profile_arg) {
192   collect();
193   set_label(status_arg, nullptr, nullptr, 0);
194 }
195 
PROF_MEASUREMENT(QUERY_PROFILE * profile_arg,const char * status_arg,const char * function_arg,const char * file_arg,unsigned int line_arg)196 PROF_MEASUREMENT::PROF_MEASUREMENT(QUERY_PROFILE *profile_arg,
197                                    const char *status_arg,
198                                    const char *function_arg,
199                                    const char *file_arg, unsigned int line_arg)
200     : profile(profile_arg) {
201   collect();
202   set_label(status_arg, function_arg, file_arg, line_arg);
203 }
204 
~PROF_MEASUREMENT()205 PROF_MEASUREMENT::~PROF_MEASUREMENT() {
206   my_free(allocated_status_memory);
207   status = function = file = nullptr;
208 }
209 
set_label(const char * status_arg,const char * function_arg,const char * file_arg,unsigned int line_arg)210 void PROF_MEASUREMENT::set_label(const char *status_arg,
211                                  const char *function_arg, const char *file_arg,
212                                  unsigned int line_arg) {
213   size_t sizes[3]; /* 3 == status+function+file */
214   char *cursor;
215 
216   /*
217     Compute all the space we'll need to allocate one block for everything
218     we'll need, instead of N mallocs.
219   */
220   sizes[0] = (status_arg == nullptr) ? 0 : strlen(status_arg) + 1;
221   sizes[1] = (function_arg == nullptr) ? 0 : strlen(function_arg) + 1;
222   sizes[2] = (file_arg == nullptr) ? 0 : strlen(file_arg) + 1;
223 
224   allocated_status_memory = (char *)my_malloc(
225       key_memory_PROFILE, sizes[0] + sizes[1] + sizes[2], MYF(0));
226   DBUG_ASSERT(allocated_status_memory != nullptr);
227 
228   cursor = allocated_status_memory;
229 
230   if (status_arg != nullptr) {
231     strcpy(cursor, status_arg);
232     status = cursor;
233     cursor += sizes[0];
234   } else
235     status = nullptr;
236 
237   if (function_arg != nullptr) {
238     strcpy(cursor, function_arg);
239     function = cursor;
240     cursor += sizes[1];
241   } else
242     function = nullptr;
243 
244   if (file_arg != nullptr) {
245     strcpy(cursor, file_arg);
246     file = cursor;
247     cursor += sizes[2];
248   } else
249     file = nullptr;
250 
251   line = line_arg;
252 }
253 
254 /**
255   This updates the statistics for this moment of time.  It captures the state
256   of the running system, so later we can compare points in time and infer what
257   happened in the mean time.  It should only be called immediately upon
258   instantiation of this PROF_MEASUREMENT.
259 
260   @todo  Implement resource capture for OSes not like BSD.
261 */
collect()262 void PROF_MEASUREMENT::collect() {
263   time_usecs = (double)my_getsystime() / 10.0; /* 1 sec was 1e7, now is 1e6 */
264 #ifdef HAVE_GETRUSAGE
265   getrusage(RUSAGE_SELF, &rusage);
266 #elif defined(_WIN32)
267   FILETIME ftDummy;
268   // NOTE: Get{Process|Thread}Times has a granularity of the clock interval,
269   // which is typically ~15ms. So intervals shorter than that will not be
270   // measurable by this function.
271   GetProcessTimes(GetCurrentProcess(), &ftDummy, &ftDummy, &ftKernel, &ftUser);
272 #endif
273 }
274 
QUERY_PROFILE(PROFILING * profiling_arg,const char * status_arg)275 QUERY_PROFILE::QUERY_PROFILE(PROFILING *profiling_arg, const char *status_arg)
276     : profiling(profiling_arg),
277       profiling_query_id(0),
278       m_query_source(NULL_STR) {
279   m_seq_counter = 1;
280   PROF_MEASUREMENT *prof = new PROF_MEASUREMENT(this, status_arg);
281   prof->m_seq = m_seq_counter++;
282   m_start_time_usecs = prof->time_usecs;
283   m_end_time_usecs = m_start_time_usecs;
284   entries.push_back(prof);
285 }
286 
~QUERY_PROFILE()287 QUERY_PROFILE::~QUERY_PROFILE() {
288   while (!entries.is_empty()) delete entries.pop();
289 
290   my_free(m_query_source.str);
291 }
292 
293 /**
294   @todo  Provide a way to include the full text, as in  SHOW PROCESSLIST.
295 */
set_query_source(const char * query_source_arg,size_t query_length_arg)296 void QUERY_PROFILE::set_query_source(const char *query_source_arg,
297                                      size_t query_length_arg) {
298   /* Truncate to avoid DoS attacks. */
299   size_t length = min(MAX_QUERY_LENGTH, query_length_arg);
300 
301   DBUG_ASSERT(m_query_source.str == nullptr); /* we don't leak memory */
302   if (query_source_arg != nullptr) {
303     m_query_source.str =
304         my_strndup(key_memory_PROFILE, query_source_arg, length, MYF(0));
305     m_query_source.length = length;
306   }
307 }
308 
new_status(const char * status_arg,const char * function_arg,const char * file_arg,unsigned int line_arg)309 void QUERY_PROFILE::new_status(const char *status_arg, const char *function_arg,
310                                const char *file_arg, unsigned int line_arg) {
311   PROF_MEASUREMENT *prof;
312   DBUG_TRACE;
313 
314   DBUG_ASSERT(status_arg != nullptr);
315 
316   if ((function_arg != nullptr) && (file_arg != nullptr))
317     prof = new PROF_MEASUREMENT(this, status_arg, function_arg,
318                                 base_name(file_arg), line_arg);
319   else
320     prof = new PROF_MEASUREMENT(this, status_arg);
321 
322   prof->m_seq = m_seq_counter++;
323   m_end_time_usecs = prof->time_usecs;
324   entries.push_back(prof);
325 
326   /* Maintain the query history size. */
327   while (entries.elements > MAX_QUERY_HISTORY) delete entries.pop();
328 }
329 
PROFILING()330 PROFILING::PROFILING()
331     : profile_id_counter(1), current(nullptr), last(nullptr) {}
332 
~PROFILING()333 PROFILING::~PROFILING() {
334   while (!history.is_empty()) delete history.pop();
335 
336   if (current != nullptr) delete current;
337 }
338 
339 /**
340   A new state is given, and that signals the profiler to start a new
341   timed step for the current query's profile.
342 
343   @param  status_arg  name of this step
344   @param  function_arg  calling function (usually supplied from compiler)
345   @param  file_arg      calling file (usually supplied from compiler)
346   @param  line_arg      calling line number (usually supplied from compiler)
347 */
status_change(const char * status_arg,const char * function_arg,const char * file_arg,unsigned int line_arg)348 void PROFILING::status_change(const char *status_arg, const char *function_arg,
349                               const char *file_arg, unsigned int line_arg) {
350   DBUG_TRACE;
351 
352   if (status_arg == nullptr) /* We don't know how to handle that */
353     return;
354 
355   if (current == nullptr) /* This profile was already discarded. */
356     return;
357 
358   if (unlikely(enabled))
359     current->new_status(status_arg, function_arg, file_arg, line_arg);
360 }
361 
362 /**
363   Prepare to start processing a new query.  It is an error to do this
364   if there's a query already in process; nesting is not supported.
365 
366   @param  initial_state  (optional) name of period before first state change
367 */
start_new_query(const char * initial_state)368 void PROFILING::start_new_query(const char *initial_state) {
369   DBUG_TRACE;
370 
371   /* This should never happen unless the server is radically altered. */
372   if (unlikely(current != nullptr)) {
373     DBUG_PRINT("warning", ("profiling code was asked to start a new query "
374                            "before the old query was finished.  This is "
375                            "probably a bug."));
376     finish_current_query();
377   }
378 
379   enabled = ((thd->variables.option_bits & OPTION_PROFILING) != 0);
380 
381   if (!enabled) return;
382 
383   DBUG_ASSERT(current == nullptr);
384   current = new QUERY_PROFILE(this, initial_state);
385 }
386 
387 /**
388   Throw away the current profile, because it's useless or unwanted
389   or corrupted.
390 */
discard_current_query()391 void PROFILING::discard_current_query() {
392   DBUG_TRACE;
393 
394   delete current;
395   current = nullptr;
396 }
397 
398 /**
399   Try to save the current profile entry, clean up the data if it shouldn't be
400   saved, and maintain the profile history size.  Naturally, this may not
401   succeed if the profile was previously discarded, and that's expected.
402 */
finish_current_query()403 void PROFILING::finish_current_query() {
404   DBUG_TRACE;
405   if (current != nullptr) {
406     /* The last fence-post, so we can support the span before this. */
407     status_change("ending", nullptr, nullptr, 0);
408 
409     if ((enabled) && /* ON at start? */
410         ((thd->variables.option_bits & OPTION_PROFILING) !=
411          0) && /* and ON at end? */
412         (current->m_query_source.str != nullptr) &&
413         (!current->entries.is_empty())) {
414       current->profiling_query_id = next_profile_id(); /* assign an id */
415 
416       history.push_back(current);
417       last = current; /* never contains something that is not in the history. */
418       current = nullptr;
419     } else {
420       delete current;
421       current = nullptr;
422     }
423   }
424 
425   /* Maintain the history size. */
426   while (history.elements > thd->variables.profiling_history_size)
427     delete history.pop();
428 }
429 
show_profiles()430 bool PROFILING::show_profiles() {
431   DBUG_TRACE;
432   QUERY_PROFILE *prof;
433   List<Item> field_list;
434 
435   field_list.push_back(new Item_return_int("Query_ID", 10, MYSQL_TYPE_LONG));
436   field_list.push_back(new Item_return_int("Duration", TIME_FLOAT_DIGITS - 1,
437                                            MYSQL_TYPE_DOUBLE));
438   field_list.push_back(new Item_empty_string("Query", 40));
439 
440   if (thd->send_result_metadata(&field_list,
441                                 Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
442     return true;
443 
444   SELECT_LEX *sel = thd->lex->select_lex;
445   SELECT_LEX_UNIT *unit = thd->lex->unit;
446   ha_rows idx = 0;
447   Protocol *protocol = thd->get_protocol();
448 
449   unit->set_limit(thd, sel);
450 
451   void *iterator;
452   for (iterator = history.new_iterator(); iterator != nullptr;
453        iterator = history.iterator_next(iterator)) {
454     prof = history.iterator_value(iterator);
455 
456     double query_time_usecs = prof->m_end_time_usecs - prof->m_start_time_usecs;
457 
458     if (++idx <= unit->offset_limit_cnt) continue;
459     if (idx > unit->select_limit_cnt) break;
460 
461     protocol->start_row();
462     protocol->store((uint32)(prof->profiling_query_id));
463     protocol->store_double(query_time_usecs / (1000.0 * 1000),
464                            TIME_FLOAT_DIGITS - 1, 0);
465     if (prof->m_query_source.str != nullptr)
466       protocol->store_string(prof->m_query_source.str,
467                              prof->m_query_source.length, system_charset_info);
468     else
469       protocol->store_null();
470 
471     if (protocol->end_row()) return true;
472   }
473   my_eof(thd);
474   return false;
475 }
476 
477 /**
478   At a point in execution where we know the query source, save the text
479   of it in the query profile.
480 
481   This must be called exactly once per descrete statement.
482 */
set_query_source(const char * query_source_arg,size_t query_length_arg)483 void PROFILING::set_query_source(const char *query_source_arg,
484                                  size_t query_length_arg) {
485   DBUG_TRACE;
486 
487   if (!enabled) return;
488 
489   if (current != nullptr)
490     current->set_query_source(query_source_arg, query_length_arg);
491   else
492     DBUG_PRINT("info", ("no current profile to send query source to"));
493 }
494 
495 /**
496   Fill the information schema table, "query_profile", as defined in show.cc .
497   There are two ways to get to this function:  Selecting from the information
498   schema, and a SHOW command.
499 */
fill_statistics_info(THD * thd_arg,TABLE_LIST * tables)500 int PROFILING::fill_statistics_info(THD *thd_arg, TABLE_LIST *tables) {
501   DBUG_TRACE;
502   TABLE *table = tables->table;
503   ulonglong row_number = 0;
504 
505   QUERY_PROFILE *query;
506   /* Go through each query in this thread's stored history... */
507   void *history_iterator;
508   for (history_iterator = history.new_iterator(); history_iterator != nullptr;
509        history_iterator = history.iterator_next(history_iterator)) {
510     query = history.iterator_value(history_iterator);
511 
512     /*
513       Because we put all profiling info into a table that may be reordered, let
514       us also include a numbering of each state per query.  The query_id and
515       the "seq" together are unique.
516     */
517     ulong seq;
518 
519     void *entry_iterator;
520     PROF_MEASUREMENT *entry, *previous = nullptr;
521     /* ...and for each query, go through all its state-change steps. */
522     for (entry_iterator = query->entries.new_iterator();
523          entry_iterator != nullptr;
524          entry_iterator = query->entries.iterator_next(entry_iterator),
525         previous = entry, row_number++) {
526       entry = query->entries.iterator_value(entry_iterator);
527       seq = entry->m_seq;
528 
529       /* Skip the first.  We count spans of fence, not fence-posts. */
530       if (previous == nullptr) continue;
531 
532       if (thd_arg->lex->sql_command == SQLCOM_SHOW_PROFILE) {
533         /*
534           We got here via a SHOW command.  That means that we stored
535           information about the query we wish to show and that isn't
536           in a WHERE clause at a higher level to filter out rows we
537           wish to exclude.
538 
539           Because that functionality isn't available in the server yet,
540           we must filter here, at the wrong level.  Once one can con-
541           struct where and having conditions at the SQL layer, then this
542           condition should be ripped out.
543         */
544         if (thd_arg->lex->show_profile_query_id ==
545             0) /* 0 == show final query */
546         {
547           if (query != last) continue;
548         } else {
549           if (thd_arg->lex->show_profile_query_id != query->profiling_query_id)
550             continue;
551         }
552       }
553 
554       /* Set default values for this row. */
555       restore_record(table, s->default_values);
556 
557       /*
558         The order of these fields is set by the  query_profile_statistics_info
559         array.
560       */
561       table->field[0]->store((ulonglong)query->profiling_query_id, true);
562       table->field[1]->store((ulonglong)seq,
563                              true); /* the step in the sequence */
564       /*
565         This entry, n, has a point in time, T(n), and a status phrase, S(n).
566         The status phrase S(n) describes the period of time that begins at
567         T(n).  The previous status phrase S(n-1) describes the period of time
568         that starts at T(n-1) and ends at T(n).  Since we want to describe the
569         time that a status phrase took T(n)-T(n-1), this line must describe the
570         previous status.
571       */
572       table->field[2]->store(previous->status, strlen(previous->status),
573                              system_charset_info);
574 
575       my_decimal duration_decimal;
576       double2my_decimal(
577           E_DEC_FATAL_ERROR,
578           (entry->time_usecs - previous->time_usecs) / (1000.0 * 1000),
579           &duration_decimal);
580 
581       table->field[3]->store_decimal(&duration_decimal);
582 
583 #ifdef HAVE_GETRUSAGE
584 
585       my_decimal cpu_utime_decimal, cpu_stime_decimal;
586 
587       double2my_decimal(
588           E_DEC_FATAL_ERROR,
589           RUSAGE_DIFF_USEC(entry->rusage.ru_utime, previous->rusage.ru_utime) /
590               (1000.0 * 1000),
591           &cpu_utime_decimal);
592 
593       double2my_decimal(
594           E_DEC_FATAL_ERROR,
595           RUSAGE_DIFF_USEC(entry->rusage.ru_stime, previous->rusage.ru_stime) /
596               (1000.0 * 1000),
597           &cpu_stime_decimal);
598 
599       table->field[4]->store_decimal(&cpu_utime_decimal);
600       table->field[5]->store_decimal(&cpu_stime_decimal);
601       table->field[4]->set_notnull();
602       table->field[5]->set_notnull();
603 #elif defined(_WIN32)
604       my_decimal cpu_utime_decimal, cpu_stime_decimal;
605 
606       double2my_decimal(E_DEC_FATAL_ERROR,
607                         GetTimeDiffInSeconds(&entry->ftUser, &previous->ftUser),
608                         &cpu_utime_decimal);
609       double2my_decimal(
610           E_DEC_FATAL_ERROR,
611           GetTimeDiffInSeconds(&entry->ftKernel, &previous->ftKernel),
612           &cpu_stime_decimal);
613 
614       // Store the result.
615       table->field[4]->store_decimal(&cpu_utime_decimal);
616       table->field[5]->store_decimal(&cpu_stime_decimal);
617       table->field[4]->set_notnull();
618       table->field[5]->set_notnull();
619 #else
620       /* TODO: Add CPU-usage info for non-BSD systems */
621 #endif
622 
623 #ifdef HAVE_GETRUSAGE
624       table->field[6]->store(
625           (uint32)(entry->rusage.ru_nvcsw - previous->rusage.ru_nvcsw));
626       table->field[6]->set_notnull();
627       table->field[7]->store(
628           (uint32)(entry->rusage.ru_nivcsw - previous->rusage.ru_nivcsw));
629       table->field[7]->set_notnull();
630 #else
631       /* TODO: Add context switch info for non-BSD systems */
632 #endif
633 
634 #ifdef HAVE_GETRUSAGE
635       table->field[8]->store(
636           (uint32)(entry->rusage.ru_inblock - previous->rusage.ru_inblock));
637       table->field[8]->set_notnull();
638       table->field[9]->store(
639           (uint32)(entry->rusage.ru_oublock - previous->rusage.ru_oublock));
640       table->field[9]->set_notnull();
641 #else
642       /* TODO: Add block IO info for non-BSD systems */
643 #endif
644 
645 #ifdef HAVE_GETRUSAGE
646       table->field[10]->store(
647           (uint32)(entry->rusage.ru_msgsnd - previous->rusage.ru_msgsnd), true);
648       table->field[10]->set_notnull();
649       table->field[11]->store(
650           (uint32)(entry->rusage.ru_msgrcv - previous->rusage.ru_msgrcv), true);
651       table->field[11]->set_notnull();
652 #else
653       /* TODO: Add message info for non-BSD systems */
654 #endif
655 
656 #ifdef HAVE_GETRUSAGE
657       table->field[12]->store(
658           (uint32)(entry->rusage.ru_majflt - previous->rusage.ru_majflt), true);
659       table->field[12]->set_notnull();
660       table->field[13]->store(
661           (uint32)(entry->rusage.ru_minflt - previous->rusage.ru_minflt), true);
662       table->field[13]->set_notnull();
663 #else
664       /* TODO: Add page fault info for non-BSD systems */
665 #endif
666 
667 #ifdef HAVE_GETRUSAGE
668       table->field[14]->store(
669           (uint32)(entry->rusage.ru_nswap - previous->rusage.ru_nswap), true);
670       table->field[14]->set_notnull();
671 #else
672       /* TODO: Add swap info for non-BSD systems */
673 #endif
674 
675       /* Emit the location that started this step, not that ended it. */
676       if ((previous->function != nullptr) && (previous->file != nullptr)) {
677         table->field[15]->store(previous->function, strlen(previous->function),
678                                 system_charset_info);
679         table->field[15]->set_notnull();
680         table->field[16]->store(previous->file, strlen(previous->file),
681                                 system_charset_info);
682         table->field[16]->set_notnull();
683         table->field[17]->store(previous->line, true);
684         table->field[17]->set_notnull();
685       }
686 
687       if (schema_table_store_record(thd_arg, table)) return 1;
688     }
689   }
690 
691   return 0;
692 }
693 /**
694   Clear all the profiling information.
695 */
cleanup()696 void PROFILING::cleanup() {
697   while (!history.is_empty()) delete history.pop();
698   delete current;
699   current = nullptr;
700 }
701 
702 #endif /* ENABLED_PROFILING */
703