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