gnome-shell-3.6.3.1/src/shell-perf-log.c

Location Tool Test ID Function Issue
shell-perf-log.c:385:3 clang-analyzer Null pointer passed as an argument to a 'nonnull' parameter
  1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
  2 
  3 #include "config.h"
  4 
  5 #include <string.h>
  6 
  7 #include "shell-perf-log.h"
  8 
  9 typedef struct _ShellPerfEvent ShellPerfEvent;
 10 typedef struct _ShellPerfStatistic ShellPerfStatistic;
 11 typedef struct _ShellPerfStatisticsClosure ShellPerfStatisticsClosure;
 12 typedef union  _ShellPerfStatisticValue ShellPerfStatisticValue;
 13 typedef struct _ShellPerfBlock ShellPerfBlock;
 14 
 15 /**
 16  * SECTION:shell-perf-log
 17  * @short_description: Event recorder for performance measurement
 18  *
 19  * ShellPerfLog provides a way for different parts of the code to
 20  * record information for subsequent analysis and interactive
 21  * exploration. Events exist of a timestamp, an event ID, and
 22  * arguments to the event.
 23  *
 24  * Emphasis is placed on storing recorded events in a compact
 25  * fashion so log recording disturbs the execution of the program
 26  * as little as possible, however events should not be recorded
 27  * at too fine a granularity - an event that is recorded once
 28  * per frame or once per user action is appropriate, an event that
 29  * occurs many times per frame is not.
 30  *
 31  * Arguments are identified by a D-Bus style signature; at the moment
 32  * only a limited number of event signatures are supported to
 33  * simplify the code.
 34  */
 35 struct _ShellPerfLog
 36 {
 37   GObject parent;
 38 
 39   GPtrArray *events;
 40   GHashTable *events_by_name;
 41   GPtrArray *statistics;
 42   GHashTable *statistics_by_name;
 43 
 44   GPtrArray *statistics_closures;
 45 
 46   GQueue *blocks;
 47 
 48   gint64 start_time;
 49   gint64 last_time;
 50 
 51   guint statistics_timeout_id;
 52 
 53   guint enabled : 1;
 54 };
 55 
 56 struct _ShellPerfLogClass
 57 {
 58   GObjectClass parent_class;
 59 };
 60 
 61 struct _ShellPerfEvent
 62 {
 63   guint16 id;
 64   char *name;
 65   char *description;
 66   char *signature;
 67 };
 68 
 69 union _ShellPerfStatisticValue
 70 {
 71   int i;
 72   gint64 x;
 73 };
 74 
 75 struct _ShellPerfStatistic
 76 {
 77   ShellPerfEvent *event;
 78 
 79   ShellPerfStatisticValue current_value;
 80   ShellPerfStatisticValue last_value;
 81 
 82   guint initialized : 1;
 83   guint recorded : 1;
 84 };
 85 
 86 struct _ShellPerfStatisticsClosure
 87 {
 88   ShellPerfStatisticsCallback callback;
 89   gpointer user_data;
 90   GDestroyNotify notify;
 91 };
 92 
 93 /* The events in the log are stored in a linked list of fixed size
 94  * blocks.
 95  *
 96  * Note that the power-of-two nature of BLOCK_SIZE here is superficial
 97  * since the allocated block has the 'bytes' field and malloc
 98  * overhead. The current value is well below the size that will
 99  * typically be independently mmapped by the malloc implementation so
100  * it doesn't matter. If we switched to mmapping blocks manually
101  * (perhaps to avoid polluting malloc statistics), we'd want to use a
102  * different value of BLOCK_SIZE.
103  */
104 #define BLOCK_SIZE 8192
105 
106 struct _ShellPerfBlock
107 {
108   guint32 bytes;
109   guchar buffer[BLOCK_SIZE];
110 };
111 
112 /* Number of milliseconds between periodic statistics collection when
113  * events are enabled. Statistics collection can also be explicitly
114  * triggered.
115  */
116 #define STATISTIC_COLLECTION_INTERVAL_MS 5000
117 
118 /* Builtin events */
119 enum {
120   EVENT_SET_TIME,
121   EVENT_STATISTICS_COLLECTED
122 };
123 
124 G_DEFINE_TYPE(ShellPerfLog, shell_perf_log, G_TYPE_OBJECT);
125 
126 static gint64
127 get_time (void)
128 {
129   GTimeVal timeval;
130 
131   g_get_current_time (&timeval);
132 
133   return timeval.tv_sec * G_GINT64_CONSTANT(1000000) + timeval.tv_usec;
134 }
135 
136 static void
137 shell_perf_log_init (ShellPerfLog *perf_log)
138 {
139   perf_log->events = g_ptr_array_new ();
140   perf_log->events_by_name = g_hash_table_new (g_str_hash, g_str_equal);
141   perf_log->statistics = g_ptr_array_new ();
142   perf_log->statistics_by_name = g_hash_table_new (g_str_hash, g_str_equal);
143   perf_log->statistics_closures = g_ptr_array_new ();
144   perf_log->blocks = g_queue_new ();
145 
146   /* This event is used when timestamp deltas are greater than
147    * fits in a gint32. 0xffffffff microseconds is about 70 minutes, so this
148    * is not going to happen in normal usage. It might happen if performance
149    * logging is enabled some time after starting the shell */
150   shell_perf_log_define_event (perf_log, "perf.setTime", "", "x");
151   g_assert (perf_log->events->len == EVENT_SET_TIME + 1);
152 
153   /* The purpose of this event is to allow us to optimize out storing
154    * statistics that haven't changed. We want to mark every time we
155    * collect statistics even if we don't record any individual
156    * statistics so that we can distinguish sudden changes from gradual changes.
157    *
158    * The argument is the number of microseconds that statistics collection
159    * took; we record that since statistics collection could start taking
160    * significant time if we do things like grub around in /proc/
161    */
162   shell_perf_log_define_event (perf_log, "perf.statisticsCollected",
163                                "Finished collecting statistics",
164                                "x");
165   g_assert (perf_log->events->len == EVENT_STATISTICS_COLLECTED + 1);
166 
167   perf_log->start_time = perf_log->last_time = get_time();
168 }
169 
170 static void
171 shell_perf_log_class_init (ShellPerfLogClass *class)
172 {
173 }
174 
175 /**
176  * shell_perf_log_get_default:
177  *
178  * Gets the global singleton performance log. This is initially disabled
179  * and must be explicitly enabled with shell_perf_log_set_enabled().
180  *
181  * Return value: (transfer none): the global singleton performance log
182  */
183 ShellPerfLog *
184 shell_perf_log_get_default (void)
185 {
186   static ShellPerfLog *perf_log;
187 
188   if (perf_log == NULL)
189     perf_log = g_object_new (SHELL_TYPE_PERF_LOG, NULL);
190 
191   return perf_log;
192 }
193 
194 static gboolean
195 statistics_timeout (gpointer data)
196 {
197   ShellPerfLog *perf_log = data;
198 
199   shell_perf_log_collect_statistics (perf_log);
200 
201   return TRUE;
202 }
203 
204 /**
205  * shell_perf_log_set_enabled:
206  * @perf_log: a #ShellPerfLog
207  * @enabled: whether to record events
208  *
209  * Sets whether events are currently being recorded.
210  */
211 void
212 shell_perf_log_set_enabled (ShellPerfLog *perf_log,
213                             gboolean      enabled)
214 {
215   enabled = enabled != FALSE;
216 
217   if (enabled != perf_log->enabled)
218     {
219       perf_log->enabled = enabled;
220 
221       if (enabled)
222         {
223           perf_log->statistics_timeout_id = g_timeout_add (STATISTIC_COLLECTION_INTERVAL_MS,
224                                                            statistics_timeout,
225                                                            perf_log);
226         }
227       else
228         {
229           g_source_remove (perf_log->statistics_timeout_id);
230           perf_log->statistics_timeout_id = 0;
231         }
232     }
233 }
234 
235 static ShellPerfEvent *
236 define_event (ShellPerfLog *perf_log,
237               const char   *name,
238               const char   *description,
239               const char   *signature)
240 {
241   ShellPerfEvent *event;
242 
243   if (strcmp (signature, "") != 0 &&
244       strcmp (signature, "s") != 0 &&
245       strcmp (signature, "i") != 0 &&
246       strcmp (signature, "x") != 0)
247     {
248       g_warning ("Only supported event signatures are '', 's', 'i', and 'x'\n");
249       return NULL;
250     }
251 
252   if (perf_log->events->len == 65536)
253     {
254       g_warning ("Maximum number of events defined\n");
255       return NULL;
256     }
257 
258   /* We could do stricter validation, but this will break our JSON dumps */
259   if (strchr (name, '"') != NULL)
260     {
261       g_warning ("Event names can't include '\"'");
262       return NULL;
263     }
264 
265   if (g_hash_table_lookup (perf_log->events_by_name, name) != NULL)
266     {
267       g_warning ("Duplicate event event for '%s'\n", name);
268       return NULL;
269     }
270 
271   event = g_slice_new (ShellPerfEvent);
272 
273   event->id = perf_log->events->len;
274   event->name = g_strdup (name);
275   event->signature = g_strdup (signature);
276   event->description = g_strdup (description);
277 
278   g_ptr_array_add (perf_log->events, event);
279   g_hash_table_insert (perf_log->events_by_name, event->name, event);
280 
281   return event;
282 }
283 
284 /**
285  * shell_perf_log_define_event:
286  * @perf_log: a #ShellPerfLog
287  * @name: name of the event. This should of the form
288  *   '<namespace>.<specific eventf'>, for example
289  *   'clutter.stagePaintDone'.
290  * @description: human readable description of the event.
291  * @signature: signature defining the arguments that event takes.
292  *   This is a string of type characters, using the same characters
293  *   as D-Bus or GVariant. Only a very limited number of signatures
294  *   are supported: , '', 's', 'i', and 'x'. This mean respectively:
295  *   no arguments, one string, one 32-bit integer, and one 64-bit
296  *   integer.
297  *
298  * Defines a performance event for later recording.
299  */
300 void
301 shell_perf_log_define_event (ShellPerfLog *perf_log,
302                              const char   *name,
303                              const char   *description,
304                              const char   *signature)
305 {
306   define_event (perf_log, name, description, signature);
307 }
308 
309 static ShellPerfEvent *
310 lookup_event (ShellPerfLog *perf_log,
311               const char   *name,
312               const char   *signature)
313 {
314   ShellPerfEvent *event = g_hash_table_lookup (perf_log->events_by_name, name);
315 
316   if (G_UNLIKELY (event == NULL))
317     {
318       g_warning ("Discarding unknown event '%s'\n", name);
319       return NULL;
320     }
321 
322   if (G_UNLIKELY (strcmp (event->signature, signature) != 0))
323     {
324       g_warning ("Event '%s'; defined with signature '%s', used with '%s'\n",
325                  name, event->signature, signature);
326       return NULL;
327     }
328 
329   return event;
330 }
331 
332 static void
333 record_event (ShellPerfLog   *perf_log,
334               gint64          event_time,
335               ShellPerfEvent *event,
336               const guchar   *bytes,
337               size_t          bytes_len)
338 {
339   ShellPerfBlock *block;
340   size_t total_bytes;
341   guint32 time_delta;
342   guint32 pos;
343 
344   if (!perf_log->enabled)
345     return;
346 
347   total_bytes = sizeof (gint32) + sizeof (gint16) + bytes_len;
348   if (G_UNLIKELY (bytes_len > BLOCK_SIZE || total_bytes > BLOCK_SIZE))
349     {
350       g_warning ("Discarding oversize event '%s'\n", event->name);
351       return;
352     }
353 
354   if (event_time > perf_log->last_time + G_GINT64_CONSTANT(0xffffffff))
355     {
356       perf_log->last_time = event_time;
357       record_event (perf_log, event_time,
358                     lookup_event (perf_log, "perf.setTime", "x"),
359                     (const guchar *)&event_time, sizeof(gint64));
360       time_delta = 0;
361     }
362   else if (event_time < perf_log->last_time)
363     time_delta = 0;
364   else
365     time_delta = (guint32)(event_time - perf_log->last_time);
366 
367   perf_log->last_time = event_time;
368 
369   if (perf_log->blocks->tail == NULL ||
370       total_bytes + ((ShellPerfBlock *)perf_log->blocks->tail->data)->bytes > BLOCK_SIZE)
371     {
372       block = g_new (ShellPerfBlock, 1);
373       block->bytes = 0;
374       g_queue_push_tail (perf_log->blocks, block);
375     }
376   else
377     {
378       block = (ShellPerfBlock *)perf_log->blocks->tail->data;
379     }
380 
381   pos = block->bytes;
382 
383   memcpy (block->buffer + pos, &time_delta, sizeof (guint32));
384   pos += sizeof (guint32);
385   memcpy (block->buffer + pos, &event->id, sizeof (guint16));
Null pointer passed as an argument to a 'nonnull' parameter
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

386 pos += sizeof (guint16); 387 memcpy (block->buffer + pos, bytes, bytes_len); 388 pos += bytes_len; 389 390 block->bytes = pos; 391 } 392 393 /** 394 * shell_perf_log_event: 395 * @perf_log: a #ShellPerfLog 396 * @name: name of the event 397 * 398 * Records a performance event with no arguments. 399 */ 400 void 401 shell_perf_log_event (ShellPerfLog *perf_log, 402 const char *name) 403 { 404 ShellPerfEvent *event = lookup_event (perf_log, name, ""); 405 if (G_UNLIKELY (event == NULL)) 406 return; 407 408 record_event (perf_log, get_time(), event, NULL, 0); 409 } 410 411 /** 412 * shell_perf_log_event_i: 413 * @perf_log: a #ShellPerfLog 414 * @name: name of the event 415 * @arg: the argument 416 * 417 * Records a performance event with one 32-bit integer argument. 418 */ 419 void 420 shell_perf_log_event_i (ShellPerfLog *perf_log, 421 const char *name, 422 gint32 arg) 423 { 424 ShellPerfEvent *event = lookup_event (perf_log, name, "i"); 425 if (G_UNLIKELY (event == NULL)) 426 return; 427 428 record_event (perf_log, get_time(), event, 429 (const guchar *)&arg, sizeof (arg)); 430 } 431 432 /** 433 * shell_perf_log_event_x: 434 * @perf_log: a #ShellPerfLog 435 * @name: name of the event 436 * @arg: the argument 437 * 438 * Records a performance event with one 64-bit integer argument. 439 */ 440 void 441 shell_perf_log_event_x (ShellPerfLog *perf_log, 442 const char *name, 443 gint64 arg) 444 { 445 ShellPerfEvent *event = lookup_event (perf_log, name, "x"); 446 if (G_UNLIKELY (event == NULL)) 447 return; 448 449 record_event (perf_log, get_time(), event, 450 (const guchar *)&arg, sizeof (arg)); 451 } 452 453 /** 454 * shell_perf_log_event_s: 455 * @perf_log: a #ShellPerfLog 456 * @name: name of the event 457 * @arg: the argument 458 * 459 * Records a performance event with one string argument. 460 */ 461 void 462 shell_perf_log_event_s (ShellPerfLog *perf_log, 463 const char *name, 464 const char *arg) 465 { 466 ShellPerfEvent *event = lookup_event (perf_log, name, "s"); 467 if (G_UNLIKELY (event == NULL)) 468 return; 469 470 record_event (perf_log, get_time(), event, 471 (const guchar *)arg, strlen (arg) + 1); 472 } 473 474 /** 475 * shell_perf_log_define_statistic: 476 * @name: name of the statistic and of the corresponding event. 477 * This should follow the same guidelines as for shell_perf_log_define_event() 478 * @description: human readable description of the statistic. 479 * @signature: The type of the data stored for statistic. Must 480 * currently be 'i' or 'x'. 481 * 482 * Defines a statistic. A statistic is a numeric value that is stored 483 * by the performance log and recorded periodically or when 484 * shell_perf_log_collect_statistics() is called explicitly. 485 * 486 * Code that defines a statistic should update it by calling 487 * the update function for the particular data type of the statistic, 488 * such as shell_perf_log_update_statistic_i(). This can be done 489 * at any time, but would normally done inside a function registered 490 * with shell_perf_log_add_statistics_callback(). These functions 491 * are called immediately before statistics are recorded. 492 */ 493 void 494 shell_perf_log_define_statistic (ShellPerfLog *perf_log, 495 const char *name, 496 const char *description, 497 const char *signature) 498 { 499 ShellPerfEvent *event; 500 ShellPerfStatistic *statistic; 501 502 if (strcmp (signature, "i") != 0 && 503 strcmp (signature, "x") != 0) 504 { 505 g_warning ("Only supported statistic signatures are 'i' and 'x'\n"); 506 return; 507 } 508 509 event = define_event (perf_log, name, description, signature); 510 if (event == NULL) 511 return; 512 513 statistic = g_slice_new (ShellPerfStatistic); 514 statistic->event = event; 515 516 statistic->initialized = FALSE; 517 statistic->recorded = FALSE; 518 519 g_ptr_array_add (perf_log->statistics, statistic); 520 g_hash_table_insert (perf_log->statistics_by_name, event->name, statistic); 521 } 522 523 static ShellPerfStatistic * 524 lookup_statistic (ShellPerfLog *perf_log, 525 const char *name, 526 const char *signature) 527 { 528 ShellPerfStatistic *statistic = g_hash_table_lookup (perf_log->statistics_by_name, name); 529 530 if (G_UNLIKELY (statistic == NULL)) 531 { 532 g_warning ("Unknown statistic '%s'\n", name); 533 return NULL; 534 } 535 536 if (G_UNLIKELY (strcmp (statistic->event->signature, signature) != 0)) 537 { 538 g_warning ("Statistic '%s'; defined with signature '%s', used with '%s'\n", 539 name, statistic->event->signature, signature); 540 return NULL; 541 } 542 543 return statistic; 544 } 545 546 /** 547 * shell_perf_log_update_statistic_i: 548 * @perf_log: a #ShellPerfLog 549 * @name: name of the statistic 550 * @value: new value for the statistic 551 * 552 * Updates the current value of an 32-bit integer statistic. 553 */ 554 void 555 shell_perf_log_update_statistic_i (ShellPerfLog *perf_log, 556 const char *name, 557 gint32 value) 558 { 559 ShellPerfStatistic *statistic; 560 561 statistic = lookup_statistic (perf_log, name, "i"); 562 if (G_UNLIKELY (statistic == NULL)) 563 return; 564 565 statistic->current_value.i = value; 566 statistic->initialized = TRUE; 567 } 568 569 /** 570 * shell_perf_log_update_statistic_x: 571 * @perf_log: a #ShellPerfLog 572 * @name: name of the statistic 573 * @value: new value for the statistic 574 * 575 * Updates the current value of an 64-bit integer statistic. 576 */ 577 void 578 shell_perf_log_update_statistic_x (ShellPerfLog *perf_log, 579 const char *name, 580 gint64 value) 581 { 582 ShellPerfStatistic *statistic; 583 584 statistic = lookup_statistic (perf_log, name, "x"); 585 if (G_UNLIKELY (statistic == NULL)) 586 return; 587 588 statistic->current_value.x = value; 589 statistic->initialized = TRUE; 590 } 591 592 /** 593 * shell_perf_log_add_statistics_callback: 594 * @perf_log: a #ShellPerfLog 595 * @callback: function to call before recording statistics 596 * @user_data: data to pass to @callback 597 * @notify: function to call when @user_data is no longer needed 598 * 599 * Adds a function that will be called before statistics are recorded. 600 * The function would typically compute one or more statistics values 601 * and call a function such as shell_perf_log_update_statistic_i() 602 * to update the value that will be recorded. 603 */ 604 void 605 shell_perf_log_add_statistics_callback (ShellPerfLog *perf_log, 606 ShellPerfStatisticsCallback callback, 607 gpointer user_data, 608 GDestroyNotify notify) 609 { 610 ShellPerfStatisticsClosure *closure = g_slice_new (ShellPerfStatisticsClosure); 611 612 closure->callback = callback; 613 closure->user_data = user_data; 614 closure->notify = notify; 615 616 g_ptr_array_add (perf_log->statistics_closures, closure); 617 } 618 619 /** 620 * shell_perf_log_collect_statistics: 621 * @perf_log: a #ShellPerfLog 622 * 623 * Calls all the update functions added with 624 * shell_perf_log_add_statistics_callback() and then records events 625 * for all statistics, followed by a perf.statisticsCollected event. 626 */ 627 void 628 shell_perf_log_collect_statistics (ShellPerfLog *perf_log) 629 { 630 gint64 event_time = get_time (); 631 gint64 collection_time; 632 int i; 633 634 if (!perf_log->enabled) 635 return; 636 637 for (i = 0; i < perf_log->statistics_closures->len; i++) 638 { 639 ShellPerfStatisticsClosure *closure; 640 641 closure = g_ptr_array_index (perf_log->statistics_closures, i); 642 closure->callback (perf_log, closure->user_data); 643 } 644 645 collection_time = get_time() - event_time; 646 647 for (i = 0; i < perf_log->statistics->len; i++) 648 { 649 ShellPerfStatistic *statistic = g_ptr_array_index (perf_log->statistics, i); 650 651 if (!statistic->initialized) 652 continue; 653 654 switch (statistic->event->signature[0]) 655 { 656 case 'i': 657 if (!statistic->recorded || 658 statistic->current_value.i != statistic->last_value.i) 659 { 660 record_event (perf_log, event_time, statistic->event, 661 (const guchar *)&statistic->current_value.i, 662 sizeof (gint32)); 663 statistic->last_value.i = statistic->current_value.i; 664 statistic->recorded = TRUE; 665 } 666 break; 667 case 'x': 668 if (!statistic->recorded || 669 statistic->current_value.x != statistic->last_value.x) 670 { 671 record_event (perf_log, event_time, statistic->event, 672 (const guchar *)&statistic->current_value.x, 673 sizeof (gint64)); 674 statistic->last_value.x = statistic->current_value.x; 675 statistic->recorded = TRUE; 676 } 677 break; 678 } 679 } 680 681 record_event (perf_log, event_time, 682 g_ptr_array_index (perf_log->events, EVENT_STATISTICS_COLLECTED), 683 (const guchar *)&collection_time, sizeof (gint64)); 684 } 685 686 /** 687 * shell_perf_log_replay: 688 * @perf_log: a #ShellPerfLog 689 * @replay_function: (scope call): function to call for each event in the log 690 * @user_data: data to pass to @replay_function 691 * 692 * Replays the log by calling the given function for each event 693 * in the log. 694 */ 695 void 696 shell_perf_log_replay (ShellPerfLog *perf_log, 697 ShellPerfReplayFunction replay_function, 698 gpointer user_data) 699 { 700 gint64 event_time = perf_log->start_time; 701 GList *iter; 702 703 for (iter = perf_log->blocks->head; iter; iter = iter->next) 704 { 705 ShellPerfBlock *block = iter->data; 706 guint32 pos = 0; 707 708 while (pos < block->bytes) 709 { 710 ShellPerfEvent *event; 711 guint16 id; 712 guint32 time_delta; 713 GValue arg = { 0, }; 714 715 memcpy (&time_delta, block->buffer + pos, sizeof (guint32)); 716 pos += sizeof (guint32); 717 memcpy (&id, block->buffer + pos, sizeof (guint16)); 718 pos += sizeof (guint16); 719 720 if (id == EVENT_SET_TIME) 721 { 722 /* Internal, we don't include in the replay */ 723 memcpy (&event_time, block->buffer + pos, sizeof (gint64)); 724 pos += sizeof (gint64); 725 continue; 726 } 727 else 728 { 729 event_time += time_delta; 730 } 731 732 event = g_ptr_array_index (perf_log->events, id); 733 734 if (strcmp (event->signature, "") == 0) 735 { 736 /* We need to pass something, so pass an empty string */ 737 g_value_init (&arg, G_TYPE_STRING); 738 } 739 else if (strcmp (event->signature, "i") == 0) 740 { 741 gint32 l; 742 743 memcpy (&l, block->buffer + pos, sizeof (gint32)); 744 pos += sizeof (gint32); 745 746 g_value_init (&arg, G_TYPE_INT); 747 g_value_set_int (&arg, l); 748 } 749 else if (strcmp (event->signature, "x") == 0) 750 { 751 gint64 l; 752 753 memcpy (&l, block->buffer + pos, sizeof (gint64)); 754 pos += sizeof (gint64); 755 756 g_value_init (&arg, G_TYPE_INT64); 757 g_value_set_int64 (&arg, l); 758 } 759 else if (strcmp (event->signature, "s") == 0) 760 { 761 g_value_init (&arg, G_TYPE_STRING); 762 g_value_set_string (&arg, (char *)block->buffer + pos); 763 pos += strlen ((char *)(block->buffer + pos)) + 1; 764 } 765 766 replay_function (event_time, event->name, event->signature, &arg, user_data); 767 g_value_unset (&arg); 768 } 769 } 770 } 771 772 static char * 773 escape_quotes (const char *input) 774 { 775 GString *result; 776 const char *p; 777 778 if (strchr (input, '"') == NULL) 779 return (char *)input; 780 781 result = g_string_new (NULL); 782 for (p = input; *p; p++) 783 { 784 if (*p == '"') 785 g_string_append (result, "\\\""); 786 else 787 g_string_append_c (result, *p); 788 } 789 790 return g_string_free (result, FALSE); 791 } 792 793 static gboolean 794 write_string (GOutputStream *out, 795 const char *str, 796 GError **error) 797 { 798 return g_output_stream_write_all (out, str, strlen (str), 799 NULL, NULL, 800 error); 801 } 802 803 /** 804 * shell_perf_log_dump_events: 805 * @perf_log: a #ShellPerfLog 806 * @out: output stream into which to write the event definitions 807 * @error: location to store #GError, or %NULL 808 * 809 * Dump the definition of currently defined events and statistics, formatted 810 * as JSON, to the specified output stream. The JSON output is an array, 811 * with each element being a dictionary of the form: 812 * 813 * { name: <name of event>, 814 * description: <descrition of string, 815 * statistic: true } (only for statistics) 816 * 817 * Return value: %TRUE if the dump succeeded. %FALSE if an IO error occurred 818 */ 819 gboolean 820 shell_perf_log_dump_events (ShellPerfLog *perf_log, 821 GOutputStream *out, 822 GError **error) 823 { 824 GString *output; 825 int i; 826 827 output = g_string_new (NULL); 828 g_string_append (output, "[ "); 829 830 for (i = 0; i < perf_log->events->len; i++) 831 { 832 ShellPerfEvent *event = g_ptr_array_index (perf_log->events, i); 833 char *escaped_description = escape_quotes (event->description); 834 gboolean is_statistic = g_hash_table_lookup (perf_log->statistics_by_name, event->name) != NULL; 835 836 if (i != 0) 837 g_string_append (output, ",\n "); 838 839 g_string_append_printf (output, 840 "{ \"name\": \"%s\",\n" 841 " \"description\": \"%s\"", 842 event->name, escaped_description); 843 if (is_statistic) 844 g_string_append (output, ",\n \"statistic\": true"); 845 846 g_string_append (output, " }"); 847 848 if (escaped_description != event->description) 849 g_free (escaped_description); 850 } 851 852 g_string_append (output, " ]"); 853 854 return write_string (out, g_string_free (output, FALSE), error); 855 } 856 857 typedef struct { 858 GOutputStream *out; 859 GError *error; 860 gboolean first; 861 } ReplayToJsonClosure; 862 863 static void 864 replay_to_json (gint64 time, 865 const char *name, 866 const char *signature, 867 GValue *arg, 868 gpointer user_data) 869 { 870 ReplayToJsonClosure *closure = user_data; 871 char *event_str; 872 873 if (closure->error != NULL) 874 return; 875 876 if (!closure->first) 877 { 878 if (!write_string (closure->out, ",\n ", &closure->error)) 879 return; 880 } 881 882 closure->first = FALSE; 883 884 if (strcmp (signature, "") == 0) 885 { 886 event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\"]", time, name); 887 } 888 else if (strcmp (signature, "i") == 0) 889 { 890 event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\", %i]", 891 time, 892 name, 893 g_value_get_int (arg)); 894 } 895 else if (strcmp (signature, "x") == 0) 896 { 897 event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\", %"G_GINT64_FORMAT "]", 898 time, 899 name, 900 g_value_get_int64 (arg)); 901 } 902 else if (strcmp (signature, "s") == 0) 903 { 904 const char *arg_str = g_value_get_string (arg); 905 char *escaped = escape_quotes (arg_str); 906 907 event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\", \"%s\"]", 908 time, 909 name, 910 g_value_get_string (arg)); 911 912 if (escaped != arg_str) 913 g_free (escaped); 914 } 915 else 916 { 917 g_assert_not_reached (); 918 } 919 920 if (!write_string (closure->out, event_str, &closure->error)) 921 return; 922 } 923 924 /** 925 * shell_perf_log_dump_log: 926 * @perf_log: a #ShellPerfLog 927 * @out: output stream into which to write the event log 928 * @error: location to store #GError, or %NULL 929 * 930 * Writes the performance event log, formatted as JSON, to the specified 931 * output stream. For performance reasons, the output stream passed 932 * in should generally be a buffered (or memory) output stream, since 933 * it will be written to in small pieces. The JSON output is an array 934 * with the elements of the array also being arrays, of the form 935 * '[' <time>, <event name> [, <event_arg>... ] ']'. 936 * 937 * Return value: %TRUE if the dump succeeded. %FALSE if an IO error occurred 938 */ 939 gboolean 940 shell_perf_log_dump_log (ShellPerfLog *perf_log, 941 GOutputStream *out, 942 GError **error) 943 { 944 ReplayToJsonClosure closure; 945 946 closure.out = out; 947 closure.error = NULL; 948 closure.first = TRUE; 949 950 if (!write_string (out, "[ ", &closure.error)) 951 return FALSE; 952 953 shell_perf_log_replay (perf_log, replay_to_json, &closure); 954 955 if (closure.error != NULL) 956 { 957 g_propagate_error (error, closure.error); 958 return FALSE; 959 } 960 961 if (!write_string (out, " ]", &closure.error)) 962 return FALSE; 963 964 return TRUE; 965 }