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));
(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 }