summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/catapult/tracing/tracing/extras
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/catapult/tracing/tracing/extras')
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/__init__.py3
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/ads/domain_category.html87
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/ads/domain_category_test.html39
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/android/android_auditor.html767
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/android/android_auditor_test.html424
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/blame_context.html75
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/blame_context_test.html64
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/frame_blame_context_test.html44
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/frame_tree_node.html70
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/render_frame.html82
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/top_level.html59
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/blink/blink_scheduler_async_slice.html48
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/blink/blink_scheduler_async_slice_test.html54
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/cc.html12
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/constants.html27
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/debug_colors.html77
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/display_item_list.html54
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/display_item_list_test.html46
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/input_latency_async_slice.html645
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/input_latency_async_slice_test.html702
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_impl.html228
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_host_impl.html198
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_host_impl_test.html30
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js345
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_impl.html178
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/picture.html451
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/picture_as_image_data.html70
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/picture_test.html81
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/raster_task.html66
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/region.html61
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/render_pass.html46
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/tile.html91
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/tile_coverage_rect.html28
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/tile_test.html30
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/util.html129
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/util_test.html114
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor.html84
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor_test.html129
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_processes.html69
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_test_utils.html161
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver.html261
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver_test.html84
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time.html164
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time_multidimensional_view.md79
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time_test.html1503
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time_test_utils.html126
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/estimated_input_latency.html367
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/estimated_input_latency_test.html384
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/event_finder_utils.html234
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/event_finder_utils_test.html256
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/gpu_async_slice.html44
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/gpu_async_slice_test.html73
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/state.html53
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/state_test.html31
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/state_test_data.js22
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/layout_object.html207
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/layout_object_test.html77
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/layout_tree.html47
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/slice_title_fixer.html38
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/slice_title_fixer_test.html28
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/time_to_interactive.html397
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome/time_to_interactive_test.html329
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/chrome_config.html37
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/cpu/cpu_usage_auditor.html117
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/full_config.html12
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/android/atrace_process_dump_importer.html216
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/android/atrace_process_dump_importer_test.html87
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/android/event_log_importer.html324
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/android/process_data_importer.html178
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/android/process_data_importer_test.html140
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/battor_importer.html186
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/battor_importer_test.html103
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/ddms_importer.html223
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/ddms_importer_test.html188
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/etw/etw_importer.html483
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/etw/etw_importer_test.html293
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/etw/eventtrace_parser.html111
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/etw/eventtrace_parser_test.html119
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/etw/parser.html73
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/etw/process_parser.html190
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/etw/process_parser_test.html149
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/etw/thread_parser.html256
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/etw/thread_parser_test.html176
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/fuchsia_importer.html234
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/fuchsia_importer_test.html183
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/gcloud_trace/gcloud_trace_importer.html99
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/gcloud_trace/gcloud_trace_importer_test.html101
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/gzip_importer.html252
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/gzip_importer_test.html108
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/heap_dump_trace_event_importer.html91
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/jszip.html35
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/legacy_heap_dump_trace_event_importer.html214
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/android_parser.html239
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/android_parser_test.html233
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/binder_parser.html732
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/binder_parser_test.html273
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/bus_parser.html97
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/bus_parser_test.html68
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/clock_parser.html112
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/clock_parser_test.html110
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/cpufreq_parser.html114
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/cpufreq_parser_test.html173
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/disk_parser.html302
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/disk_parser_test.html119
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/drm_parser.html69
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/drm_parser_test.html34
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/exynos_parser.html125
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/exynos_parser_test.html134
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/fence_parser.html163
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/fence_parser_test.html32
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ftrace_importer.html941
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ftrace_importer_test.html686
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/gesture_parser.html143
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/gesture_parser_test.html213
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i2c_parser.html177
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i2c_parser_test.html82
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i915_parser.html363
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i915_parser_test.html105
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ion_heap_parser.html109
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ion_heap_parser_test.html39
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/irq_parser.html271
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/irq_parser_test.html67
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/kfunc_parser.html111
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/kfunc_parser_test.html60
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/mali_parser.html659
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/mali_parser_test.html559
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/memreclaim_parser.html181
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/memreclaim_parser_test.html170
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/parser.html90
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/power_parser.html179
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/power_parser_test.html97
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/regulator_parser.html139
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/regulator_parser_test.html45
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/rss_parser.html87
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/rss_parser_test.html35
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sched_parser.html161
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sched_parser_test.html262
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sync_parser.html121
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sync_parser_test.html55
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/workqueue_parser.html100
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/workqueue_parser_test.html38
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/oboe.html32
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/pako.html9
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/profiling_dictionary_reader.html315
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/profiling_dictionary_reader_test.html275
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/trace2html_importer.html94
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/trace2html_importer_test.html73
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/trace_code_entry.html128
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/trace_code_entry_test.html123
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/trace_code_map.html124
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer.html3394
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer_perf_test.html103
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer_test.html6593
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/v8/codemap.html383
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/v8/log_reader.html213
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/v8/splaytree.html295
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/v8/v8_log_importer.html393
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/v8/v8_log_importer_test.html290
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/importer/zip_importer.html72
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/lean_config.html14
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/measure/measure.html8
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/measure/measure_async_slice.html55
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/measure/measure_async_slice_test.html63
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/memory/lowmemory_auditor.html58
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/net/net.html8
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/net/net_async_slice.html103
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/net/net_async_slice_test.html195
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/symbolizer/OWNERS2
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/symbolizer/__init__.py3
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/addr2line-pdb.exe.sha11
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/linux_trace_v2_presymbolization.json.gz.sha11
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/mac_trace_v2_presymbolization.json.gz.sha11
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/windows_trace_v1_presymbolization.json.gz.sha11
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/windows_trace_v2_presymbolization.json.gz.sha11
-rwxr-xr-xchromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace.py1707
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_atos_regex.py36
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_atos_regex_unittest.py49
-rwxr-xr-xchromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_end_to_end_test_slow.py195
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader.py26
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader_unittest.py26
-rwxr-xr-xchromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_unittest.py69
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/system_stats/system_stats_snapshot.html54
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/systrace_config.html19
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/tquery/context.html39
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/tquery/filter.html51
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_all_of.html61
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_any_of.html66
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_has_ancestor.html53
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_has_duration.html48
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_has_title.html41
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_is_top_level.html48
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_not.html51
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/tquery/tquery.html188
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/tquery/tquery_test.html247
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/v8/ic_stats_entry.html209
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/v8/ic_stats_entry_test.html112
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/v8/runtime_stats_entry.html221
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/v8/runtime_stats_entry_test.html322
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/v8/v8_cpu_profile_node.html115
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/v8/v8_gc_stats_thread_slice.html49
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/v8/v8_ic_stats_thread_slice.html46
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/v8/v8_thread_slice.html46
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/v8_config.html11
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/vsync/vsync_auditor.html143
-rw-r--r--chromium/third_party/catapult/tracing/tracing/extras/vsync/vsync_auditor_test.html140
205 files changed, 43135 insertions, 0 deletions
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/__init__.py b/chromium/third_party/catapult/tracing/tracing/extras/__init__.py
new file mode 100644
index 00000000000..a22a6ee39a9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/ads/domain_category.html b/chromium/third_party/catapult/tracing/tracing/extras/ads/domain_category.html
new file mode 100644
index 00000000000..bb153e81a05
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/ads/domain_category.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.ads', function() {
+ function Rule(expr, category) {
+ this.expr = expr;
+ this.category = category;
+ }
+
+ const CATEGORY_ADS = 'Ads';
+ const CATEGORY_ANALYTICS = 'Analytics';
+ const CATEGORY_CONTENT_RECOMMENDATION = 'Content Recommendation';
+ const CATEGORY_EXTENSIONS = 'Extensions';
+ const CATEGORY_OTHER = 'Other';
+ const CATEGORY_SOCIAL = 'Social';
+ const CATEGORY_TRACING = 'Tracing';
+
+ // Each rule is a tuple of (RegEx, Category). The first match will be used.
+ // For now we only assign 1 category to a domain; it may make sense to
+ // generalize this later to labeling domains with several categories.
+ const CATEGORY_DOMAIN_RULES = [
+ new Rule(/\.googleadservices\.com$/, CATEGORY_ADS),
+ new Rule(/\.moatads\.com$/, CATEGORY_ANALYTICS),
+ // Yahoo-Bing Contextual Ads
+ new Rule(/contextual\.media\.net$/, CATEGORY_ADS),
+ new Rule(/\.postrelease\.com$/, CATEGORY_CONTENT_RECOMMENDATION),
+ new Rule(/\.burt\.io$/, CATEGORY_ANALYTICS),
+ // Google DoubleClick, GPT
+ new Rule(/\.googletagservices\.com$/, CATEGORY_ADS),
+ new Rule(/\.ensighten\.com$/, CATEGORY_ANALYTICS),
+ // Amazon CloudFront
+ new Rule(/\.cloudfront\.net$/, CATEGORY_ADS),
+ new Rule(/\.viafoura\.net$/, CATEGORY_ANALYTICS),
+ // Google Content Ads/DoubleClick
+ new Rule(/\.googlesyndication\.com$/, CATEGORY_ADS),
+ // Google DoubleClick
+ new Rule(/\.doubleclick\.net$/, CATEGORY_ADS),
+ // Adobe Analytics/TagManager
+ new Rule(/\.adobetag\.com$/, CATEGORY_ANALYTICS),
+ new Rule(/\.rubiconproject\.com$/, CATEGORY_ADS),
+ new Rule(/\.sharethrough\.com$/, CATEGORY_ADS),
+ new Rule(/\.facebook\.net$/, CATEGORY_SOCIAL),
+ new Rule(/\.facebook\.com$/, CATEGORY_SOCIAL),
+ new Rule(/\.outbrain\.com$/, CATEGORY_CONTENT_RECOMMENDATION),
+ // Google Content Ads/Others
+ new Rule(/\.gstatic\.com$/, CATEGORY_ADS),
+ new Rule(/\.newrelic\.com$/, CATEGORY_ANALYTICS),
+ new Rule(/\.newsinc\.com$/, CATEGORY_ADS),
+ new Rule(/\.spingo\.com$/, CATEGORY_CONTENT_RECOMMENDATION),
+ new Rule(/\.visualrevenue\.com$/, CATEGORY_ANALYTICS),
+ new Rule(/cdn\.livefyre\.com$/, CATEGORY_SOCIAL),
+ new Rule(/\.dynamicyield\.com$/, CATEGORY_ANALYTICS),
+ new Rule(/cdn\.krxd\.net$/, CATEGORY_ANALYTICS),
+ new Rule(/\.chartbeat\.com$/, CATEGORY_ANALYTICS),
+ new Rule(/\.clicktale\.net$/, CATEGORY_ANALYTICS),
+ new Rule(/\.gravity\.com$/, CATEGORY_ANALYTICS),
+ new Rule(/\.brightcove\.com$/, CATEGORY_ANALYTICS),
+ new Rule(/^chrome:\/\//, CATEGORY_TRACING),
+ new Rule(/^chrome-extension:\/\//, CATEGORY_EXTENSIONS)
+ ];
+
+ function DomainCategory() {
+ }
+
+ DomainCategory.fromDomain = function(domain) {
+ for (let i = 0; i < CATEGORY_DOMAIN_RULES.length; i++) {
+ if (CATEGORY_DOMAIN_RULES[i].expr.test(domain)) {
+ return CATEGORY_DOMAIN_RULES[i].category;
+ }
+ }
+ return CATEGORY_OTHER;
+ };
+
+ return {
+ DomainCategory
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/ads/domain_category_test.html b/chromium/third_party/catapult/tracing/tracing/extras/ads/domain_category_test.html
new file mode 100644
index 00000000000..531726071dd
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/ads/domain_category_test.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/ads/domain_category.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const DomainCategory = tr.e.ads.DomainCategory;
+ test('domainCategory', function() {
+ assert.strictEqual(
+ 'Ads',
+ DomainCategory.fromDomain('https://parnter.googleadservices.com'));
+ assert.strictEqual(
+ 'Analytics',
+ DomainCategory.fromDomain('https://x.adobetag.com'));
+ assert.strictEqual(
+ 'Social',
+ DomainCategory.fromDomain('http://x.facebook.net'));
+ assert.strictEqual(
+ 'Content Recommendation',
+ DomainCategory.fromDomain('http://xyz.outbrain.com'));
+ assert.strictEqual(
+ 'Tracing', DomainCategory.fromDomain('chrome://tracing'));
+ assert.strictEqual(
+ 'Extensions',
+ DomainCategory.fromDomain('chrome-extension://oiuy325oiu'));
+ assert.strictEqual(
+ 'Other',
+ DomainCategory.fromDomain('http://xyz.outbrain.com.au'));
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/android/android_auditor.html b/chromium/third_party/catapult/tracing/tracing/extras/android/android_auditor.html
new file mode 100644
index 00000000000..a1638d24baa
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/android/android_auditor.html
@@ -0,0 +1,767 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/math/range_utils.html">
+<link rel="import" href="/tracing/base/math/statistics.html">
+<link rel="import" href="/tracing/base/scalar.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/core/auditor.html">
+<link rel="import" href="/tracing/model/alert.html">
+<link rel="import" href="/tracing/model/frame.html">
+<link rel="import" href="/tracing/model/helpers/android_model_helper.html">
+<link rel="import" href="/tracing/model/thread_time_slice.html">
+<link rel="import" href="/tracing/model/user_model/response_expectation.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Class for Android-specific Auditing.
+ */
+tr.exportTo('tr.e.audits', function() {
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const Auditor = tr.c.Auditor;
+ const AndroidModelHelper = tr.model.helpers.AndroidModelHelper;
+ const ColorScheme = tr.b.ColorScheme;
+ const Statistics = tr.b.math.Statistics;
+ const FRAME_PERF_CLASS = tr.model.FRAME_PERF_CLASS;
+ const Alert = tr.model.Alert;
+ const EventInfo = tr.model.EventInfo;
+ const Scalar = tr.b.Scalar;
+ const timeDurationInMs = tr.b.Unit.byName.timeDurationInMs;
+
+ // TODO: extract from VSYNC, since not all devices have vsync near 60fps
+ const EXPECTED_FRAME_TIME_MS = 16.67;
+
+ function getStart(e) { return e.start; }
+ function getDuration(e) { return e.duration; }
+ // used for general UI thread responsiveness alerts, falls back to duration
+ function getCpuDuration(e) {
+ return (e.cpuDuration !== undefined) ? e.cpuDuration : e.duration;
+ }
+
+ function frameIsActivityStart(frame) {
+ return frame.associatedEvents.any(x => x.title === 'activityStart');
+ }
+
+ function frameMissedDeadline(frame) {
+ return frame.args.deadline && frame.args.deadline < frame.end;
+ }
+
+ /** Builder object for EventInfo docLink structures */
+ function DocLinkBuilder() {
+ this.docLinks = [];
+ }
+ DocLinkBuilder.prototype = {
+ addAppVideo(name, videoId) {
+ this.docLinks.push({
+ label: 'Video Link',
+ textContent: ('Android Performance Patterns: ' + name),
+ href: 'https://www.youtube.com/watch?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE&v=' + videoId // @suppress longLineCheck
+ });
+ return this;
+ },
+ addDacRef(name, link) {
+ this.docLinks.push({
+ label: 'Doc Link',
+ textContent: (name + ' documentation'),
+ href: 'https://developer.android.com/reference/' + link
+ });
+ return this;
+ },
+ build() {
+ return this.docLinks;
+ }
+ };
+
+ /**
+ * Auditor for Android-specific traces.
+ * @constructor
+ */
+ function AndroidAuditor(model) {
+ Auditor.call(this, model);
+
+ const helper = model.getOrCreateHelper(AndroidModelHelper);
+ if (helper.apps.length || helper.surfaceFlinger) {
+ this.helper = helper;
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Rendering / RenderThread alerts - only available on SDK 22+
+ //////////////////////////////////////////////////////////////////////////////
+
+ AndroidAuditor.viewAlphaAlertInfo_ = new EventInfo(
+ 'Inefficient View alpha usage',
+ 'Setting an alpha between 0 and 1 has significant performance costs, if one of the fast alpha paths is not used.', // @suppress longLineCheck
+ new DocLinkBuilder()
+ .addAppVideo('Hidden Cost of Transparency', 'wIy8g8yNhNk')
+ .addDacRef('View#setAlpha()', 'android/view/View.html#setAlpha(float)') // @suppress longLineCheck
+ .build());
+ AndroidAuditor.saveLayerAlertInfo_ = new EventInfo(
+ 'Expensive rendering with Canvas#saveLayer()',
+ 'Canvas#saveLayer() incurs extremely high rendering cost. They disrupt the rendering pipeline when drawn, forcing a flush of drawing content. Instead use View hardware layers, or static Bitmaps. This enables the offscreen buffers to be reused in between frames, and avoids the disruptive render target switch.', // @suppress longLineCheck
+ new DocLinkBuilder()
+ .addAppVideo('Hidden Cost of Transparency', 'wIy8g8yNhNk')
+ .addDacRef('Canvas#saveLayerAlpha()', 'android/graphics/Canvas.html#saveLayerAlpha(android.graphics.RectF, int, int)') // @suppress longLineCheck
+ .build());
+ AndroidAuditor.getSaveLayerAlerts_ = function(frame) {
+ const badAlphaRegEx =
+ /^(.+) alpha caused (unclipped )?saveLayer (\d+)x(\d+)$/;
+ const saveLayerRegEx = /^(unclipped )?saveLayer (\d+)x(\d+)$/;
+
+ const ret = [];
+ const events = [];
+
+ frame.associatedEvents.forEach(function(slice) {
+ const match = badAlphaRegEx.exec(slice.title);
+ if (match) {
+ // due to bug in tracing code on SDK 22, ignore
+ // presence of 'unclipped' string in View alpha slices
+ const args = { 'view name': match[1],
+ 'width': parseInt(match[3]),
+ 'height': parseInt(match[4]) };
+ ret.push(new Alert(AndroidAuditor.viewAlphaAlertInfo_,
+ slice.start, [slice], args));
+ } else if (saveLayerRegEx.test(slice.title)) {
+ events.push(slice);
+ }
+ }, this);
+
+ if (events.length > ret.length) {
+ // more saveLayers than bad alpha can account for - add another alert
+
+ const unclippedSeen = Statistics.sum(events, function(slice) {
+ return saveLayerRegEx.exec(slice.title)[1] ? 1 : 0;
+ });
+ const clippedSeen = events.length - unclippedSeen;
+ const earliestStart = Statistics.min(events, function(slice) {
+ return slice.start;
+ });
+
+ const args = {
+ 'Unclipped saveLayer count (especially bad!)': unclippedSeen,
+ 'Clipped saveLayer count': clippedSeen
+ };
+
+ events.push(frame);
+ ret.push(new Alert(AndroidAuditor.saveLayerAlertInfo_,
+ earliestStart, events, args));
+ }
+
+ return ret;
+ };
+
+
+ AndroidAuditor.pathAlertInfo_ = new EventInfo(
+ 'Path texture churn',
+ 'Paths are drawn with a mask texture, so when a path is modified / newly drawn, that texture must be generated and uploaded to the GPU. Ensure that you cache paths between frames and do not unnecessarily call Path#reset(). You can cut down on this cost by sharing Path object instances between drawables/views.'); // @suppress longLineCheck
+ AndroidAuditor.getPathAlert_ = function(frame) {
+ const uploadRegEx = /^Generate Path Texture$/;
+
+ const events = frame.associatedEvents.filter(function(event) {
+ return event.title === 'Generate Path Texture';
+ });
+ const start = Statistics.min(events, getStart);
+ const duration = Statistics.sum(events, getDuration);
+
+ if (duration < 3) return undefined;
+
+ events.push(frame);
+ return new Alert(AndroidAuditor.pathAlertInfo_, start, events,
+ { 'Time spent': new Scalar(timeDurationInMs, duration) });
+ };
+
+
+ AndroidAuditor.uploadAlertInfo_ = new EventInfo(
+ 'Expensive Bitmap uploads',
+ 'Bitmaps that have been modified / newly drawn must be uploaded to the GPU. Since this is expensive if the total number of pixels uploaded is large, reduce the amount of Bitmap churn in this animation/context, per frame.'); // @suppress longLineCheck
+ AndroidAuditor.getUploadAlert_ = function(frame) {
+ const uploadRegEx = /^Upload (\d+)x(\d+) Texture$/;
+
+ const events = [];
+ let start = Number.POSITIVE_INFINITY;
+ let duration = 0;
+ let pixelsUploaded = 0;
+ frame.associatedEvents.forEach(function(event) {
+ const match = uploadRegEx.exec(event.title);
+ if (match) {
+ events.push(event);
+ start = Math.min(start, event.start);
+ duration += event.duration;
+ pixelsUploaded += parseInt(match[1]) * parseInt(match[2]);
+ }
+ });
+ if (events.length === 0 || duration < 3) return undefined;
+
+ const mPixels = (pixelsUploaded / 1000000).toFixed(2) + ' million';
+ const args = { 'Pixels uploaded': mPixels,
+ 'Time spent': new Scalar(timeDurationInMs, duration) };
+ events.push(frame);
+ return new Alert(AndroidAuditor.uploadAlertInfo_, start, events, args);
+ };
+
+ //////////////////////////////////////////////////////////////////////////////
+ // UI responsiveness alerts
+ //////////////////////////////////////////////////////////////////////////////
+
+ AndroidAuditor.ListViewInflateAlertInfo_ = new EventInfo(
+ 'Inflation during ListView recycling',
+ 'ListView item recycling involved inflating views. Ensure your Adapter#getView() recycles the incoming View, instead of constructing a new one.'); // @suppress longLineCheck
+ AndroidAuditor.ListViewBindAlertInfo_ = new EventInfo(
+ 'Inefficient ListView recycling/rebinding',
+ 'ListView recycling taking too much time per frame. Ensure your Adapter#getView() binds data efficiently.'); // @suppress longLineCheck
+ AndroidAuditor.getListViewAlert_ = function(frame) {
+ const events = frame.associatedEvents.filter(function(event) {
+ return event.title === 'obtainView' || event.title === 'setupListItem';
+ });
+ const duration = Statistics.sum(events, getCpuDuration);
+
+ if (events.length === 0 || duration < 3) return undefined;
+
+ // simplifying assumption - check for *any* inflation.
+ // TODO(ccraik): make 'inflate' slices associated events.
+ let hasInflation = false;
+ for (const event of events) {
+ if (event.findDescendentSlice('inflate')) {
+ hasInflation = true;
+ }
+ }
+
+ const start = Statistics.min(events, getStart);
+ const args = { 'Time spent': new Scalar(timeDurationInMs, duration) };
+ args['ListView items ' + (hasInflation ? 'inflated' : 'rebound')] =
+ events.length / 2;
+ const eventInfo = hasInflation ? AndroidAuditor.ListViewInflateAlertInfo_ :
+ AndroidAuditor.ListViewBindAlertInfo_;
+ events.push(frame);
+ return new Alert(eventInfo, start, events, args);
+ };
+
+
+ AndroidAuditor.measureLayoutAlertInfo_ = new EventInfo(
+ 'Expensive measure/layout pass',
+ 'Measure/Layout took a significant time, contributing to jank. Avoid triggering layout during animations.', // @suppress longLineCheck
+ new DocLinkBuilder()
+ .addAppVideo('Invalidations, Layouts, and Performance', 'we6poP0kw6E')
+ .build());
+ AndroidAuditor.getMeasureLayoutAlert_ = function(frame) {
+ const events = frame.associatedEvents.filter(function(event) {
+ return event.title === 'measure' || event.title === 'layout';
+ });
+ const duration = Statistics.sum(events, getCpuDuration);
+
+ if (events.length === 0 || duration < 3) return undefined;
+
+ const start = Statistics.min(events, getStart);
+ events.push(frame);
+ return new Alert(AndroidAuditor.measureLayoutAlertInfo_, start, events,
+ { 'Time spent': new Scalar(timeDurationInMs, duration) });
+ };
+
+
+ AndroidAuditor.viewDrawAlertInfo_ = new EventInfo(
+ 'Long View#draw()',
+ 'Recording the drawing commands of invalidated Views took a long time. Avoid significant work in View or Drawable custom drawing, especially allocations or drawing to Bitmaps.', // @suppress longLineCheck
+ new DocLinkBuilder()
+ .addAppVideo('Invalidations, Layouts, and Performance', 'we6poP0kw6E')
+ .addAppVideo('Avoiding Allocations in onDraw()', 'HAK5acHQ53E')
+ .build());
+ AndroidAuditor.getViewDrawAlert_ = function(frame) {
+ let slice = undefined;
+ for (const event of frame.associatedEvents) {
+ if (event.title === 'getDisplayList' ||
+ event.title === 'Record View#draw()') {
+ slice = event;
+ break;
+ }
+ }
+
+ if (!slice || getCpuDuration(slice) < 3) return undefined;
+ return new Alert(AndroidAuditor.viewDrawAlertInfo_, slice.start,
+ [slice, frame],
+ { 'Time spent': new Scalar(
+ timeDurationInMs, getCpuDuration(slice)) });
+ };
+
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Runtime alerts
+ //////////////////////////////////////////////////////////////////////////////
+
+ AndroidAuditor.blockingGcAlertInfo_ = new EventInfo(
+ 'Blocking Garbage Collection',
+ 'Blocking GCs are caused by object churn, and made worse by having large numbers of objects in the heap. Avoid allocating objects during animations/scrolling, and recycle Bitmaps to avoid triggering garbage collection.', // @suppress longLineCheck
+ new DocLinkBuilder()
+ .addAppVideo('Garbage Collection in Android', 'pzfzz50W5Uo')
+ .addAppVideo('Avoiding Allocations in onDraw()', 'HAK5acHQ53E')
+ .build());
+ AndroidAuditor.getBlockingGcAlert_ = function(frame) {
+ const events = frame.associatedEvents.filter(function(event) {
+ return event.title === 'DVM Suspend' ||
+ event.title === 'GC: Wait For Concurrent';
+ });
+ const blockedDuration = Statistics.sum(events, getDuration);
+ if (blockedDuration < 3) return undefined;
+
+ const start = Statistics.min(events, getStart);
+ events.push(frame);
+ return new Alert(AndroidAuditor.blockingGcAlertInfo_, start, events,
+ { 'Blocked duration': new Scalar(
+ timeDurationInMs, blockedDuration) });
+ };
+
+
+ AndroidAuditor.lockContentionAlertInfo_ = new EventInfo(
+ 'Lock contention',
+ 'UI thread lock contention is caused when another thread holds a lock that the UI thread is trying to use. UI thread progress is blocked until the lock is released. Inspect locking done within the UI thread, and ensure critical sections are short.'); // @suppress longLineCheck
+ AndroidAuditor.getLockContentionAlert_ = function(frame) {
+ const events = frame.associatedEvents.filter(function(event) {
+ return /^Lock Contention on /.test(event.title);
+ });
+
+ const blockedDuration = Statistics.sum(events, getDuration);
+ if (blockedDuration < 1) return undefined;
+
+ const start = Statistics.min(events, getStart);
+ events.push(frame);
+ return new Alert(AndroidAuditor.lockContentionAlertInfo_, start, events,
+ { 'Blocked duration': new Scalar(
+ timeDurationInMs, blockedDuration) });
+ };
+
+ AndroidAuditor.schedulingAlertInfo_ = new EventInfo(
+ 'Scheduling delay',
+ 'Work to produce this frame was descheduled for several milliseconds, contributing to jank. Ensure that code on the UI thread doesn\'t block on work being done on other threads, and that background threads (doing e.g. network or bitmap loading) are running at android.os.Process#THREAD_PRIORITY_BACKGROUND or lower so they are less likely to interrupt the UI thread. These background threads should show up with a priority number of 130 or higher in the scheduling section under the Kernel process.'); // @suppress longLineCheck
+ AndroidAuditor.getSchedulingAlert_ = function(frame) {
+ let totalDuration = 0;
+ const totalStats = {};
+ for (const ttr of frame.threadTimeRanges) {
+ const stats = ttr.thread.getSchedulingStatsForRange(ttr.start, ttr.end);
+ for (const [key, value] of Object.entries(stats)) {
+ if (!(key in totalStats)) {
+ totalStats[key] = 0;
+ }
+ totalStats[key] += value;
+ totalDuration += value;
+ }
+ }
+
+ // only alert if frame not running for > 3ms. Note that we expect a frame
+ // to never describe intentionally idle time.
+ if (!(SCHEDULING_STATE.RUNNING in totalStats) ||
+ totalDuration === 0 ||
+ totalDuration - totalStats[SCHEDULING_STATE.RUNNING] < 3) {
+ return;
+ }
+
+ const args = {};
+ for (const [key, value] of Object.entries(totalStats)) {
+ let newKey = key;
+ if (key === SCHEDULING_STATE.RUNNABLE) {
+ newKey = 'Not scheduled, but runnable';
+ } else if (key === SCHEDULING_STATE.UNINTR_SLEEP) {
+ newKey = 'Blocking I/O delay';
+ }
+ args[newKey] = new Scalar(timeDurationInMs, value);
+ }
+
+ return new Alert(AndroidAuditor.schedulingAlertInfo_, frame.start, [frame],
+ args);
+ };
+
+ AndroidAuditor.prototype = {
+ __proto__: Auditor.prototype,
+
+ renameAndSort_() {
+ this.model.kernel.important = false;// auto collapse
+ // SurfaceFlinger first, other processes sorted by slice count
+ this.model.getAllProcesses().forEach(function(process) {
+ if (this.helper.surfaceFlinger &&
+ process === this.helper.surfaceFlinger.process) {
+ if (!process.name) {
+ process.name = 'SurfaceFlinger';
+ }
+ process.sortIndex = Number.NEGATIVE_INFINITY;
+ process.important = false; // auto collapse
+ return;
+ }
+
+ const uiThread = process.getThread(process.pid);
+ if (!process.name && uiThread && uiThread.name) {
+ if (/^ndroid\./.test(uiThread.name)) {
+ uiThread.name = 'a' + uiThread.name;
+ }
+ process.name = uiThread.name;
+
+ uiThread.name = 'UI Thread';
+ }
+
+ process.sortIndex = 0;
+ for (const tid in process.threads) {
+ process.sortIndex -= process.threads[tid].sliceGroup.slices.length;
+ }
+ }, this);
+
+ // ensure sequential, relative order for UI/Render/Worker threads
+ this.model.getAllThreads().forEach(function(thread) {
+ if (thread.tid === thread.parent.pid) {
+ thread.sortIndex = -3;
+ }
+ if (thread.name === 'RenderThread') {
+ thread.sortIndex = -2;
+ }
+ if (/^hwuiTask/.test(thread.name)) {
+ thread.sortIndex = -1;
+ }
+ });
+ },
+
+ pushFramesAndJudgeJank_() {
+ let badFramesObserved = 0;
+ let framesObserved = 0;
+ const surfaceFlinger = this.helper.surfaceFlinger;
+
+ this.helper.apps.forEach(function(app) {
+ // override frame list
+ app.process.frames = app.getFrames();
+
+ app.process.frames.forEach(function(frame) {
+ if (frame.totalDuration > EXPECTED_FRAME_TIME_MS * 2) {
+ badFramesObserved += 2;
+ frame.perfClass = FRAME_PERF_CLASS.TERRIBLE;
+ } else if (frame.totalDuration > EXPECTED_FRAME_TIME_MS ||
+ frameMissedDeadline(frame)) {
+ badFramesObserved++;
+ frame.perfClass = FRAME_PERF_CLASS.BAD;
+ } else {
+ frame.perfClass = FRAME_PERF_CLASS.GOOD;
+ }
+ });
+ framesObserved += app.process.frames.length;
+ });
+
+ if (framesObserved) {
+ const portionBad = badFramesObserved / framesObserved;
+ if (portionBad > 0.3) {
+ this.model.faviconHue = 'red';
+ } else if (portionBad > 0.05) {
+ this.model.faviconHue = 'yellow';
+ } else {
+ this.model.faviconHue = 'green';
+ }
+ }
+ },
+
+ pushEventInfo_() {
+ const appAnnotator = new AppAnnotator();
+ this.helper.apps.forEach(function(app) {
+ if (app.uiThread) {
+ appAnnotator.applyEventInfos(app.uiThread.sliceGroup);
+ }
+ if (app.renderThread) {
+ appAnnotator.applyEventInfos(app.renderThread.sliceGroup);
+ }
+ });
+ },
+
+ runAnnotate() {
+ if (!this.helper) return;
+
+ this.renameAndSort_();
+ this.pushFramesAndJudgeJank_();
+ this.pushEventInfo_();
+
+ this.helper.iterateImportantSlices(function(slice) {
+ slice.important = true;
+ });
+ },
+
+ runAudit() {
+ if (!this.helper) return;
+
+ const alerts = this.model.alerts;
+ this.helper.apps.forEach(function(app) {
+ app.getFrames().forEach(function(frame) {
+ alerts.push.apply(alerts, AndroidAuditor.getSaveLayerAlerts_(frame));
+
+ // skip most alerts for neutral or good frames
+ if (frame.perfClass === FRAME_PERF_CLASS.NEUTRAL ||
+ frame.perfClass === FRAME_PERF_CLASS.GOOD) {
+ return;
+ }
+
+ let alert = AndroidAuditor.getPathAlert_(frame);
+ if (alert) alerts.push(alert);
+
+ alert = AndroidAuditor.getUploadAlert_(frame);
+ if (alert) alerts.push(alert);
+
+ alert = AndroidAuditor.getListViewAlert_(frame);
+ if (alert) alerts.push(alert);
+
+ alert = AndroidAuditor.getMeasureLayoutAlert_(frame);
+ if (alert) alerts.push(alert);
+
+ alert = AndroidAuditor.getViewDrawAlert_(frame);
+ if (alert) alerts.push(alert);
+
+ alert = AndroidAuditor.getBlockingGcAlert_(frame);
+ if (alert) alerts.push(alert);
+
+ alert = AndroidAuditor.getLockContentionAlert_(frame);
+ if (alert) alerts.push(alert);
+
+ alert = AndroidAuditor.getSchedulingAlert_(frame);
+ if (alert) alerts.push(alert);
+ });
+ }, this);
+
+ this.addRenderingInteractionRecords();
+ this.addInputInteractionRecords();
+ },
+
+ addRenderingInteractionRecords() {
+ const events = [];
+ this.helper.apps.forEach(function(app) {
+ events.push.apply(events, app.getAnimationAsyncSlices());
+ events.push.apply(events, app.getFrames());
+ });
+
+ const mergerFunction = function(events) {
+ const ir = new tr.model.um.ResponseExpectation(
+ this.model, 'Rendering',
+ events[0].min,
+ events[events.length - 1].max - events[0].min);
+ this.model.userModel.expectations.push(ir);
+ }.bind(this);
+ tr.b.math.mergeRanges(
+ tr.b.math.convertEventsToRanges(events), 30, mergerFunction);
+ },
+
+ addInputInteractionRecords() {
+ const inputSamples = [];
+ this.helper.apps.forEach(function(app) {
+ inputSamples.push.apply(inputSamples, app.getInputSamples());
+ });
+
+ const mergerFunction = function(events) {
+ const ir = new tr.model.um.ResponseExpectation(
+ this.model, 'Input',
+ events[0].min,
+ events[events.length - 1].max - events[0].min);
+ this.model.userModel.expectations.push(ir);
+ }.bind(this);
+ const inputRanges = inputSamples.map(function(sample) {
+ return tr.b.math.Range.fromExplicitRange(
+ sample.timestamp, sample.timestamp);
+ });
+ tr.b.math.mergeRanges(inputRanges, 30, mergerFunction);
+ }
+ };
+
+ Auditor.register(AndroidAuditor);
+
+ function AppAnnotator() {
+ this.titleInfoLookup = new Map();
+ this.titleParentLookup = new Map();
+ this.build_();
+ }
+
+ AppAnnotator.prototype = {
+ build_() {
+ const registerEventInfo = function(dict) {
+ this.titleInfoLookup.set(dict.title, new EventInfo(
+ dict.title, dict.description, dict.docLinks));
+ if (dict.parents) {
+ this.titleParentLookup.set(dict.title, dict.parents);
+ }
+ }.bind(this);
+
+ registerEventInfo({
+ title: 'inflate',
+ description: 'Constructing a View hierarchy from pre-processed XML via LayoutInflater#layout. This includes constructing all of the View objects in the hierarchy, and applying styled attributes.'}); // @suppress longLineCheck
+
+ //////////////////////////////////////////////////////////////////////////
+ // Adapter view
+ //////////////////////////////////////////////////////////////////////////
+ registerEventInfo({
+ title: 'obtainView',
+ description: 'Adapter#getView() called to bind content to a recycled View that is being presented.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'setupListItem',
+ description: 'Attached a newly-bound, recycled View to its parent ListView.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'setupGridItem',
+ description: 'Attached a newly-bound, recycled View to its parent GridView.'}); // @suppress longLineCheck
+
+ //////////////////////////////////////////////////////////////////////////
+ // Choreographer (tracing enabled on M+)
+ //////////////////////////////////////////////////////////////////////////
+ const choreographerLinks = new DocLinkBuilder()
+ .addDacRef('Choreographer', 'android/view/Choreographer.html') // @suppress longLineCheck
+ .build();
+ registerEventInfo({
+ title: 'Choreographer#doFrame',
+ docLinks: choreographerLinks,
+ description: 'Choreographer executes frame callbacks for inputs, animations, and rendering traversals. When this work is done, a frame will be presented to the user.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'input',
+ parents: ['Choreographer#doFrame'],
+ docLinks: choreographerLinks,
+ description: 'Input callbacks are processed. This generally encompasses dispatching input to Views, as well as any work the Views do to process this input/gesture.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'animation',
+ parents: ['Choreographer#doFrame'],
+ docLinks: choreographerLinks,
+ description: 'Animation callbacks are processed. This is generally minimal work, as animations determine progress for the frame, and push new state to animated objects (such as setting View properties).'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'traversals',
+ parents: ['Choreographer#doFrame'],
+ docLinks: choreographerLinks,
+ description: 'Primary draw traversals. This is the primary traversal of the View hierarchy, including layout and draw passes.'}); // @suppress longLineCheck
+
+ //////////////////////////////////////////////////////////////////////////
+ // performTraversals + sub methods
+ //////////////////////////////////////////////////////////////////////////
+ const traversalParents = ['Choreographer#doFrame', 'performTraversals'];
+ const layoutLinks = new DocLinkBuilder()
+ .addDacRef('View#Layout', 'android/view/View.html#Layout')
+ .build();
+ registerEventInfo({
+ title: 'performTraversals',
+ description: 'A drawing traversal of the View hierarchy, comprised of all layout and drawing needed to produce the frame.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'measure',
+ parents: traversalParents,
+ docLinks: layoutLinks,
+ description: 'First of two phases in view hierarchy layout. Views are asked to size themselves according to constraints supplied by their parent. Some ViewGroups may measure a child more than once to help satisfy their own constraints. Nesting ViewGroups that measure children more than once can lead to excessive and repeated work.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'layout',
+ parents: traversalParents,
+ docLinks: layoutLinks,
+ description: 'Second of two phases in view hierarchy layout, repositioning content and child Views into their new locations.'}); // @suppress longLineCheck
+ const drawString = 'Draw pass over the View hierarchy. Every invalidated View will have its drawing commands recorded. On Android versions prior to Lollipop, this would also include the issuing of draw commands to the GPU. Starting with Lollipop, it only includes the recording of commands, and syncing that information to the RenderThread.'; // @suppress longLineCheck
+ registerEventInfo({
+ title: 'draw',
+ parents: traversalParents,
+ description: drawString});
+
+ const recordString = 'Every invalidated View\'s drawing commands are recorded. Each will have View#draw() called, and is passed a Canvas that will record and store its drawing commands until it is next invalidated/rerecorded.'; // @suppress longLineCheck
+ registerEventInfo({
+ title: 'getDisplayList', // Legacy name for compatibility.
+ parents: ['draw'],
+ description: recordString});
+ registerEventInfo({
+ title: 'Record View#draw()',
+ parents: ['draw'],
+ description: recordString});
+
+ registerEventInfo({
+ title: 'drawDisplayList',
+ parents: ['draw'],
+ description: 'Execution of recorded draw commands to generate a frame. This represents the actual formation and issuing of drawing commands to the GPU. On Android L and higher devices, this work is done on a dedicated RenderThread, instead of on the UI Thread.'}); // @suppress longLineCheck
+
+ //////////////////////////////////////////////////////////////////////////
+ // RenderThread
+ //////////////////////////////////////////////////////////////////////////
+ registerEventInfo({
+ title: 'DrawFrame',
+ description: 'RenderThread portion of the standard UI/RenderThread split frame. This represents the actual formation and issuing of drawing commands to the GPU.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'doFrame',
+ description: 'RenderThread animation frame. Represents drawing work done by the RenderThread on a frame where the UI thread did not produce new drawing content.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'syncFrameState',
+ description: 'Sync stage between the UI thread and the RenderThread, where the UI thread hands off a frame (including information about modified Views). Time in this method primarily consists of uploading modified Bitmaps to the GPU. After this sync is completed, the UI thread is unblocked, and the RenderThread starts to render the frame.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'flush drawing commands',
+ description: 'Issuing the now complete drawing commands to the GPU.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'eglSwapBuffers',
+ description: 'Complete GPU rendering of the frame.'}); // @suppress longLineCheck
+
+ //////////////////////////////////////////////////////////////////////////
+ // RecyclerView
+ //////////////////////////////////////////////////////////////////////////
+ registerEventInfo({
+ title: 'RV Scroll',
+ description: 'RecyclerView is calculating a scroll. If there are too many of these in Systrace, some Views inside RecyclerView might be causing it. Try to avoid using EditText, focusable views or handle them with care.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'RV OnLayout',
+ description: 'OnLayout has been called by the View system. If this shows up too many times in Systrace, make sure the children of RecyclerView do not update themselves directly. This will cause a full re-layout but when it happens via the Adapter notifyItemChanged, RecyclerView can avoid full layout calculation.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'RV FullInvalidate',
+ description: 'NotifyDataSetChanged or equal has been called. If this is taking a long time, try sending granular notify adapter changes instead of just calling notifyDataSetChanged or setAdapter / swapAdapter. Adding stable ids to your adapter might help.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'RV PartialInvalidate',
+ description: 'RecyclerView is rebinding a View. If this is taking a lot of time, consider optimizing your layout or make sure you are not doing extra operations in onBindViewHolder call.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'RV OnBindView',
+ description: 'RecyclerView is rebinding a View. If this is taking a lot of time, consider optimizing your layout or make sure you are not doing extra operations in onBindViewHolder call.'}); // @suppress longLineCheck
+ registerEventInfo({
+ title: 'RV CreateView',
+ description: 'RecyclerView is creating a new View. If too many of these are present: 1) There might be a problem in Recycling (e.g. custom Animations that set transient state and prevent recycling or ItemAnimator not implementing the contract properly. See Adapter#onFailedToRecycleView(ViewHolder). 2) There may be too many item view types. Try merging them. 3) There might be too many itemChange animations and not enough space in RecyclerPool. Try increasing your pool size and item cache size.'}); // @suppress longLineCheck
+
+ //////////////////////////////////////////////////////////////////////////
+ // Graphics + Composition
+ //////////////////////////////////////////////////////////////////////////
+ // TODO(ccraik): SurfaceFlinger work
+ registerEventInfo({
+ title: 'eglSwapBuffers',
+ description: 'The CPU has finished producing drawing commands, and is flushing drawing work to the GPU, and posting that buffer to the consumer (which is often SurfaceFlinger window composition). Once this is completed, the GPU can produce the frame content without any involvement from the CPU.'}); // @suppress longLineCheck
+ },
+
+ applyEventInfosRecursive_(parentNames, slice) {
+ const checkExpectedParentNames = function(expectedParentNames) {
+ if (!expectedParentNames) return true;
+ return expectedParentNames.some(function(name) {
+ return parentNames.has(name);
+ });
+ };
+
+ // Set EventInfo on the slice if it matches title, and parent.
+ if (this.titleInfoLookup.has(slice.title)) {
+ if (checkExpectedParentNames(this.titleParentLookup.get(slice.title))) {
+ slice.info = this.titleInfoLookup.get(slice.title);
+ }
+ }
+
+ // Push slice into parentNames, and recurse over subSlices.
+ if (slice.subSlices.length > 0) {
+ // Increment title in parentName dict.
+ if (!parentNames.has(slice.title)) {
+ parentNames.set(slice.title, 0);
+ }
+ parentNames.set(slice.title, parentNames.get(slice.title) + 1);
+
+ // Recurse over subSlices.
+ slice.subSlices.forEach(function(subSlice) {
+ this.applyEventInfosRecursive_(parentNames, subSlice);
+ }, this);
+
+ // Decrement title in parentName dict.
+ parentNames.set(slice.title, parentNames.get(slice.title) - 1);
+ if (parentNames.get(slice.title) === 0) {
+ delete parentNames[slice.title];
+ }
+ }
+ },
+
+ applyEventInfos(sliceGroup) {
+ sliceGroup.topLevelSlices.forEach(function(slice) {
+ this.applyEventInfosRecursive_(new Map(), slice);
+ }, this);
+ }
+ };
+
+ return {
+ AndroidAuditor,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/android/android_auditor_test.html b/chromium/third_party/catapult/tracing/tracing/extras/android/android_auditor_test.html
new file mode 100644
index 00000000000..bb042eac514
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/android/android_auditor_test.html
@@ -0,0 +1,424 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/scalar.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/android/android_auditor.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+<link rel="import" href="/tracing/model/frame.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+ const FRAME_PERF_CLASS = tr.model.FRAME_PERF_CLASS;
+ const newThreadSlice = tr.c.TestUtils.newThreadSlice;
+ const Scalar = tr.b.Scalar;
+ const timeDurationInMs = tr.b.Unit.byName.timeDurationInMs;
+
+ test('constructorSliceName', function() {
+ // verify 'constructor' slice name doesn't break the auditor
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const renderThread = model.getOrCreateProcess(1).getOrCreateThread(2);
+ renderThread.name = 'RenderThread';
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'constructor', start: 200, duration: 5}));
+ }, tr.e.audits.AndroidAuditor);
+
+ assert.strictEqual(model.alerts.length, 0);
+ });
+
+ test('saveLayerAlert_badAlpha', function() {
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const renderThread = model.getOrCreateProcess(1).getOrCreateThread(2);
+ renderThread.name = 'RenderThread';
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'doFrame', start: 200, duration: 5}));
+ renderThread.sliceGroup.pushSlice(newSliceEx({
+ title: 'BadAlphaView alpha caused saveLayer 480x320',
+ start: 203,
+ duration: 1
+ }));
+
+ // doesn't create alert, since bad alpha accounts for this savelayer
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'unclipped saveLayer 480x320', start: 204, duration: 1}));
+ }, tr.e.audits.AndroidAuditor);
+
+ assert.strictEqual(model.alerts.length, 1);
+
+ const alert = model.alerts[0];
+ assert.strictEqual(alert.args['view name'], 'BadAlphaView');
+ assert.strictEqual(alert.args.width, 480);
+ assert.strictEqual(alert.args.height, 320);
+ });
+
+ test('saveLayerAlert_canvas', function() {
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const renderThread = model.getOrCreateProcess(1).getOrCreateThread(2);
+ renderThread.name = 'RenderThread';
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'doFrame', start: 200, duration: 5}));
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'saveLayer 480x320', start: 204, duration: 1}));
+ }, tr.e.audits.AndroidAuditor);
+
+ assert.strictEqual(model.alerts.length, 1);
+
+ const alert = model.alerts[0];
+ assert.strictEqual(alert.args['Clipped saveLayer count'], 1);
+ assert.strictEqual(alert.associatedEvents.length, 2);
+ });
+
+ test('generatePathAlert', function() {
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const renderThread = model.getOrCreateProcess(1).getOrCreateThread(2);
+ renderThread.name = 'RenderThread';
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'doFrame', start: 0, duration: 20}));
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'Generate Path Texture', start: 0, duration: 3}));
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'Generate Path Texture', start: 3, duration: 6}));
+ }, tr.e.audits.AndroidAuditor);
+
+ assert.strictEqual(model.alerts.length, 1);
+
+ const alert = model.alerts[0];
+ assert.deepEqual(alert.args['Time spent'],
+ new Scalar(timeDurationInMs, 9));
+ assert.strictEqual(alert.associatedEvents.length, 3);
+ });
+
+ test('uploadAlert', function() {
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const renderThread = model.getOrCreateProcess(1).getOrCreateThread(2);
+ renderThread.name = 'RenderThread';
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'doFrame', start: 0, duration: 20}));
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'Upload 1000x1000 Texture', start: 0, duration: 15}));
+ }, tr.e.audits.AndroidAuditor);
+
+ assert.strictEqual(model.alerts.length, 1);
+
+ const alert = model.alerts[0];
+ assert.strictEqual(alert.args['Pixels uploaded'], '1.00 million');
+ assert.deepEqual(alert.args['Time spent'],
+ new Scalar(timeDurationInMs, 15));
+ assert.strictEqual(alert.associatedEvents.length, 2);
+ });
+
+ test('listViewAlert', function() {
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'obtainView', start: 0, duration: 5}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'setupListItem', start: 5, duration: 5}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'obtainView', start: 10, duration: 5}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'setupListItem', start: 15, duration: 5}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 20, duration: 5}));
+
+ // short frame, so no alert should be generated
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'obtainView', start: 50, duration: 5}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'setupListItem', start: 55, duration: 5}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 60, duration: 1}));
+
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'obtainView', start: 100, duration: 10}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'inflate', start: 101, duration: 8}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'setupListItem', start: 110, duration: 10}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 120, duration: 5}));
+ }, tr.e.audits.AndroidAuditor);
+
+ assert.strictEqual(model.alerts.length, 2);
+ let alert = model.alerts[0];
+ assert.strictEqual(alert.args['ListView items rebound'], 2);
+ assert.deepEqual(alert.args['Time spent'],
+ new Scalar(timeDurationInMs, 20));
+ assert.strictEqual(alert.associatedEvents.length, 5);
+
+ alert = model.alerts[1];
+ assert.strictEqual(alert.args['ListView items inflated'], 1);
+ assert.deepEqual(alert.args['Time spent'],
+ new Scalar(timeDurationInMs, 20));
+ // note: inflate not assoc.
+ assert.strictEqual(alert.associatedEvents.length, 3);
+ });
+
+ test('measureLayoutAlert', function() {
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 0, duration: 20}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'measure', start: 0, duration: 5}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'layout', start: 10, duration: 5}));
+ }, tr.e.audits.AndroidAuditor);
+
+ assert.strictEqual(model.alerts.length, 1);
+
+ const alert = model.alerts[0];
+ assert.deepEqual(alert.args['Time spent'],
+ new Scalar(timeDurationInMs, 10));
+ assert.strictEqual(alert.associatedEvents.length, 3);
+ });
+
+ test('viewDrawAlert', function() {
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ // modern naming
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 0, duration: 20}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'Record View#draw()', start: 0, duration: 10}));
+
+ // legacy naming
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 40, duration: 20}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'getDisplayList', start: 40, duration: 10}));
+ }, tr.e.audits.AndroidAuditor);
+
+ assert.strictEqual(model.alerts.length, 2);
+ assert.deepEqual(model.alerts[0].args['Time spent'],
+ new Scalar(timeDurationInMs, 10));
+ assert.deepEqual(model.alerts[1].args['Time spent'],
+ new Scalar(timeDurationInMs, 10));
+ });
+
+ test('blockingGcAlert', function() {
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ const sliceGroup = uiThread.sliceGroup;
+ sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 0, duration: 20}));
+ sliceGroup.pushSlice(newSliceEx(
+ {title: 'DVM Suspend', start: 0, duration: 15}));
+
+ sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 50, duration: 20}));
+ sliceGroup.pushSlice(newSliceEx(
+ {title: 'GC: Wait For Concurrent', start: 50, duration: 15}));
+ }, tr.e.audits.AndroidAuditor);
+
+ assert.strictEqual(model.alerts.length, 2);
+ assert.deepEqual(model.alerts[0].args['Blocked duration'],
+ new Scalar(timeDurationInMs, 15));
+ assert.deepEqual(model.alerts[1].args['Blocked duration'],
+ new Scalar(timeDurationInMs, 15));
+ });
+
+ test('lockContentionAlert', function() {
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ const sliceGroup = uiThread.sliceGroup;
+ sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 0, duration: 20}));
+ sliceGroup.pushSlice(newSliceEx(
+ {title: 'Lock Contention on a lock', start: 0, duration: 15}));
+ }, tr.e.audits.AndroidAuditor);
+
+ assert.strictEqual(model.alerts.length, 1);
+ assert.deepEqual(model.alerts[0].args['Blocked duration'],
+ new Scalar(timeDurationInMs, 15));
+ });
+
+ test('schedulingAlerts', function() {
+ let model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 0, duration: 20}));
+ uiThread.timeSlices = [
+ newThreadSlice(uiThread, SCHEDULING_STATE.RUNNING, 0, 6),
+ newThreadSlice(uiThread, SCHEDULING_STATE.RUNNABLE, 6, 10),
+ newThreadSlice(uiThread, SCHEDULING_STATE.RUNNING, 16, 4)];
+ }, tr.e.audits.AndroidAuditor);
+ assert.strictEqual(model.alerts.length, 1);
+ let alert = model.alerts[0];
+ assert.strictEqual(alert.info.title, 'Scheduling delay');
+ assert.deepEqual(alert.args['Not scheduled, but runnable'],
+ new Scalar(timeDurationInMs, 10));
+
+ model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 0, duration: 20}));
+ uiThread.timeSlices = [
+ newThreadSlice(uiThread, SCHEDULING_STATE.RUNNING, 0, 5),
+ newThreadSlice(uiThread, SCHEDULING_STATE.UNINTR_SLEEP, 5, 10),
+ newThreadSlice(uiThread, SCHEDULING_STATE.RUNNING, 15, 5)];
+ }, tr.e.audits.AndroidAuditor);
+ assert.strictEqual(model.alerts.length, 1);
+ alert = model.alerts[0];
+ assert.strictEqual(alert.info.title, 'Scheduling delay');
+ assert.deepEqual(alert.args['Blocking I/O delay'],
+ new Scalar(timeDurationInMs, 10));
+ });
+
+ test('addFramesToModel', function() {
+ let process;
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ process = model.getOrCreateProcess(1);
+ const uiThread = process.getOrCreateThread(1);
+
+ // High level choreographer frame signal
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'Choreographer#doFrame', start: 0, duration: 8}));
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'Choreographer#doFrame', start: 16, duration: 20}));
+
+ // Old devices only have 'performTraversals'
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 40, duration: 90}));
+ }, tr.e.audits.AndroidAuditor);
+
+ assert.strictEqual(process.frames.length, 3);
+ assert.closeTo(process.frames[0].totalDuration, 8, 1e-5);
+ assert.closeTo(process.frames[1].totalDuration, 20, 1e-5);
+ assert.closeTo(process.frames[2].totalDuration, 90, 1e-5);
+
+ assert.strictEqual(process.frames[0].perfClass,
+ FRAME_PERF_CLASS.GOOD);
+ assert.strictEqual(process.frames[1].perfClass,
+ FRAME_PERF_CLASS.BAD);
+ assert.strictEqual(process.frames[2].perfClass,
+ FRAME_PERF_CLASS.TERRIBLE);
+ });
+
+ test('processRenameAndSort', function() {
+ let appProcess;
+ let sfProcess;
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ appProcess = model.getOrCreateProcess(1);
+ const uiThread = appProcess.getOrCreateThread(1);
+ uiThread.name = 'ndroid.systemui';
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 0, duration: 8}));
+
+ sfProcess = model.getOrCreateProcess(2);
+ const sfThread = sfProcess.getOrCreateThread(2);
+ sfThread.name = '/system/bin/surfaceflinger';
+ sfThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'doComposition', start: 8, duration: 2}));
+ }, tr.e.audits.AndroidAuditor);
+
+ // both processes should be renamed
+ assert.strictEqual(appProcess.name, 'android.systemui');
+ assert.strictEqual(sfProcess.name, 'SurfaceFlinger');
+
+ assert.isTrue(sfProcess.sortIndex < appProcess.sortIndex);
+ assert.isTrue(appProcess.important);
+ assert.isFalse(sfProcess.important);
+ });
+
+ test('eventInfo', function() {
+ const eventsExpectingInfo = [];
+ const eventsNotExpectingInfo = [];
+
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const appProcess = model.getOrCreateProcess(1);
+ const uiThread = appProcess.getOrCreateThread(1);
+ uiThread.name = 'ndroid.systemui';
+
+ const pushInfoSlice = function(slice) {
+ eventsExpectingInfo.push(slice);
+ uiThread.sliceGroup.pushSlice(slice);
+ };
+ const pushNonInfoSlice = function(slice) {
+ eventsNotExpectingInfo.push(slice);
+ uiThread.sliceGroup.pushSlice(slice);
+ };
+
+ pushInfoSlice(newSliceEx(
+ {title: 'performTraversals', start: 0, duration: 10}));
+ pushInfoSlice(newSliceEx({title: 'measure', start: 0, duration: 2}));
+ pushInfoSlice(newSliceEx({title: 'layout', start: 2, duration: 1}));
+ pushInfoSlice(newSliceEx({title: 'draw', start: 3, duration: 7}));
+
+ // Out of place slices should not be tagged.
+ pushNonInfoSlice(newSliceEx({title: 'measure', start: 11, duration: 1}));
+ pushNonInfoSlice(newSliceEx({title: 'draw', start: 12, duration: 1}));
+ }, tr.e.audits.AndroidAuditor);
+
+ eventsExpectingInfo.forEach(function(event) {
+ assert.notEqual(event.info, undefined);
+ });
+
+ eventsNotExpectingInfo.forEach(function(event) {
+ assert.strictEqual(event.info, undefined);
+ });
+ });
+
+ test('drawingThreadPriorities', function() {
+ let uiThread;
+ let renderThread;
+ let workerThread;
+ let otherThread;
+ const model = tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const appProcess = model.getOrCreateProcess(1);
+
+ uiThread = appProcess.getOrCreateThread(1);
+ uiThread.name = 'ndroid.systemui';
+ uiThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'performTraversals', start: 0, duration: 4}));
+
+ renderThread = appProcess.getOrCreateThread(2);
+ renderThread.name = 'RenderThread';
+ renderThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'DrawFrame', start: 3, duration: 4}));
+
+ workerThread = appProcess.getOrCreateThread(3);
+ workerThread.name = 'hwuiTask1';
+ workerThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'work', start: 4, duration: 1}));
+
+ otherThread = appProcess.getOrCreateThread(4);
+ otherThread.name = 'other';
+ otherThread.sliceGroup.pushSlice(newSliceEx(
+ {title: 'otherWork', start: 0, duration: 2}));
+ }, tr.e.audits.AndroidAuditor);
+
+ assert.isTrue(uiThread.sortIndex < renderThread.sortIndex);
+ assert.isTrue(renderThread.sortIndex < workerThread.sortIndex);
+ assert.isTrue(workerThread.sortIndex < otherThread.sortIndex);
+ });
+
+ test('favicon', function() {
+ const createModelWithJank = function(percentageJank) {
+ return tr.c.TestUtils.newModelWithAuditor(function(model) {
+ const uiThread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ for (let i = 0; i < 100; i++) {
+ const slice = newSliceEx({
+ title: 'performTraversals',
+ start: 30 * i,
+ duration: i <= percentageJank ? 24 : 8
+ });
+ uiThread.sliceGroup.pushSlice(slice);
+ }
+ }, tr.e.audits.AndroidAuditor);
+ };
+ assert.strictEqual(createModelWithJank(3).faviconHue, 'green');
+ assert.strictEqual(createModelWithJank(10).faviconHue, 'yellow');
+ assert.strictEqual(createModelWithJank(50).faviconHue, 'red');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/blame_context.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/blame_context.html
new file mode 100644
index 00000000000..08a8842b1f9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/blame_context.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/model/object_instance.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview BlameContext is the Trace Viewer side correspondence of
+ * Chrome's class base::trace_event::BlameContext. More specifically,
+ *
+ * BlameContextSnapshot, which inherits from ObjectSnapshot, is the base class
+ * of all snapshots of blame contexts traced in Chrome.
+ *
+ * BlameContextInstance, which inherits from ObjectInstance, gathers snapshots
+ * of the same blame context traced in Chrome.
+ *
+ * BlameContextSnapshot and BlameContextInstance should never be instantiated
+ * directly. Subclasses corresponding to different BlameContexts in Chrome
+ * should define their own BlameContextSnapshot and BlameContextInstance
+ * specializations for instantiation.
+ *
+ */
+tr.exportTo('tr.e.chrome', function() {
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+ const ObjectInstance = tr.model.ObjectInstance;
+
+ function BlameContextSnapshot() {
+ ObjectSnapshot.apply(this, arguments);
+ }
+
+ BlameContextSnapshot.prototype = {
+ __proto__: ObjectSnapshot.prototype,
+
+ /**
+ * Returns the parent in the context tree.
+ */
+ get parentContext() {
+ if (this.args.parent instanceof BlameContextSnapshot) {
+ return this.args.parent;
+ }
+ return undefined;
+ },
+
+ get userFriendlyName() {
+ return 'BlameContext';
+ }
+ };
+
+ function BlameContextInstance() {
+ ObjectInstance.apply(this, arguments);
+ }
+
+ BlameContextInstance.prototype = {
+ __proto__: ObjectInstance.prototype,
+
+ /**
+ * Returns the type of the blame context, to be overriden by subclasses.
+ */
+ get blameContextType() {
+ throw new Error('Not implemented');
+ }
+ };
+
+ return {
+ BlameContextSnapshot,
+ BlameContextInstance,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/blame_context_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/blame_context_test.html
new file mode 100644
index 00000000000..8d6f4b5be5c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/blame_context_test.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/blame_context.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const BlameContextSnapshot = tr.e.chrome.BlameContextSnapshot;
+ const BlameContextInstance = tr.e.chrome.BlameContextInstance;
+
+ function TestBlameContextSnapshot() {
+ BlameContextSnapshot.apply(this, arguments);
+ }
+
+ TestBlameContextSnapshot.prototype = {
+ __proto__: BlameContextSnapshot.prototype,
+
+ get userFriendlyName() {
+ return 'Test';
+ }
+ };
+
+ tr.model.ObjectSnapshot.subTypes.register(
+ TestBlameContextSnapshot,
+ {typeName: 'Test'});
+
+ function TestBlameContextInstance() {
+ BlameContextInstance.apply(this, arguments);
+ }
+
+ TestBlameContextInstance.prototype = {
+ __proto__: BlameContextInstance.prototype,
+
+ get blameContextType() {
+ return 'Test';
+ }
+ };
+
+ tr.model.ObjectInstance.subTypes.register(
+ TestBlameContextInstance,
+ {typeName: 'Test'});
+
+ const TestUtils = tr.c.TestUtils;
+
+ test('parentContext', function() {
+ let parent;
+ let child;
+ TestUtils.newModel(function(model) {
+ parent = TestUtils.newSnapshot(model, {id: '0x1', name: 'Test'});
+ child = TestUtils.newSnapshot(model, {id: '0x2', name: 'Test',
+ args: {parent: {id_ref: '0x1'}}});
+ });
+
+ assert.isTrue(child.parentContext === parent);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/frame_blame_context_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/frame_blame_context_test.html
new file mode 100644
index 00000000000..43b15ae43be
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/frame_blame_context_test.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/frame_tree_node.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/render_frame.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/top_level.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const TestUtils = tr.c.TestUtils;
+
+ test('crossProcessCounterpart', function() {
+ let frameTreeNode;
+ let renderFrame;
+ TestUtils.newModel(function(model) {
+ // Add a toplevel to make the context tree consistent with the spec,
+ // though its functionality is not tested here.
+ TestUtils.newSnapshot(model, {
+ pid: 1, name: 'TopLevel', id: '0x1',
+ scope: 'PlatformThread', category: 'blink'});
+ renderFrame = TestUtils.newSnapshot(
+ model, {
+ pid: 1, name: 'RenderFrame', id: '0x2', scope: 'RenderFrame',
+ category: 'blink', args: {
+ parent: {scope: 'PlatformThread', id_ref: '0x1'}}});
+ frameTreeNode = TestUtils.newSnapshot(
+ model, {
+ pid: 2, name: 'FrameTreeNode', id: '0x3', scope: 'FrameTreeNode',
+ category: 'navigation', args: {
+ renderFrame: {scope: 'RenderFrame', id_ref: '0x2', pid_ref: 1}}});
+ });
+
+ assert.isTrue(frameTreeNode.renderFrame === renderFrame);
+ assert.isTrue(renderFrame.frameTreeNode === frameTreeNode);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/frame_tree_node.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/frame_tree_node.html
new file mode 100644
index 00000000000..bfd21093de5
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/frame_tree_node.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/blame_context/blame_context.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Trace Viewer side's correspondence of Chrome's
+ * content::FrameTreeNode class.
+ *
+ */
+tr.exportTo('tr.e.chrome', function() {
+ const BlameContextSnapshot = tr.e.chrome.BlameContextSnapshot;
+ const BlameContextInstance = tr.e.chrome.BlameContextInstance;
+
+ function FrameTreeNodeSnapshot() {
+ BlameContextSnapshot.apply(this, arguments);
+ }
+
+ FrameTreeNodeSnapshot.prototype = {
+ __proto__: BlameContextSnapshot.prototype,
+
+ get renderFrame() {
+ if (this.args.renderFrame instanceof tr.e.chrome.RenderFrameSnapshot) {
+ return this.args.renderFrame;
+ }
+ return undefined;
+ },
+
+ get url() {
+ return this.args.url;
+ },
+
+ get userFriendlyName() {
+ return 'FrameTreeNode';
+ }
+ };
+
+ tr.model.ObjectSnapshot.subTypes.register(
+ FrameTreeNodeSnapshot,
+ {typeName: 'FrameTreeNode'});
+
+ function FrameTreeNodeInstance() {
+ BlameContextInstance.apply(this, arguments);
+ }
+
+ FrameTreeNodeInstance.prototype = {
+ __proto__: BlameContextInstance.prototype,
+
+ get blameContextType() {
+ return 'Frame';
+ }
+ };
+
+ tr.model.ObjectInstance.subTypes.register(
+ FrameTreeNodeInstance,
+ {typeName: 'FrameTreeNode'});
+
+ return {
+ FrameTreeNodeSnapshot,
+ FrameTreeNodeInstance,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/render_frame.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/render_frame.html
new file mode 100644
index 00000000000..1543403dae8
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/render_frame.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/blame_context/blame_context.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Trace Viewer side's correspondence of Chrome's
+ * content::FrameBlameContext class.
+ *
+ */
+tr.exportTo('tr.e.chrome', function() {
+ const BlameContextSnapshot = tr.e.chrome.BlameContextSnapshot;
+ const BlameContextInstance = tr.e.chrome.BlameContextInstance;
+
+ function RenderFrameSnapshot() {
+ BlameContextSnapshot.apply(this, arguments);
+ }
+
+ RenderFrameSnapshot.prototype = {
+ __proto__: BlameContextSnapshot.prototype,
+
+ referencedAt(item, object, field) {
+ if (item instanceof tr.e.chrome.FrameTreeNodeSnapshot &&
+ object === item.args &&
+ field === 'renderFrame') {
+ this.args.frameTreeNode = item;
+ }
+ },
+
+ get frameTreeNode() {
+ if (this.args.frameTreeNode instanceof
+ tr.e.chrome.FrameTreeNodeSnapshot) {
+ return this.args.frameTreeNode;
+ }
+ return undefined;
+ },
+
+ get url() {
+ if (this.frameTreeNode) {
+ return this.frameTreeNode.url;
+ }
+ return undefined;
+ },
+
+ get userFriendlyName() {
+ return 'RenderFrame';
+ }
+ };
+
+ tr.model.ObjectSnapshot.subTypes.register(
+ RenderFrameSnapshot,
+ {typeName: 'RenderFrame'});
+
+ function RenderFrameInstance() {
+ BlameContextInstance.apply(this, arguments);
+ }
+
+ RenderFrameInstance.prototype = {
+ __proto__: BlameContextInstance.prototype,
+
+ get blameContextType() {
+ return 'Frame';
+ }
+ };
+
+ tr.model.ObjectInstance.subTypes.register(
+ RenderFrameInstance,
+ {typeName: 'RenderFrame'});
+
+ return {
+ RenderFrameSnapshot,
+ RenderFrameInstance,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/top_level.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/top_level.html
new file mode 100644
index 00000000000..b69bdb83b0d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blame_context/top_level.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/blame_context/blame_context.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Trace Viewer side's correspondence of Chrome's
+ * content::TopLevelBlameContext class.
+ *
+ */
+tr.exportTo('tr.e.chrome', function() {
+ const BlameContextSnapshot = tr.e.chrome.BlameContextSnapshot;
+ const BlameContextInstance = tr.e.chrome.BlameContextInstance;
+
+ function TopLevelSnapshot() {
+ BlameContextSnapshot.apply(this, arguments);
+ }
+
+ TopLevelSnapshot.prototype = {
+ __proto__: BlameContextSnapshot.prototype,
+
+ get userFriendlyName() {
+ return 'TopLevel';
+ }
+ };
+
+ tr.model.ObjectSnapshot.subTypes.register(
+ TopLevelSnapshot,
+ {typeName: 'TopLevel'});
+
+ function TopLevelInstance() {
+ BlameContextInstance.apply(this, arguments);
+ }
+
+ TopLevelInstance.prototype = {
+ __proto__: BlameContextInstance.prototype,
+
+ get blameContextType() {
+ return 'TopLevel';
+ }
+ };
+
+ tr.model.ObjectInstance.subTypes.register(
+ TopLevelInstance,
+ {typeName: 'TopLevel'});
+
+ return {
+ TopLevelSnapshot,
+ TopLevelInstance,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/blink/blink_scheduler_async_slice.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blink/blink_scheduler_async_slice.html
new file mode 100644
index 00000000000..1916a699f7a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blink/blink_scheduler_async_slice.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/model/async_slice.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.blink', function() {
+ class BlinkSchedulerAsyncSlice extends tr.model.AsyncSlice {
+ get viewSubGroupGroupingKey() {
+ if (this.title.startsWith('FrameScheduler.')) {
+ return 'Frame' + this.id;
+ }
+ if (this.title.startsWith('Scheduler.')) {
+ return 'Renderer Scheduler';
+ }
+ return undefined;
+ }
+
+ get viewSubGroupTitle() {
+ // NOTE: Be careful with hardcoded (for performance) string length.
+ if (this.title.startsWith('FrameScheduler.')) {
+ return this.title.substring(15);
+ }
+ if (this.title.startsWith('Scheduler.')) {
+ return this.title.substring(10);
+ }
+ return this.title;
+ }
+ }
+
+ tr.model.AsyncSlice.subTypes.register(BlinkSchedulerAsyncSlice, {
+ categoryParts: [
+ 'renderer.scheduler',
+ 'disabled-by-default-renderer.scheduler',
+ 'disabled-by-default-renderer.scheduler.debug',
+ ]
+ });
+
+ return {
+ BlinkSchedulerAsyncSlice,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/blink/blink_scheduler_async_slice_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blink/blink_scheduler_async_slice_test.html
new file mode 100644
index 00000000000..be078c3d1ed
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/blink/blink_scheduler_async_slice_test.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<!--
+Copyright 2018 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/blink/blink_scheduler_async_slice.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const AsyncSlice = tr.model.AsyncSlice;
+ const BlinkSchedulerAsyncSlice = tr.e.blink.BlinkSchedulerAsyncSlice;
+
+ test('construct', function() {
+ assert.strictEqual(
+ AsyncSlice.subTypes.getConstructor(
+ 'renderer.scheduler', 'Scheduler.Foo'),
+ BlinkSchedulerAsyncSlice);
+ assert.strictEqual(
+ AsyncSlice.subTypes.getConstructor(
+ 'disabled-by-default-renderer.scheduler', 'Scheduler.Bar'),
+ BlinkSchedulerAsyncSlice);
+ assert.strictEqual(
+ AsyncSlice.subTypes.getConstructor(
+ 'disabled-by-default-renderer.scheduler.debug', 'Scheduler.Baz'),
+ BlinkSchedulerAsyncSlice);
+ });
+
+ test('subgroups', function() {
+ const rendererSlice = new BlinkSchedulerAsyncSlice(
+ 'renderer.scheduler', 'Scheduler.Foo', 7, 0, {}, 3);
+ assert.strictEqual(rendererSlice.viewSubGroupGroupingKey,
+ 'Renderer Scheduler');
+ assert.strictEqual(rendererSlice.viewSubGroupTitle, 'Foo');
+
+
+ const frameSlice = new BlinkSchedulerAsyncSlice(
+ 'renderer.scheduler', 'FrameScheduler.Bar', 7, 0, {}, 3);
+ frameSlice.id = ':ptr:0x1';
+ assert.strictEqual(frameSlice.viewSubGroupGroupingKey, 'Frame:ptr:0x1');
+ assert.strictEqual(frameSlice.viewSubGroupTitle, 'Bar');
+
+ const otherSlice = new BlinkSchedulerAsyncSlice(
+ 'renderer.scheduler', 'Something.Baz', 7, 0, {}, 3);
+ assert.isUndefined(otherSlice.viewSubGroupGroupingKey);
+ assert.strictEqual(otherSlice.viewSubGroupTitle, 'Something.Baz');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/cc.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/cc.html
new file mode 100644
index 00000000000..64b825f6a9f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/cc.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/display_item_list.html">
+<link rel="import" href="/tracing/extras/chrome/cc/input_latency_async_slice.html">
+<link rel="import" href="/tracing/extras/chrome/cc/layer_tree_host_impl.html">
+<link rel="import" href="/tracing/extras/chrome/cc/picture.html">
+<link rel="import" href="/tracing/extras/chrome/cc/tile.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/constants.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/constants.html
new file mode 100644
index 00000000000..1f4aa35b7a2
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/constants.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/base/base.html">
+<script>
+'use strict';
+
+tr.exportTo('tr.e.cc', function() {
+ const constants = {};
+ constants.ACTIVE_TREE = 0;
+ constants.PENDING_TREE = 1;
+
+ constants.HIGH_PRIORITY_BIN = 0;
+ constants.LOW_PRIORITY_BIN = 1;
+
+ constants.SEND_BEGIN_FRAME_EVENT =
+ 'ThreadProxy::ScheduledActionSendBeginMainFrame';
+ constants.BEGIN_MAIN_FRAME_EVENT = 'ThreadProxy::BeginMainFrame';
+
+ return {
+ constants
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/debug_colors.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/debug_colors.html
new file mode 100644
index 00000000000..07486dcd03c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/debug_colors.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/base/base.html">
+<script>
+'use strict';
+
+/**
+ * @fileoverview Mapping of different tile configuration
+ * to border colors and widths.
+ */
+tr.exportTo('tr.e.cc', function() {
+ const tileTypes = {
+ highRes: 'highRes',
+ lowRes: 'lowRes',
+ extraHighRes: 'extraHighRes',
+ extraLowRes: 'extraLowRes',
+ missing: 'missing',
+ culled: 'culled',
+ solidColor: 'solidColor',
+ picture: 'picture',
+ directPicture: 'directPicture',
+ unknown: 'unknown'
+ };
+
+ const tileBorder = {
+ highRes: {
+ color: 'rgba(80, 200, 200, 0.7)',
+ width: 1
+ },
+ lowRes: {
+ color: 'rgba(212, 83, 192, 0.7)',
+ width: 2
+ },
+ extraHighRes: {
+ color: 'rgba(239, 231, 20, 0.7)',
+ width: 2
+ },
+ extraLowRes: {
+ color: 'rgba(93, 186, 18, 0.7)',
+ width: 2
+ },
+ missing: {
+ color: 'rgba(255, 0, 0, 0.7)',
+ width: 1
+ },
+ culled: {
+ color: 'rgba(160, 100, 0, 0.8)',
+ width: 1
+ },
+ solidColor: {
+ color: 'rgba(128, 128, 128, 0.7)',
+ width: 1
+ },
+ picture: {
+ color: 'rgba(64, 64, 64, 0.7)',
+ width: 1
+ },
+ directPicture: {
+ color: 'rgba(127, 255, 0, 1.0)',
+ width: 1
+ },
+ unknown: {
+ color: 'rgba(0, 0, 0, 1.0)',
+ width: 2
+ }
+ };
+
+ return {
+ tileTypes,
+ tileBorder
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/display_item_list.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/display_item_list.html
new file mode 100644
index 00000000000..bb23acf149f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/display_item_list.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/picture.html">
+<link rel="import" href="/tracing/model/object_instance.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.cc', function() {
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+
+ function DisplayItemList(skp64, layerRect) {
+ tr.e.cc.Picture.apply(this, arguments);
+ }
+
+ DisplayItemList.prototype = {
+ __proto__: tr.e.cc.Picture.prototype
+ };
+
+ /**
+ * @constructor
+ */
+ function DisplayItemListSnapshot() {
+ tr.e.cc.PictureSnapshot.apply(this, arguments);
+ }
+
+ DisplayItemListSnapshot.prototype = {
+ __proto__: tr.e.cc.PictureSnapshot.prototype,
+
+ initialize() {
+ tr.e.cc.PictureSnapshot.prototype.initialize.call(this);
+ this.displayItems_ = this.args.params.items;
+ },
+
+ get items() {
+ return this.displayItems_;
+ }
+ };
+
+ ObjectSnapshot.subTypes.register(
+ DisplayItemListSnapshot,
+ {typeNames: ['cc::DisplayItemList']});
+
+ return {
+ DisplayItemListSnapshot,
+ DisplayItemList,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/display_item_list_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/display_item_list_test.html
new file mode 100644
index 00000000000..78d5d21a57d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/display_item_list_test.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/cc/cc.html">
+<link rel="import" href="/tracing/extras/chrome/cc/display_item_list.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script src="/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js">
+</script>
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('basic', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = Object.values(m.processes)[0];
+
+ const instance = p.objects.getAllInstancesNamed('cc::DisplayItemList')[0];
+ const snapshot = instance.snapshots[0];
+
+ assert.instanceOf(snapshot, tr.e.cc.DisplayItemListSnapshot);
+ instance.wasDeleted(150);
+ });
+
+ test('getItems', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = Object.values(m.processes)[0];
+
+ const instance = p.objects.getAllInstancesNamed('cc::DisplayItemList')[0];
+ const snapshot = instance.snapshots[0];
+
+ const items = snapshot.items;
+ assert.strictEqual(items.length, 2);
+
+ assert.strictEqual(items[0], 'BeginClipDisplayItem');
+ assert.strictEqual(items[1], 'EndClipDisplayItem');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/input_latency_async_slice.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/input_latency_async_slice.html
new file mode 100644
index 00000000000..e3bd9bf2010
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/input_latency_async_slice.html
@@ -0,0 +1,645 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/model/async_slice.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.cc', function() {
+ const AsyncSlice = tr.model.AsyncSlice;
+ const EventSet = tr.model.EventSet;
+
+ const UI_COMP_NAME = 'INPUT_EVENT_LATENCY_UI_COMPONENT';
+ const ORIGINAL_COMP_NAME = 'INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT';
+ const BEGIN_COMP_NAME = 'INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT';
+ const END_COMP_NAME = 'INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT';
+ const LEGACY_END_COMP_NAME =
+ 'INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT';
+
+ const MAIN_RENDERER_THREAD_NAME = 'CrRendererMain';
+ const COMPOSITOR_THREAD_NAME = 'Compositor';
+
+ const POSTTASK_FLOW_EVENT = 'disabled-by-default-toplevel.flow';
+ const IPC_FLOW_EVENT = 'disabled-by-default-ipc.flow';
+
+ const INPUT_EVENT_TYPE_NAMES = {
+ CHAR: 'Char',
+ CLICK: 'GestureClick',
+ CONTEXT_MENU: 'ContextMenu',
+ FLING_CANCEL: 'GestureFlingCancel',
+ FLING_START: 'GestureFlingStart',
+ KEY_DOWN: 'KeyDown',
+ KEY_DOWN_RAW: 'RawKeyDown',
+ KEY_UP: 'KeyUp',
+ LATENCY_SCROLL_UPDATE: 'ScrollUpdate',
+ MOUSE_DOWN: 'MouseDown',
+ MOUSE_ENTER: 'MouseEnter',
+ MOUSE_LEAVE: 'MouseLeave',
+ MOUSE_MOVE: 'MouseMove',
+ MOUSE_UP: 'MouseUp',
+ MOUSE_WHEEL: 'MouseWheel',
+ PINCH_BEGIN: 'GesturePinchBegin',
+ PINCH_END: 'GesturePinchEnd',
+ PINCH_UPDATE: 'GesturePinchUpdate',
+ SCROLL_BEGIN: 'GestureScrollBegin',
+ SCROLL_END: 'GestureScrollEnd',
+ SCROLL_UPDATE: 'GestureScrollUpdate',
+ SCROLL_UPDATE_RENDERER: 'ScrollUpdate',
+ SHOW_PRESS: 'GestureShowPress',
+ TAP: 'GestureTap',
+ TAP_CANCEL: 'GestureTapCancel',
+ TAP_DOWN: 'GestureTapDown',
+ TOUCH_CANCEL: 'TouchCancel',
+ TOUCH_END: 'TouchEnd',
+ TOUCH_MOVE: 'TouchMove',
+ TOUCH_START: 'TouchStart',
+ UNKNOWN: 'UNKNOWN'
+ };
+
+ function InputLatencyAsyncSlice() {
+ AsyncSlice.apply(this, arguments);
+ this.associatedEvents_ = new EventSet();
+ this.typeName_ = undefined;
+ if (!this.isLegacyEvent) {
+ this.determineModernTypeName_();
+ }
+ }
+
+ InputLatencyAsyncSlice.prototype = {
+ __proto__: AsyncSlice.prototype,
+
+ // Legacy InputLatencyAsyncSlices involve a top-level slice titled
+ // "InputLatency" containing a subSlice whose title starts with
+ // "InputLatency:". Modern InputLatencyAsyncSlices involve a single
+ // top-level slice whose title starts with "InputLatency::".
+ // Legacy subSlices are not available at construction time, so
+ // determineLegacyTypeName_() must be called at get time.
+ // So this returns false for the legacy subSlice events titled like
+ // "InputLatency:Foo" even though they are technically legacy events.
+ get isLegacyEvent() {
+ return this.title === 'InputLatency';
+ },
+
+ get typeName() {
+ if (!this.typeName_) {
+ this.determineLegacyTypeName_();
+ }
+ return this.typeName_;
+ },
+
+ checkTypeName_() {
+ if (!this.typeName_) {
+ throw new Error('Unable to determine typeName');
+ }
+ let found = false;
+ for (const typeName in INPUT_EVENT_TYPE_NAMES) {
+ if (this.typeName === INPUT_EVENT_TYPE_NAMES[typeName]) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ this.typeName_ = INPUT_EVENT_TYPE_NAMES.UNKNOWN;
+ }
+ },
+
+ determineModernTypeName_() {
+ // This method works both on modern events titled like
+ // "InputLatency::Foo" and also on the legacy subSlices titled like
+ // "InputLatency:Foo". Modern events' titles contain 2 colons, whereas the
+ // legacy subSlices events contain 1 colon.
+
+ const lastColonIndex = this.title.lastIndexOf(':');
+ if (lastColonIndex < 0) return;
+
+ const characterAfterLastColonIndex = lastColonIndex + 1;
+ this.typeName_ = this.title.slice(characterAfterLastColonIndex);
+
+ // Check that the determined typeName is known.
+ this.checkTypeName_();
+ },
+
+ determineLegacyTypeName_() {
+ // Iterate over all descendent subSlices.
+ for (const subSlice of this.enumerateAllDescendents()) {
+ // If |subSlice| is not an InputLatencyAsyncSlice, then ignore it.
+ const subSliceIsAInputLatencyAsyncSlice = (
+ subSlice instanceof InputLatencyAsyncSlice);
+ if (!subSliceIsAInputLatencyAsyncSlice) continue;
+
+ // If |subSlice| does not have a typeName, then ignore it.
+ if (!subSlice.typeName) continue;
+
+ // If |this| already has a typeName and |subSlice| has a different
+ // typeName, then explode!
+ if (this.typeName_ && subSlice.typeName_) {
+ const subSliceHasDifferentTypeName = (
+ this.typeName_ !== subSlice.typeName_);
+ if (subSliceHasDifferentTypeName) {
+ throw new Error(
+ 'InputLatencyAsyncSlice.determineLegacyTypeName_() ' +
+ ' found multiple typeNames');
+ }
+ }
+
+ // The typeName of |this| top-level event is whatever the typeName of
+ // |subSlice| is. Set |this.typeName_| to the subSlice's typeName.
+ this.typeName_ = subSlice.typeName_;
+ }
+
+ // If typeName could not be determined, then explode!
+ if (!this.typeName_) {
+ throw new Error(
+ 'InputLatencyAsyncSlice.determineLegacyTypeName_() failed');
+ }
+
+ // Check that the determined typeName is known.
+ this.checkTypeName_();
+ },
+
+ getRendererHelper(sourceSlices) {
+ const traceModel = this.startThread.parent.model;
+ const modelHelper = traceModel.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (!modelHelper) return undefined;
+
+ let mainThread = undefined;
+ let compositorThread = undefined;
+
+ for (const i in sourceSlices) {
+ if (sourceSlices[i].parentContainer.name ===
+ MAIN_RENDERER_THREAD_NAME) {
+ mainThread = sourceSlices[i].parentContainer;
+ } else if (sourceSlices[i].parentContainer.name ===
+ COMPOSITOR_THREAD_NAME) {
+ compositorThread = sourceSlices[i].parentContainer;
+ }
+
+ if (mainThread && compositorThread) break;
+ }
+
+ const rendererHelpers = modelHelper.rendererHelpers;
+
+ const pids = Object.keys(rendererHelpers);
+ for (let i = 0; i < pids.length; i++) {
+ const pid = pids[i];
+ const rendererHelper = rendererHelpers[pid];
+ if (rendererHelper.mainThread === mainThread ||
+ rendererHelper.compositorThread === compositorThread) {
+ return rendererHelper;
+ }
+ }
+
+ return undefined;
+ },
+
+ addEntireSliceHierarchy(slice) {
+ this.associatedEvents_.push(slice);
+ slice.iterateAllSubsequentSlices(function(subsequentSlice) {
+ this.associatedEvents_.push(subsequentSlice);
+ }, this);
+ },
+
+ addDirectlyAssociatedEvents(flowEvents) {
+ const slices = [];
+
+ flowEvents.forEach(function(flowEvent) {
+ this.associatedEvents_.push(flowEvent);
+ const newSource = flowEvent.startSlice.mostTopLevelSlice;
+ if (slices.indexOf(newSource) === -1) {
+ slices.push(newSource);
+ }
+ }, this);
+
+ const lastFlowEvent = flowEvents[flowEvents.length - 1];
+ const lastSource = lastFlowEvent.endSlice.mostTopLevelSlice;
+ if (slices.indexOf(lastSource) === -1) {
+ slices.push(lastSource);
+ }
+
+ return slices;
+ },
+
+ // Find the Latency::ScrollUpdate slice that corresponds to the
+ // InputLatency::GestureScrollUpdate slice.
+ // The C++ CL that makes this connection is at:
+ // https://codereview.chromium.org/1178963003
+ addScrollUpdateEvents(rendererHelper) {
+ if (!rendererHelper || !rendererHelper.compositorThread) {
+ return;
+ }
+
+ const compositorThread = rendererHelper.compositorThread;
+ const gestureScrollUpdateStart = this.start;
+ const gestureScrollUpdateEnd = this.end;
+
+ const allCompositorAsyncSlices =
+ compositorThread.asyncSliceGroup.slices;
+
+ for (const i in allCompositorAsyncSlices) {
+ const slice = allCompositorAsyncSlices[i];
+
+ if (slice.title !== 'Latency::ScrollUpdate') continue;
+
+ const parentId = slice.args.data.
+ INPUT_EVENT_LATENCY_FORWARD_SCROLL_UPDATE_TO_MAIN_COMPONENT.
+ sequence_number;
+
+ if (parentId === undefined) {
+ // Old trace, we can only rely on the timestamp to find the slice
+ if (slice.start < gestureScrollUpdateStart ||
+ slice.start >= gestureScrollUpdateEnd) {
+ continue;
+ }
+ } else {
+ // New trace, we can definitively find the latency slice by comparing
+ // its sequence number with gesture id
+ if (parseInt(parentId) !== parseInt(this.id)) {
+ continue;
+ }
+ }
+
+ slice.associatedEvents.forEach(function(event) {
+ this.associatedEvents_.push(event);
+ }, this);
+ break;
+ }
+ },
+
+ // Return true if the slice hierarchy is tracked by LatencyInfo of other
+ // input latency events. If the slice hierarchy is tracked by both, this
+ // function still returns true.
+ belongToOtherInputs(slice, flowEvents) {
+ let fromOtherInputs = false;
+
+ slice.iterateEntireHierarchy(function(subsequentSlice) {
+ if (fromOtherInputs) return;
+
+ subsequentSlice.inFlowEvents.forEach(function(inflow) {
+ if (fromOtherInputs) return;
+
+ if (inflow.category.indexOf('input') > -1) {
+ if (flowEvents.indexOf(inflow) === -1) {
+ fromOtherInputs = true;
+ }
+ }
+ }, this);
+ }, this);
+
+ return fromOtherInputs;
+ },
+
+ // Return true if |event| triggers slices of other inputs.
+ triggerOtherInputs(event, flowEvents) {
+ if (event.outFlowEvents === undefined ||
+ event.outFlowEvents.length === 0) {
+ return false;
+ }
+
+ // Once we fix the bug of flow event binding, there should exist one and
+ // only one outgoing flow (PostTask) from ScheduleBeginImplFrameDeadline
+ // and PostComposite.
+ const flow = event.outFlowEvents[0];
+
+ if (flow.category !== POSTTASK_FLOW_EVENT ||
+ !flow.endSlice) {
+ return false;
+ }
+
+ const endSlice = flow.endSlice;
+ if (this.belongToOtherInputs(endSlice.mostTopLevelSlice, flowEvents)) {
+ return true;
+ }
+
+ return false;
+ },
+
+ // Follow outgoing flow of subsequentSlices in the current hierarchy.
+ // We also handle cases where different inputs interfere with each other.
+ followSubsequentSlices(event, queue, visited, flowEvents) {
+ let stopFollowing = false;
+ let inputAck = false;
+
+ event.iterateAllSubsequentSlices(function(slice) {
+ if (stopFollowing) return;
+
+ // Do not follow TaskQueueManager::RunTask because it causes
+ // many false events to be included.
+ if (slice.title === 'TaskQueueManager::RunTask') return;
+
+ // Do not follow ScheduledActionSendBeginMainFrame because the real
+ // main thread BeginMainFrame is already traced by LatencyInfo flow.
+ if (slice.title === 'ThreadProxy::ScheduledActionSendBeginMainFrame') {
+ return;
+ }
+
+ // Do not follow ScheduleBeginImplFrameDeadline that triggers an
+ // OnBeginImplFrameDeadline that is tracked by another LatencyInfo.
+ if (slice.title === 'Scheduler::ScheduleBeginImplFrameDeadline') {
+ if (this.triggerOtherInputs(slice, flowEvents)) return;
+ }
+
+ // Do not follow PostComposite that triggers CompositeImmediately
+ // that is tracked by another LatencyInfo.
+ if (slice.title === 'CompositorImpl::PostComposite') {
+ if (this.triggerOtherInputs(slice, flowEvents)) return;
+ }
+
+ // Stop following the rest of the current slice hierarchy if
+ // FilterAndSendWebInputEvent occurs after ProcessInputEventAck.
+ if (slice.title === 'InputRouterImpl::ProcessInputEventAck') {
+ inputAck = true;
+ }
+ if (inputAck &&
+ slice.title === 'InputRouterImpl::FilterAndSendWebInputEvent') {
+ stopFollowing = true;
+ }
+
+ this.followCurrentSlice(slice, queue, visited);
+ }, this);
+ },
+
+ // Follow outgoing flow events of the current slice.
+ followCurrentSlice(event, queue, visited) {
+ event.outFlowEvents.forEach(function(outflow) {
+ if ((outflow.category === POSTTASK_FLOW_EVENT ||
+ outflow.category === IPC_FLOW_EVENT) &&
+ outflow.endSlice) {
+ this.associatedEvents_.push(outflow);
+
+ const nextEvent = outflow.endSlice.mostTopLevelSlice;
+ if (!visited.contains(nextEvent)) {
+ visited.push(nextEvent);
+ queue.push(nextEvent);
+ }
+ }
+ }, this);
+ },
+
+ backtraceFromDraw(beginImplFrame, visited) {
+ const pendingEventQueue = [];
+ pendingEventQueue.push(beginImplFrame.mostTopLevelSlice);
+
+ while (pendingEventQueue.length !== 0) {
+ const event = pendingEventQueue.pop();
+
+ this.addEntireSliceHierarchy(event);
+
+ // TODO(yuhao): For now, we backtrace all the way to the source input.
+ // But is this really needed? I will have an entry in the design
+ // doc to discuss this.
+ event.inFlowEvents.forEach(function(inflow) {
+ if (inflow.category === POSTTASK_FLOW_EVENT && inflow.startSlice) {
+ const nextEvent = inflow.startSlice.mostTopLevelSlice;
+ if (!visited.contains(nextEvent)) {
+ visited.push(nextEvent);
+ pendingEventQueue.push(nextEvent);
+ }
+ }
+ }, this);
+ }
+ },
+
+ sortRasterizerSlices(rasterWorkerThreads,
+ sortedRasterizerSlices) {
+ rasterWorkerThreads.forEach(function(rasterizer) {
+ Array.prototype.push.apply(sortedRasterizerSlices,
+ rasterizer.sliceGroup.slices);
+ }, this);
+
+ sortedRasterizerSlices.sort(function(a, b) {
+ if (a.start !== b.start) {
+ return a.start - b.start;
+ }
+ return a.guid - b.guid;
+ });
+ },
+
+ // Find rasterization slices that have the source_prepare_tiles_id
+ // same as the prepare_tiles_id of TileManager::PrepareTiles
+ // The C++ CL that makes this connection is at:
+ // https://codereview.chromium.org/1208683002/
+ addRasterizationEvents(prepareTiles, rendererHelper,
+ visited, flowEvents, sortedRasterizerSlices) {
+ if (!prepareTiles.args.prepare_tiles_id) return;
+
+ if (!rendererHelper || !rendererHelper.rasterWorkerThreads) {
+ return;
+ }
+
+ const rasterWorkerThreads = rendererHelper.rasterWorkerThreads;
+ const prepareTileId = prepareTiles.args.prepare_tiles_id;
+ const pendingEventQueue = [];
+
+ // Collect all the rasterizer tasks. Return the cached copy if possible.
+ if (sortedRasterizerSlices.length === 0) {
+ this.sortRasterizerSlices(rasterWorkerThreads, sortedRasterizerSlices);
+ }
+
+ // TODO(yuhao): Once TaskSetFinishedTaskImpl also get the prepareTileId
+ // we can simply track by checking id rather than counting.
+ let numFinishedTasks = 0;
+ const RASTER_TASK_TITLE = 'RasterizerTaskImpl::RunOnWorkerThread';
+ const IMAGEDECODE_TASK_TITLE = 'ImageDecodeTaskImpl::RunOnWorkerThread';
+ const FINISHED_TASK_TITLE = 'TaskSetFinishedTaskImpl::RunOnWorkerThread';
+
+ for (let i = 0; i < sortedRasterizerSlices.length; i++) {
+ const task = sortedRasterizerSlices[i];
+
+ if (task.title === RASTER_TASK_TITLE ||
+ task.title === IMAGEDECODE_TASK_TITLE) {
+ if (task.args.source_prepare_tiles_id === prepareTileId) {
+ this.addEntireSliceHierarchy(task.mostTopLevelSlice);
+ }
+ } else if (task.title === FINISHED_TASK_TITLE) {
+ if (task.start > prepareTiles.start) {
+ pendingEventQueue.push(task.mostTopLevelSlice);
+ if (++numFinishedTasks === 3) break;
+ }
+ }
+ }
+
+ // Trace PostTask from rasterizer tasks.
+ while (pendingEventQueue.length !== 0) {
+ const event = pendingEventQueue.pop();
+
+ this.addEntireSliceHierarchy(event);
+ this.followSubsequentSlices(event, pendingEventQueue, visited,
+ flowEvents);
+ }
+ },
+
+ addOtherCausallyRelatedEvents(rendererHelper, sourceSlices,
+ flowEvents, sortedRasterizerSlices) {
+ const pendingEventQueue = [];
+ // Keep track of visited nodes when traversing a DAG
+ const visitedEvents = new EventSet();
+ let beginImplFrame = undefined;
+ let prepareTiles = undefined;
+ sortedRasterizerSlices = [];
+
+ sourceSlices.forEach(function(sourceSlice) {
+ if (!visitedEvents.contains(sourceSlice)) {
+ visitedEvents.push(sourceSlice);
+ pendingEventQueue.push(sourceSlice);
+ }
+ }, this);
+
+ while (pendingEventQueue.length !== 0) {
+ const event = pendingEventQueue.pop();
+
+ // Push the current event chunk into associatedEvents.
+ this.addEntireSliceHierarchy(event);
+
+ this.followCurrentSlice(event, pendingEventQueue, visitedEvents);
+
+ this.followSubsequentSlices(event, pendingEventQueue, visitedEvents,
+ flowEvents);
+
+ // The rasterization work (CompositorTileWorker thread) and the
+ // Compositor tile manager are connect by the prepare_tiles_id
+ // instead of flow events.
+ const COMPOSITOR_PREPARE_TILES = 'TileManager::PrepareTiles';
+ prepareTiles = event.findDescendentSlice(COMPOSITOR_PREPARE_TILES);
+ if (prepareTiles) {
+ this.addRasterizationEvents(prepareTiles, rendererHelper,
+ visitedEvents, flowEvents, sortedRasterizerSlices);
+ }
+
+ // OnBeginImplFrameDeadline could be triggered by other inputs.
+ // For now, we backtrace from it.
+ // TODO(yuhao): There are more such slices that we need to backtrace
+ const COMPOSITOR_ON_BIFD = 'Scheduler::OnBeginImplFrameDeadline';
+ beginImplFrame = event.findDescendentSlice(COMPOSITOR_ON_BIFD);
+ if (beginImplFrame) {
+ this.backtraceFromDraw(beginImplFrame, visitedEvents);
+ }
+ }
+
+ // A separate pass on GestureScrollUpdate.
+ // Scroll update doesn't go through the main thread, but the compositor
+ // may go back to the main thread if there is an onscroll event handler.
+ // This is captured by a different flow event, which does not have the
+ // same ID as the Input Latency Event, but it is technically causally
+ // related to the GestureScrollUpdate input. Add them manually for now.
+ const INPUT_GSU = 'InputLatency::GestureScrollUpdate';
+ if (this.title === INPUT_GSU) {
+ this.addScrollUpdateEvents(rendererHelper);
+ }
+ },
+
+ get associatedEvents() {
+ if (this.associatedEvents_.length !== 0) {
+ return this.associatedEvents_;
+ }
+
+ const modelIndices = this.startThread.parent.model.modelIndices;
+ const flowEvents = modelIndices.getFlowEventsWithId(this.id);
+
+ if (flowEvents.length === 0) {
+ return this.associatedEvents_;
+ }
+
+ // Step 1: Get events that are directly connected by the LatencyInfo
+ // flow events. This gives us a small set of events that are guaranteed
+ // to be associated with the input, but are almost certain incomplete.
+ // We call this set "source" event set.
+ // This step returns the "source" event set (sourceSlices), which is then
+ // used in the second step.
+ const sourceSlices = this.addDirectlyAssociatedEvents(flowEvents);
+
+ // Step 2: Start from the previously constructed "source" event set, we
+ // follow the toplevel (i.e., PostTask) and IPC flow events. Any slices
+ // that are reachable from the "source" event set via PostTasks or IPCs
+ // are conservatively considered associated with the input event.
+ // We then deal with specific cases where flow events either over include
+ // or miss capturing slices.
+ const rendererHelper = this.getRendererHelper(sourceSlices);
+ this.addOtherCausallyRelatedEvents(rendererHelper, sourceSlices,
+ flowEvents);
+
+ return this.associatedEvents_;
+ },
+
+ get inputLatency() {
+ if (!('data' in this.args)) return undefined;
+
+ const data = this.args.data;
+ const endTimeComp = data[END_COMP_NAME] || data[LEGACY_END_COMP_NAME];
+ if (endTimeComp === undefined) return undefined;
+
+ let latency = 0;
+ const endTime = endTimeComp.time;
+ if (ORIGINAL_COMP_NAME in data) {
+ latency = endTime - data[ORIGINAL_COMP_NAME].time;
+ } else if (UI_COMP_NAME in data) {
+ latency = endTime - data[UI_COMP_NAME].time;
+ } else if (BEGIN_COMP_NAME in data) {
+ latency = endTime - data[BEGIN_COMP_NAME].time;
+ } else {
+ throw new Error('No valid begin latency component');
+ }
+ return latency;
+ }
+ };
+
+ const eventTypeNames = [
+ 'Char',
+ 'ContextMenu',
+ 'GestureClick',
+ 'GestureFlingCancel',
+ 'GestureFlingStart',
+ 'GestureScrollBegin',
+ 'GestureScrollEnd',
+ 'GestureScrollUpdate',
+ 'GestureShowPress',
+ 'GestureTap',
+ 'GestureTapCancel',
+ 'GestureTapDown',
+ 'GesturePinchBegin',
+ 'GesturePinchEnd',
+ 'GesturePinchUpdate',
+ 'KeyDown',
+ 'KeyUp',
+ 'MouseDown',
+ 'MouseEnter',
+ 'MouseLeave',
+ 'MouseMove',
+ 'MouseUp',
+ 'MouseWheel',
+ 'RawKeyDown',
+ 'ScrollUpdate',
+ 'TouchCancel',
+ 'TouchEnd',
+ 'TouchMove',
+ 'TouchStart'
+ ];
+ const allTypeNames = ['InputLatency'];
+ eventTypeNames.forEach(function(eventTypeName) {
+ // Old style.
+ allTypeNames.push('InputLatency:' + eventTypeName);
+
+ // New style.
+ allTypeNames.push('InputLatency::' + eventTypeName);
+ });
+
+ AsyncSlice.subTypes.register(
+ InputLatencyAsyncSlice,
+ {
+ typeNames: allTypeNames,
+ categoryParts: ['latencyInfo']
+ });
+
+ return {
+ InputLatencyAsyncSlice,
+ INPUT_EVENT_TYPE_NAMES,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/input_latency_async_slice_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/input_latency_async_slice_test.html
new file mode 100644
index 00000000000..e65613ea647
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/input_latency_async_slice_test.html
@@ -0,0 +1,702 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/cc/input_latency_async_slice.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/model_indices.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const newAsyncSliceEx = tr.c.TestUtils.newAsyncSliceEx;
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+ const newFlowEventEx = tr.c.TestUtils.newFlowEventEx;
+ const newModel = tr.c.TestUtils.newModel;
+ const EventSet = tr.model.EventSet;
+
+ test('matchByType_oldStyle', function() {
+ const sOuter = newAsyncSliceEx({
+ title: 'InputLatency',
+ cat: 'benchmark',
+ start: 0,
+ end: 10,
+ id: '0x100',
+ isTopLevel: true,
+ args: {
+ data: {
+ INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT: {'time': 0},
+ INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT: {time: 10}
+ }
+ }
+ });
+ assert.throws(function() {
+ sOuter.typeName;
+ });
+
+ const sInner = newAsyncSliceEx({
+ title: 'InputLatency:GestureScrollUpdate',
+ cat: 'benchmark',
+ start: 2,
+ end: 10,
+ id: '0x100',
+ args: {
+ 'step': 'GestureScrollUpdate'
+ }
+ });
+ sOuter.subSlices.push(sInner);
+ assert.isTrue(sOuter instanceof tr.e.cc.InputLatencyAsyncSlice);
+ assert.isTrue(sInner instanceof tr.e.cc.InputLatencyAsyncSlice);
+ assert.strictEqual(sOuter.inputLatency, 10);
+ assert.strictEqual(
+ tr.e.cc.INPUT_EVENT_TYPE_NAMES.SCROLL_UPDATE, sInner.typeName);
+ assert.strictEqual(
+ tr.e.cc.INPUT_EVENT_TYPE_NAMES.SCROLL_UPDATE, sOuter.typeName);
+ });
+
+ test('matchByType_newStyle', function() {
+ const sInfo = newAsyncSliceEx({
+ title: 'InputLatency::GestureScrollUpdate',
+ cat: 'benchmark',
+ start: 2,
+ end: 10,
+ id: '0x100',
+ isTopLevel: true,
+ args: {
+ data: {
+ INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT: {'time': 0},
+ INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT: {time: 10}
+ }
+ }
+ });
+
+ assert.isTrue(sInfo instanceof tr.e.cc.InputLatencyAsyncSlice);
+ assert.strictEqual(sInfo.inputLatency, 10);
+ assert.strictEqual(
+ tr.e.cc.INPUT_EVENT_TYPE_NAMES.SCROLL_UPDATE, sInfo.typeName);
+ });
+
+ test('unknownType', function() {
+ const sInfo = newAsyncSliceEx({
+ title: 'InputLatency::BadTypeName',
+ cat: 'benchmark,latencyInfo',
+ start: 2,
+ end: 10,
+ id: '0x100',
+ isTopLevel: true,
+ args: {
+ data: {
+ INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT: {'time': 0},
+ INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT: {'time': 0},
+ INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT: {time: 10}
+ }
+ }
+ });
+ assert.strictEqual(tr.e.cc.INPUT_EVENT_TYPE_NAMES.UNKNOWN, sInfo.typeName);
+ });
+
+ test('getAssociatedEventsBypassRendererMain', function() {
+ const m = newModel(function(m) {
+ const pb = m.getOrCreateProcess(1);
+ const pr = m.getOrCreateProcess(2);
+ const mainBrowserThread = pb.getOrCreateThread(10);
+ const mainRendererThread = pr.getOrCreateThread(20);
+ const compositorThread = pr.getOrCreateThread(21);
+
+ mainBrowserThread.name = 'CrBrowserMain';
+ mainRendererThread.name = 'CrRendererMain';
+ compositorThread.name = 'Compositor';
+
+ // Expectation: None of s2 and s3 should be included
+ // CrBrowserMain: [s0] [s1]
+ // | /|\
+ // CrRendererMain: | [s2] [s3] |
+ // \|/ |
+ // Compositor: [s4]-------------|
+
+ m.s0 = mainBrowserThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's0', start: 0.0, duration: 1.0 }));
+ m.s1 = mainBrowserThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's1', start: 6.0, duration: 1.0 }));
+ m.s2 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's2', start: 2.0, duration: 1.0 }));
+ m.s3 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's3', start: 4.0, duration: 1.0 }));
+ m.s4 = compositorThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's4', start: 0.5, duration: 1.0 }));
+
+ m.f1 = newFlowEventEx({
+ title: 'test1',
+ start: 0,
+ end: 10,
+ startSlice: m.s0,
+ endSlice: m.s4,
+ id: '0x100'
+ });
+
+ m.f2 = newFlowEventEx({
+ title: 'test2',
+ start: 20,
+ end: 30,
+ startSlice: m.s4,
+ endSlice: m.s1,
+ id: '0x100'
+ });
+
+ m.flowEvents.push(m.f1);
+ m.flowEvents.push(m.f2);
+
+ m.as0 = newAsyncSliceEx({
+ title: 'test1',
+ cat: 'benchmark,latencyInfo',
+ start: 2,
+ end: 10,
+ id: '0x101',
+ isTopLevel: true,
+ startThread: mainBrowserThread
+ });
+
+ m.as1 = newAsyncSliceEx({
+ title: 'test2',
+ cat: 'benchmark,latencyInfo',
+ start: 2,
+ end: 10,
+ id: '0x100',
+ isTopLevel: true,
+ startThread: compositorThread
+ });
+ });
+
+ assert.isTrue(m.as0.associatedEvents.length === 0);
+ assert.isTrue(m.as1.associatedEvents.equals(
+ new EventSet([m.f1, m.s0, m.f2, m.s4, m.s1])));
+ });
+
+ test('getAssociatedEventsBypassRendererMainWithOnScroll', function() {
+ const m = newModel(function(m) {
+ const pb = m.getOrCreateProcess(1);
+ const pr = m.getOrCreateProcess(2);
+ const mainBrowserThread = pb.getOrCreateThread(10);
+ const mainRendererThread = pr.getOrCreateThread(20);
+ const compositorThread = pr.getOrCreateThread(21);
+
+ mainBrowserThread.name = 'CrBrowserMain';
+ mainRendererThread.name = 'CrRendererMain';
+ compositorThread.name = 'Compositor';
+
+ // Expectation: s2 should be included but not s3
+ // GestureScrollUpdate: [ as1 ]
+ // CrBrowserMain: [s0] [s1]
+ // | /|\
+ // CrRendererMain: | [s2] [s3] |
+ // \|/ /|\ |
+ // Compositor: [s4]___|__________|
+ // ScrollUpdate: [ as2 ]
+
+ m.s0 = mainBrowserThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's0', start: 0.0, duration: 1.0 }));
+ m.s1 = mainBrowserThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's1', start: 6.0, duration: 1.0 }));
+ m.s2 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's2', start: 2.0, duration: 1.0 }));
+ m.s3 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's3', start: 4.0, duration: 1.0 }));
+ m.s4 = compositorThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's4', start: 0.5, duration: 1.0 }));
+
+ m.f1 = newFlowEventEx({
+ title: 'f1',
+ start: 0,
+ end: 10,
+ startSlice: m.s0,
+ endSlice: m.s4,
+ id: '0x100'
+ });
+
+ m.f2 = newFlowEventEx({
+ title: 'f2',
+ start: 20,
+ end: 30,
+ startSlice: m.s4,
+ endSlice: m.s1,
+ id: '0x100'
+ });
+
+ m.f3 = newFlowEventEx({
+ title: 'f3',
+ start: 20,
+ end: 30,
+ startSlice: m.s4,
+ endSlice: m.s2,
+ id: '0x800'
+ });
+
+ m.flowEvents.push(m.f1);
+ m.flowEvents.push(m.f2);
+ m.flowEvents.push(m.f3);
+
+ m.as0 = mainBrowserThread.asyncSliceGroup.push(newAsyncSliceEx({
+ title: 'InputLatency::GestureScrollUpdate',
+ cat: 'benchmark,latencyInfo',
+ args: {
+ data: {
+ INPUT_EVENT_LATENCY_FORWARD_SCROLL_UPDATE_TO_MAIN_COMPONENT: 100
+ }
+ },
+ start: 2,
+ end: 10,
+ id: '0x101',
+ isTopLevel: true,
+ startThread: mainBrowserThread
+ }));
+ assert.strictEqual(tr.e.cc.INPUT_EVENT_TYPE_NAMES.SCROLL_UPDATE,
+ m.as0.typeName);
+
+ m.as1 = mainBrowserThread.asyncSliceGroup.push(newAsyncSliceEx({
+ title: 'InputLatency::GestureScrollUpdate',
+ cat: 'benchmark,latencyInfo',
+ args: {
+ data: {}
+ },
+ start: 0,
+ end: 10,
+ id: '0x100',
+ isTopLevel: true,
+ startThread: mainBrowserThread
+ }));
+ assert.strictEqual(tr.e.cc.INPUT_EVENT_TYPE_NAMES.SCROLL_UPDATE,
+ m.as1.typeName);
+
+ m.as2 = compositorThread.asyncSliceGroup.push(newAsyncSliceEx({
+ title: 'Latency::ScrollUpdate',
+ cat: 'benchmark,latencyInfo',
+ args: {
+ data: {
+ INPUT_EVENT_LATENCY_FORWARD_SCROLL_UPDATE_TO_MAIN_COMPONENT: 100
+ }
+ },
+ start: 1.5,
+ end: 8,
+ id: '0x800',
+ isTopLevel: true,
+ startThread: compositorThread
+ }));
+ assert.strictEqual(tr.e.cc.INPUT_EVENT_TYPE_NAMES.LATENCY_SCROLL_UPDATE,
+ m.as2.typeName);
+ });
+
+ assert.isTrue(m.as0.associatedEvents.length === 0);
+ assert.isTrue(m.as1.associatedEvents.equals(
+ new EventSet([m.f1, m.s0, m.f2, m.s4, m.s1, m.f3, m.s2])));
+ });
+
+ test('getAssociatedEventsWithoutCommit', function() {
+ const m = newModel(function(m) {
+ const pb = m.getOrCreateProcess(1);
+ const pr = m.getOrCreateProcess(2);
+ const mainBrowserThread = pb.getOrCreateThread(10);
+ const mainRendererThread = pr.getOrCreateThread(20);
+ const compositorThread = pr.getOrCreateThread(21);
+
+ mainBrowserThread.name = 'CrBrowserMain';
+ mainRendererThread.name = 'CrRendererMain';
+ compositorThread.name = 'Compositor';
+
+ // Expectation: none of s3 and s5 should be included
+ // CrBrowserMain: [s0] [s1]
+ // | /|\
+ // | __________|
+ // | |
+ // CrRendererMain: | [s2] [s3] [s5]
+ // \|/ /|\
+ // Compositor: [s4]___|
+
+ m.s0 = mainBrowserThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's0', start: 0.0, duration: 1.0 }));
+ m.s1 = mainBrowserThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's1', start: 6.0, duration: 1.0 }));
+ m.s2 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's2', start: 2.0, duration: 1.0 }));
+ m.s3 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's3', start: 4.0, duration: 1.0 }));
+ m.s4 = compositorThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's4', start: 0.5, duration: 1.0 }));
+ m.s5 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's5', start: 100.0, duration: 1.0 }));
+
+ m.f1 = newFlowEventEx({
+ title: 'f1',
+ start: 0,
+ end: 10,
+ startSlice: m.s0,
+ endSlice: m.s4,
+ id: '0x100'
+ });
+
+ m.f2 = newFlowEventEx({
+ title: 'f2',
+ start: 20,
+ end: 30,
+ startSlice: m.s4,
+ endSlice: m.s2,
+ id: '0x100'
+ });
+
+ m.f3 = newFlowEventEx({
+ title: 'f3',
+ start: 20,
+ end: 30,
+ startSlice: m.s2,
+ endSlice: m.s1,
+ id: '0x100'
+ });
+
+ m.flowEvents.push(m.f1);
+ m.flowEvents.push(m.f2);
+ m.flowEvents.push(m.f3);
+
+ m.as0 = newAsyncSliceEx({
+ title: 'test1',
+ cat: 'benchmark,latencyInfo',
+ start: 2,
+ end: 10,
+ id: '0x101',
+ isTopLevel: true,
+ startThread: mainBrowserThread
+ });
+
+ m.as1 = newAsyncSliceEx({
+ title: 'test2',
+ cat: 'benchmark,latencyInfo',
+ start: 2,
+ end: 10,
+ id: '0x100',
+ isTopLevel: true,
+ startThread: mainBrowserThread
+ });
+ });
+
+ assert.isTrue(m.as0.associatedEvents.length === 0);
+ assert.isTrue(m.as1.associatedEvents.equals(
+ new EventSet([m.f1, m.s0, m.f2, m.s4, m.f3, m.s2, m.s1])));
+ });
+
+ test('getAssociatedEventsWillCommit', function() {
+ const m = newModel(function(m) {
+ const pb = m.getOrCreateProcess(1);
+ const pr = m.getOrCreateProcess(2);
+ const mainBrowserThread = pb.getOrCreateThread(10);
+ const mainRendererThread = pr.getOrCreateThread(20);
+ const compositorThread = pr.getOrCreateThread(21);
+
+ mainBrowserThread.name = 'CrBrowserMain';
+ mainRendererThread.name = 'CrRendererMain';
+ compositorThread.name = 'Compositor';
+
+ // Expectation: s3 should be included by getOtherCasuallyRelatedEvents,
+ // because there is a PostTask s7 -> s3, but s6 shouldn't be included.
+ // CrBrowserMain: [s0] [ s1 ]
+ // | /|\
+ // | |
+ // | [ s2 ]____ |
+ // CrRendererMain: | /|\ [s7] | | [s6]
+ // | | | | |
+ // | | | | |
+ // \|/ | \|/ \|/ |
+ // Compositor: [s4]__| [s3] [s5]__|
+
+ m.s0 = mainBrowserThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's0', start: 0.0, duration: 1.0 }));
+ m.s1 = mainBrowserThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's1', start: 6.0, duration: 1.0 }));
+ m.s2 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's2', start: 2.0, duration: 1.0 }));
+ m.s3 = compositorThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's3', start: 4.0, duration: 1.0 }));
+ m.s4 = compositorThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's4', start: 0.5, duration: 1.0 }));
+ m.s5 = compositorThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's5', start: 5.5, duration: 1.0 }));
+ m.s6 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's6', start: 1000.0, duration: 1.0 }));
+ m.s7 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's7', start: 2.5, duration: 0.2 }));
+
+ mainBrowserThread.sliceGroup.createSubSlices();
+ mainRendererThread.sliceGroup.createSubSlices();
+ compositorThread.sliceGroup.createSubSlices();
+
+ m.f1 = newFlowEventEx({
+ title: 'f1',
+ start: 0,
+ end: 10,
+ startSlice: m.s0,
+ endSlice: m.s4,
+ id: '0x100'
+ });
+
+ m.f2 = newFlowEventEx({
+ title: 'f2',
+ start: 20,
+ end: 30,
+ startSlice: m.s4,
+ endSlice: m.s2,
+ id: '0x100'
+ });
+
+ m.f3 = newFlowEventEx({
+ title: 'f3',
+ start: 20,
+ end: 30,
+ startSlice: m.s2,
+ endSlice: m.s5,
+ id: '0x100'
+ });
+
+ m.f4 = newFlowEventEx({
+ title: 'f4',
+ start: 20,
+ end: 30,
+ startSlice: m.s5,
+ endSlice: m.s1,
+ id: '0x100'
+ });
+
+ m.f5 = newFlowEventEx({
+ title: 'f5',
+ cat: 'disabled-by-default-toplevel.flow',
+ start: 20,
+ end: 30,
+ startSlice: m.s7,
+ endSlice: m.s3,
+ id: '0xAAA'
+ });
+
+ m.flowEvents.push(m.f1);
+ m.flowEvents.push(m.f2);
+ m.flowEvents.push(m.f3);
+ m.flowEvents.push(m.f4);
+ m.flowEvents.push(m.f5);
+
+ m.as0 = newAsyncSliceEx({
+ title: 'test1',
+ cat: 'benchmark,latencyInfo',
+ start: 2,
+ end: 10,
+ id: '0x101',
+ isTopLevel: true,
+ startThread: mainBrowserThread
+ });
+
+ m.as1 = newAsyncSliceEx({
+ title: 'test2',
+ cat: 'benchmark,latencyInfo',
+ start: 2,
+ end: 10,
+ id: '0x100',
+ isTopLevel: true,
+ startThread: mainBrowserThread
+ });
+ });
+
+ assert.isTrue(m.as0.associatedEvents.length === 0);
+ assert.isTrue(m.as1.associatedEvents.equals(new EventSet(
+ [m.f1, m.f2, m.f3, m.f4, m.f5,
+ m.s0, m.s1, m.s2, m.s3, m.s4, m.s5, m.s7])));
+ });
+
+ test('getAssociatedEventsExcludeOtherInputs', function() {
+ const m = newModel(function(m) {
+ const pb = m.getOrCreateProcess(1);
+ const pr = m.getOrCreateProcess(2);
+ const mainBrowserThread = pb.getOrCreateThread(10);
+ const mainRendererThread = pr.getOrCreateThread(20);
+ const compositorThread = pr.getOrCreateThread(21);
+
+ mainBrowserThread.name = 'CrBrowserMain';
+ mainRendererThread.name = 'CrRendererMain';
+ compositorThread.name = 'Compositor';
+
+ // Expectation: s3 should be included by getOtherCasuallyRelatedEvents,
+ // because there is a PostTask s7 -> s3. Even though there is also a
+ // PostTask from s9 to s6, s6 shouldn't be included because it's tracked
+ // by LatencyInfo of another input.
+ // CrBrowserMain: [s0] [ s1 ] [s8]
+ // | /|\ |
+ // | | |
+ // | [ s2 ]____ | \|/
+ // CrRendererMain: | /|\ [s7] | | [s6]
+ // | | | | | /|\
+ // | | | | | |
+ // \|/ | \|/ \|/ | |
+ // Compositor: [s4]__| [s3] [ s5 ]_| |
+ // [s9]_________|
+
+ m.s0 = mainBrowserThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's0', start: 0.0, duration: 1.0 }));
+ m.s1 = mainBrowserThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's1', start: 6.0, duration: 1.0 }));
+ m.s2 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's2', start: 2.0, duration: 1.0 }));
+ m.s3 = compositorThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's3', start: 4.0, duration: 1.0 }));
+ m.s4 = compositorThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's4', start: 0.5, duration: 1.0 }));
+ m.s5 = compositorThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's5', start: 5.5, duration: 1.0 }));
+ m.s6 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's6', start: 10.0, duration: 1.0 }));
+ m.s7 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's7', start: 2.5, duration: 0.2 }));
+ m.s8 = mainRendererThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 's8', start: 9.5, duration: 1.0 }));
+ m.s9 = compositorThread.sliceGroup.pushSlice(newSliceEx(
+ { title: 'Scheduler::ScheduleBeginImplFrameDeadline',
+ start: 5.7, duration: 0.2 }));
+
+ mainBrowserThread.sliceGroup.createSubSlices();
+ mainRendererThread.sliceGroup.createSubSlices();
+ compositorThread.sliceGroup.createSubSlices();
+
+ m.f1 = newFlowEventEx({
+ title: 'f1',
+ cat: 'input',
+ start: 0,
+ end: 10,
+ startSlice: m.s0,
+ endSlice: m.s4,
+ id: '0x100'
+ });
+
+ m.f2 = newFlowEventEx({
+ title: 'f2',
+ cat: 'input',
+ start: 20,
+ end: 30,
+ startSlice: m.s4,
+ endSlice: m.s2,
+ id: '0x100'
+ });
+
+ m.f3 = newFlowEventEx({
+ title: 'f3',
+ cat: 'input',
+ start: 20,
+ end: 30,
+ startSlice: m.s2,
+ endSlice: m.s5,
+ id: '0x100'
+ });
+
+ m.f4 = newFlowEventEx({
+ title: 'f4',
+ cat: 'input',
+ start: 20,
+ end: 30,
+ startSlice: m.s5,
+ endSlice: m.s1,
+ id: '0x100'
+ });
+
+ m.f5 = newFlowEventEx({
+ title: 'f5',
+ cat: 'disabled-by-default-toplevel.flow',
+ start: 20,
+ end: 30,
+ startSlice: m.s7,
+ endSlice: m.s3,
+ id: '0xAAA'
+ });
+
+ m.f6 = newFlowEventEx({
+ title: 'f6',
+ cat: 'disabled-by-default-toplevel.flow',
+ start: 20,
+ end: 30,
+ startSlice: m.s9,
+ endSlice: m.s6,
+ id: '0xAAB'
+ });
+
+ m.f7 = newFlowEventEx({
+ title: 'f7',
+ cat: 'input',
+ start: 20,
+ end: 30,
+ startSlice: m.s8,
+ endSlice: m.s6,
+ id: '0x102'
+ });
+
+ m.flowEvents.push(m.f1);
+ m.flowEvents.push(m.f2);
+ m.flowEvents.push(m.f3);
+ m.flowEvents.push(m.f4);
+ m.flowEvents.push(m.f5);
+ m.flowEvents.push(m.f6);
+ m.flowEvents.push(m.f7);
+
+ m.as0 = newAsyncSliceEx({
+ title: 'test1',
+ cat: 'benchmark,latencyInfo',
+ start: 2,
+ end: 10,
+ id: '0x101',
+ isTopLevel: true,
+ startThread: mainBrowserThread
+ });
+
+ m.as1 = newAsyncSliceEx({
+ title: 'test2',
+ cat: 'benchmark,latencyInfo',
+ start: 2,
+ end: 10,
+ id: '0x100',
+ isTopLevel: true,
+ startThread: mainBrowserThread
+ });
+
+ m.as2 = newAsyncSliceEx({
+ title: 'test2',
+ cat: 'benchmark,latencyInfo',
+ start: 2,
+ end: 10,
+ id: '0x102',
+ isTopLevel: true,
+ startThread: mainBrowserThread
+ });
+ });
+
+ assert.isTrue(m.as0.associatedEvents.length === 0);
+ assert.isTrue(m.as1.associatedEvents.equals(new EventSet(
+ [m.f1, m.f2, m.f3, m.f4, m.f5,
+ m.s0, m.s1, m.s2, m.s3, m.s4, m.s5, m.s7, m.s9])));
+ });
+
+ test('legacyEndComponent', function() {
+ const sInfo = newAsyncSliceEx({
+ title: 'InputLatency::GestureScrollUpdate',
+ cat: 'benchmark',
+ start: 2,
+ end: 10,
+ id: '0x100',
+ isTopLevel: true,
+ args: {
+ data: {
+ INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT: {'time': 0},
+ INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT: {time: 10}
+ }
+ }
+ });
+
+ assert.isTrue(sInfo instanceof tr.e.cc.InputLatencyAsyncSlice);
+ assert.strictEqual(sInfo.inputLatency, 10);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_impl.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_impl.html
new file mode 100644
index 00000000000..caf63707186
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_impl.html
@@ -0,0 +1,228 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/math/rect.html">
+<link rel="import" href="/tracing/extras/chrome/cc/constants.html">
+<link rel="import" href="/tracing/extras/chrome/cc/region.html">
+<link rel="import" href="/tracing/extras/chrome/cc/tile_coverage_rect.html">
+<link rel="import" href="/tracing/model/object_instance.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.cc', function() {
+ const constants = tr.e.cc.constants;
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+
+ /**
+ * @constructor
+ */
+ function LayerImplSnapshot() {
+ ObjectSnapshot.apply(this, arguments);
+ }
+
+ LayerImplSnapshot.prototype = {
+ __proto__: ObjectSnapshot.prototype,
+
+ preInitialize() {
+ tr.e.cc.preInitializeObject(this);
+
+ this.layerTreeImpl_ = undefined;
+ this.parentLayer = undefined;
+ },
+
+ initialize() {
+ // Defaults.
+ this.invalidation = new tr.e.cc.Region();
+ this.unrecordedRegion = new tr.e.cc.Region();
+ this.pictures = [];
+
+ // Import & validate this.args
+ tr.e.cc.moveRequiredFieldsFromArgsToToplevel(
+ this, ['layerId', 'layerQuad']);
+ tr.e.cc.moveOptionalFieldsFromArgsToToplevel(
+ this, ['children', 'maskLayer', 'replicaLayer',
+ 'idealContentsScale', 'geometryContentsScale',
+ 'layoutRects', 'usingGpuRasterization']);
+
+ // Leave gpu memory usage in both places.
+ this.gpuMemoryUsageInBytes = this.args.gpuMemoryUsage;
+
+ // Leave bounds in both places.
+ this.bounds = tr.b.math.Rect.fromXYWH(
+ 0, 0,
+ this.args.bounds.width, this.args.bounds.height);
+
+ if (this.args.animationBounds) {
+ // AnimationBounds[2] and [5] are the Z-component of the box.
+ this.animationBoundsRect = tr.b.math.Rect.fromXYWH(
+ this.args.animationBounds[0], this.args.animationBounds[1],
+ this.args.animationBounds[3], this.args.animationBounds[4]);
+ }
+
+ // After Slimming Paint v2, compositor no longer knows hierarchy
+ // information. If the children value is undefined, the tracing
+ // data comes from the new version of cc, otherwise we set the
+ // parentLayer as we did before SPv2.
+ if (this.children) {
+ for (let i = 0; i < this.children.length; i++) {
+ this.children[i].parentLayer = this;
+ }
+ }
+ if (this.maskLayer) {
+ this.maskLayer.parentLayer = this;
+ }
+ if (this.replicaLayer) {
+ this.replicaLayer.parentLayer = this;
+ }
+ if (!this.geometryContentsScale) {
+ this.geometryContentsScale = 1.0;
+ }
+ if (!this.idealContentsScale) {
+ this.idealContentsScale = 1.0;
+ }
+
+ this.touchEventHandlerRegion = tr.e.cc.Region.fromArrayOrUndefined(
+ this.args.touchEventHandlerRegion);
+ this.wheelEventHandlerRegion = tr.e.cc.Region.fromArrayOrUndefined(
+ this.args.wheelEventHandlerRegion);
+ this.nonFastScrollableRegion = tr.e.cc.Region.fromArrayOrUndefined(
+ this.args.nonFastScrollableRegion);
+ },
+
+ get layerTreeImpl() {
+ if (this.layerTreeImpl_) {
+ return this.layerTreeImpl_;
+ }
+ if (this.parentLayer) {
+ return this.parentLayer.layerTreeImpl;
+ }
+ return undefined;
+ },
+ set layerTreeImpl(layerTreeImpl) {
+ this.layerTreeImpl_ = layerTreeImpl;
+ },
+
+ get activeLayer() {
+ if (this.layerTreeImpl.whichTree === constants.ACTIVE_TREE) {
+ return this;
+ }
+ const activeTree = this.layerTreeImpl.layerTreeHostImpl.activeTree;
+ return activeTree.findLayerWithId(this.layerId);
+ },
+
+ get pendingLayer() {
+ if (this.layerTreeImpl.whichTree === constants.PENDING_TREE) {
+ return this;
+ }
+ const pendingTree = this.layerTreeImpl.layerTreeHostImpl.pendingTree;
+ return pendingTree.findLayerWithId(this.layerId);
+ }
+ };
+
+ /**
+ * @constructor
+ */
+ function PictureLayerImplSnapshot() {
+ LayerImplSnapshot.apply(this, arguments);
+ }
+
+ PictureLayerImplSnapshot.prototype = {
+ __proto__: LayerImplSnapshot.prototype,
+
+ initialize() {
+ LayerImplSnapshot.prototype.initialize.call(this);
+
+ // Backward compatibility: the new format puts debug info fields under
+ // 'debugInfo', while the old format puts them directly under args.
+ if (this.args.debugInfo) {
+ for (const i in this.args.debugInfo) {
+ this.args[i] = this.args.debugInfo[i];
+ }
+ delete this.args.debugInfo;
+ }
+
+ if (this.args.annotatedInvalidationRects) {
+ this.invalidation = new tr.e.cc.Region();
+ for (const annotatedRect of this.args.annotatedInvalidationRects) {
+ const rect = annotatedRect.geometryRect;
+ rect.reason = annotatedRect.reason;
+ rect.client = annotatedRect.client;
+ this.invalidation.addRect(rect);
+ }
+ delete this.args.annotatedInvalidationRects;
+ } else if (this.args.invalidation) {
+ // Use unannotated invalidation rect if no annotated rects are
+ // available.
+ this.invalidation = tr.e.cc.Region.fromArray(this.args.invalidation);
+ }
+ delete this.args.invalidation;
+
+ if (this.args.unrecordedRegion) {
+ this.unrecordedRegion = tr.e.cc.Region.fromArray(
+ this.args.unrecordedRegion);
+ delete this.args.unrecordedRegion;
+ }
+
+ if (this.args.pictures) {
+ this.pictures = this.args.pictures;
+
+ // The picture list comes in with an unknown ordering. We resort based
+ // on timestamp order so we will draw the base picture first and the
+ // various fixes on top of that.
+ this.pictures.sort(function(a, b) { return a.ts - b.ts; });
+ }
+
+ this.tileCoverageRects = [];
+ if (this.args.coverageTiles) {
+ for (let i = 0; i < this.args.coverageTiles.length; ++i) {
+ const rect = this.args.coverageTiles[i].geometryRect.scale(
+ this.idealContentsScale);
+ const tile = this.args.coverageTiles[i].tile;
+ this.tileCoverageRects.push(new tr.e.cc.TileCoverageRect(rect, tile));
+ }
+ delete this.args.coverageTiles;
+ }
+ }
+ };
+
+ ObjectSnapshot.subTypes.register(
+ PictureLayerImplSnapshot,
+ {
+ typeName: 'cc::PictureLayerImpl'
+ });
+
+ ObjectSnapshot.subTypes.register(
+ LayerImplSnapshot,
+ {
+ typeNames: [
+ 'cc::LayerImpl',
+ 'cc::DelegatedRendererLayerImpl',
+ 'cc::HeadsUpDisplayLayerImpl',
+ 'cc::IOSurfaceLayerImpl',
+ 'cc::NinePatchLayerImpl',
+ 'cc::PictureImageLayerImpl',
+ 'cc::ScrollbarLayerImpl',
+ 'cc::SolidColorLayerImpl',
+ 'cc::SolidColorScrollbarLayerImpl',
+ 'cc::SurfaceLayerImpl',
+ 'cc::TextureLayerImpl',
+ 'cc::TiledLayerImpl',
+ 'cc::VideoLayerImpl',
+ 'cc::PaintedScrollbarLayerImpl',
+ 'ClankPatchLayer',
+ 'TabBorderLayer',
+ 'CounterLayer'
+ ]
+ });
+
+ return {
+ LayerImplSnapshot,
+ PictureLayerImplSnapshot,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_host_impl.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_host_impl.html
new file mode 100644
index 00000000000..d857a3b5bbd
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_host_impl.html
@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/math/bbox2.html">
+<link rel="import" href="/tracing/extras/chrome/cc/constants.html">
+<link rel="import" href="/tracing/extras/chrome/cc/layer_tree_impl.html">
+<link rel="import" href="/tracing/extras/chrome/cc/util.html">
+<link rel="import" href="/tracing/model/object_instance.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the LayerTreeHostImpl model-level objects.
+ */
+tr.exportTo('tr.e.cc', function() {
+ const constants = tr.e.cc.constants;
+
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+ const ObjectInstance = tr.model.ObjectInstance;
+
+ /**
+ * @constructor
+ */
+ function LayerTreeHostImplSnapshot() {
+ ObjectSnapshot.apply(this, arguments);
+ }
+
+ LayerTreeHostImplSnapshot.prototype = {
+ __proto__: ObjectSnapshot.prototype,
+
+ preInitialize() {
+ tr.e.cc.preInitializeObject(this);
+ },
+
+ initialize() {
+ tr.e.cc.moveRequiredFieldsFromArgsToToplevel(
+ this, ['deviceViewportSize',
+ 'activeTree']);
+ tr.e.cc.moveOptionalFieldsFromArgsToToplevel(
+ this, ['pendingTree']);
+
+ // Move active_tiles into this.tiles. If that doesn't exist then, then as
+ // a backward compatability move tiles into this.tiles.
+ if (this.args.activeTiles !== undefined) {
+ this.activeTiles = this.args.activeTiles;
+ delete this.args.activeTiles;
+ } else if (this.args.tiles !== undefined) {
+ this.activeTiles = this.args.tiles;
+ delete this.args.tiles;
+ }
+
+ if (!this.activeTiles) {
+ this.activeTiles = [];
+ }
+
+ this.activeTree.layerTreeHostImpl = this;
+ this.activeTree.whichTree = constants.ACTIVE_TREE;
+ if (this.pendingTree) {
+ this.pendingTree.layerTreeHostImpl = this;
+ this.pendingTree.whichTree = constants.PENDING_TREE;
+ }
+ },
+
+ /**
+ * Get all of tile scales and their associated names.
+ */
+ getContentsScaleNames() {
+ const scales = {};
+ for (let i = 0; i < this.activeTiles.length; ++i) {
+ const tile = this.activeTiles[i];
+ // Return scale -> scale name mappings.
+ // Example:
+ // 0.25 -> LOW_RESOLUTION
+ // 1.0 -> HIGH_RESOLUTION
+ // 0.75 -> NON_IDEAL_RESOLUTION
+ scales[tile.contentsScale] = tile.resolution;
+ }
+ return scales;
+ },
+
+ getTree(whichTree) {
+ if (whichTree === constants.ACTIVE_TREE) {
+ return this.activeTree;
+ }
+ if (whichTree === constants.PENDING_TREE) {
+ return this.pendingTree;
+ }
+ throw new Exception('Unknown tree type + ' + whichTree);
+ },
+
+ get tilesHaveGpuMemoryUsageInfo() {
+ if (this.tilesHaveGpuMemoryUsageInfo_ !== undefined) {
+ return this.tilesHaveGpuMemoryUsageInfo_;
+ }
+
+ for (let i = 0; i < this.activeTiles.length; i++) {
+ if (this.activeTiles[i].gpuMemoryUsageInBytes === undefined) {
+ continue;
+ }
+ this.tilesHaveGpuMemoryUsageInfo_ = true;
+ return true;
+ }
+ this.tilesHaveGpuMemoryUsageInfo_ = false;
+ return false;
+ },
+
+ get gpuMemoryUsageInBytes() {
+ if (!this.tilesHaveGpuMemoryUsageInfo) return;
+
+ let usage = 0;
+ for (let i = 0; i < this.activeTiles.length; i++) {
+ const u = this.activeTiles[i].gpuMemoryUsageInBytes;
+ if (u !== undefined) usage += u;
+ }
+ return usage;
+ },
+
+ get userFriendlyName() {
+ let frameNumber;
+ if (!this.activeTree) {
+ frameNumber = this.objectInstance.snapshots.indexOf(this);
+ } else {
+ if (this.activeTree.sourceFrameNumber === undefined) {
+ frameNumber = this.objectInstance.snapshots.indexOf(this);
+ } else {
+ frameNumber = this.activeTree.sourceFrameNumber;
+ }
+ }
+ return 'cc::LayerTreeHostImpl frame ' + frameNumber;
+ }
+ };
+
+ ObjectSnapshot.subTypes.register(
+ LayerTreeHostImplSnapshot,
+ {typeName: 'cc::LayerTreeHostImpl'});
+
+ /**
+ * @constructor
+ */
+ function LayerTreeHostImplInstance() {
+ ObjectInstance.apply(this, arguments);
+
+ this.allLayersBBox_ = undefined;
+ }
+
+ LayerTreeHostImplInstance.prototype = {
+ __proto__: ObjectInstance.prototype,
+
+ get allContentsScales() {
+ if (this.allContentsScales_) {
+ return this.allContentsScales_;
+ }
+
+ const scales = {};
+ for (const tileID in this.allTileHistories_) {
+ const tileHistory = this.allTileHistories_[tileID];
+ scales[tileHistory.contentsScale] = true;
+ }
+ this.allContentsScales_ = Object.keys(scales);
+ return this.allContentsScales_;
+ },
+
+ get allLayersBBox() {
+ if (this.allLayersBBox_) {
+ return this.allLayersBBox_;
+ }
+ const bbox = new tr.b.math.BBox2();
+ function handleTree(tree) {
+ tree.renderSurfaceLayerList.forEach(function(layer) {
+ bbox.addQuad(layer.layerQuad);
+ });
+ }
+ this.snapshots.forEach(function(lthi) {
+ handleTree(lthi.activeTree);
+ if (lthi.pendingTree) {
+ handleTree(lthi.pendingTree);
+ }
+ });
+ this.allLayersBBox_ = bbox;
+ return this.allLayersBBox_;
+ }
+ };
+
+ ObjectInstance.subTypes.register(
+ LayerTreeHostImplInstance,
+ {typeName: 'cc::LayerTreeHostImpl'});
+
+ return {
+ LayerTreeHostImplSnapshot,
+ LayerTreeHostImplInstance,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_host_impl_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_host_impl_test.html
new file mode 100644
index 00000000000..207ae0a4b11
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_host_impl_test.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/cc/layer_tree_host_impl.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script src="/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js">
+</script>
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('basic', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = Object.values(m.processes)[0];
+
+ const instance = p.objects.getAllInstancesNamed('cc::LayerTreeHostImpl')[0];
+ const snapshot = instance.snapshots[0];
+
+ assert.instanceOf(snapshot, tr.e.cc.LayerTreeHostImplSnapshot);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js
new file mode 100644
index 00000000000..b8e443aa0a1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js
@@ -0,0 +1,345 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+// A single LTHI sort of manually created from a Google search for cats.
+// This cannot be const because this file may be loaded more than once.
+global.g_catLTHIEvents = [
+ {
+ 'name': 'cc::Picture',
+ 'args': {
+ 'snapshot': {
+ 'params': {
+ 'opaque_rect': [
+ -15,
+ -15,
+ 0,
+ 0
+ ],
+ 'layer_rect': [
+ -15,
+ -15,
+ 1260,
+ 1697
+ ]
+ },
+ 'skp64': 'c2tpYXBpY3QWAAAAOAQAABQDAAADAAAAAWRhZXKoCQAACAAAHgMAAAAIAAAeAwAAAAwAACMAAHBBAABwQRwAAAMAAHDBAABwwQAghUQAQEFEAQAAAKAJAAAYAAAVAQAAAAAAAAAAAAAAAECDRACAPUQIAAAeAwAAABwAAAMAAAAAAAAAAABAg0QAAMhBAQAAAIwAAAAYAAAVAgAAAAAAAAAAAAAAAECDRAAAyEEEAAAcCAAAHgMAAAAcAAADAAAAAAAAyEEAQINEAADQQQEAAADYAAAADAAAIwAAAAAAAMhBGAAAFQIAAAAAAAAAAAAAAABAg0QAAMhBBAAAHBgAABUDAAAAAAAAAAAAyEEAQINEAADQQQgAAB4DAAAACAAAHgMAAAAMAAAjAABwwQAAcMEUAAAGAAAAAAAAAAAAAIhBAACIQQQAABwEAAAcCAAAHgMAAAAcAAADAACAQAAAAEAAAHxCAADAQQEAAABQAQAABAAAHAgAAB4DAAAAHAAAAwAAgEAAAABAAAB8QgAAwEEBAAAAeAEAAAQAABwIAAAeAwAAABwAAAMAAIBAAAAAQAAAfEIAAMBBAQAAAOABAABAAAAUBAAAAAwAAAA1AEgARgBSAFUARwAGAAAAAKDcPwCG4kEAAJBBAAAgQQDSlkEAONVBAEsHQgCxKUIAm0BCBAAAHAgAAB4DAAAAHAAAAwAAgEAAAABAAAB8QgAAwEEBAAAACAIAAAQAABwkAAAUBQAAAAIAAAADAAAAAQAAAAAAYD0AkM1BAACQQQDwhUIIAAAeAwAAAAgAAB4DAAAADAAAIwAAcMEAAHDBFAAABgAAAAABAAAAAACyQgAAsEEEAAAcBAAAHCQAABQFAAAAAgAAAAMAAAABAAAAAABgPQCQzUEAAJBBAJCxQlgAABQGAAAAFAAAADAAUgBRAEwAVwBSAFUATABRAEoACgAAAAAQE0AA3sVBAACQQQCQvUIAkNVCAJDlQgCQ9UIAkPtCAMgBQwDICUMAyA5DAMgRQwDIGUMkAAAUBQAAAAIAAAADAAAAAQAAAAAAYD0AkM1BAACQQQDII0MIAAAeAwAAAAgAAB4DAAAADAAAIwAAcMEAAHDBFAAABgAAAAACAAAAAAA5QwAAiEEEAAAcBAAAHAgAAB4DAAAAHAAAAwAALEMAAABAAIC/QwAAwEEBAAAAZAMAAAQAABwIAAAeAwAAABwAAAMAACxDAAAAQACAv0MAAMBBAQAAAIwDAAAEAAAcCAAAHgMAAAAcAAADAAAsQwAAAEAAgL9DAADAQQEAAAB0BAAAwAAAFAcAAAA2AAAAJgBEAFMAVwBYAFUASAADADAAUgBRAEwAVwBSAFUATABRAEoAAwA2AFEARABTAFYASwBSAFcAAAAbAAAAAKDcPwCG4kEAAJBBAAAyQ8CvO0MAa0NDwDpMQ0B3UUOAJ1pDAOJfQ8CuZ0PAHGxDwCt4Q6BigEPAuoRDwMCGQwBfiUPAq41DAImQQwCPkkMg55ZDgESbQ4B7nUOgQKFDwJilQ2B2qUNA3q1DwG+xQ+DHtUOgFLpDBAAAHAgAAB4DAAAAHAAAAwAALEMAAABAAIC/QwAAwEEBAAAAnAQAAAQAABwkAAAUBQAAAAIAAAADAAAAAQAAAAAAYD0AkM1BAACQQQCYwUMIAAAeAwAAAAgAAB4DAAAADAAAIwAAcMEAAHDBFAAABgAAAAADAAAAAADrQwAAiEEEAAAcBAAAHAgAAB4DAAAAHAAAAwCA5EMAAABAAAD6QwAAwEEBAAAAIAUAAAQAABwIAAAeAwAAABwAAAMAgORDAAAAQAAA+kMAAMBBAQAAAEgFAAAEAAAcCAAAHgMAAAAcAAADAIDkQwAAAEAAAPpDAADAQQEAAACkBQAANAAAFAcAAAAIAAAANgBEAFkASAAEAAAAAKDcPwCG4kEAAJBBAIDnQyBF60PAIu9DQMLyQwQAABwIAAAeAwAAABwAAAMAgORDAAAAQAAA+kMAAMBBAQAAAMwFAAAEAAAcJAAAFAUAAAACAAAAAwAAAAEAAAAAAGA9AJDNQQAAkEEAwvtDCAAAHgMAAAAIAAAeAwAAAAwAACMAAHDBAABwwRQAAAYAAAAABAAAAABAA0QAAIhBBAAAHAQAABwIAAAeAwAAABwAAAMAAABEAAAAQAAAC0QAAMBBAQAAAFAGAAAEAAAcCAAAHgMAAAAcAAADAAAARAAAAEAAAAtEAADAQQEAAAB4BgAABAAAHAgAAB4DAAAAHAAAAwAAAEQAAABAAAALRAAAwEEBAAAA1AYAADQAABQEAAAACAAAAC8AUgBEAEcABAAAAACg3D8AhuJBAACQQQCAAUTAXQNEIIQFRPByB0QEAAAcCAAAHgMAAAAcAAADAAAARAAAAEAAAAtEAADAQQEAAAD8BgAABAAAHDAAABQGAAAABgAAAEEAQgBBAAAAAwAAAACAmD4A3rVBAACAQQCADEQAQA5EAEAQRBgAABUIAAAAAECARAAAAEAAIINEAADAQQwAAA4JAAAAAQAAACQAABQKAAAAAgAAACIAAAABAAAAAICYPgDetUEAAIBBADCBRBgAABUDAAAAAAAAAAAA+kMAQINEAID6QxgAABULAAAAAICARAAA0EEAQINEAAD6QxgAABUMAAAAAICARAAA0EEAoIBEAAD6QxgAABUNAAAAAAAAAAAA/kMAQINEAIA9RBgAABUIAAAAAMBNRAAAAEAAQHhEAACoQQwAAA4JAAAAAgAAACQAABQOAAAAAgAAAJ0DAAABAAAAAIAmwABQ3kEAAIBBAEB4RCQAABQOAAAAAgAAAJ8DAAABAAAAAIAmwABQ3kEAAIBBAEB8RAgAAB4DAAAAHAAAAwDATUQAAEBAAEB4RAAAsEEBAAAAfAgAABAAAB8AAAAADwAAAB8AAAAEAAAcBAAAHAgAAB4DAAAAHAAAAwAAAAAAgPpDAECDRAAA+0MBAAAA1AgAAAwAACMAAAAAAID6QwwAACMAAACAAACAwBgAABUQAAAAAAAAAAAAAAAAQINEAACgQAQAABwIAAAeAwAAABwAAAMAAAAAAAD7QwBAg0QAgP1DAQAAACAJAAAMAAAjAAAAAAAA+0MYAAAVEAAAAAAAAAAAAAAAAECDRAAAoEAEAAAcCAAAHgMAAAAcAAADAAAAAACA/UMAQINEAAD+QwEAAABsCQAADAAAIwAAAAAAgP1DGAAAFRAAAAAAAAAAAAAAAABAg0QAAKBABAAAHBgAABURAAAAAAAAAACA+kMAQINEAAD7QxgAABUSAAAAAAAAAACA/UMAQINEAAD+QwQAABwEAAAcdGNhZiMAAAACAAAADVNrU3JjWGZlcm1vZGUQU2tMaW5lYXJHcmFkaWVudGNmcHQCAAAAAAENTHVjaWRhIEdyYW5kZQQNTHVjaWRhIEdyYW5kZQYMTHVjaWRhR3JhbmRl/v8AAAABCUhlbHZldGljYQQJSGVsdmV0aWNhBglIZWx2ZXRpY2H+/wAAeWFyYcQIAABwbXRiBQAAAD8AAAAWAAAAAAAAAKkAAACJUE5HDQoaCgAAAA1JSERSAAAAPwAAABYIBgAAAHf8RCEAAABwSURBVFiF7ZSxAcQgDMRk8KQMRQdD0lElxf8MueKsCSSMHXPOB1MSYIyh9vicvfcv/t6rdpGQAOcctYeEphZQkgCteb5Bxffe1R4SEiAi1B4SKt7123teuj/Wk6+dd42vnXedfMVXvCEVv9ZSe0h4ASOjDeti06sSAAAAAElFTkSuQmCCAAAAAAAAAAAAAAAMAAAADQAAAAAAAAAPAQAAiVBORw0KGgoAAAANSUhEUgAAAAwAAAANCAYAAACdKY9CAAAA1klEQVQokZWSMWrDQBBF38hrxW5SGJdSkyqtyRXSB3QRnUUXEWzvK+QKwSCxxbIE5CpaZMmFsuBCK/DAr+a9zxQjwBY4AK/AjuX5A67ArwIOVVV9FEXxlmXZcYlu29bVdf1TluW3AO9N03zG4Ecpz/OzAKdpmr7W4DAiohUgwzAwjuMqnCQJgCiAJwQUgPce7/2qkKbpLAY7NMTawz5OxWSgN8a40BKLMcYBfQJ0WuuLtdYppViKtdZprS9AJ8AeODK/xkvkkp75NZwAAmz+IxFhAm7A7Q619kxK1JGuJQAAAABJRU5ErkJgggAAAAAAAAAAANcAAAAWAAAAAAAAAMcAAACJUE5HDQoaCgAAAA1JSERSAAAA1wAAABYIBgAAAFPJCaQAAACOSURBVHic7duxDYQwEETRObSVupJrgpQiiRwS0AIrS9Z7FUzytU78O8/zH+BzlSRjjNU7YCvXdb1xzTlXb4HtVJLc9716B2znWD0AdlVJchwag69VklTV6h2wHZcLmqgKmrhc0ERc0ERc0ERc0ERV0ERc0MSzEJqoCpq4XNBEVdDE5YImqoImlbz//YFvPbqBDWEab+OuAAAAAElFTkSuQmCCAAAAAAAAAAAALwAAABYAAAAAAAAApgAAAIlQTkcNChoKAAAADUlIRFIAAAAvAAAAFggGAAAAUFLFyQAAAG1JREFUWIXt17ENwCAUA9EL8qRMkiVoGZKKMgVZIKSwLPEmOKzfcLXWbkIJoNbq7vis977i55zuli0CGGO4O7YUd8AfAigl8w0CkOTu2BK9fGb1K3r5E+8SHZ9Z/YqOP2fjcpZ3Ocu7CNZ/MNEDHg4NYflXb+0AAAAASUVORK5CYIIAAAAAAAAAAAAAMAAAABYAAAAAAAAArgAAAIlQTkcNChoKAAAADUlIRFIAAAAwAAAAFggGAAAAhvcfrAAAAHVJREFUWIXtk7ERwCAMxGTwpAxFB0PSUSVFMoFT/JmLJpDOfuu9XyTGAVprao8Qc84nYO+tdgnjAGsttUeYohb4igOUkrfjjIBaq9ojjAOYmdojzBkBmV8o73pf0l/gjA1kDjhjA5kv8Aeo+QPUOMAYQ+0R5gZf3A3r3/HMCQAAAABJRU5ErkJgggAAAAAAAAAAAAAgdG5wEgAAAAAAQEEAAIA/AAAAAAAAAAAAAIBA/////wIwAwAAAAAAAAAAAAAAAAABAAAABAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBBAACAPwAAAAAAAAAAAACAQAAAAP8CMAMAAAAAAAAAAAACAAAATAAAAAAAAAAAAAAAAgAAAOXl5f/R0dH/EAAAAAAAAAAK1yM9AAAAAArXI70AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAyEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQQAAgD8AAAAAAAAAAAAAgECOjo7/ADADAAAAAAAAAGBBAACAPwAAAAAAAAAAAACAQAAAAP8BMIMCAwAAAAEAAAAAAIBBAACAPwAAAAAAAAAAAACAQAAAAP8BMIMCAwAAAAIAAAAAAGBBAACAPwAAAAAAAAAAAACAQAAAAP8BMIMCAwAAAAIAAAAAAGBBAACAPwAAAAAAAAAAAACAQH9/f/8BMIMCAwAAAAEAAAAAAEBBAACAPwAAAAAAAAAAAACAQPj4+P8AMAMAAAAAAAAAQEEAAIA/AAAAAAAAAAAAAIBAAAAAfwAwAwAAAAAAAABgQQAAgD8AAAAAAAAAAAAAgEAAAADMATCDAgMAAAACAAAAAABAQQAAgD8AAAAAAAAAAAAAgEDs7Oz/ADADAAAAAAAAAEBBAACAPwAAAAAAAAAAAACAQAAAAP8AMAMAAAAAAAAAQEEAAIA/AAAAAAAAAAAAAIBA/////wAwAwAAAAAAAACAQQAAgD8AAAAAAAAAAAAAgEAAAAD/ATCDAgMAAAABAAAAAABAQQAAgD8AAAAAAAAAAAAAgEAAAAA/ADAAAAAAAAAAAEBBAACAPwAAAAAAAAAAAACAQAAAAP8CMAMAAAAAAAAAAAACAAAATAAAAAAAAAAAAAAAAgAAAOXl5f/R0dH/EAAAAAAAAADNzEw+AAAAAM3MTL4AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAoEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQQAAgD8AAAAAAAAAAAAAgED/////ADACAAAAAAAAAEBBAACAPwAAAAAAAAAAAACAQI6Ojv8AMAIAAAAAACBodHACAAAAAgAAAAABAAABAAACAAAAAAoAAAAIAAAAAAAAAAUBAQEABQEBAQAAQIBEAAAAQAAgg0QAAABAACCDRAAAwEEAQIBEAADAQQBggEQAAEBAAACDRAAAQEAAAINEAAC4QQBggEQAALhBAECARAAAAEAAIINEAADAQQAAAAEAAAEAAAIAAAAACgAAAAgAAAAAAAAABQEBAQAFAQEBAADATUQAAABAAEB4RAAAAEAAQHhEAACoQQDATUQAAKhBAABORAAAQEAAAHhEAABAQAAAeEQAAKBBAABORAAAoEEAwE1EAAAAQABAeEQAAKhBAAAgZm9l' // @suppress longLineCheck
+ }
+ },
+ 'pid': 1,
+ 'ts': 100,
+ 'cat': 'disabled-by-default-cc.debug',
+ 'tid': 1,
+ 'ph': 'O',
+ 'id': 'PICTURE_1'
+ },
+ {
+ 'name': 'AnalyzeTask',
+ 'args': {
+ 'data': {
+ 'source_frame_number': 107,
+ 'tile_id': {
+ 'id_ref': 'TILE_1'
+ },
+ 'resolution': 'HIGH_RESOLUTION',
+ 'is_tile_in_pending_tree_now_bin': true
+ }
+ },
+ 'pid': 1,
+ 'ts': 101,
+ 'cat': 'cc',
+ 'tid': 1,
+ 'ph': 'B'
+ },
+ {
+ 'name': 'AnalyzeTask',
+ 'args': {},
+ 'pid': 1,
+ 'ts': 105,
+ 'cat': 'cc',
+ 'tid': 1,
+ 'ph': 'E'
+ },
+ {
+ 'name': 'RasterTask',
+ 'args': {
+ 'data': {
+ 'source_frame_number': 107,
+ 'tile_id': {
+ 'id_ref': 'TILE_1'
+ },
+ 'resolution': 'HIGH_RESOLUTION',
+ 'is_tile_in_pending_tree_now_bin': true
+ }
+ },
+ 'pid': 1,
+ 'ts': 110,
+ 'cat': 'cc',
+ 'tid': 1,
+ 'ph': 'B'
+ },
+ {
+ 'name': 'RasterTask',
+ 'args': {},
+ 'pid': 1,
+ 'ts': 150,
+ 'cat': 'cc',
+ 'tid': 1,
+ 'ph': 'E'
+ },
+ {
+ 'name': 'RasterTask',
+ 'args': {
+ 'data': {
+ 'source_frame_number': 107,
+ 'tile_id': {
+ 'id_ref': 'TILE_2'
+ },
+ 'resolution': 'HIGH_RESOLUTION',
+ 'is_tile_in_pending_tree_now_bin': true
+ }
+ },
+ 'pid': 1,
+ 'ts': 170,
+ 'cat': 'cc',
+ 'tid': 1,
+ 'ph': 'B'
+ },
+ {
+ 'name': 'RasterTask',
+ 'args': {},
+ 'pid': 1,
+ 'ts': 180,
+ 'cat': 'cc',
+ 'tid': 1,
+ 'ph': 'E'
+ },
+ {
+ 'name': 'cc::LayerTreeHostImpl',
+ 'args': {
+ 'snapshot': {
+ 'device_viewport_size': {
+ 'width': 2460,
+ 'height': 1606
+ },
+ 'active_tree': {
+ 'source_frame_number': 7,
+ 'root_layer': {
+ 'tilings': [
+ {
+ 'content_scale': 2,
+ 'content_bounds': {
+ 'width': 2460,
+ 'height': 3334
+ },
+ 'num_tiles': 1
+ },
+ {
+ 'content_scale': 0.25,
+ 'content_bounds': {
+ 'width': 308,
+ 'height': 417
+ },
+ 'num_tiles': 1
+ }
+ ],
+ 'coverage_tiles': [
+ {
+ 'geometry_rect': [0, 0, 256, 256],
+ 'tile': {
+ 'id_ref': 'TILE_1'
+ }
+ },
+ {
+ 'geometry_rect': [256, 0, 256, 256]
+ },
+ {
+ 'geometry_rect': [512, 0, 256, 256]
+ },
+ {
+ 'geometry_rect': [0, 256, 256, 512]
+ },
+ {
+ 'geometry_rect': [256, 256, 512, 512]
+ }
+ ],
+ 'gpu_memory_usage': 22069248,
+ 'draws_content': 1,
+ 'layer_id': 6,
+ 'invalidation': [10, 20, 30, 40],
+ 'bounds': {
+ 'width': 1230,
+ 'height': 1667
+ },
+ 'children': [
+ {
+ 'tilings': [
+ {
+ 'content_scale': 2,
+ 'content_bounds': {
+ 'width': 200,
+ 'height': 100
+ },
+ 'num_tiles': 1
+ }
+ ],
+ 'gpu_memory_usage': 128000,
+ 'draws_content': 1,
+ 'layer_id': 7,
+ 'invalidation': [],
+ 'bounds': {
+ 'width': 100,
+ 'height': 50
+ },
+ 'children': [
+ ],
+ 'ideal_contents_scale': 2,
+ 'layer_quad': [
+ 0,
+ 0,
+ 200,
+ 0,
+ 200,
+ 100,
+ 0,
+ 100
+ ],
+ 'pictures': [
+ ],
+ 'debug_info': {
+ 'annotated_invalidation_rects': [
+ {
+ 'geometry_rect': [11, 22, 33, 44],
+ 'reason': 'appeared',
+ 'client': 'client1'
+ },
+ {
+ 'geometry_rect': [22, 33, 44, 55],
+ 'reason': 'disappeared',
+ 'client': 'client2'
+ },
+ ]
+ },
+ 'id': 'cc::PictureLayerImpl/LAYER_2'
+ }
+ ],
+ 'ideal_contents_scale': 2,
+ 'layer_quad': [
+ 0,
+ -1022,
+ 2460,
+ -1022,
+ 2460,
+ 2312,
+ 0,
+ 2312
+ ],
+ 'pictures': [
+ {
+ 'id_ref': 'PICTURE_1'
+ }
+ ],
+ 'id': 'cc::PictureLayerImpl/LAYER_1'
+ },
+ 'render_surface_layer_list': [
+ {'id_ref': 'LAYER_1'},
+ {'id_ref': 'LAYER_2'}
+ ],
+ 'id': 'cc::LayerTreeImpl/0x7d246ee0'
+ },
+ 'tiles': [
+ {
+ 'active_priority': {
+ 'time_to_visible_in_seconds': 0,
+ 'resolution': 'HIGH_RESOLUTION',
+ 'distance_to_visible_in_pixels': 0
+ },
+ 'pending_priority': {
+ 'time_to_visible_in_seconds': 3.4028234663852886e+38,
+ 'resolution': 'NON_IDEAL_RESOLUTION',
+ 'distance_to_visible_in_pixels': 3.4028234663852886e+38
+ },
+ 'managed_state': {
+ 'resolution': 'HIGH_RESOLUTION',
+ 'is_solid_color': false,
+ 'is_using_gpu_memory': true,
+ 'has_resource': true,
+ 'scheduled_priority': 10,
+ 'distance_to_visible': 0,
+ 'gpu_memory_usage': 1024000
+ },
+ 'layer_id': '6',
+ 'picture_pile': {
+ 'id_ref': 'PICTURE_1'
+ },
+ 'contents_scale': 2,
+ 'content_rect': [0, 0, 1024, 1024],
+ 'id': 'cc::Tile/TILE_1'
+ },
+ {
+ 'active_priority': {
+ 'time_to_visible_in_seconds': 0,
+ 'resolution': 'HIGH_RESOLUTION',
+ 'distance_to_visible_in_pixels': 0
+ },
+ 'pending_priority': {
+ 'time_to_visible_in_seconds': 3.4028234663852886e+38,
+ 'resolution': 'NON_IDEAL_RESOLUTION',
+ 'distance_to_visible_in_pixels': 3.4028234663852886e+38
+ },
+ 'managed_state': {
+ 'resolution': 'HIGH_RESOLUTION',
+ 'is_solid_color': false,
+ 'is_using_gpu_memory': true,
+ 'has_resource': true,
+ 'scheduled_priority': 12,
+ 'distance_to_visible': 0,
+ 'gpu_memory_usage': 1024000
+ },
+ 'layer_id': '6',
+ 'picture_pile': {
+ 'id_ref': 'PICTURE_1'
+ },
+ 'contents_scale': 2,
+ 'content_rect': [0, 1024, 1024, 1024],
+ 'id': 'cc::Tile/TILE_2'
+ }
+ ]
+ }
+ },
+ 'pid': 1,
+ 'ts': 500,
+ 'cat': 'disabled-by-default-cc.debug',
+ 'tid': 28163,
+ 'ph': 'O',
+ 'id': 'LTHI_1'
+ },
+ {
+ 'name': 'cc::DisplayItemList',
+ 'args': {
+ 'snapshot': {
+ 'params': {
+ 'layer_rect': [
+ -15,
+ -15,
+ 1260,
+ 1697
+ ],
+ 'items': [
+ 'BeginClipDisplayItem',
+ 'EndClipDisplayItem'
+ ]
+ },
+ 'skp64': '[base 64 encoded skia picture]'
+ }
+ },
+ 'pid': 1,
+ 'ts': 300,
+ 'cat': 'disabled-by-default-cc.debug',
+ 'tid': 1,
+ 'ph': 'O',
+ 'id': 'PICTURE_3'
+ }
+];
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_impl.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_impl.html
new file mode 100644
index 00000000000..8b2d4120737
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/layer_tree_impl.html
@@ -0,0 +1,178 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/constants.html">
+<link rel="import" href="/tracing/extras/chrome/cc/layer_impl.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/model/object_instance.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.cc', function() {
+ const constants = tr.e.cc.constants;
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+
+ /**
+ * @constructor
+ */
+ function LayerTreeImplSnapshot() {
+ ObjectSnapshot.apply(this, arguments);
+ }
+
+ LayerTreeImplSnapshot.prototype = {
+ __proto__: ObjectSnapshot.prototype,
+
+ preInitialize() {
+ tr.e.cc.preInitializeObject(this);
+ this.layerTreeHostImpl = undefined;
+ this.whichTree = undefined;
+ this.sourceFrameNumber = undefined;
+ },
+
+ initialize() {
+ tr.e.cc.moveRequiredFieldsFromArgsToToplevel(
+ this, ['renderSurfaceLayerList']);
+ tr.e.cc.moveOptionalFieldsFromArgsToToplevel(
+ this, ['rootLayer', 'layers']);
+ if (this.args.sourceFrameNumber) {
+ this.sourceFrameNumber = this.args.sourceFrameNumber;
+ }
+
+ // Slimming Paint v2 removes the tree hierarchy and replace
+ // it with a layer list. The tracing data should have either
+ // rootLayer or layers available.
+ if (this.rootLayer) {
+ // The version before SPv2
+ this.rootLayer.layerTreeImpl = this;
+ } else {
+ // The SPv2 version, where the layer list contains all
+ // non-mask, non-replica layers.
+ for (let i = 0; i < this.layers.length; i++) {
+ this.layers[i].layerTreeImpl = this;
+ }
+ }
+
+ if (this.args.swapPromiseTraceIds &&
+ this.args.swapPromiseTraceIds.length) {
+ this.tracedInputLatencies = [];
+
+ const ownProcess = this.objectInstance.parent;
+ const modelHelper = ownProcess.model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (modelHelper) {
+ this._initializeTracedInputLatencies(modelHelper);
+ }
+ }
+ },
+
+ _initializeTracedInputLatencies(modelHelper) {
+ const latencyEvents = modelHelper.browserHelper.getLatencyEventsInRange(
+ modelHelper.model.bounds);
+
+ // Convert all ids to InputLatency Async objects.
+ latencyEvents.forEach(function(event) {
+ for (let i = 0; i < this.args.swapPromiseTraceIds.length; i++) {
+ if (!event.args.data || !event.args.data.trace_id) {
+ continue;
+ }
+ if (parseInt(event.args.data.trace_id) ===
+ this.args.swapPromiseTraceIds[i]) {
+ this.tracedInputLatencies.push(event);
+ }
+ }
+ }, this);
+ },
+
+ get hasSourceFrameBeenDrawnBefore() {
+ if (this.whichTree === tr.e.cc.constants.PENDING_TREE) {
+ return false;
+ }
+
+ // Old chrome's don't produce sourceFrameNumber.
+ if (this.sourceFrameNumber === undefined) return;
+
+ const thisLTHI = this.layerTreeHostImpl;
+ const thisLTHIIndex = thisLTHI.objectInstance.snapshots.indexOf(
+ thisLTHI);
+ const prevLTHIIndex = thisLTHIIndex - 1;
+ if (prevLTHIIndex < 0 ||
+ prevLTHIIndex >= thisLTHI.objectInstance.snapshots.length) {
+ return false;
+ }
+ const prevLTHI = thisLTHI.objectInstance.snapshots[prevLTHIIndex];
+ if (!prevLTHI.activeTree) return false;
+
+ // Old chrome's don't produce sourceFrameNumber.
+ if (prevLTHI.activeTree.sourceFrameNumber === undefined) return;
+ return prevLTHI.activeTree.sourceFrameNumber === this.sourceFrameNumber;
+ },
+
+ get otherTree() {
+ const other = this.whichTree === constants.ACTIVE_TREE ?
+ constants.PENDING_TREE : constants.ACTIVE_TREE;
+ return this.layerTreeHostImpl.getTree(other);
+ },
+
+ get gpuMemoryUsageInBytes() {
+ let totalBytes = 0;
+ this.iterLayers(function(layer) {
+ if (layer.gpuMemoryUsageInBytes !== undefined) {
+ totalBytes += layer.gpuMemoryUsageInBytes;
+ }
+ });
+ return totalBytes;
+ },
+
+ iterLayers(func, thisArg) {
+ const visitedLayers = {};
+ function visitLayer(layer, depth, isMask, isReplica) {
+ if (visitedLayers[layer.layerId]) return;
+
+ visitedLayers[layer.layerId] = true;
+ func.call(thisArg, layer, depth, isMask, isReplica);
+ if (layer.children) {
+ for (let i = 0; i < layer.children.length; i++) {
+ visitLayer(layer.children[i], depth + 1);
+ }
+ }
+ if (layer.maskLayer) {
+ visitLayer(layer.maskLayer, depth + 1, true, false);
+ }
+ if (layer.replicaLayer) {
+ visitLayer(layer.replicaLayer, depth + 1, false, true);
+ }
+ }
+ if (this.rootLayer) {
+ visitLayer(this.rootLayer, 0, false, false);
+ } else {
+ for (let i = 0; i < this.layers.length; i++) {
+ visitLayer(this.layers[i], 0, false, false);
+ }
+ }
+ },
+ findLayerWithId(id) {
+ let foundLayer = undefined;
+ function visitLayer(layer) {
+ if (layer.layerId === id) {
+ foundLayer = layer;
+ }
+ }
+ this.iterLayers(visitLayer);
+ return foundLayer;
+ }
+ };
+
+ ObjectSnapshot.subTypes.register(
+ LayerTreeImplSnapshot,
+ {typeName: 'cc::LayerTreeImpl'});
+
+ return {
+ LayerTreeImplSnapshot,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/picture.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/picture.html
new file mode 100644
index 00000000000..b1a6f2ef288
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/picture.html
@@ -0,0 +1,451 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/guid.html">
+<link rel="import" href="/tracing/base/math/rect.html">
+<link rel="import" href="/tracing/base/raf.html">
+<link rel="import" href="/tracing/extras/chrome/cc/picture_as_image_data.html">
+<link rel="import" href="/tracing/extras/chrome/cc/util.html">
+<link rel="import" href="/tracing/model/object_instance.html">
+
+<script>
+'use strict';
+/* eslint-disable no-console */
+
+tr.exportTo('tr.e.cc', function() {
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+
+ // Number of pictures created. Used as an uniqueId because we are immutable.
+ const PictureCount = 0;
+ const OPS_TIMING_ITERATIONS = 3;
+
+ function Picture(skp64, layerRect) {
+ this.skp64_ = skp64;
+ this.layerRect_ = layerRect;
+
+ this.guid_ = tr.b.GUID.allocateSimple();
+ }
+
+ Picture.prototype = {
+ get canSave() {
+ return true;
+ },
+
+ get layerRect() {
+ return this.layerRect_;
+ },
+
+ get guid() {
+ return this.guid_;
+ },
+
+ getBase64SkpData() {
+ return this.skp64_;
+ },
+
+ getOps() {
+ if (!PictureSnapshot.CanGetOps()) {
+ console.error(PictureSnapshot.HowToEnablePictureDebugging());
+ return undefined;
+ }
+
+ const ops = window.chrome.skiaBenchmarking.getOps({
+ skp64: this.skp64_,
+ params: {
+ layer_rect: this.layerRect_.toArray()
+ }
+ });
+
+ if (!ops) {
+ console.error('Failed to get picture ops.');
+ }
+
+ return ops;
+ },
+
+ getOpTimings() {
+ if (!PictureSnapshot.CanGetOpTimings()) {
+ console.error(PictureSnapshot.HowToEnablePictureDebugging());
+ return undefined;
+ }
+
+ const opTimings = window.chrome.skiaBenchmarking.getOpTimings({
+ skp64: this.skp64_,
+ params: {
+ layer_rect: this.layerRect_.toArray()
+ }
+ });
+
+ if (!opTimings) {
+ console.error('Failed to get picture op timings.');
+ }
+
+ return opTimings;
+ },
+
+ /**
+ * Tag each op with the time it takes to rasterize.
+ *
+ * FIXME: We should use real statistics to get better numbers here, see
+ * https://code.google.com/p/trace-viewer/issues/detail?id=357
+ *
+ * @param {Array} ops Array of Skia operations.
+ * @return {Array} Skia ops where op.cmd_time contains the associated time
+ * for a given op.
+ */
+ tagOpsWithTimings(ops) {
+ const opTimings = [];
+ for (let iteration = 0; iteration < OPS_TIMING_ITERATIONS; iteration++) {
+ opTimings[iteration] = this.getOpTimings();
+ if (!opTimings[iteration] || !opTimings[iteration].cmd_times) {
+ return ops;
+ }
+ if (opTimings[iteration].cmd_times.length !== ops.length) {
+ return ops;
+ }
+ }
+
+ for (let opIndex = 0; opIndex < ops.length; opIndex++) {
+ let min = Number.MAX_VALUE;
+ for (let i = 0; i < OPS_TIMING_ITERATIONS; i++) {
+ min = Math.min(min, opTimings[i].cmd_times[opIndex]);
+ }
+ ops[opIndex].cmd_time = min;
+ }
+
+ return ops;
+ },
+
+ /**
+ * Rasterize the picture.
+ *
+ * @param {{opt_stopIndex: number, params}} The SkPicture operation to
+ * rasterize up to. If not defined, the entire SkPicture is rasterized.
+ * @param {{opt_showOverdraw: bool, params}} Defines whether pixel overdraw
+ should be visualized in the image.
+ * @param {function(tr.e.cc.PictureAsImageData)} The callback function that
+ * is called after rasterization is complete or fails.
+ */
+ rasterize(params, rasterCompleteCallback) {
+ if (!PictureSnapshot.CanRasterize() || !PictureSnapshot.CanGetOps()) {
+ rasterCompleteCallback(new tr.e.cc.PictureAsImageData(
+ this, tr.e.cc.PictureSnapshot.HowToEnablePictureDebugging()));
+ return;
+ }
+
+ if (!this.layerRect_.width || !this.layerRect_.height) {
+ rasterCompleteCallback(new tr.e.cc.PictureAsImageData(this, null));
+ return;
+ }
+
+ const raster = window.chrome.skiaBenchmarking.rasterize(
+ {
+ skp64: this.skp64_,
+ params: {
+ layer_rect: this.layerRect_.toArray()
+ }
+ },
+ {
+ stop: params.stopIndex === undefined ? -1 : params.stopIndex,
+ overdraw: !!params.showOverdraw,
+ params: { }
+ });
+
+ if (raster) {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ canvas.width = raster.width;
+ canvas.height = raster.height;
+ const imageData = ctx.createImageData(raster.width, raster.height);
+ imageData.data.set(new Uint8ClampedArray(raster.data));
+ rasterCompleteCallback(new tr.e.cc.PictureAsImageData(this, imageData));
+ } else {
+ const error = 'Failed to rasterize picture. ' +
+ 'Your recording may be from an old Chrome version. ' +
+ 'The SkPicture format is not backward compatible.';
+ rasterCompleteCallback(new tr.e.cc.PictureAsImageData(this, error));
+ }
+ }
+ };
+
+ function LayeredPicture(pictures) {
+ this.guid_ = tr.b.GUID.allocateSimple();
+ this.pictures_ = pictures;
+ this.layerRect_ = undefined;
+ }
+
+ LayeredPicture.prototype = {
+ __proto__: Picture.prototype,
+
+ get canSave() {
+ return false;
+ },
+
+ get typeName() {
+ return 'cc::LayeredPicture';
+ },
+
+ get layerRect() {
+ if (this.layerRect_ !== undefined) {
+ return this.layerRect_;
+ }
+
+ this.layerRect_ = {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0
+ };
+
+ for (let i = 0; i < this.pictures_.length; ++i) {
+ const rect = this.pictures_[i].layerRect;
+ this.layerRect_.x = Math.min(this.layerRect_.x, rect.x);
+ this.layerRect_.y = Math.min(this.layerRect_.y, rect.y);
+ this.layerRect_.width =
+ Math.max(this.layerRect_.width, rect.x + rect.width);
+ this.layerRect_.height =
+ Math.max(this.layerRect_.height, rect.y + rect.height);
+ }
+ return this.layerRect_;
+ },
+
+ get guid() {
+ return this.guid_;
+ },
+
+ getBase64SkpData() {
+ throw new Error('Not available with a LayeredPicture.');
+ },
+
+ getOps() {
+ let ops = [];
+ for (let i = 0; i < this.pictures_.length; ++i) {
+ ops = ops.concat(this.pictures_[i].getOps());
+ }
+ return ops;
+ },
+
+ getOpTimings() {
+ const opTimings = this.pictures_[0].getOpTimings();
+ for (let i = 1; i < this.pictures_.length; ++i) {
+ const timings = this.pictures_[i].getOpTimings();
+ opTimings.cmd_times = opTimings.cmd_times.concat(timings.cmd_times);
+ opTimings.total_time += timings.total_time;
+ }
+ return opTimings;
+ },
+
+ tagOpsWithTimings(ops) {
+ const opTimings = [];
+ for (let iteration = 0; iteration < OPS_TIMING_ITERATIONS; iteration++) {
+ opTimings[iteration] = this.getOpTimings();
+ if (!opTimings[iteration] || !opTimings[iteration].cmd_times) {
+ return ops;
+ }
+ }
+
+ for (let opIndex = 0; opIndex < ops.length; opIndex++) {
+ let min = Number.MAX_VALUE;
+ for (let i = 0; i < OPS_TIMING_ITERATIONS; i++) {
+ min = Math.min(min, opTimings[i].cmd_times[opIndex]);
+ }
+ ops[opIndex].cmd_time = min;
+ }
+ return ops;
+ },
+
+ rasterize(params, rasterCompleteCallback) {
+ this.picturesAsImageData_ = [];
+ const rasterCallback = function(pictureAsImageData) {
+ this.picturesAsImageData_.push(pictureAsImageData);
+ if (this.picturesAsImageData_.length !== this.pictures_.length) {
+ return;
+ }
+
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ canvas.width = this.layerRect.width;
+ canvas.height = this.layerRect.height;
+
+ // TODO(dsinclair): Verify these finish in the order started.
+ // Do the rasterize calls run sync or asyn? As the imageData
+ // going to be in the same order as the pictures_ list?
+ for (let i = 0; i < this.picturesAsImageData_.length; ++i) {
+ ctx.putImageData(this.picturesAsImageData_[i].imageData,
+ this.pictures_[i].layerRect.x,
+ this.pictures_[i].layerRect.y);
+ }
+ this.picturesAsImageData_ = [];
+
+ rasterCompleteCallback(new tr.e.cc.PictureAsImageData(this,
+ ctx.getImageData(this.layerRect.x, this.layerRect.y,
+ this.layerRect.width, this.layerRect.height)));
+ }.bind(this);
+
+ for (let i = 0; i < this.pictures_.length; ++i) {
+ this.pictures_[i].rasterize(params, rasterCallback);
+ }
+ }
+ };
+
+
+ /**
+ * @constructor
+ */
+ function PictureSnapshot() {
+ ObjectSnapshot.apply(this, arguments);
+ }
+
+ PictureSnapshot.HasSkiaBenchmarking = function() {
+ return tr.isExported('chrome.skiaBenchmarking');
+ };
+
+ PictureSnapshot.CanRasterize = function() {
+ if (!PictureSnapshot.HasSkiaBenchmarking()) {
+ return false;
+ }
+ if (!window.chrome.skiaBenchmarking.rasterize) {
+ return false;
+ }
+ return true;
+ };
+
+ PictureSnapshot.CanGetOps = function() {
+ if (!PictureSnapshot.HasSkiaBenchmarking()) {
+ return false;
+ }
+ if (!window.chrome.skiaBenchmarking.getOps) {
+ return false;
+ }
+ return true;
+ };
+
+ PictureSnapshot.CanGetOpTimings = function() {
+ if (!PictureSnapshot.HasSkiaBenchmarking()) {
+ return false;
+ }
+ if (!window.chrome.skiaBenchmarking.getOpTimings) {
+ return false;
+ }
+ return true;
+ };
+
+ PictureSnapshot.CanGetInfo = function() {
+ if (!PictureSnapshot.HasSkiaBenchmarking()) {
+ return false;
+ }
+ if (!window.chrome.skiaBenchmarking.getInfo) {
+ return false;
+ }
+ return true;
+ };
+
+ PictureSnapshot.HowToEnablePictureDebugging = function() {
+ if (tr.isHeadless) {
+ return 'Pictures only work in chrome';
+ }
+
+ const usualReason = [
+ 'For pictures to show up, you need to have Chrome running with ',
+ '--enable-skia-benchmarking. Please restart chrome with this flag ',
+ 'and try again.'
+ ].join('');
+
+ if (!PictureSnapshot.HasSkiaBenchmarking()) {
+ return usualReason;
+ }
+ if (!PictureSnapshot.CanRasterize()) {
+ return 'Your chrome is old: chrome.skipBenchmarking.rasterize not found';
+ }
+ if (!PictureSnapshot.CanGetOps()) {
+ return 'Your chrome is old: chrome.skiaBenchmarking.getOps not found';
+ }
+ if (!PictureSnapshot.CanGetOpTimings()) {
+ return 'Your chrome is old: ' +
+ 'chrome.skiaBenchmarking.getOpTimings not found';
+ }
+ if (!PictureSnapshot.CanGetInfo()) {
+ return 'Your chrome is old: chrome.skiaBenchmarking.getInfo not found';
+ }
+ return undefined;
+ };
+
+ PictureSnapshot.CanDebugPicture = function() {
+ return PictureSnapshot.HowToEnablePictureDebugging() === undefined;
+ };
+
+ PictureSnapshot.prototype = {
+ __proto__: ObjectSnapshot.prototype,
+
+ preInitialize() {
+ tr.e.cc.preInitializeObject(this);
+ this.rasterResult_ = undefined;
+ },
+
+ initialize() {
+ // If we have an alias args, that means this picture was represented
+ // by an alias, and the real args is in alias.args.
+ if (this.args.alias) {
+ this.args = this.args.alias.args;
+ }
+
+ if (!this.args.params.layerRect) {
+ throw new Error('Missing layer rect');
+ }
+
+ this.layerRect_ = this.args.params.layerRect;
+ this.picture_ = new Picture(this.args.skp64, this.args.params.layerRect);
+ },
+
+ set picture(picture) {
+ this.picture_ = picture;
+ },
+
+ get canSave() {
+ return this.picture_.canSave;
+ },
+
+ get layerRect() {
+ return this.layerRect_ ? this.layerRect_ : this.picture_.layerRect;
+ },
+
+ get guid() {
+ return this.picture_.guid;
+ },
+
+ getBase64SkpData() {
+ return this.picture_.getBase64SkpData();
+ },
+
+ getOps() {
+ return this.picture_.getOps();
+ },
+
+ getOpTimings() {
+ return this.picture_.getOpTimings();
+ },
+
+ tagOpsWithTimings(ops) {
+ return this.picture_.tagOpsWithTimings(ops);
+ },
+
+ rasterize(params, rasterCompleteCallback) {
+ this.picture_.rasterize(params, rasterCompleteCallback);
+ }
+ };
+
+ ObjectSnapshot.subTypes.register(
+ PictureSnapshot,
+ {typeNames: ['cc::Picture']});
+
+ return {
+ PictureSnapshot,
+ Picture,
+ LayeredPicture,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/picture_as_image_data.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/picture_as_image_data.html
new file mode 100644
index 00000000000..0d33fc2f70c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/picture_as_image_data.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+<script>
+'use strict';
+
+tr.exportTo('tr.e.cc', function() {
+ /**
+ * @constructor
+ */
+ function PictureAsImageData(picture, errorOrImageData) {
+ this.picture_ = picture;
+ if (errorOrImageData instanceof ImageData) {
+ this.error_ = undefined;
+ this.imageData_ = errorOrImageData;
+ } else {
+ this.error_ = errorOrImageData;
+ this.imageData_ = undefined;
+ }
+ }
+
+ /**
+ * Creates a new pending PictureAsImageData (no image data and no error).
+ *
+ * @return {PictureAsImageData} a new pending PictureAsImageData.
+ */
+ PictureAsImageData.Pending = function(picture) {
+ return new PictureAsImageData(picture, undefined);
+ };
+
+ PictureAsImageData.prototype = {
+ get picture() {
+ return this.picture_;
+ },
+
+ get error() {
+ return this.error_;
+ },
+
+ get imageData() {
+ return this.imageData_;
+ },
+
+ isPending() {
+ return this.error_ === undefined && this.imageData_ === undefined;
+ },
+
+ asCanvas() {
+ if (!this.imageData_) return;
+
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ canvas.width = this.imageData_.width;
+ canvas.height = this.imageData_.height;
+ ctx.putImageData(this.imageData_, 0, 0);
+ return canvas;
+ }
+ };
+
+ return {
+ PictureAsImageData,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/picture_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/picture_test.html
new file mode 100644
index 00000000000..71b17b78389
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/picture_test.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/cc/cc.html">
+<link rel="import" href="/tracing/extras/chrome/cc/picture.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script src="/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js">
+</script>
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('basic', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = Object.values(m.processes)[0];
+
+ const instance = p.objects.getAllInstancesNamed('cc::Picture')[0];
+ const snapshot = instance.snapshots[0];
+
+ assert.instanceOf(snapshot, tr.e.cc.PictureSnapshot);
+ instance.wasDeleted(150);
+ });
+
+ test('getOps', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = Object.values(m.processes)[0];
+
+ const instance = p.objects.getAllInstancesNamed('cc::Picture')[0];
+ const snapshot = instance.snapshots[0];
+
+ const ops = snapshot.getOps();
+ if (!ops) return;
+ assert.strictEqual(ops.length, 142);
+
+ const op0 = ops[0];
+ assert.strictEqual(op0.cmd_string, 'Save');
+ assert.instanceOf(op0.info, Array);
+ });
+
+ function setUpFakeSkiaBenchmarking() {
+ if (tr.e.cc.PictureSnapshot.CanRasterize() &&
+ tr.e.cc.PictureSnapshot.CanGetOps()) {
+ return window.chrome;
+ }
+
+ const oldChrome = window.chrome;
+ window.chrome = {
+ skiaBenchmarking: {
+ rasterize() {},
+ getOps() {},
+ }
+ };
+ return oldChrome;
+ }
+
+ test('rasterizeZeroSize', function() {
+ if (!window) return;
+
+ const oldChrome = setUpFakeSkiaBenchmarking();
+ try {
+ const picture = new tr.e.cc.Picture(
+ '', {x: 0, y: 0, width: 0, height: 50});
+ let result;
+ picture.rasterize({}, function(data) { result = data; });
+ assert.strictEqual(result.picture, picture);
+ assert.isUndefined(result.imageData);
+ assert.isNull(result.error);
+ } finally {
+ window.chrome = oldChrome;
+ }
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/raster_task.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/raster_task.html
new file mode 100644
index 00000000000..cc506aac902
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/raster_task.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/extras/chrome/cc/tile.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.cc', function() {
+ const knownRasterTaskNames = [
+ 'TileManager::RunRasterTask',
+ 'RasterWorkerPoolTaskImpl::RunRasterOnThread',
+ 'RasterWorkerPoolTaskImpl::Raster',
+ 'RasterTaskImpl::Raster',
+ 'cc::RasterTask',
+ 'RasterTask'
+ ];
+
+ const knownAnalysisTaskNames = [
+ 'TileManager::RunAnalyzeTask',
+ 'RasterWorkerPoolTaskImpl::RunAnalysisOnThread',
+ 'RasterWorkerPoolTaskImpl::Analyze',
+ 'RasterTaskImpl::Analyze',
+ 'cc::AnalyzeTask',
+ 'AnalyzeTask'
+ ];
+
+ function getTileFromRasterTaskSlice(slice) {
+ if (!(isSliceDoingRasterization(slice) || isSliceDoingAnalysis(slice))) {
+ return undefined;
+ }
+
+ let tileData;
+ if (slice.args.data) {
+ tileData = slice.args.data;
+ } else {
+ tileData = slice.args.tileData;
+ }
+ if (tileData === undefined) return undefined;
+ if (tileData.tile_id) return tileData.tile_id;
+
+ const tile = tileData.tileId;
+ if (!(tile instanceof tr.e.cc.TileSnapshot)) {
+ return undefined;
+ }
+ return tileData.tileId;
+ }
+
+ function isSliceDoingRasterization(slice) {
+ return knownRasterTaskNames.includes(slice.title);
+ }
+
+ function isSliceDoingAnalysis(slice) {
+ return knownAnalysisTaskNames.includes(slice.title);
+ }
+
+ return {
+ getTileFromRasterTaskSlice,
+ isSliceDoingRasterization,
+ isSliceDoingAnalysis
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/region.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/region.html
new file mode 100644
index 00000000000..e2601dd2641
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/region.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/math/rect.html">
+<script>
+'use strict';
+
+tr.exportTo('tr.e.cc', function() {
+ /**
+ * @constructor
+ */
+ function Region() {
+ this.rects = [];
+ }
+
+ Region.fromArray = function(array) {
+ if (array.length % 4 !== 0) {
+ throw new Error('Array must consist be a multiple of 4 in length');
+ }
+
+ const r = new Region();
+ for (let i = 0; i < array.length; i += 4) {
+ r.rects.push(tr.b.math.Rect.fromXYWH(array[i], array[i + 1],
+ array[i + 2], array[i + 3]));
+ }
+ return r;
+ };
+
+ /**
+ * @return {Region} If array is undefined, returns an empty region. Otherwise
+ * returns Region.fromArray(array).
+ */
+ Region.fromArrayOrUndefined = function(array) {
+ if (array === undefined) return new Region();
+ return Region.fromArray(array);
+ };
+
+ Region.prototype = {
+ __proto__: Region.prototype,
+
+ rectIntersects(r) {
+ for (let i = 0; i < this.rects.length; i++) {
+ if (this.rects[i].intersects(r)) return true;
+ }
+ return false;
+ },
+
+ addRect(r) {
+ this.rects.push(r);
+ }
+ };
+
+ return {
+ Region,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/render_pass.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/render_pass.html
new file mode 100644
index 00000000000..ec78354ae6b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/render_pass.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/math/rect.html">
+<link rel="import" href="/tracing/extras/chrome/cc/util.html">
+<link rel="import" href="/tracing/model/object_instance.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.cc', function() {
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+
+ /**
+ * @constructor
+ */
+ function RenderPassSnapshot() {
+ ObjectSnapshot.apply(this, arguments);
+ }
+
+ RenderPassSnapshot.prototype = {
+ __proto__: ObjectSnapshot.prototype,
+
+ preInitialize() {
+ tr.e.cc.preInitializeObject(this);
+ },
+
+ initialize() {
+ tr.e.cc.moveRequiredFieldsFromArgsToToplevel(
+ this, ['quadList']);
+ }
+ };
+
+ ObjectSnapshot.subTypes.register(
+ RenderPassSnapshot,
+ {typeName: 'cc::RenderPass'});
+
+ return {
+ RenderPassSnapshot,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/tile.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/tile.html
new file mode 100644
index 00000000000..f9c232ab974
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/tile.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/math/rect.html">
+<link rel="import" href="/tracing/extras/chrome/cc/debug_colors.html">
+<link rel="import" href="/tracing/extras/chrome/cc/util.html">
+<link rel="import" href="/tracing/model/object_instance.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.cc', function() {
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+
+ /**
+ * @constructor
+ */
+ function TileSnapshot() {
+ ObjectSnapshot.apply(this, arguments);
+ }
+
+ TileSnapshot.prototype = {
+ __proto__: ObjectSnapshot.prototype,
+
+ preInitialize() {
+ tr.e.cc.preInitializeObject(this);
+ },
+
+ initialize() {
+ tr.e.cc.moveOptionalFieldsFromArgsToToplevel(
+ this, ['layerId', 'contentsScale', 'contentRect']);
+ if (this.args.managedState) {
+ this.resolution = this.args.managedState.resolution;
+ this.isSolidColor = this.args.managedState.isSolidColor;
+ this.isUsingGpuMemory = this.args.managedState.isUsingGpuMemory;
+ this.hasResource = this.args.managedState.hasResource;
+ this.scheduledPriority = this.args.scheduledPriority;
+ this.gpuMemoryUsageInBytes = this.args.gpuMemoryUsage;
+ } else {
+ this.resolution = this.args.resolution;
+ this.isSolidColor = this.args.drawInfo.isSolidColor;
+ this.isUsingGpuMemory = this.args.isUsingGpuMemory;
+ this.hasResource = this.args.hasResource;
+ this.scheduledPriority = this.args.scheduledPriority;
+ this.gpuMemoryUsageInBytes = this.args.gpuMemoryUsage;
+ }
+
+ // This check is for backward compatability. It can probably
+ // be removed once we're confident that most traces contain
+ // content_rect.
+ if (this.contentRect) {
+ this.layerRect = this.contentRect.scale(1.0 / this.contentsScale);
+ }
+
+ if (this.isSolidColor) {
+ this.type_ = tr.e.cc.tileTypes.solidColor;
+ } else if (!this.hasResource) {
+ this.type_ = tr.e.cc.tileTypes.missing;
+ } else if (this.resolution === 'HIGH_RESOLUTION') {
+ this.type_ = tr.e.cc.tileTypes.highRes;
+ } else if (this.resolution === 'LOW_RESOLUTION') {
+ this.type_ = tr.e.cc.tileTypes.lowRes;
+ } else {
+ this.type_ = tr.e.cc.tileTypes.unknown;
+ }
+ },
+
+ getTypeForLayer(layer) {
+ let type = this.type_;
+ if (type === tr.e.cc.tileTypes.unknown) {
+ if (this.contentsScale < layer.idealContentsScale) {
+ type = tr.e.cc.tileTypes.extraLowRes;
+ } else if (this.contentsScale > layer.idealContentsScale) {
+ type = tr.e.cc.tileTypes.extraHighRes;
+ }
+ }
+ return type;
+ }
+ };
+
+ ObjectSnapshot.subTypes.register(TileSnapshot, {typeName: 'cc::Tile'});
+
+ return {
+ TileSnapshot,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/tile_coverage_rect.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/tile_coverage_rect.html
new file mode 100644
index 00000000000..d1c65fa608a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/tile_coverage_rect.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/base/base.html">
+<script>
+
+'use strict';
+
+tr.exportTo('tr.e.cc', function() {
+ /**
+ * This class represents a tile (from impl side) and its final rect on the
+ * layer. Note that the rect is determined by what is needed to cover all
+ * of the layer without overlap.
+ * @constructor
+ */
+ function TileCoverageRect(rect, tile) {
+ this.geometryRect = rect;
+ this.tile = tile;
+ }
+
+ return {
+ TileCoverageRect,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/tile_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/tile_test.html
new file mode 100644
index 00000000000..218af949862
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/tile_test.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/cc/tile.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script src="/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js">
+</script>
+
+<script>
+
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('basic', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = Object.values(m.processes)[0];
+ const instance = p.objects.getAllInstancesNamed('cc::Tile')[0];
+ const snapshot = instance.snapshots[0];
+
+ assert.instanceOf(snapshot, tr.e.cc.TileSnapshot);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/util.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/util.html
new file mode 100644
index 00000000000..e074e68287c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/util.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/math/quad.html">
+<link rel="import" href="/tracing/base/math/rect.html">
+<link rel="import" href="/tracing/model/object_instance.html">
+<script>
+
+'use strict';
+
+tr.exportTo('tr.e.cc', function() {
+ const convertedNameCache = {};
+ function convertNameToJSConvention(name) {
+ if (name in convertedNameCache) {
+ return convertedNameCache[name];
+ }
+
+ if (name[0] === '_' ||
+ name[name.length - 1] === '_') {
+ convertedNameCache[name] = name;
+ return name;
+ }
+
+ const words = name.split('_');
+ if (words.length === 1) {
+ convertedNameCache[name] = words[0];
+ return words[0];
+ }
+
+ for (let i = 1; i < words.length; i++) {
+ words[i] = words[i][0].toUpperCase() + words[i].substring(1);
+ }
+
+ convertedNameCache[name] = words.join('');
+ return convertedNameCache[name];
+ }
+
+ function moveRequiredFieldsFromArgsToToplevel(object, fields) {
+ for (let i = 0; i < fields.length; i++) {
+ const key = fields[i];
+ if (object.args[key] === undefined) {
+ throw Error('Expected field ' + key + ' not found in args');
+ }
+ if (object[key] !== undefined) {
+ throw Error('Field ' + key + ' already in object');
+ }
+ object[key] = object.args[key];
+ delete object.args[key];
+ }
+ }
+
+ function moveOptionalFieldsFromArgsToToplevel(object, fields) {
+ for (let i = 0; i < fields.length; i++) {
+ const key = fields[i];
+ if (object.args[key] === undefined) continue;
+ if (object[key] !== undefined) {
+ throw Error('Field ' + key + ' already in object');
+ }
+ object[key] = object.args[key];
+ delete object.args[key];
+ }
+ }
+
+ function preInitializeObject(object) {
+ preInitializeObjectInner(object.args, false);
+ }
+
+ function preInitializeObjectInner(object, hasRecursed) {
+ if (!(object instanceof Object)) return;
+
+ if (object instanceof Array) {
+ for (let i = 0; i < object.length; i++) {
+ preInitializeObjectInner(object[i], true);
+ }
+ return;
+ }
+
+ if (hasRecursed &&
+ (object instanceof tr.model.ObjectSnapshot ||
+ object instanceof tr.model.ObjectInstance)) {
+ return;
+ }
+
+ for (let key in object) {
+ const newKey = convertNameToJSConvention(key);
+ if (newKey !== key) {
+ const value = object[key];
+ delete object[key];
+ object[newKey] = value;
+ key = newKey;
+ }
+
+ // Convert objects with keys ending with Quad to tr.b.math.Quad type.
+ if (/Quad$/.test(key) && !(object[key] instanceof tr.b.math.Quad)) {
+ let q;
+ try {
+ q = tr.b.math.Quad.from8Array(object[key]);
+ } catch (e) {
+ }
+ object[key] = q;
+ continue;
+ }
+
+ // Convert objects with keys ending with Rect to tr.b.math.Rect type.
+ if (/Rect$/.test(key) && !(object[key] instanceof tr.b.math.Rect)) {
+ let r;
+ try {
+ r = tr.b.math.Rect.fromArray(object[key]);
+ } catch (e) {
+ }
+ object[key] = r;
+ }
+
+ preInitializeObjectInner(object[key], true);
+ }
+ }
+
+ return {
+ preInitializeObject,
+ convertNameToJSConvention,
+ moveRequiredFieldsFromArgsToToplevel,
+ moveOptionalFieldsFromArgsToToplevel,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/util_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/util_test.html
new file mode 100644
index 00000000000..9c0adeb0f76
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cc/util_test.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/math/quad.html">
+<link rel="import" href="/tracing/base/math/rect.html">
+<link rel="import" href="/tracing/extras/chrome/cc/util.html">
+
+<script>
+
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('nameConvert', function() {
+ assert.strictEqual(tr.e.cc.convertNameToJSConvention('_foo'), '_foo');
+ assert.strictEqual(tr.e.cc.convertNameToJSConvention('foo_'), 'foo_');
+ assert.strictEqual(tr.e.cc.convertNameToJSConvention('foo'), 'foo');
+ assert.strictEqual(tr.e.cc.convertNameToJSConvention('foo_bar'), 'fooBar');
+ assert.strictEqual(tr.e.cc.convertNameToJSConvention('foo_bar_baz'),
+ 'fooBarBaz');
+ });
+
+ test('objectConvertNested', function() {
+ const object = {
+ un_disturbed: true,
+ args: {
+ foo_bar: {
+ a_field: 7
+ }
+ }
+ };
+ const expected = {
+ un_disturbed: true,
+ args: {
+ fooBar: {
+ aField: 7
+ }
+ }
+ };
+ tr.e.cc.preInitializeObject(object);
+ assert.deepEqual(object, expected);
+ });
+
+ test('arrayConvert', function() {
+ const object = {
+ un_disturbed: true,
+ args: [
+ {foo_bar: 7},
+ {foo_bar: 8}
+ ]
+ };
+ const expected = {
+ un_disturbed: true,
+ args: [
+ {fooBar: 7},
+ {fooBar: 8}
+ ]
+ };
+ tr.e.cc.preInitializeObject(object);
+ assert.deepEqual(object, expected);
+ });
+
+ test('quadCoversion', function() {
+ const object = {
+ args: {
+ some_quad: [1, 2, 3, 4, 5, 6, 7, 8]
+ }
+ };
+ tr.e.cc.preInitializeObject(object);
+ assert.instanceOf(object.args.someQuad, tr.b.math.Quad);
+ });
+
+ test('quadConversionNested', function() {
+ const object = {
+ args: {
+ nested_field: {
+ a_quad: [1, 2, 3, 4, 5, 6, 7, 8]
+ },
+ non_nested_quad: [1, 2, 3, 4, 5, 6, 7, 8]
+ }
+ };
+ tr.e.cc.preInitializeObject(object);
+ assert.instanceOf(object.args.nestedField.aQuad, tr.b.math.Quad);
+ assert.instanceOf(object.args.nonNestedQuad, tr.b.math.Quad);
+ });
+
+ test('rectCoversion', function() {
+ const object = {
+ args: {
+ some_rect: [1, 2, 3, 4]
+ }
+ };
+ tr.e.cc.preInitializeObject(object);
+ assert.instanceOf(object.args.someRect, tr.b.math.Rect);
+ });
+
+ test('rectCoversionNested', function() {
+ const object = {
+ args: {
+ nested_field: {
+ a_rect: [1, 2, 3, 4]
+ },
+ non_nested_rect: [1, 2, 3, 4]
+ }
+ };
+ tr.e.cc.preInitializeObject(object);
+ assert.instanceOf(object.args.nestedField.aRect, tr.b.math.Rect);
+ assert.instanceOf(object.args.nonNestedRect, tr.b.math.Rect);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor.html
new file mode 100644
index 00000000000..a786c061bf9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+<link rel="import" href="/tracing/base/math/range_utils.html">
+<link rel="import" href="/tracing/core/auditor.html">
+<link rel="import"
+ href="/tracing/extras/chrome/cc/input_latency_async_slice.html">
+<link rel="import"
+ href="/tracing/extras/chrome/chrome_user_friendly_category_driver.html">
+<link rel="import" href="/tracing/model/constants.html">
+<link rel="import" href="/tracing/model/event_info.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Base class for trace data Auditors.
+ */
+tr.exportTo('tr.e.audits', function() {
+ const Auditor = tr.c.Auditor;
+
+ /**
+ * Auditor for Chrome-specific traces.
+ * @constructor
+ */
+ function ChromeAuditor(model) {
+ Auditor.call(this, model);
+
+ const modelHelper = this.model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ if (modelHelper && modelHelper.browserHelper) {
+ // Must be a browserHelper in order to do audits.
+ this.modelHelper = modelHelper;
+ } else {
+ this.modelHelper = undefined;
+ }
+ }
+
+ ChromeAuditor.prototype = {
+ __proto__: Auditor.prototype,
+
+ runAnnotate() {
+ if (!this.modelHelper) return;
+
+ for (const pid in this.modelHelper.rendererHelpers) {
+ const rendererHelper = this.modelHelper.rendererHelpers[pid];
+
+ if (rendererHelper.isChromeTracingUI) {
+ rendererHelper.process.important = false;
+ }
+ }
+ },
+
+ /**
+ * Called by import to install userFriendlyCategoryDriver.
+ */
+ installUserFriendlyCategoryDriverIfNeeded() {
+ this.model.addUserFriendlyCategoryDriver(
+ tr.e.chrome.ChromeUserFriendlyCategoryDriver);
+ },
+
+ runAudit() {
+ if (!this.modelHelper) return;
+
+ this.model.replacePIDRefsInPatchups(
+ tr.model.BROWSER_PROCESS_PID_REF,
+ this.modelHelper.browserProcess.pid);
+ this.model.applyObjectRefPatchups();
+ }
+ };
+
+ Auditor.register(ChromeAuditor);
+
+ return {
+ ChromeAuditor,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor_test.html
new file mode 100644
index 00000000000..5947cfb35b9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_auditor_test.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/color_scheme.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/chrome_auditor.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/scoped_id.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ColorScheme = tr.b.ColorScheme;
+
+ function createMainProcesses(m) {
+ m.browserProcess = m.getOrCreateProcess(1);
+ m.browserMain = m.browserProcess.getOrCreateThread(2);
+ m.browserMain.name = 'CrBrowserMain';
+
+ m.renderer1 = m.getOrCreateProcess(3);
+ m.renderer1Main = m.renderer1.getOrCreateThread(4);
+ m.renderer1Main.name = 'CrRendererMain';
+
+ m.renderer1Compositor = m.renderer1.getOrCreateThread(4);
+ m.renderer1Compositor.name = 'Compositor';
+ }
+
+ function newInputLatencyEvent(tsStart, tsEnd, opt_args) {
+ const e = new tr.model.AsyncSlice(
+ 'benchmark', 'InputLatency',
+ ColorScheme.getColorIdForGeneralPurposeString('InputLatency'),
+ tsStart, opt_args);
+ e.duration = tsEnd - tsStart;
+ return e;
+ }
+
+ function newImplRenderingStatsEvent(ts, opt_args) {
+ const e = new tr.model.ThreadSlice(
+ 'benchmark', 'BenchmarkInstrumentation::ImplThreadRenderingStats',
+ ColorScheme.getColorIdForGeneralPurposeString('x'),
+ ts, opt_args, 0);
+ return e;
+ }
+
+ test('simple', function() {
+ tr.c.TestUtils.newModelWithAuditor(function(m) {
+ createMainProcesses(m);
+ const bAsyncSlices = m.browserMain.asyncSliceGroup;
+ bAsyncSlices.push(newInputLatencyEvent(100, 130));
+ bAsyncSlices.push(newInputLatencyEvent(116, 150));
+ bAsyncSlices.push(newInputLatencyEvent(133, 166));
+ bAsyncSlices.push(newInputLatencyEvent(150, 183));
+ bAsyncSlices.push(newInputLatencyEvent(166, 200));
+ bAsyncSlices.push(newInputLatencyEvent(183, 216));
+
+ const rm1Slices = m.renderer1Compositor.sliceGroup;
+ rm1Slices.pushSlice(newImplRenderingStatsEvent(113));
+ rm1Slices.pushSlice(newImplRenderingStatsEvent(130));
+ rm1Slices.pushSlice(newImplRenderingStatsEvent(147));
+ rm1Slices.pushSlice(newImplRenderingStatsEvent(163));
+ rm1Slices.pushSlice(newImplRenderingStatsEvent(180));
+ rm1Slices.pushSlice(newImplRenderingStatsEvent(197));
+ rm1Slices.pushSlice(newImplRenderingStatsEvent(213));
+ rm1Slices.pushSlice(newImplRenderingStatsEvent(230));
+ rm1Slices.pushSlice(newImplRenderingStatsEvent(247));
+ }, tr.e.audits.ChromeAuditor);
+ });
+
+ test('refsToBrowser', function() {
+ const events = [
+ // An object created and snapshotted in the browser process.
+ {ts: 1000, pid: 1, tid: 2, ph: 'N', cat: 'c', id: '0x1000', name: 'a',
+ args: {}},
+ {ts: 1100, pid: 1, tid: 2, ph: 'O', cat: 'c', id: '0x1000', name: 'a',
+ args: {snapshot: {foo: 12345}}},
+ {ts: 1300, pid: 1, tid: 2, ph: 'D', cat: 'c', id: '0x1000', name: 'a',
+ args: {}},
+
+ // A reference to the object in the browser from the renderer process.
+ {ts: 1200, pid: 3, tid: 4, ph: 'X', cat: 'c', name: 'b', dur: 100,
+ args: {bar: {pid_ref: -1, id_ref: '0x1000'}}}
+ ];
+
+ const m = tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false,
+ pruneEmptyContainers: false,
+ customizeModelCallback: createMainProcesses,
+ auditorConstructors: [tr.e.audits.ChromeAuditor]
+ });
+
+ const browserObject = m.browserProcess.objects.getObjectInstanceAt(
+ new tr.model.ScopedId('ptr', '0x1000'), 1.2);
+ assert.isDefined(browserObject);
+ const foo = browserObject.getSnapshotAt(1.2);
+ assert.isDefined(foo);
+
+ assert.strictEqual(m.renderer1Main.sliceGroup.slices.length, 1);
+ const slice = m.renderer1Main.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'b');
+ assert.strictEqual(slice.args.bar, foo);
+ });
+
+ test('filterTracingUI', function() {
+ const m = tr.c.TestUtils.newModelWithAuditor(function(m) {
+ m.browserProcess = m.getOrCreateProcess(1);
+ m.browserMain = m.browserProcess.getOrCreateThread(2);
+ m.browserMain.name = 'CrBrowserMain';
+
+ m.renderer1 = m.getOrCreateProcess(3);
+ m.renderer1.labels = ['https://google.com'];
+ m.renderer1Main = m.renderer1.getOrCreateThread(4);
+ m.renderer1Main.name = 'CrRendererMain';
+
+ m.renderer2 = m.getOrCreateProcess(5);
+ m.renderer2.labels = ['chrome://tracing'];
+ m.renderer2Main = m.renderer2.getOrCreateThread(6);
+ m.renderer2Main.name = 'CrRendererMain';
+ }, tr.e.audits.ChromeAuditor);
+
+ assert.isTrue(m.renderer1.important);
+ assert.isFalse(m.renderer2.important);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_processes.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_processes.html
new file mode 100644
index 00000000000..0456b29126e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_processes.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/fixed_color_scheme.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.chrome.chrome_processes', function() {
+ const CHROME_PROCESS_NAMES = {
+ BROWSER: 'browser_process',
+ RENDERER: 'renderer_processes', // Intentionally plural.
+ ALL: 'all_processes',
+ GPU: 'gpu_process',
+ PPAPI: 'ppapi_process',
+ UNKNOWN: 'unknown_processes',
+ };
+
+ const PROCESS_COLOR_SCHEME_NAME = 'ChromeProcessNames';
+ const PROCESS_COLOR_SCHEME =
+ tr.b.FixedColorScheme.fromNames(Object.values(CHROME_PROCESS_NAMES));
+
+ tr.b.FixedColorSchemeRegistry.register(() => PROCESS_COLOR_SCHEME, {
+ name: PROCESS_COLOR_SCHEME_NAME,
+ });
+
+ /**
+ * Converts name to lower case and replaces spaces with underscores.
+ */
+ function canonicalizeName(name) {
+ return name.toLowerCase().replace(' ', '_');
+ }
+
+ /**
+ * Convert raw process name to canonical process names used in catapult.
+ *
+ * Examples:
+ * browser -> CHROME_PROCESS_NAME.BROWSER
+ * renderer -> CHROME_PROCESS_NAME.RENDERER
+ */
+ function canonicalizeProcessName(rawProcessName) {
+ if (!rawProcessName) return CHROME_PROCESS_NAMES.UNKNOWN;
+
+ const baseCanonicalName = canonicalizeName(rawProcessName);
+ switch (baseCanonicalName) {
+ case 'renderer': return CHROME_PROCESS_NAMES.RENDERER;
+ case 'browser': return CHROME_PROCESS_NAMES.BROWSER;
+ }
+
+ if (Object.values(CHROME_PROCESS_NAMES).includes(baseCanonicalName)) {
+ return baseCanonicalName;
+ }
+
+ return CHROME_PROCESS_NAMES.UNKNOWN;
+ }
+
+ return {
+ CHROME_PROCESS_NAMES,
+ PROCESS_COLOR_SCHEME,
+ PROCESS_COLOR_SCHEME_NAME,
+ canonicalizeName,
+ canonicalizeProcessName,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_test_utils.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_test_utils.html
new file mode 100644
index 00000000000..69128010fa9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_test_utils.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/base/base.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Base class for trace data Auditors.
+ */
+tr.exportTo('tr.e.chrome', function() {
+ function ChromeTestUtils() {
+ }
+
+ ChromeTestUtils.newChromeModel = function(customizeModelCallback) {
+ return tr.c.TestUtils.newModel(function(model) {
+ model.browserProcess = model.getOrCreateProcess(1);
+ model.browserMain = model.browserProcess.getOrCreateThread(2);
+ model.browserMain.name = 'CrBrowserMain';
+
+ model.rendererProcess = model.getOrCreateProcess(2);
+ model.rendererMain = model.rendererProcess.getOrCreateThread(3);
+ model.rendererMain.name = 'CrRendererMain';
+
+ model.rendererCompositor = model.rendererProcess.getOrCreateThread(4);
+ model.rendererCompositor.name = 'Compositor';
+
+ model.rasterWorker1 = model.rendererProcess.getOrCreateThread(5);
+ model.rasterWorker1.name = 'CompositorTileWorker1';
+
+ customizeModelCallback(model);
+ });
+ };
+
+ ChromeTestUtils.addEvent = function(thread, dict) {
+ const slice = tr.c.TestUtils.newAsyncSliceEx(dict);
+ thread.asyncSliceGroup.push(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addNavigationStartEvent = function(model, dict) {
+ dict.title = 'NavigationTiming navigationStart';
+ const event = tr.c.TestUtils.newInstantEvent(dict);
+ model.instantEvents.push(event);
+ return event;
+ };
+
+ ChromeTestUtils.addFirstContentfulPaintEvent = function(model, dict) {
+ dict.title = 'firstContentfulPaint';
+ const event = tr.c.TestUtils.newInstantEvent(dict);
+ model.instantEvents.push(event);
+ return event;
+ };
+
+ ChromeTestUtils.addInputEvent = function(model, typeName, dict) {
+ dict.title = 'InputLatency::' + typeName;
+ dict.isTopLevel = (dict.isTopLevel === undefined);
+ dict.startThread = model.browserMain;
+ const slice = tr.c.TestUtils.newAsyncSliceEx(dict);
+ model.browserMain.asyncSliceGroup.push(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addFlingAnimationEvent = function(model, dict) {
+ dict.title = 'InputHandlerProxy::HandleGestureFling::started';
+ const slice = tr.c.TestUtils.newAsyncSliceEx(dict);
+ model.rendererCompositor.asyncSliceGroup.push(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addRenderingEvent = function(model, dict) {
+ dict.title = dict.title || 'DummyEvent';
+ dict.type = tr.model.ThreadSlice;
+ const slice = tr.c.TestUtils.newSliceEx(dict);
+ model.rendererMain.sliceGroup.pushSlice(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addFrameEvent = function(model, dict) {
+ dict.title = tr.model.helpers.IMPL_RENDERING_STATS;
+ dict.type = tr.model.ThreadSlice;
+ const slice = tr.c.TestUtils.newSliceEx(dict);
+ model.rendererMain.sliceGroup.pushSlice(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addLoadingEvent = function(model, dict) {
+ dict.title = 'WebContentsImpl Loading';
+ const slice = tr.c.TestUtils.newAsyncSliceEx(dict);
+ model.rendererMain.asyncSliceGroup.push(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addNetworkEvent = function(model, dict) {
+ dict.cat = 'netlog';
+ dict.title = 'Generic Network event';
+ const slice = tr.c.TestUtils.newAsyncSliceEx(dict);
+ model.browserMain.asyncSliceGroup.push(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addCommitLoadEvent = function(model, dict) {
+ dict.title = 'RenderFrameImpl::didCommitProvisionalLoad';
+ const slice = tr.c.TestUtils.newAsyncSliceEx(dict);
+ model.rendererMain.sliceGroup.pushSlice(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addCreateChildFrameEvent = function(model, dict) {
+ dict.title = 'RenderFrameImpl::createChildFrame';
+ const slice = tr.c.TestUtils.newAsyncSliceEx(dict);
+ model.rendererMain.sliceGroup.pushSlice(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addStartProvisionalLoadEvent = function(model, dict) {
+ dict.title = 'RenderFrameImpl::didStartProvisionalLoad';
+ const slice = tr.c.TestUtils.newAsyncSliceEx(dict);
+ model.rendererMain.sliceGroup.pushSlice(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addFailProvisionalLoadEvent = function(model, dict) {
+ dict.title = 'RenderFrameImpl::didFailProvisionalLoad';
+ const slice = tr.c.TestUtils.newAsyncSliceEx(dict);
+ model.rendererMain.sliceGroup.pushSlice(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addFinishLoadEvent = function(model, dict) {
+ dict.title = 'RenderFrameImpl::didFinishLoad';
+ const slice = tr.c.TestUtils.newSliceEx(dict);
+ model.rendererMain.sliceGroup.pushSlice(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addLoadFinishedEvent = function(model, dict) {
+ dict.title = 'LoadFinished';
+ const slice = tr.c.TestUtils.newSliceEx(dict);
+ model.rendererMain.sliceGroup.pushSlice(slice);
+ return slice;
+ };
+
+ ChromeTestUtils.addCreateThreadsEvent = function(model, dict) {
+ dict.title = 'BrowserMainLoop::CreateThreads';
+ const slice = tr.c.TestUtils.newSliceEx(dict);
+ model.rendererMain.sliceGroup.pushSlice(slice);
+ return slice;
+ };
+
+ return {
+ ChromeTestUtils,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver.html
new file mode 100644
index 00000000000..d81818658de
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver.html
@@ -0,0 +1,261 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/event.html">
+<link rel="import" href="/tracing/base/sinebow_color_generator.html">
+<link rel="import" href="/tracing/base/utils.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.chrome', function() {
+ const SAME_AS_PARENT = 'same-as-parent';
+
+ const TITLES_FOR_USER_FRIENDLY_CATEGORY = {
+ composite: [
+ 'CompositingInputsUpdater::update',
+ 'ThreadProxy::SetNeedsUpdateLayers',
+ 'LayerTreeHost::DoUpdateLayers',
+ 'LayerTreeHost::UpdateLayers::BuildPropertyTrees',
+ 'LocalFrameView::pushPaintArtifactToCompositor',
+ 'LocalFrameView::updateCompositedSelectionIfNeeded',
+ 'LocalFrameView::RunCompositingLifecyclePhase',
+ 'UpdateLayerTree',
+ ],
+
+ gc: [
+ 'minorGC',
+ 'majorGC',
+ 'MajorGC',
+ 'MinorGC',
+ 'V8.GCScavenger',
+ 'V8.GCIncrementalMarking',
+ 'V8.GCIdleNotification',
+ 'V8.GCContext',
+ 'V8.GCCompactor',
+ 'V8GCController::traceDOMWrappers',
+ ],
+
+ iframe_creation: [
+ 'WebLocalFrameImpl::createChildframe',
+ ],
+
+ imageDecode: [
+ 'Decode Image',
+ 'ImageFrameGenerator::decode',
+ 'ImageFrameGenerator::decodeAndScale',
+ 'ImageResourceContent::updateImage',
+ ],
+
+ input: [
+ 'HitTest',
+ 'ScrollableArea::scrollPositionChanged',
+ 'EventHandler::handleMouseMoveEvent',
+ ],
+
+ layout: [
+ 'IntersectionObserverController::computeTrackedIntersectionObservations',
+ 'LocalFrameView::invalidateTree',
+ 'LocalFrameView::layout',
+ 'LocalFrameView::performLayout',
+ 'LocalFrameView::performPostLayoutTasks',
+ 'LocalFrameView::performPreLayoutTasks',
+ 'LocalFrameView::RunStyleAndLayoutCompositingPhases',
+ 'Layout',
+ 'PaintLayer::updateLayerPositionsAfterLayout',
+ 'ResourceLoadPriorityOptimizer::updateAllImageResourcePriorities',
+ 'WebViewImpl::updateAllLifecyclePhases',
+ 'WebViewImpl::beginFrame',
+ ],
+
+ parseHTML: [
+ 'BackgroundHTMLParser::pumpTokenizer',
+ 'BackgroundHTMLParser::sendTokensToMainThread',
+ 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser',
+ 'HTMLDocumentParser::documentElementAvailable',
+ 'HTMLDocumentParser::notifyPendingTokenizedChunks',
+ 'HTMLDocumentParser::processParsedChunkFromBackgroundParser',
+ 'HTMLDocumentParser::processTokenizedChunkFromBackgroundParser',
+ 'ParseHTML',
+ ],
+
+ raster: [
+ 'DisplayListRasterSource::PerformSolidColorAnalysis',
+ 'Picture::Raster',
+ 'RasterBufferImpl::Playback',
+ 'RasterTask',
+ 'RasterizerTaskImpl::RunOnWorkerThread',
+ 'SkCanvas::drawImageRect()',
+ 'SkCanvas::drawPicture()',
+ 'SkCanvas::drawTextBlob()',
+ 'TileTaskWorkerPool::PlaybackToMemory',
+ ],
+
+ record: [
+ 'Canvas2DLayerBridge::flushRecordingOnly',
+ 'CompositingInputsUpdater::update',
+ 'CompositingRequirementsUpdater::updateRecursive',
+ 'ContentLayerDelegate::paintContents',
+ 'DisplayItemList::Finalize',
+ 'LocalFrameView::RunPaintLifecyclePhase',
+ 'LocalFrameView::RunPrePaintLifecyclePhase',
+ 'Paint',
+ 'PaintController::commitNewDisplayItems',
+ 'PaintLayerCompositor::updateIfNeededRecursive',
+ 'Picture::Record',
+ 'PictureLayer::Update',
+ ],
+
+ style: [
+ 'CSSParserImpl::parseStyleSheet.parse',
+ 'CSSParserImpl::parseStyleSheet.tokenize',
+ 'Document::rebuildLayoutTree',
+ 'Document::recalcStyle',
+ 'Document::updateActiveStyle',
+ 'Document::updateStyle',
+ 'Document::updateStyleInvalidationIfNeeded',
+ 'LocalFrameView::updateStyleAndLayoutIfNeededRecursive',
+ 'ParseAuthorStyleSheet',
+ 'RuleSet::addRulesFromSheet',
+ 'StyleElement::processStyleSheet',
+ 'StyleEngine::createResolver',
+ 'StyleEngine::updateActiveStyleSheets',
+ 'StyleSheetContents::parseAuthorStyleSheet',
+ 'UpdateLayoutTree',
+ ],
+
+ script_parse_and_compile: [
+ 'V8.CompileFullCode',
+ 'V8.NewContext',
+ 'V8.Parse',
+ 'V8.ParseLazy',
+ 'V8.RecompileSynchronous',
+ 'V8.ScriptCompiler',
+ 'v8.compile',
+ 'v8.parseOnBackground',
+ ],
+
+ script_execute: [
+ 'EvaluateScript',
+ 'FunctionCall',
+ 'HTMLParserScriptRunner ExecuteScript',
+ 'V8.Execute',
+ 'V8.RunMicrotasks',
+ 'V8.Task',
+ 'WindowProxy::initialize',
+ 'v8.callFunction',
+ 'v8.run',
+ ],
+
+ resource_loading: [
+ 'RenderFrameImpl::didFinishDocumentLoad',
+ 'RenderFrameImpl::didFinishLoad',
+ 'Resource::appendData',
+ 'ResourceDispatcher::OnReceivedData',
+ 'ResourceDispatcher::OnReceivedResponse',
+ 'ResourceDispatcher::OnRequestComplete',
+ 'ResourceFetcher::requestResource',
+ 'WebURLLoaderImpl::Context::Cancel',
+ 'WebURLLoaderImpl::Context::OnCompletedRequest',
+ 'WebURLLoaderImpl::Context::OnReceivedData',
+ 'WebURLLoaderImpl::Context::OnReceivedRedirect',
+ 'WebURLLoaderImpl::Context::OnReceivedResponse',
+ 'WebURLLoaderImpl::Context::Start',
+ 'WebURLLoaderImpl::loadAsynchronously',
+ 'WebURLLoaderImpl::loadSynchronously',
+ 'content::mojom::URLLoaderClient',
+ ],
+
+ // Where do these go?
+ renderer_misc: [
+ 'DecodeFont',
+ 'ThreadState::completeSweep', // blink_gc
+ ],
+
+ // TODO(fmeawad): https://github.com/catapult-project/catapult/issues/2572
+ v8_runtime: [
+ // Dynamically populated.
+ ],
+
+ [SAME_AS_PARENT]: [
+ 'SyncChannel::Send',
+ ]
+ };
+
+ const COLOR_FOR_USER_FRIENDLY_CATEGORY = new tr.b.SinebowColorGenerator();
+ const USER_FRIENDLY_CATEGORY_FOR_TITLE = new Map();
+
+ for (const category in TITLES_FOR_USER_FRIENDLY_CATEGORY) {
+ TITLES_FOR_USER_FRIENDLY_CATEGORY[category].forEach(function(title) {
+ USER_FRIENDLY_CATEGORY_FOR_TITLE.set(title, category);
+ });
+ }
+
+ // keys: event.category part
+ // values: user friendly category
+ const USER_FRIENDLY_CATEGORY_FOR_EVENT_CATEGORY = {
+ netlog: 'net',
+ overhead: 'overhead',
+ startup: 'startup',
+ gpu: 'gpu',
+ };
+
+ function ChromeUserFriendlyCategoryDriver() {
+ }
+
+ ChromeUserFriendlyCategoryDriver.fromEvent = function(event) {
+ let userFriendlyCategory =
+ USER_FRIENDLY_CATEGORY_FOR_TITLE.get(event.title);
+ if (userFriendlyCategory) {
+ if (userFriendlyCategory === SAME_AS_PARENT) {
+ if (event.parentSlice) {
+ return ChromeUserFriendlyCategoryDriver.fromEvent(event.parentSlice);
+ }
+ } else {
+ return userFriendlyCategory;
+ }
+ }
+
+ const eventCategoryParts = tr.b.getCategoryParts(event.category);
+ for (let i = 0; i < eventCategoryParts.length; ++i) {
+ const eventCategory = eventCategoryParts[i];
+ userFriendlyCategory = USER_FRIENDLY_CATEGORY_FOR_EVENT_CATEGORY[
+ eventCategory];
+ if (userFriendlyCategory) {
+ return userFriendlyCategory;
+ }
+ }
+
+ return 'other';
+ };
+
+ ChromeUserFriendlyCategoryDriver.getColor = function(ufc) {
+ return COLOR_FOR_USER_FRIENDLY_CATEGORY.colorForKey(ufc);
+ };
+
+ ChromeUserFriendlyCategoryDriver.ALL_TITLES = ['other'];
+ for (const category in TITLES_FOR_USER_FRIENDLY_CATEGORY) {
+ if (category === SAME_AS_PARENT) continue;
+ ChromeUserFriendlyCategoryDriver.ALL_TITLES.push(category);
+ }
+ for (const category of Object.values(
+ USER_FRIENDLY_CATEGORY_FOR_EVENT_CATEGORY)) {
+ ChromeUserFriendlyCategoryDriver.ALL_TITLES.push(category);
+ }
+ ChromeUserFriendlyCategoryDriver.ALL_TITLES.sort();
+
+ // Prime the color generator by iterating through all UFCs in alphabetical
+ // order.
+ for (const category of ChromeUserFriendlyCategoryDriver.ALL_TITLES) {
+ ChromeUserFriendlyCategoryDriver.getColor(category);
+ }
+
+ return {
+ ChromeUserFriendlyCategoryDriver,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver_test.html
new file mode 100644
index 00000000000..5251a7d2edc
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/chrome_user_friendly_category_driver_test.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/guid.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import"
+ href="/tracing/extras/chrome/chrome_user_friendly_category_driver.html">
+<link rel="import" href="/tracing/model/event_set.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ufcFromEvent = tr.e.chrome.ChromeUserFriendlyCategoryDriver.fromEvent;
+
+ test('userFriendlyCategory', function() {
+ assert.strictEqual(ufcFromEvent({
+ guid: tr.b.GUID.allocateSimple(),
+ title: 'LocalFrameView::layout',
+ category: 'cat'
+ }), 'layout');
+
+ assert.strictEqual(ufcFromEvent({
+ guid: tr.b.GUID.allocateSimple(),
+ title: 'V8.Execute',
+ category: 'cat'
+ }), 'script_execute');
+
+ assert.strictEqual(ufcFromEvent({
+ guid: tr.b.GUID.allocateSimple(),
+ title: 'Paint',
+ category: 'cat'
+ }), 'record');
+ assert.strictEqual(ufcFromEvent({
+ guid: tr.b.GUID.allocateSimple(),
+ title: 'Document::updateStyle',
+ category: 'cat'
+ }), 'style');
+ assert.strictEqual(ufcFromEvent({
+ guid: tr.b.GUID.allocateSimple(),
+ title: 'HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser',
+ category: 'cat'
+ }), 'parseHTML');
+
+ assert.strictEqual(ufcFromEvent({
+ guid: tr.b.GUID.allocateSimple(),
+ title: 'constructor',
+ category: 'cat'
+ }), 'other');
+ });
+
+ test('ufcFromTraceCategory', function() {
+ assert.strictEqual(ufcFromEvent({
+ guid: tr.b.GUID.allocateSimple(),
+ title: 'a',
+ category: 'netlog'
+ }), 'net');
+
+ assert.strictEqual(ufcFromEvent({
+ guid: tr.b.GUID.allocateSimple(),
+ title: 'a',
+ category: 'foobar,overhead'
+ }), 'overhead');
+
+ assert.strictEqual(ufcFromEvent({
+ guid: tr.b.GUID.allocateSimple(),
+ title: 'a',
+ category: 'startup'
+ }), 'startup');
+ });
+
+ test('ufcOther', function() {
+ assert.strictEqual(ufcFromEvent({
+ guid: tr.b.GUID.allocateSimple(),
+ title: 'a',
+ category: 'other'
+ }), 'other');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time.html
new file mode 100644
index 00000000000..24468cb10e0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time.html
@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/multi_dimensional_view.html">
+<link rel="import" href="/tracing/extras/chrome/chrome_processes.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/model/helpers/chrome_renderer_helper.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.chrome', function() {
+ class CpuTime {
+ /**
+ * Returns two level map of rail stage to initiator type to set of bounds of
+ * associated segments, intersected with |rangeOfInterest|.
+ *
+ * For each rail stage, we additionally have a key 'all_initiators' that
+ * returns all the segment bounds associated with that rail stage across all
+ * initiator types. For completeness, there is an additional rail stage
+ * 'all_stages' that has all the segment bounds across all rail stages.
+ *
+ * If a segment is not contained within |rangeOfInterest| it is not
+ * included.
+ *
+ * There is a unique segment bound for each segment in the map. For example,
+ * assume
+ * - |segmentA| is associated with both Click Response and Scroll Animation
+ * - |bound1| is the interesting bound of |segmentA| in Response -> Click
+ * set.
+ * - |bound2| is the interesting bound of |segmentA| in Animation -> Scroll
+ * set.
+ * Then bound1 === bound2. These segment bounds can therefore be used as
+ * keys in a map to represent the segment.
+ *
+ * Example return value (all bounds are intersected with |rangeOfInterest|):
+ *
+ * {
+ * 'Animation': {
+ * 'CSS': {Segment bounds for CSS Animation},
+ * 'Video': {Segment bounds for Video Animation},
+ * ...
+ * 'all_initiators': {All Animation segment bounds}
+ * },
+ * 'Response': {
+ * 'Click': {Segment bounds for Click Response},
+ * 'Scroll': {Segment bounds for Scroll Response},
+ * ...
+ * 'all_initiators': {All Response segment bounds}
+ * },
+ * ...
+ * 'all_stages': {
+ * 'all_initiators': {All segment bounds}
+ * }
+ * }
+ *
+ * @param {!Array.<!tr.model.um.Segment>} segments
+ * @param {!Array.<!tr.b.math.Range>} rangeOfInterest
+ * @returns {!Map.<string, Map.<string, Set.<!tr.b.math.Range>>}
+ */
+ static getStageToInitiatorToSegmentBounds(segments, rangeOfInterest) {
+ const stageToInitiatorToRanges = new Map();
+ stageToInitiatorToRanges.set('all_stages',
+ new Map([['all_initiators', new Set()]]));
+ const allRanges =
+ stageToInitiatorToRanges.get('all_stages').get('all_initiators');
+
+ for (const segment of segments) {
+ if (!rangeOfInterest.intersectsRangeInclusive(segment.range)) continue;
+ const intersectingRange =
+ rangeOfInterest.findIntersection(segment.range);
+ allRanges.add(intersectingRange);
+
+ for (const expectation of segment.expectations) {
+ const stageTitle = expectation.stageTitle;
+ if (!stageToInitiatorToRanges.has(stageTitle)) {
+ stageToInitiatorToRanges.set(stageTitle,
+ new Map([['all_initiators', new Set()]]));
+ }
+
+ const initiatorToRanges = stageToInitiatorToRanges.get(stageTitle);
+ initiatorToRanges.get('all_initiators').add(intersectingRange);
+
+ const initiatorType = expectation.initiatorType;
+ if (initiatorType) {
+ if (!initiatorToRanges.has(initiatorType)) {
+ initiatorToRanges.set(initiatorType, new Set());
+ }
+ initiatorToRanges.get(initiatorType).add(intersectingRange);
+ }
+ }
+ }
+ return stageToInitiatorToRanges;
+ }
+
+ /**
+ * Returns the root node of a MultiDimensionalView in TopDownTreeView for
+ * cpu time.
+ *
+ * The returned tree view is three dimensional (processType, threadType, and
+ * railStage + initiator). Rail stage and initiator are not separate
+ * dimensions because they are not independent - there is no such thing as
+ * CSS Response or Scroll Load.
+ *
+ * Each node in the tree view contains two values - cpuUsage and cpuTotal.
+ *
+ * See cpu_time_multidimensinoal_view.md for more details about the returned
+ * multidimensional view.
+ *
+ * @param {!tr.Model} model
+ * @param {!tr.b.math.Range} rangeOfInterest
+ * @returns {!tr.b.MultiDimensionalViewNode}
+ */
+ static constructMultiDimensionalView(model, rangeOfInterest) {
+ const mdvBuilder = new tr.b.MultiDimensionalViewBuilder(
+ 3 /* dimensions (process, thread and rail stage / initiator) */,
+ 2 /* valueCount (cpuUsage and cpuTotal) */);
+
+ const stageToInitiatorToRanges =
+ CpuTime.getStageToInitiatorToSegmentBounds(
+ model.userModel.segments, rangeOfInterest);
+
+ const allSegmentBoundsInRange =
+ stageToInitiatorToRanges.get('all_stages').get('all_initiators');
+
+ for (const [pid, process] of Object.entries(model.processes)) {
+ const processType =
+ tr.e.chrome.chrome_processes.canonicalizeProcessName(process.name);
+ for (const [tid, thread] of Object.entries(process.threads)) {
+ // Cache cpuTime for each segment bound.
+ const rangeToCpuTime = new Map();
+ for (const range of allSegmentBoundsInRange) {
+ rangeToCpuTime.set(range, thread.getCpuTimeForRange(range));
+ }
+
+ for (const [stage, initiatorToRanges] of stageToInitiatorToRanges) {
+ for (const [initiator, ranges] of initiatorToRanges) {
+ const cpuTime = tr.b.math.Statistics.sum(ranges,
+ range => rangeToCpuTime.get(range));
+ const duration = tr.b.math.Statistics.sum(ranges,
+ range => range.duration);
+ const cpuTimePerSecond = cpuTime / duration;
+ mdvBuilder.addPath(
+ [[processType], [thread.type], [stage, initiator]],
+ [cpuTimePerSecond, cpuTime],
+ tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL);
+ }
+ }
+ }
+ }
+
+ return mdvBuilder.buildTopDownTreeView();
+ }
+ }
+
+ return {
+ CpuTime,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time_multidimensional_view.md b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time_multidimensional_view.md
new file mode 100644
index 00000000000..dcc646c3f9a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time_multidimensional_view.md
@@ -0,0 +1,79 @@
+<!-- Copyright 2017 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+
+# CPU Time MultiDimensionalView Explainer
+
+This document explains the MultiDimensionalView returned by `constructMultiDimensionalView` in `cpuTime.html`.
+
+The returned MultiDimensionalView is in TopDownTreeView mode. It is three
+dimensional (processType, threadType, and railStage + initiator). Rail stage and
+initiator are not separate dimensions because they are not independent - there
+is no such thing as CSS Response or Scroll Load.
+
+Each node in the tree view contains two values - cpuUsage and cpuTotal.
+
+When talking about multidimensional tree views, a useful abstration is "path",
+which uniquely determines a node in the tree: A path is a 3 element array, and
+each of these three elements is a possibly empty array of strings. Here is an
+example path:
+```
+[ ['browser_process'], ['CrBrowserMain'], ['Animation', 'CSS'] ]
+ Dimension 1 Dimension 2 Dimension 3
+```
+
+We can arrive at the node denoted by this path in many different ways starting
+from the root node, so this path is not to be confused with the graph theoretic
+notion of path. Here is one of the ways to reach the node (we show the
+intermediate paths during the traversal inline):
+
+```javascript
+const node = treeRoot // [[], [], []]
+ .children[0] // access children along first dimension
+ .get('browser_process') // [['browser_process'], [], []]
+ .children[2] // access children along third dimension
+ .get('Animation') // [['browser_process'], [], ['Animation']]
+ .children[1] // Access children along second dimension
+ .get('CrBrowserMain') // [['browser_process'], ['CrBrowserMain'], ['Animation']]
+ .children[2] // Go further down along third dimension
+ .get('CSS') // [['browser_process'], ['CrBrowserMain'], ['Animation', 'CSS']]
+```
+Now node.values contains the cpu time data for the browser main thread during
+the CSS Animation stage:
+- `node.values[0]` is `cpuUsage` - cpu time over per unit of wall clock time
+- `node.values[1]` is `cpuTotal` - total miliseconds of used cpu time
+
+The path for the node that hold data for all threads of renderer process
+during scroll response expectations is `[['renderer_process'], [], ['Response', 'Scroll']]`.
+
+As we can see, we simply have an empty array for the second dimension. This
+works similarly if we want to get data for all processes for a particular
+thread.
+
+However, if we want to access data for all rail stages and all initiator
+types, we have to use the special rail stage `all_stages`, and initiator
+type `all_initiators`. For example, to get cpu data during all Response
+stages for all processes and threads, we use the node at path
+ `[[], [], ['Response', 'all_initiators']]`
+
+To get cpu data for all rail stages for ChildIOThread, we use the path
+ `[[], ['ChildIOThread'], ['all_stages', 'all_initiators']]`
+
+This is because the tree view automatically aggregates cpu time
+data along each dimension by summing values on the children nodes. For
+aggregating rail stages and initiator types, summing is not the right thing
+to do since
+
+ 1. User Expectations can overlap (for example, one tab can go through a
+ Video Animation while another tab is concurrently going through a CSS
+ Animation - it's worth noting that user expectations are not scoped to a
+ tab.)
+
+ 2. Different rail stages have different durations (for example, if we
+ have 200ms of Video Animation with 50% cpuUsage, and 500ms of CSS
+ Animation with 60% cpuUage, cpuUsage for all Animations is clearly not
+ 110%.)
+
+We therefore more manually do the appropriate aggregations and store the
+data in `all_stages` and `all_initiators` nodes.
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time_test.html
new file mode 100644
index 00000000000..606463737ce
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time_test.html
@@ -0,0 +1,1503 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/cpu_time.html">
+<link rel="import" href="/tracing/extras/chrome/cpu_time_test_utils.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const getStageToInitiatorToSegmentBounds =
+ tr.e.chrome.CpuTime.getStageToInitiatorToSegmentBounds;
+
+ const INITIATOR_TYPE = tr.model.um.INITIATOR_TYPE;
+
+ const CHROME_PROCESS_NAMES =
+ tr.e.chrome.chrome_processes.CHROME_PROCESS_NAMES;
+
+ const constructMultiDimensionalView =
+ tr.e.chrome.CpuTime.constructMultiDimensionalView;
+
+ const buildModelFromSpec = tr.e.chrome.cpuTimeTestUtils.buildModelFromSpec;
+
+ test('getStageToInitiatorToSegmentBounds', () => {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const thread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ // This is needed for a model to have segments.
+ thread.name = 'CrBrowserMain';
+
+ model.userModel.expectations.push(new tr.model.um.AnimationExpectation(
+ model, INITIATOR_TYPE.CSS,
+ 100, // start time.
+ 300 // duration.
+ ));
+ model.userModel.expectations.push(new tr.model.um.AnimationExpectation(
+ model, INITIATOR_TYPE.VIDEO, 300, 100));
+ model.userModel.expectations.push(new tr.model.um.ResponseExpectation(
+ model, INITIATOR_TYPE.SCROLL, 400, 200));
+ });
+
+ const segments = model.userModel.segments;
+
+ const map = getStageToInitiatorToSegmentBounds(
+ model.userModel.segments, model.bounds);
+
+ // Ignoring Idle Expectations, we have the following segments:
+ // [100, 300]: CSS Animation
+ // [300, 400]: CSS Animation, Video Animation
+ // [400, 600]: Scroll Response
+ const allSegments = [...map.get('all_stages').get('all_initiators')];
+ assert.sameDeepMembers(
+ allSegments.map(s => [s.min, s.max]),
+ [[100, 300], [300, 400], [400, 600]]
+ );
+
+ const videoAnimationSegments =
+ [...map.get('Animation').get(INITIATOR_TYPE.VIDEO)];
+ assert.sameDeepMembers(
+ videoAnimationSegments.map(s => [s.min, s.max]),
+ [[300, 400]]);
+
+ const cssAnimationSegments =
+ [...map.get('Animation').get(INITIATOR_TYPE.CSS)];
+ assert.sameDeepMembers(
+ cssAnimationSegments.map(s => [s.min, s.max]),
+ [[100, 300], [300, 400]]);
+
+ const allAnimationSegments =
+ [...map.get('Animation').get('all_initiators')];
+ assert.sameDeepMembers(
+ allAnimationSegments.map(s => [s.min, s.max]),
+ [[100, 300], [300, 400]]);
+
+ const scrollResponseSegments =
+ [...map.get('Response').get(INITIATOR_TYPE.SCROLL)];
+ assert.sameDeepMembers(
+ scrollResponseSegments.map(s => [s.min, s.max]),
+ [[400, 600]]);
+
+ const allResponseSegments =
+ [...map.get('Response').get('all_initiators')];
+ assert.sameDeepMembers(
+ allResponseSegments.map(s => [s.min, s.max]),
+ [[400, 600]]);
+ });
+
+ test('getStageToInitiatorToSegmentBounds-rangeOfInterest', () => {
+ const model = tr.c.TestUtils.newModel(function(model) {
+ const thread = model.getOrCreateProcess(1).getOrCreateThread(1);
+ // This is needed for a model to have segments.
+ thread.name = 'CrBrowserMain';
+
+ model.userModel.expectations.push(new tr.model.um.AnimationExpectation(
+ model, INITIATOR_TYPE.CSS,
+ 100, // start time.
+ 300 // duration.
+ ));
+ model.userModel.expectations.push(new tr.model.um.AnimationExpectation(
+ model, INITIATOR_TYPE.VIDEO, 300, 100));
+ model.userModel.expectations.push(new tr.model.um.ResponseExpectation(
+ model, INITIATOR_TYPE.SCROLL, 400, 200));
+ });
+
+ const segments = model.userModel.segments;
+
+ const map = getStageToInitiatorToSegmentBounds(model.userModel.segments,
+ tr.b.math.Range.fromExplicitRange(150, 350));
+
+ // Ignoring Idle Expectations, we have the following segments in range:
+ // [150, 300]: CSS Animation
+ // [300, 350]: CSS Animation, Video Animation
+ const allSegments = [...map.get('all_stages').get('all_initiators')];
+ assert.sameDeepMembers(
+ allSegments.map(s => [s.min, s.max]),
+ [[150, 300], [300, 350]]
+ );
+
+ const videoAnimationSegments =
+ [...map.get('Animation').get(INITIATOR_TYPE.VIDEO)];
+ assert.sameDeepMembers(
+ videoAnimationSegments.map(s => [s.min, s.max]),
+ [[300, 350]]);
+
+ const cssAnimationSegments =
+ [...map.get('Animation').get(INITIATOR_TYPE.CSS)];
+ assert.sameDeepMembers(
+ cssAnimationSegments.map(s => [s.min, s.max]),
+ [[150, 300], [300, 350]]);
+
+ const allAnimationSegments =
+ [...map.get('Animation').get('all_initiators')];
+ assert.sameDeepMembers(
+ allAnimationSegments.map(s => [s.min, s.max]),
+ [[150, 300], [300, 350]]);
+
+ // There should be no Response segments
+ assert.isFalse(map.has('Response'));
+ });
+
+ /**
+ * Given the root node of a top down multidimensional tree view, returns
+ * the node at |path|.
+ */
+ function getNodeValues_(root, path) {
+ let node = root;
+ for (let i = 0; i < path.length; i++) {
+ for (const component of path[i]) {
+ node = node.children[i].get(component);
+ }
+ }
+ return node.values;
+ }
+
+ const getCpuUsage_ = nodeValues => nodeValues[0].total;
+ const getCpuTotal_ = nodeValues => nodeValues[1].total;
+
+ /**
+ * Returns a simple model spec with one process (browser process) and one
+ * thread (CrBrowserMain).
+ *
+ * It does not contain any slices or expectations - those should be added
+ * manually.
+ *
+ * This is a function instead of just a variable because the test functions
+ * are meant to modify this modelSpec and insert suitable expectations.
+ */
+ function getSimpleModelSpec_() {
+ return {
+ processes: [
+ {
+ name: 'Browser',
+ pid: 12345,
+ threads: [
+ {
+ name: 'CrBrowserMain',
+ tid: 1,
+ slices: []
+ },
+ ],
+ },
+ ],
+ expectations: [],
+ };
+ }
+
+ test('constructMultiDimensionalView_' +
+ 'slicesDoNotStraddleExpecationBoundaries', () => {
+ const simpleModelSpec = getSimpleModelSpec_();
+ simpleModelSpec.processes[0].threads[0].slices.push(
+ {range: [150, 200], cpu: 30},
+ {range: [205, 255], cpu: 20}
+ );
+ simpleModelSpec.expectations.push({
+ stage: 'Animation',
+ initiatorType: INITIATOR_TYPE.CSS,
+ range: [100, 300],
+ });
+
+ const path = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['CrBrowserMain'], ['Animation', 'CSS']];
+
+ const model = buildModelFromSpec(simpleModelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ // Total CPU Time is 30 + 20 from the two slices, and the total duration of
+ // CSS Animation expectation is 200 (because the range is [100, 300]). CPU
+ // Usage is therefore (30 + 20) / 200.
+ assert.closeTo(getCpuUsage_(values), (30 + 20) / 200, 1e-7);
+ assert.closeTo(getCpuTotal_(values), (30 + 20), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_' +
+ 'slicesStraddleExpectationBoundaries', () => {
+ const simpleModelSpec = getSimpleModelSpec_();
+ simpleModelSpec.processes[0].threads[0].slices.push(
+ {range: [150, 200], cpu: 30},
+ {range: [205, 255], cpu: 20}
+ );
+ simpleModelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [75, 175]}
+ );
+ const path = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['CrBrowserMain'], ['Animation', 'Video']
+ ];
+
+ const model = buildModelFromSpec(simpleModelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ // Only half of the first slice is in the expectation range, so we get a
+ // total CPU contribution of 30 / 2 = 15. The second slice is not in the
+ // expectation at all, so CPU contribution from that slice is 0. So total
+ // CPU Usage during Video Animation expectation is 15.
+ // The total duration of the expectation is 100, so CPU usage is 15 / 100.
+ assert.closeTo(getCpuUsage_(values), 15 / 100, 1e-7);
+ assert.closeTo(getCpuTotal_(values), 15, 1e-7);
+ });
+
+ test('constructMultiDimensionalView_' +
+ 'singleThread-disjointExpectationsOfSameInitiator', () => {
+ const simpleModelSpec = getSimpleModelSpec_();
+ simpleModelSpec.processes[0].threads[0].slices.push(
+ {range: [150, 200], cpu: 30},
+ {range: [205, 255], cpu: 20}
+ );
+ simpleModelSpec.expectations.push(
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [100, 160]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [205, 225]}
+ );
+ const path = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['CrBrowserMain'], ['Response', 'Scroll']
+ ];
+
+ const model = buildModelFromSpec(simpleModelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ // 1/5 of the first slice and 2/5 of the second slice is in the expectation
+ // ranges, so total CPU is 0.2 * 30 + 0.4 * 20.
+ // Total duration of Scroll Response expectation is 60 + 20, since the two
+ // expectation ranges are disjoint.
+ assert.closeTo(getCpuUsage_(values),
+ (0.2 * 30 + 0.4 * 20) / (60 + 20), 1e-7);
+ assert.closeTo(getCpuTotal_(values),
+ 0.2 * 30 + 0.4 * 20, 1e-7);
+ });
+
+ test('constructMultiDimensionalView_' +
+ 'singleThread-overlappingExpectationsOfSameInitiators', () => {
+ const simpleModelSpec = getSimpleModelSpec_();
+ simpleModelSpec.processes[0].threads[0].slices.push(
+ {range: [150, 200], cpu: 30},
+ {range: [205, 255], cpu: 20}
+ );
+ simpleModelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [100, 190]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [160, 230]}
+ );
+ const path = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['CrBrowserMain'], ['Animation', 'CSS']
+ ];
+
+ const model = buildModelFromSpec(simpleModelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ // The whole of the first slice is covered by CSS Animation expectation,
+ // even though the expectations are from two different ranges. The second
+ // slice is only half covered. Total CPU usage: 30 + 0.5 * 20.
+ // The total range covered by the expectation is [100, 230], so total
+ // duration is 130.
+ assert.closeTo(getCpuUsage_(values), (30 + 0.5 * 20) / 130, 1e-7);
+ assert.closeTo(getCpuTotal_(values), 30 + 0.5 * 20, 1e-7);
+ });
+
+ test('constructMultiDimensionalView_' +
+ 'singleThread-overlappingExpectationsOfDifferentInitiators', () => {
+ const simpleModelSpec = getSimpleModelSpec_();
+ simpleModelSpec.processes[0].threads[0].slices.push(
+ {range: [150, 200], cpu: 30},
+ {range: [205, 255], cpu: 20}
+ );
+ simpleModelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [100, 190]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [160, 230]}
+ );
+ const path = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['CrBrowserMain'], ['Animation', 'all_initiators']
+ ];
+
+ const model = buildModelFromSpec(simpleModelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ assert.closeTo(getCpuUsage_(values),
+ (30 + 0.5 * 20) / 130, 1e-7);
+ assert.closeTo(getCpuTotal_(values),
+ 30 + 0.5 * 20, 1e-7);
+ });
+
+ test('constructMultiDimensionalView_' +
+ 'singleThread-allStages-customRangeOfInterest', () => {
+ const simpleModelSpec = getSimpleModelSpec_();
+ simpleModelSpec.processes[0].threads[0].slices.push(
+ {range: [150, 200], cpu: 30},
+ {range: [205, 255], cpu: 20}
+ );
+ simpleModelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [100, 190]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [160, 230]}
+ );
+ const path = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['CrBrowserMain'], ['all_stages', 'all_initiators']
+ ];
+
+ const model = buildModelFromSpec(simpleModelSpec);
+ const rangeOfInterest = new tr.b.math.Range.fromExplicitRange(100, 210);
+ const root = constructMultiDimensionalView(model, rangeOfInterest);
+ const values = getNodeValues_(root, path);
+
+ // Only 1/10 of the second slice is included in the range of interest, so
+ // contribution from that slice is 0.1 * 20.
+ // The total range of expectation within range of interest is [100, 210], so
+ // total duration is 110.
+ assert.closeTo(getCpuUsage_(values), (30 + 0.1 * 20) / 110, 1e-7);
+ assert.closeTo(getCpuTotal_(values), 30 + 0.1 * 20, 1e-7);
+ });
+
+ /**
+ * Returns a model spec where the browser process has two worker threads.
+ *
+ * This is a function instead of just a variable because the test functions
+ * are meant to modify this modelSpec and insert suitable expectations.
+ *
+ * Thread 1 looks like
+ *
+ * |0 |10 |20 |30 |40 |50 |60 |70 .... Time
+ * [ ] [ ] [ ] [ ] .... Slices
+ *
+ * where each slice has a duration of 10ms and CPU time of 5ms.
+ *
+ * Thread 2 looks like
+ *
+ * |0 |50 |100 |150 |200 |250 |300 |350 .... Time
+ * [ ] [ ] [ ] [ ] .... Slices
+ * where each slice has a duration of 80ms and CPU time of 40ms.
+ */
+ function getMultipleThreadsOfSameTypeModelSpec_() {
+ return {
+ processes: [
+ {
+ name: 'Browser',
+ pid: 12345,
+ threads: [
+ {
+ name: 'CrBrowserMain',
+ tid: 1,
+ slices: [],
+ },
+ {
+ name: 'Worker/1',
+ tid: 42,
+ slices: (() => {
+ const slices = [];
+ for (let i = 0; i < 1000; i += 20) {
+ slices.push({range: [i, i + 10], cpu: 5});
+ }
+ return slices;
+ })(),
+ },
+ {
+ name: 'Worker/2',
+ tid: 52,
+ slices: (() => {
+ const slices = [];
+ for (let i = 0; i < 1000; i += 100) {
+ slices.push({range: [i, i + 80], cpu: 40});
+ }
+ return slices;
+ })(),
+ },
+ ],
+ },
+ ],
+
+ expectations: [],
+ };
+ }
+
+ test('constructMultiDimensionalView_multipleThreadsOfSameType_' +
+ 'singleExpectation', () => {
+ const modelSpec = getMultipleThreadsOfSameTypeModelSpec_();
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]}
+ );
+
+ const path = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['Worker'], ['Animation', 'Video']
+ ];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ // There are five thread-1 slices within [0, 90] and one thread-2 slice.
+ assert.closeTo(getCpuUsage_(values), (5 * 5 + 40) / 90, 1e-7);
+ assert.closeTo(getCpuTotal_(values), 5 * 5 + 40, 1e-7);
+ });
+
+ test('constructMultiDimensionalView_multipleThreadsOfSameType_' +
+ 'disjointExpectationSameInitiator', () => {
+ const modelSpec = getMultipleThreadsOfSameTypeModelSpec_();
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ );
+
+ const path = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['Worker'], ['Animation', 'CSS']
+ ];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ // Both worker threads are of the same type ('Worker'), so their CPU times
+ // will be added together.
+ // There are 3 thread-1 slices and 6/8 of a thread-2 slice within
+ // [500, 560].
+ // There are 1 thread-1 slices and two thread-2 slices within [690, 890].
+ // Total expectation duration is 60 + 200.
+ assert.closeTo(getCpuUsage_(values),
+ ((5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2)) / (60 + 200), 1e-7);
+ assert.closeTo(getCpuTotal_(values),
+ (5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_multipleThreadsOfSameType_' +
+ 'overlappingExpectationsOfSameInitiator', () => {
+ const modelSpec = getMultipleThreadsOfSameTypeModelSpec_();
+ // [Scroll R]
+ // [Scroll R]
+ modelSpec.expectations.push(
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]}
+ );
+
+ const path = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['Worker'], ['Response', 'Scroll']
+ ];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ assert.closeTo(getCpuUsage_(values),
+ (5 * 4 + 40 * (70 / 80)) / 90, 1e-7);
+ assert.closeTo(getCpuTotal_(values),
+ 5 * 4 + 40 * (70 / 80), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_multipleThreadsOfSameType_' +
+ 'disjointExpectationsAllInitiators', () => {
+ const modelSpec = getMultipleThreadsOfSameTypeModelSpec_();
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ );
+
+ const path = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['Worker'], ['Animation', 'all_initiators']
+ ];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ assert.closeTo(getCpuUsage_(values),
+ ((5 * 5 + 40) + ((5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2))) /
+ (90 + 60 + 200), 1e-7);
+ assert.closeTo(getCpuTotal_(values),
+ (5 * 5 + 40) + ((5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2)),
+ 1e-7);
+ });
+
+
+ test('constructMultiDimensionalView_multipleThreadsOfSameType_' +
+ 'overlappingExpectationsAllInitiators', () => {
+ const modelSpec = getMultipleThreadsOfSameTypeModelSpec_();
+ // [Click R]
+ // [Scroll R]
+ // [Scroll R]
+ modelSpec.expectations.push(
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK,
+ range: [200, 220]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]}
+ );
+
+ const path = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['Worker'], ['Response', 'all_initiators']
+ ];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ assert.closeTo(getCpuUsage_(values),
+ (5 * 5 + 40) / 100, 1e-7);
+ assert.closeTo(getCpuTotal_(values),
+ 5 * 5 + 40, 1e-7);
+ });
+
+
+ test('constructMultiDimensionalView_multipleThreadsOfSameType_' +
+ 'allStagesAllInitiators', () => {
+ const modelSpec = getMultipleThreadsOfSameTypeModelSpec_();
+ // [Video A] [Click R] [CSS A] [ CSS A ]
+ // [Scroll R]
+ // [Scroll R]
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK,
+ range: [200, 220]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ );
+
+ const path = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['Worker'], ['all_stages', 'all_initiators']
+ ];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ assert.closeTo(getCpuUsage_(values), (250 + 400) / 990, 1e-7);
+ assert.closeTo(getCpuTotal_(values), 250 + 400, 1e-7);
+ });
+
+ /**
+ * Returns a model spec where there are two renderer processes, each with a
+ * renderer main thread.
+ *
+ * This is a function instead of just a variable because the test functions
+ * are meant to modify this modelSpec and insert suitable expectations.
+ *
+ * The main thread on renderer-1 looks like
+ *
+ * |0 |10 |20 |30 |40 |50 |60 |70 .... Time
+ * [ ] [ ] [ ] [ ] .... Slices
+ *
+ * where each slice has a duration of 10ms and CPU time of 5ms.
+ *
+ * The main thread on renderer-2 looks like
+ *
+ * |0 |50 |100 |150 |200 |250 |300 |350 .... Time
+ * [ ] [ ] [ ] [ ] .... Slices
+ *
+ * where each slice has a duration of 80ms and CPU time of 40ms.
+ */
+ function getMultipleProcessesOfSameTypeModelSpec_() {
+ return {
+ processes: [
+ {
+ name: 'Browser',
+ pid: 12345,
+ threads: [
+ {
+ name: 'CrBrowserMain',
+ tid: 1,
+ slices: [],
+ },
+ ],
+ },
+ {
+ name: 'Renderer',
+ pid: 20001,
+ threads: [
+ {
+ name: 'CrRendererMain',
+ tid: 42,
+ slices: (() => {
+ const slices = [];
+ for (let i = 0; i < 1000; i += 20) {
+ slices.push({range: [i, i + 10], cpu: 5});
+ }
+ return slices;
+ })(),
+ },
+ ],
+ },
+ {
+ name: 'Renderer',
+ pid: 30001,
+ threads: [
+ {
+ name: 'CrRendererMain',
+ tid: 52,
+ slices: (() => {
+ const slices = [];
+ for (let i = 0; i < 1000; i += 100) {
+ slices.push({range: [i, i + 80], cpu: 40});
+ }
+ return slices;
+ })(),
+ },
+ ]
+ },
+ ],
+
+ expectations: [],
+ };
+ }
+
+ test('constructMultiDimensionalView_multipleProcessesOfSameType_' +
+ 'singleExpectation', () => {
+ const modelSpec = getMultipleProcessesOfSameTypeModelSpec_();
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]}
+ );
+
+ const path = [
+ [CHROME_PROCESS_NAMES.RENDERER],
+ ['CrRendererMain'], ['Animation', 'Video']
+ ];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ assert.closeTo(getCpuUsage_(values), (5 * 5 + 40) / 90, 1e-7);
+ assert.closeTo(getCpuTotal_(values), 5 * 5 + 40, 1e-7);
+ });
+
+ test('constructMultiDimensionalView_multipleProcessesOfSameType_' +
+ 'disjointExpectationSameInitiator', () => {
+ const modelSpec = getMultipleProcessesOfSameTypeModelSpec_();
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ );
+
+ const path = [
+ [CHROME_PROCESS_NAMES.RENDERER],
+ ['CrRendererMain'], ['Animation', 'CSS']
+ ];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ assert.closeTo(getCpuUsage_(values),
+ ((5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2)) / (60 + 200), 1e-7);
+ assert.closeTo(getCpuTotal_(values),
+ (5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_multipleProcessesOfSameType_' +
+ 'overlappingExpectationsOfSameInitiator', () => {
+ const modelSpec = getMultipleProcessesOfSameTypeModelSpec_();
+ // [Scroll R]
+ // [Scroll R]
+ modelSpec.expectations.push(
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]}
+ );
+
+ const path = [
+ [CHROME_PROCESS_NAMES.RENDERER],
+ ['CrRendererMain'], ['Response', 'Scroll']
+ ];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ assert.closeTo(getCpuUsage_(values),
+ (5 * 4 + 40 * (70 / 80)) / 90, 1e-7);
+ assert.closeTo(getCpuTotal_(values),
+ 5 * 4 + 40 * (70 / 80), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_multipleProcessesOfSameType_' +
+ 'disjointExpectationsAllInitiators', () => {
+ const modelSpec = getMultipleProcessesOfSameTypeModelSpec_();
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ );
+
+ const path = [
+ [CHROME_PROCESS_NAMES.RENDERER],
+ ['CrRendererMain'], ['Animation', 'all_initiators']
+ ];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ assert.closeTo(getCpuUsage_(values),
+ ((5 * 5 + 40) + ((5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2))) /
+ (90 + 60 + 200), 1e-7);
+ assert.closeTo(getCpuTotal_(values),
+ (5 * 5 + 40) + ((5 * 3 + 40 * (60 / 80)) + (5 * 10 + 40 * 2)),
+ 1e-7);
+ });
+
+
+ test('constructMultiDimensionalView_multipleProcessesOfSameType_' +
+ 'overlappingExpectationsAllInitiators', () => {
+ const modelSpec = getMultipleProcessesOfSameTypeModelSpec_();
+ // [Click R]
+ // [Scroll R]
+ // [Scroll R]
+ modelSpec.expectations.push(
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK,
+ range: [200, 220]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]}
+ );
+
+ const path = [
+ [CHROME_PROCESS_NAMES.RENDERER],
+ ['CrRendererMain'], ['Response', 'all_initiators']
+ ];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ assert.closeTo(getCpuUsage_(values), (5 * 5 + 40) / 100, 1e-7);
+ assert.closeTo(getCpuTotal_(values), 5 * 5 + 40, 1e-7);
+ });
+
+
+ test('constructMultiDimensionalView_multipleProcessesOfSameType_' +
+ 'allStagesAllInitiators', () => {
+ const modelSpec = getMultipleProcessesOfSameTypeModelSpec_();
+ // [Video A] [Click R] [CSS A] [ CSS A ]
+ // [Scroll R]
+ // [Scroll R]
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK,
+ range: [200, 220]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ );
+
+ const path = [
+ [CHROME_PROCESS_NAMES.RENDERER],
+ ['CrRendererMain'], ['all_stages', 'all_initiators']
+ ];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ assert.closeTo(getCpuUsage_(values), (250 + 400) / 990, 1e-7);
+ assert.closeTo(getCpuTotal_(values), 250 + 400, 1e-7);
+ });
+
+ /**
+ * Returns a model spec where the browser process has a main thread and an IO
+ * thread.
+ *
+ * This is a function instead of just a variable because the test functions
+ * are meant to modify this modelSpec and insert suitable expectations.
+ *
+ * The browser main thread looks like
+ *
+ * |0 |10 |20 |30 |40 |50 |60 |70 .... Time
+ * [ ] [ ] [ ] [ ] .... Slices
+ *
+ * where each slice has a duration of 10ms and CPU time of 5ms.
+ *
+ * The IO Thread looks like
+ *
+ * |0 |50 |100 |150 |200 |250 |300 |350 .... Time
+ * [ ] [ ] [ ] [ ] .... Slices
+ *
+ * where each slice has a duration of 80ms and CPU time of 40ms.
+ */
+ function getAllThreadsOfSameProcessModelSpec_() {
+ return {
+ processes: [
+ {
+ name: 'Browser',
+ pid: 12345,
+ threads: [
+ {
+ name: 'CrBrowserMain',
+ tid: 1,
+ slices: (() => {
+ const slices = [];
+ for (let i = 0; i < 1000; i += 20) {
+ slices.push({range: [i, i + 10], cpu: 5});
+ }
+ return slices;
+ })(),
+ },
+ {
+ name: 'Chrome_IOThread',
+ tid: 5,
+ slices: (() => {
+ const slices = [];
+ for (let i = 0; i < 1000; i += 100) {
+ slices.push({range: [i, i + 80], cpu: 40});
+ }
+ return slices;
+ })(),
+ }
+ ],
+ },
+ ],
+
+ expectations: [],
+ };
+ }
+
+
+ test('constructMultiDimensionalView_AllThreadsOfSameProcess_' +
+ 'singleExpectation', () => {
+ const modelSpec = getAllThreadsOfSameProcessModelSpec_();
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]}
+ );
+
+ const pathForAllThreads = [
+ [CHROME_PROCESS_NAMES.BROWSER], [], ['Animation', 'Video']];
+
+ const pathForThread1 = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['CrBrowserMain'], ['Animation', 'Video']];
+
+ const pathForThread2 = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['Chrome_IOThread'], ['Animation', 'Video']];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const valueForAllThreads = getNodeValues_(root, pathForAllThreads);
+ const valueForThread1 = getNodeValues_(root, pathForThread1);
+ const valueForThread2 = getNodeValues_(root, pathForThread2);
+
+ assert.closeTo(getCpuUsage_(valueForAllThreads),
+ getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7);
+ assert.closeTo(getCpuTotal_(valueForAllThreads),
+ getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_AllThreadsOfSameProcess_' +
+ 'disjointExpectationSameInitiator', () => {
+ const modelSpec = getAllThreadsOfSameProcessModelSpec_();
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ );
+
+ const pathForAllThreads = [
+ [CHROME_PROCESS_NAMES.BROWSER], [], ['Animation', 'CSS']];
+
+ const pathForThread1 = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['CrBrowserMain'], ['Animation', 'CSS']];
+
+ const pathForThread2 = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['Chrome_IOThread'], ['Animation', 'CSS']];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const valueForAllThreads = getNodeValues_(root, pathForAllThreads);
+ const valueForThread1 = getNodeValues_(root, pathForThread1);
+ const valueForThread2 = getNodeValues_(root, pathForThread2);
+
+ assert.closeTo(getCpuUsage_(valueForAllThreads),
+ getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7);
+ assert.closeTo(getCpuTotal_(valueForAllThreads),
+ getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_AllThreadsOfSameProcess_' +
+ 'overlappingExpectationsOfSameInitiator', () => {
+ const modelSpec = getAllThreadsOfSameProcessModelSpec_();
+ // [Scroll R]
+ // [Scroll R]
+ modelSpec.expectations.push(
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]}
+ );
+
+ const pathForAllThreads = [
+ [CHROME_PROCESS_NAMES.BROWSER], [], ['Response', 'Scroll']];
+
+ const pathForThread1 = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['CrBrowserMain'], ['Response', 'Scroll']];
+
+ const pathForThread2 = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['Chrome_IOThread'], ['Response', 'Scroll']];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const valueForAllThreads = getNodeValues_(root, pathForAllThreads);
+ const valueForThread1 = getNodeValues_(root, pathForThread1);
+ const valueForThread2 = getNodeValues_(root, pathForThread2);
+
+ assert.closeTo(getCpuUsage_(valueForAllThreads),
+ getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7);
+ assert.closeTo(getCpuTotal_(valueForAllThreads),
+ getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_AllThreadsOfSameProcess_' +
+ 'disjointExpectationsAllInitiators', () => {
+ const modelSpec = getAllThreadsOfSameProcessModelSpec_();
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ );
+
+ const pathForAllThreads = [
+ [CHROME_PROCESS_NAMES.BROWSER], [], ['Animation', 'all_initiators']];
+
+ const pathForThread1 = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['CrBrowserMain'], ['Animation', 'all_initiators']];
+
+ const pathForThread2 = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['Chrome_IOThread'], ['Animation', 'all_initiators']];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const valueForAllThreads = getNodeValues_(root, pathForAllThreads);
+ const valueForThread1 = getNodeValues_(root, pathForThread1);
+ const valueForThread2 = getNodeValues_(root, pathForThread2);
+
+ assert.closeTo(getCpuUsage_(valueForAllThreads),
+ getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7);
+ assert.closeTo(getCpuTotal_(valueForAllThreads),
+ getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_AllThreadsOfSameProcess_' +
+ 'overlappingExpectationsAllInitiators', () => {
+ const modelSpec = getAllThreadsOfSameProcessModelSpec_();
+ // [Click R]
+ // [Scroll R]
+ // [Scroll R]
+ modelSpec.expectations.push(
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK,
+ range: [200, 220]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]}
+ );
+
+ const pathForAllThreads = [
+ [CHROME_PROCESS_NAMES.BROWSER], [], ['Response', 'all_initiators']];
+
+ const pathForThread1 = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['CrBrowserMain'], ['Response', 'all_initiators']];
+
+ const pathForThread2 = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['Chrome_IOThread'], ['Response', 'all_initiators']];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const valueForAllThreads = getNodeValues_(root, pathForAllThreads);
+ const valueForThread1 = getNodeValues_(root, pathForThread1);
+ const valueForThread2 = getNodeValues_(root, pathForThread2);
+
+ assert.closeTo(getCpuUsage_(valueForAllThreads),
+ getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7);
+ assert.closeTo(getCpuTotal_(valueForAllThreads),
+ getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_AllThreadsOfSameProcess_' +
+ 'allStagesAllInitiators', () => {
+ const modelSpec = getAllThreadsOfSameProcessModelSpec_();
+ // [Video A] [Click R] [CSS A] [ CSS A ]
+ // [Scroll R]
+ // [Scroll R]
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK,
+ range: [200, 220]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ );
+
+ const pathForAllThreads = [
+ [CHROME_PROCESS_NAMES.BROWSER], [], ['all_stages', 'all_initiators']];
+
+ const pathForThread1 = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['CrBrowserMain'], ['all_stages', 'all_initiators']];
+
+ const pathForThread2 = [
+ [CHROME_PROCESS_NAMES.BROWSER],
+ ['Chrome_IOThread'], ['all_stages', 'all_initiators']];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const valueForAllThreads = getNodeValues_(root, pathForAllThreads);
+ const valueForThread1 = getNodeValues_(root, pathForThread1);
+ const valueForThread2 = getNodeValues_(root, pathForThread2);
+
+ assert.closeTo(getCpuUsage_(valueForAllThreads),
+ getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7);
+ assert.closeTo(getCpuTotal_(valueForAllThreads),
+ getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7);
+ });
+
+ /**
+ * Returns a model spec where a renderer process and a GPU process both have a
+ * Chrome_ChildIOThread.
+ *
+ * This is a function instead of just a variable because the test functions
+ * are meant to modify this modelSpec and insert suitable expectations.
+ *
+ * The modelSpec includes a basic browser process because it not a valid
+ * chrome model otherwise.
+ *
+ * The renderer ChildIOThread looks like
+ *
+ * |0 |10 |20 |30 |40 |50 |60 |70 .... Time
+ * [ ] [ ] [ ] [ ] .... Slices
+ *
+ * where each slice has a duration of 10ms and CPU time of 5ms.
+ *
+ * The GPU ChildIOThread looks like
+ *
+ * |0 |50 |100 |150 |200 |250 |300 |350 .... Time
+ * [ ] [ ] [ ] [ ] .... Slices
+ *
+ * where each slice has a duration of 80ms and CPU time of 40ms.
+ */
+ function getAllProcessesOfSameThreadModelSpec_() {
+ return {
+ processes: [
+ {
+ name: 'Browser',
+ pid: 12345,
+ threads: [
+ {
+ name: 'CrBrowserMain',
+ tid: 1,
+ slices: [],
+ },
+ ],
+ },
+ {
+ name: 'Renderer',
+ pid: 20001,
+ threads: [
+ {
+ name: 'Chrome_ChildIOThread',
+ tid: 42,
+ slices: (() => {
+ const slices = [];
+ for (let i = 0; i < 1000; i += 20) {
+ slices.push({range: [i, i + 10], cpu: 5});
+ }
+ return slices;
+ })(),
+ },
+ ],
+ },
+ {
+ name: 'GPU Process',
+ pid: 30001,
+ threads: [
+ {
+ name: 'Chrome_ChildIOThread',
+ tid: 52,
+ slices: (() => {
+ const slices = [];
+ for (let i = 0; i < 1000; i += 100) {
+ slices.push({range: [i, i + 80], cpu: 40});
+ }
+ return slices;
+ })(),
+ },
+ ]
+ },
+ ],
+ expectations: [],
+ };
+ }
+
+ test('constructMultiDimensionalView_AllProcessesOfSameThread_' +
+ 'singleExpectation', () => {
+ const modelSpec = getAllProcessesOfSameThreadModelSpec_();
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]}
+ );
+
+ const pathForAllThreads = [
+ [], ['Chrome_ChildIOThread'], ['Animation', 'Video']];
+
+ const pathForThread1 = [
+ [CHROME_PROCESS_NAMES.RENDERER],
+ ['Chrome_ChildIOThread'], ['Animation', 'Video']];
+
+ const pathForThread2 = [
+ [CHROME_PROCESS_NAMES.GPU],
+ ['Chrome_ChildIOThread'], ['Animation', 'Video']];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const valueForAllThreads = getNodeValues_(root, pathForAllThreads);
+ const valueForThread1 = getNodeValues_(root, pathForThread1);
+ const valueForThread2 = getNodeValues_(root, pathForThread2);
+
+ assert.closeTo(getCpuUsage_(valueForAllThreads),
+ getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7);
+ assert.closeTo(getCpuTotal_(valueForAllThreads),
+ getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_AllProcessesOfSameThread_' +
+ 'disjointExpectationSameInitiator', () => {
+ const modelSpec = getAllProcessesOfSameThreadModelSpec_();
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ );
+
+ const pathForAllThreads = [
+ [], ['Chrome_ChildIOThread'], ['Animation', 'CSS']];
+
+ const pathForThread1 = [
+ [CHROME_PROCESS_NAMES.RENDERER],
+ ['Chrome_ChildIOThread'], ['Animation', 'CSS']];
+
+ const pathForThread2 = [
+ [CHROME_PROCESS_NAMES.GPU],
+ ['Chrome_ChildIOThread'], ['Animation', 'CSS']];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const valueForAllThreads = getNodeValues_(root, pathForAllThreads);
+ const valueForThread1 = getNodeValues_(root, pathForThread1);
+ const valueForThread2 = getNodeValues_(root, pathForThread2);
+
+ assert.closeTo(getCpuUsage_(valueForAllThreads),
+ getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7);
+ assert.closeTo(getCpuTotal_(valueForAllThreads),
+ getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_AllProcessesOfSameThread_' +
+ 'overlappingExpectationsOfSameInitiator', () => {
+ const modelSpec = getAllProcessesOfSameThreadModelSpec_();
+ // [Scroll R]
+ // [Scroll R]
+ modelSpec.expectations.push(
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]}
+ );
+
+ const pathForAllThreads = [
+ [], ['Chrome_ChildIOThread'], ['Response', 'Scroll']];
+
+ const pathForThread1 = [
+ [CHROME_PROCESS_NAMES.RENDERER],
+ ['Chrome_ChildIOThread'], ['Response', 'Scroll']];
+
+ const pathForThread2 = [
+ [CHROME_PROCESS_NAMES.GPU],
+ ['Chrome_ChildIOThread'], ['Response', 'Scroll']];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const valueForAllThreads = getNodeValues_(root, pathForAllThreads);
+
+ const valueForThread1 = getNodeValues_(root, pathForThread1);
+ const valueForThread2 = getNodeValues_(root, pathForThread2);
+
+ assert.closeTo(getCpuUsage_(valueForAllThreads),
+ getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7);
+ assert.closeTo(getCpuTotal_(valueForAllThreads),
+ getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_AllProcessesOfSameThread_' +
+ 'disjointExpectationsAllInitiators', () => {
+ const modelSpec = getAllProcessesOfSameThreadModelSpec_();
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ );
+
+ const pathForAllThreads = [
+ [], ['Chrome_ChildIOThread'], ['Animation', 'all_initiators']];
+
+ const pathForThread1 = [
+ [CHROME_PROCESS_NAMES.RENDERER],
+ ['Chrome_ChildIOThread'], ['Animation', 'all_initiators']];
+
+ const pathForThread2 = [
+ [CHROME_PROCESS_NAMES.GPU],
+ ['Chrome_ChildIOThread'], ['Animation', 'all_initiators']];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const valueForAllThreads = getNodeValues_(root, pathForAllThreads);
+ const valueForThread1 = getNodeValues_(root, pathForThread1);
+ const valueForThread2 = getNodeValues_(root, pathForThread2);
+
+ assert.closeTo(getCpuUsage_(valueForAllThreads),
+ getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7);
+ assert.closeTo(getCpuTotal_(valueForAllThreads),
+ getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_AllProcessesOfSameThread_' +
+ 'overlappingExpectationsAllInitiators', () => {
+ const modelSpec = getAllProcessesOfSameThreadModelSpec_();
+ // [Click R]
+ // [Scroll R]
+ // [Scroll R]
+ modelSpec.expectations.push(
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK,
+ range: [200, 220]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]}
+ );
+
+ const pathForAllThreads = [
+ [], ['Chrome_ChildIOThread'], ['Response', 'all_initiators']];
+
+ const pathForThread1 = [
+ [CHROME_PROCESS_NAMES.RENDERER],
+ ['Chrome_ChildIOThread'], ['Response', 'all_initiators']];
+
+ const pathForThread2 = [
+ [CHROME_PROCESS_NAMES.GPU],
+ ['Chrome_ChildIOThread'], ['Response', 'all_initiators']];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const valueForAllThreads = getNodeValues_(root, pathForAllThreads);
+ const valueForThread1 = getNodeValues_(root, pathForThread1);
+ const valueForThread2 = getNodeValues_(root, pathForThread2);
+
+ assert.closeTo(getCpuUsage_(valueForAllThreads),
+ getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7);
+ assert.closeTo(getCpuTotal_(valueForAllThreads),
+ getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_AllProcessesOfSameThread_' +
+ 'allStagesAllInitiators', () => {
+ const modelSpec = getAllProcessesOfSameThreadModelSpec_();
+ // [Video A] [Click R] [CSS A] [ CSS A ]
+ // [Scroll R]
+ // [Scroll R]
+ modelSpec.expectations.push(
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK,
+ range: [200, 220]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ );
+
+ const pathForAllThreads = [
+ [], ['Chrome_ChildIOThread'], ['all_stages', 'all_initiators']];
+
+ const pathForThread1 = [
+ [CHROME_PROCESS_NAMES.RENDERER],
+ ['Chrome_ChildIOThread'], ['all_stages', 'all_initiators']];
+
+ const pathForThread2 = [
+ [CHROME_PROCESS_NAMES.GPU],
+ ['Chrome_ChildIOThread'], ['all_stages', 'all_initiators']];
+
+ const model = buildModelFromSpec(modelSpec);
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const valueForAllThreads = getNodeValues_(root, pathForAllThreads);
+ const valueForThread1 = getNodeValues_(root, pathForThread1);
+ const valueForThread2 = getNodeValues_(root, pathForThread2);
+
+ assert.closeTo(getCpuUsage_(valueForAllThreads),
+ getCpuUsage_(valueForThread1) + getCpuUsage_(valueForThread2), 1e-7);
+ assert.closeTo(getCpuTotal_(valueForAllThreads),
+ getCpuTotal_(valueForThread1) + getCpuTotal_(valueForThread2), 1e-7);
+ });
+
+ test('constructMultiDimensionalView_completeAggregation', () => {
+ const modelSpec = {
+ processes: [
+ {
+ name: 'Browser',
+ pid: 12345,
+ threads: [
+ {
+ name: 'CrBrowserMain',
+ tid: 1,
+ slices: (() => {
+ const slices = [];
+ for (let i = 0; i < 1000; i += 50) {
+ slices.push({range: [i, i + 50], cpu: 30});
+ }
+ return slices;
+ })(),
+ },
+ ],
+ },
+ {
+ name: 'Renderer',
+ pid: 20001,
+ threads: [
+ {
+ name: 'Chrome_ChildIOThread',
+ tid: 42,
+ slices: (() => {
+ const slices = [];
+ for (let i = 0; i < 1000; i += 20) {
+ slices.push({range: [i, i + 10], cpu: 5});
+ }
+ return slices;
+ })(),
+ },
+ ],
+ },
+ {
+ name: 'GPU Process',
+ pid: 30001,
+ threads: [
+ {
+ name: 'Chrome_ChildIOThread',
+ tid: 52,
+ slices: (() => {
+ const slices = [];
+ for (let i = 0; i < 1000; i += 100) {
+ slices.push({range: [i, i + 80], cpu: 40});
+ }
+ return slices;
+ })(),
+ },
+ ]
+ },
+ ],
+
+ // [Video A] [Click R] [CSS A] [ CSS A ]
+ // [Scroll R]
+ // [Scroll R]
+ expectations: [
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.VIDEO,
+ range: [0, 90]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.CLICK,
+ range: [200, 220]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [210, 260]},
+ {stage: 'Response', initiatorType: INITIATOR_TYPE.SCROLL,
+ range: [250, 300]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [500, 560]},
+ {stage: 'Animation', initiatorType: INITIATOR_TYPE.CSS,
+ range: [690, 890]}
+ ],
+ };
+
+ const model = buildModelFromSpec(modelSpec);
+ const path = [[], [], ['all_stages', 'all_initiators']];
+ const root = constructMultiDimensionalView(model, model.bounds);
+ const values = getNodeValues_(root, path);
+
+ assert.closeTo(getCpuUsage_(values),
+ (30 * 20 + 5 * 50 + 40 * 10) / 1000, 1e-7);
+ assert.closeTo(getCpuTotal_(values),
+ (30 * 20 + 5 * 50 + 40 * 10), 1e-7);
+ });
+});
+
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time_test_utils.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time_test_utils.html
new file mode 100644
index 00000000000..3066677e6e7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/cpu_time_test_utils.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.chrome.cpuTimeTestUtils', function() {
+ /**
+ * Takes a model spec, and returns a fully constructed model.
+ *
+ * A model spec is a minimal representation of a model containing only the
+ * information needed to set up scenarios for testing cpu time metrics - it
+ * specifies
+ * - All the processes with name, pid, and threads
+ * - All the threads with name, tid, and top level slices
+ * - All the top level slices with range and cpu duration
+ * - All the User Expectations (except Idle Expectations) with range, stage,
+ * and initiator.
+ *
+ * Example model spec:
+ * {
+ * processes: [
+ * {
+ * name: 'Browser',
+ * pid: 12345,
+ * threads: [
+ * {
+ * name: 'CrBrowserMain',
+ * tid: 1,
+ * slices: [
+ * {range: [150, 200], cpu: 30},
+ * {range: [205, 255], cpu: 20}
+ * ]
+ * },
+ * ],
+ * },
+ * ],
+ * expectations: [
+ * {stage: 'Animation', initiatorType: 'Video', range: [0, 90]},
+ * {stage: 'Response', initiatorType: 'Click', range: [200, 220]},
+ * ]
+ * }
+ */
+ function buildModelFromSpec(modelSpec) {
+ return tr.c.TestUtils.newModel(model => {
+ // Create processes, threads, and slices
+ for (const processSpec of modelSpec.processes) {
+ const process = model.getOrCreateProcess(processSpec.pid);
+ process.name = processSpec.name;
+
+ for (const threadSpec of processSpec.threads) {
+ const thread = process.getOrCreateThread(threadSpec.tid);
+ thread.name = threadSpec.name;
+
+ for (const sliceSpec of threadSpec.slices) {
+ // Sanity checks on sliceSpec
+ const sliceStart = sliceSpec.range[0];
+ const sliceEnd = sliceSpec.range[1];
+ const duration = sliceEnd - sliceStart;
+ const cpuDuration = sliceSpec.cpu;
+ assert(sliceEnd >= sliceStart,
+ 'Slice end time is earlier than slice start time: ' +
+ `sliceStart: ${sliceStart}, sliceEnd: ${sliceEnd}`);
+ assert(duration >= cpuDuration,
+ `Cpu duration (${cpuDuration}) is larger than ` +
+ `slice duration (${duration})`);
+
+ thread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ type: tr.model.ThreadSlice,
+ isTopLevel: true,
+ start: sliceSpec.range[0],
+ duration: sliceSpec.range[1] - sliceSpec.range[0],
+ // We currently do not have all the data about exactly when a
+ // thread was scheduled or descheduled - for example, if a slice
+ // got descheduled twice over its duration, the trace will not
+ // contain information to indicate that. Without loss of
+ // generality, we therefore make the assumption that the cpuStart
+ // is at the beginning of the slice, and total cpu time of a slice
+ // is uniformly spread over its duration.
+ cpuStart: sliceSpec.range[0],
+ cpuDuration: sliceSpec.cpu
+ }));
+ }
+ }
+ }
+
+ const expectations = model.userModel.expectations;
+ for (const expSpec of modelSpec.expectations) {
+ // This switch statement is not exhaustive. You may have to add more
+ // cases here when you add new scenarios.
+ switch (expSpec.stage) {
+ case 'Animation':
+ expectations.push(new tr.model.um.AnimationExpectation(
+ model, expSpec.initiatorType,
+ expSpec.range[0], expSpec.range[1] - expSpec.range[0]
+ ));
+ break;
+ case 'Idle':
+ expectations.push(new tr.model.um.IdleExpectation(
+ model, expSpec.range[0], expSpec.range[1] - expSpec.range[0]
+ ));
+ break;
+ case 'Response':
+ expectations.push(new tr.model.um.ResponseExpectation(
+ model, expSpec.initiatorType,
+ expSpec.range[0], expSpec.range[1] - expSpec.range[0]
+ ));
+ break;
+ default:
+ throw new Error('Internal Error: Stage ' + expSpec.stage +
+ 'not handled yet. You should add another case here.');
+ }
+ }
+ });
+ }
+
+ return {
+ buildModelFromSpec,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/estimated_input_latency.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/estimated_input_latency.html
new file mode 100644
index 00000000000..81d9803fa71
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/estimated_input_latency.html
@@ -0,0 +1,367 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/event_finder_utils.html">
+<link rel="import" href="/tracing/value/histogram_set.html">
+
+<script>
+'use strict';
+tr.exportTo('tr.e.chrome', function() {
+ // TODO(dproy): Because title and category are properties of TimedEvent
+ // subclasses and not TimedEvent itself, we have to write our own "has title
+ // and category" function rather than having it provided by TimedEvent.
+ // This should be fixed.
+ // https://github.com/catapult-project/catapult/issues/2784
+ function hasTitleAndCategory(event, title, category) {
+ return event.title === title && event.category &&
+ tr.b.getCategoryParts(event.category).includes(category);
+ }
+
+ function getNavStartTimestamps(rendererHelper) {
+ const navStartTimestamps = [];
+ for (const e of rendererHelper.mainThread.sliceGroup.childEvents()) {
+ if (hasTitleAndCategory(e, 'navigationStart', 'blink.user_timing')) {
+ navStartTimestamps.push(e.start);
+ }
+ }
+ return navStartTimestamps;
+ }
+
+ /**
+ * Returns a map of renderer PIDs to array of timestamps at which the
+ * renderer became interactive.
+ */
+ function getInteractiveTimestamps(model) {
+ const interactiveTimestampsMap = new Map();
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ for (const rendererHelper of Object.values(chromeHelper.rendererHelpers)) {
+ const timestamps = [];
+ interactiveTimestampsMap.set(rendererHelper.pid, timestamps);
+ }
+ for (const expectation of model.userModel.expectations) {
+ if (!(expectation instanceof tr.model.um.LoadExpectation)) continue;
+ if (tr.e.chrome.CHROME_INTERNAL_URLS.includes(
+ expectation.url)) {
+ continue;
+ }
+ if (expectation.timeToInteractive === undefined) continue;
+ if (interactiveTimestampsMap.get(expectation.renderProcess.pid) ===
+ undefined) {
+ interactiveTimestampsMap.set(expectation.renderProcess.pid, []);
+ }
+ interactiveTimestampsMap.get(expectation.renderProcess.pid).push(
+ expectation.timeToInteractive);
+ }
+ return interactiveTimestampsMap;
+ }
+
+ /**
+ * Returns an Array of task windows that start with the supplied interactive
+ * timestamps.
+ *
+ * A task window is defined as the range of time from the time when the page
+ * became interactive until either
+ *
+ * 1. The beginning of the next navigationStart event or
+ * 2. The end of the trace
+ *
+ * This function only works when timestamps are from the same renderer. If
+ * windows for multiple renderers need to be computed, the timestamps should
+ * be separated for each renderer and this function should be called
+ * separately for each.
+ *
+ * @param {!Array.<number>} interactiveTimestamps
+ * @param {!Array.<number>} navStartTimestamps
+ * @param {!number} traceEndTimestamp
+ * @returns {!Array.<tr.b.math.Range>}
+ */
+ function getPostInteractiveTaskWindows(
+ interactiveTimestamps, navStartTimestamps, traceEndTimestamp) {
+ let navStartTsIndex = 0;
+ let lastTaskWindowEndTs = undefined;
+ const taskWindows = [];
+ for (const currTTI of interactiveTimestamps) {
+ // Find the first navigation start event after the interactive
+ // timestamp.
+ while (navStartTsIndex < navStartTimestamps.length &&
+ navStartTimestamps[navStartTsIndex] < currTTI) {
+ navStartTsIndex++;
+ }
+
+ const taskWindowEndTs = navStartTsIndex < navStartTimestamps.length ?
+ navStartTimestamps[navStartTsIndex] : traceEndTimestamp;
+
+ if (taskWindowEndTs === lastTaskWindowEndTs) {
+ // This is the case where we have two different interactive timestamps
+ // with no navigationStart event between them. This is only possible
+ // when two different pages are sharing the same renderer process (and
+ // therefore the same renderer scheduler). We cannot define a proper
+ // task window in this case to calculate Estimated Input Latency.
+ throw Error('Encountered two consecutive interactive timestamps ' +
+ 'with no navigationStart between them. ' +
+ 'PostInteractiveTaskWindow is not well defined in this case.');
+ }
+
+ taskWindows.push(tr.b.math.Range.fromExplicitRange(
+ currTTI, taskWindowEndTs));
+ lastTaskWindowEndTs = taskWindowEndTs;
+ }
+ return taskWindows;
+ }
+
+ /**
+ * Returns the contribution of the given task to expected queueing time
+ * in the given time window.
+ *
+ * The result is probabilityOf(task) * expectedQueueTimeDueTo(task),
+ * where
+ * - probabilityOf(task) = probability of input arriving while the given
+ * task is running.
+ * - expectedQueueingTimeDueTo(task) = expected time until the end of the
+ * given task for input arriving while the task is running.
+ *
+ * We assume that input arrival time is uniformly distributed in the given
+ * time window.
+ *
+ * @param {!tr.b.math.Range} A time window.
+ * @param {!Array.<!{start: number, end: number, weight: number}>} A list of
+ * weighted tasks. The weight of a task must be between 0.0 and 1.0.
+ * @returns {number}
+ */
+ function contributionToEQT(window, task) {
+ const startInWindow = Math.max(window.min, task.start);
+ const endInWindow = Math.min(window.max, task.end);
+ const durationInWindow = endInWindow - startInWindow;
+ if (durationInWindow <= 0) return 0;
+ const probabilityOfTask = durationInWindow / (window.max - window.min);
+ const minQueueingTime = task.end - endInWindow;
+ const maxQueueingTime = task.end - startInWindow;
+ const expectedQueueingTimeDueToTask =
+ (maxQueueingTime + minQueueingTime) / 2;
+ return probabilityOfTask * expectedQueueingTimeDueToTask;
+ }
+
+ /**
+ * Returns weighted expected queueing time (EQT) for the given time window and
+ * the given set of weighted tasks. The tasks must not overlap.
+ *
+ * The weighted EQT is computed as
+ * sum(contributionToEQT(window, task) * task.weight)
+ * for all tasks in weightedTasks, where
+ * - contributionToEQT is the function defined above.
+ * - task.weight is an arbitrary number between 0.0 and 1.0. This is useful
+ * for computing contribution of chrome subcomponents (e.g. GC) to
+ * the expected queueing time for EQT diagnostics.
+ *
+ * We assume that input arrival time is uniformly distributed in the given
+ * time window.
+ *
+ * @param {!tr.b.math.Range} A time window.
+ * @param {!Array.<!{start: number, end: number, weight: number}>} A list of
+ * weighted tasks. The weight of a task must be between 0.0 and 1.0.
+ * @returns {number}
+ */
+ function weightedExpectedQueueingTime(window, weightedTasks) {
+ let result = 0;
+ for (const task of weightedTasks) {
+ result += contributionToEQT(window, task) * task.weight;
+ }
+ return result;
+ }
+
+ /**
+ * Returns expected queueing time for the given time window and
+ * the given set of tasks. The tasks must not overlap.
+ *
+ * @param {!tr.b.math.Range} A time window.
+ * @param {!Array.<!{start: number, end: number}>} A list of tasks.
+ * @returns {number}
+ */
+ function expectedQueueingTime(window, tasks) {
+ return weightedExpectedQueueingTime(window, tasks.map(function(task) {
+ return { start: task.start, end: task.end, weight: 1 };
+ }));
+ }
+
+ /**
+ * Object of this calss represents the sliding window and maintains its
+ * main invariant: windowEQT = firstTaskEQT + innerEQT + lastTaskEQT.
+ * It is intended to be used only in maxExpectedQueueingTimeInSlidingWindow().
+ */
+ class SlidingWindow {
+ /**
+ * @param {number} The starting time of the sliding window.
+ * @param {number} The window size.
+ * @param {!Array.<!{start: number, end: number}>} A list of tasks sorted by
+ * task start time.
+ */
+ constructor(startTime, windowSize, sortedTasks) {
+ /**
+ * @private @const {number} The window size.
+ */
+ this.windowSize_ = windowSize;
+ /**
+ * @private @const {!Array.<!{start: number, end: number}>} The tasks.
+ */
+ this.sortedTasks_ = sortedTasks;
+ /**
+ * @private {!tr.b.math.Range} The endpoints of the sliding window.
+ */
+ this.range_ = tr.b.math.Range.fromExplicitRange(
+ startTime, startTime + windowSize);
+ /**
+ * @private {number} The index of the first task in the sortedTasks that
+ * ends after this window starts:
+ * this.range_.min < this.sortedTasks_[this.firstTaskIndex_].end.
+ */
+ this.firstTaskIndex_ =
+ sortedTasks.findIndex(task => startTime < task.end);
+ if (this.firstTaskIndex_ === -1) {
+ this.firstTaskIndex_ = sortedTasks.length;
+ }
+ /**
+ * @private {number} The index of the last task in the sortedTasks that
+ * starts before this window ends:
+ * this.range.max > this.sortedTasks_[lastTaskIndex_].start.
+ */
+ this.lastTaskIndex_ = -1;
+ while (this.lastTaskIndex_ + 1 < sortedTasks.length &&
+ sortedTasks[this.lastTaskIndex_ + 1].start < startTime + windowSize) {
+ this.lastTaskIndex_++;
+ }
+ /**
+ * @private {number} The sum of EQT contributions for all tasks between
+ * the first task and the last task (excluding the first and the last
+ * tasks). All such tasks are completely inside the window.
+ */
+ this.innerEQT_ = 0;
+ for (let i = this.firstTaskIndex_ + 1; i < this.lastTaskIndex_; i++) {
+ this.innerEQT_ += contributionToEQT(this.range_, sortedTasks[i]);
+ }
+ }
+
+ /**
+ * @returns the EQT for this window.
+ */
+ get getEQT() {
+ let firstTaskEQT = 0;
+ if (this.firstTaskIndex_ < this.sortedTasks_.length) {
+ firstTaskEQT = contributionToEQT(this.range_,
+ this.sortedTasks_[this.firstTaskIndex_]);
+ }
+ let lastTaskEQT = 0;
+ if (this.firstTaskIndex_ < this.lastTaskIndex_) {
+ lastTaskEQT = contributionToEQT(this.range_,
+ this.sortedTasks_[this.lastTaskIndex_]);
+ }
+ return firstTaskEQT + this.innerEQT_ + lastTaskEQT;
+ }
+
+ /**
+ * Moves the window to the given time t.
+ * @param {number} The time.
+ */
+ slide(t) {
+ this.range_ = tr.b.math.Range.fromExplicitRange(t, t + this.windowSize_);
+ if (this.firstTaskIndex_ < this.sortedTasks_.length &&
+ this.sortedTasks_[this.firstTaskIndex_].end <= t) {
+ // The first task moved out of the window.
+ this.firstTaskIndex_++;
+ if (this.firstTaskIndex_ < this.lastTaskIndex_) {
+ // The new first window was accounted in innerEQT. Undo that.
+ this.innerEQT_ -= contributionToEQT(this.range_,
+ this.sortedTasks_[this.firstTaskIndex_]);
+ }
+ }
+ if (this.lastTaskIndex_ + 1 < this.sortedTasks_.length &&
+ this.sortedTasks_[this.lastTaskIndex_ + 1].start <
+ t + this.windowSize_) {
+ // A new task moved in the window.
+ if (this.firstTaskIndex_ < this.lastTaskIndex_) {
+ // The old last task is completely inside the window.
+ // Account it in innerEQT.
+ this.innerEQT_ += contributionToEQT(this.range_,
+ this.sortedTasks_[this.lastTaskIndex_]);
+ }
+ this.lastTaskIndex_++;
+ }
+ }
+ }
+
+ /**
+ * Returns maximum expected queueing time for time window of the given size
+ * that slides from the startTime to the endTime:
+ * max { expectedQueueingTime(window(t, t + windowSize), tasks),
+ * for all startTime <= t && t + w <= endTime }.
+ * See https://goo.gl/jmWpMl for the description of the algorithm.
+ *
+ * @param {number} start time for the sliding window.
+ * @param {number} end time for the sliding window.
+ * @param {number} the size of the sliding window.
+ * @param {!Array.<!{start: number, end: number}>} A list of tasks.
+ * The tasks must not overlap.
+ * @returns {number}
+ */
+ function maxExpectedQueueingTimeInSlidingWindow(startTime, endTime,
+ windowSize, tasks) {
+ if (windowSize <= 0) {
+ throw Error('The window size must be positive number');
+ }
+ if (startTime + windowSize > endTime) {
+ throw Error('The sliding window must fit in the specified time range');
+ }
+
+ const sortedTasks = tasks.slice().sort((a, b) => a.start - b.start);
+
+ for (let i = 1; i < sortedTasks.length; i++) {
+ // Ensure that the previous task finishes not later than the current task.
+ // This can happen with short trace events late in the timeline, when
+ // floating point errors increasingly come into play.
+ if (sortedTasks[i - 1].end > sortedTasks[i].start) {
+ const midpoint = (sortedTasks[i - 1].end + sortedTasks[i].start) / 2;
+ sortedTasks[i - 1].end = midpoint;
+ sortedTasks[i].start = midpoint;
+ }
+ }
+
+ // Collect all time points that the sliding window needs to stop at.
+ // See https://goo.gl/jmWpMl for justification.
+ let endpoints = [];
+ endpoints.push(startTime);
+ endpoints.push(endTime - windowSize);
+ for (const task of tasks) {
+ endpoints.push(task.start - windowSize);
+ endpoints.push(task.start);
+ endpoints.push(task.end - windowSize);
+ endpoints.push(task.end);
+ }
+ endpoints = endpoints.filter(
+ x => (startTime <= x && x + windowSize <= endTime));
+ endpoints.sort((a, b) => a - b);
+
+ // Slide the window and compute maxEQT.
+ const slidingWindow = new SlidingWindow(
+ endpoints[0], windowSize, sortedTasks);
+ let maxEQT = 0;
+ for (const t of endpoints) {
+ slidingWindow.slide(t);
+ maxEQT = Math.max(maxEQT, slidingWindow.getEQT);
+ }
+ return maxEQT;
+ }
+
+ return {
+ getPostInteractiveTaskWindows,
+ getNavStartTimestamps,
+ getInteractiveTimestamps,
+ expectedQueueingTime,
+ maxExpectedQueueingTimeInSlidingWindow,
+ weightedExpectedQueueingTime
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/estimated_input_latency_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/estimated_input_latency_test.html
new file mode 100644
index 00000000000..3031ec2c4a2
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/estimated_input_latency_test.html
@@ -0,0 +1,384 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/assert_utils.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/chrome_test_utils.html">
+<link rel="import"
+ href="/tracing/extras/chrome/chrome_user_friendly_category_driver.html">
+<link rel="import" href="/tracing/extras/chrome/estimated_input_latency.html">
+<link rel="import" href="/tracing/metrics/system_health/loading_metric.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const getInteractiveTimestamps = tr.e.chrome.getInteractiveTimestamps;
+ const getPostInteractiveTaskWindows =
+ tr.e.chrome.getPostInteractiveTaskWindows;
+ const getNavStartTimestamps = tr.e.chrome.getNavStartTimestamps;
+ const expectedQueueingTime = tr.e.chrome.expectedQueueingTime;
+ const maxExpectedQueueingTimeInSlidingWindow =
+ tr.e.chrome.maxExpectedQueueingTimeInSlidingWindow;
+ const weightedExpectedQueueingTime = tr.e.chrome.weightedExpectedQueueingTime;
+ const assertRangeEquals = tr.b.assertRangeEquals;
+
+ function newSchedulerTask(startTime, duration) {
+ return tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: tr.e.chrome.SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ start: startTime,
+ duration
+ });
+ }
+
+ /**
+ * Adds a FrameLoader snapshot to rendererProcess that is used by test FMP
+ * candidate slices.
+ */
+ function addTestFrame(rendererProcess) {
+ rendererProcess.objects.addSnapshot(
+ 'ptr', 'loading', 'FrameLoader', 300, {
+ isLoadingMainFrame: true,
+ frame: {id_ref: '0xdeadbeef'},
+ documentLoaderURL: 'http://example.com'
+ });
+ }
+
+ function addNavigationStart(mainThread, startNavTime) {
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: startNavTime,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ }
+
+ function addNetworkRequest(rendererMain, start, duration) {
+ const networkEvents = [];
+ rendererMain.asyncSliceGroup.push(tr.c.TestUtils.newSliceEx({
+ cat: 'disabled-by-default-network',
+ title: 'ResourceLoad',
+ start,
+ duration,
+ }));
+ }
+
+ function addFMPCandidate(mainThread, fmpTime) {
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'loading',
+ title: 'firstMeaningfulPaintCandidate',
+ start: fmpTime,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ }
+
+ function addDomContentLoadedEnd(mainThread, dclTime) {
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'domContentLoadedEventEnd',
+ start: dclTime,
+ duration: 0.0,
+ args: {frame: '0xdeadbeef'}
+ }));
+ }
+
+ function addSchedulerTask(mainThread, startTime, duration) {
+ mainThread.sliceGroup.pushSlice(newSchedulerTask(startTime, duration));
+ }
+
+ function addDummyTask(mainThread, startTime) {
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'dummy',
+ title: 'dummyTitle',
+ start: startTime,
+ duration: 0.0
+ }));
+ }
+
+ test('getNavStartTimestamps', () => {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(model => {
+ const mainThread = model.rendererMain;
+ addNavigationStart(mainThread, 0);
+ addNavigationStart(mainThread, 10);
+ addNavigationStart(mainThread, 30);
+ });
+
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ const rendererHelper = chromeHelper.rendererHelpers[
+ model.rendererProcess.pid];
+ const navStartTimestamps = getNavStartTimestamps(rendererHelper);
+
+ // It is ok to assert equality for floating point numbers here because
+ // the timestamps should remain unmodified.
+ assert.deepEqual(navStartTimestamps, [0, 10, 30]);
+ });
+
+ /**
+ * Checks getInteractiveTimestamps works as intended. If the definition of
+ * TTI metric changes, this test may begin to fail and we may need to adjust
+ * our EIL implementation.
+ */
+ test('getInteractiveTimestamps', () => {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(model => {
+ addTestFrame(model.rendererProcess);
+
+ const mainThread = model.rendererMain;
+ addNavigationStart(mainThread, 0);
+ addNetworkRequest(mainThread, 0, 50);
+ addFMPCandidate(mainThread, 5000);
+ addDomContentLoadedEnd(mainThread, 5000);
+
+ addNavigationStart(mainThread, 100000);
+ addNetworkRequest(mainThread, 100000, 50);
+ addFMPCandidate(mainThread, 110000);
+ addDomContentLoadedEnd(mainThread, 110000);
+
+ // To detect when a page has become interactive, we need to find a large
+ // enough window of no long tasks. Adding a dummy task sufficiently far
+ // away extends the bounds of the model so that it can contain this
+ // window. In a non-test scenario, we always record traces for long enough
+ // that this is not an issue.
+ addDummyTask(mainThread, 900000);
+ });
+
+ const interactiveTimestampsMap = getInteractiveTimestamps(model);
+ const interactiveTimestamps =
+ interactiveTimestampsMap.get(model.rendererProcess.pid);
+ assert.deepEqual(
+ interactiveTimestamps.sort((a, b) => a - b), [5000, 110000]);
+ });
+
+ test('getInteractiveTimestampsMultiRenderer', () => {
+ const model = tr.e.chrome.ChromeTestUtils.newChromeModel(function(model) {
+ const rendererProcesses = [];
+ // ChromeModel creates one renderer. We create rest of them here.
+ for (let i = 1; i <= 5; i++) {
+ const rendererProcess = model.getOrCreateProcess(i + 100);
+ const mainThread = rendererProcess.getOrCreateThread(i + 100 + 10);
+ mainThread.name = 'CrRendererMain';
+
+ addTestFrame(rendererProcess);
+ addNavigationStart(mainThread, i * 1000);
+ addNetworkRequest(mainThread, i * 1000, 50);
+ addFMPCandidate(mainThread, i * 1000 + 50);
+ addDomContentLoadedEnd(mainThread, i * 1000 + 2000);
+ addNavigationStart(mainThread, i * 10000);
+ addNetworkRequest(mainThread, i * 10000, 50);
+ addFMPCandidate(mainThread, i * 10000 + 2000);
+ addDomContentLoadedEnd(mainThread, i * 10000 + 2000);
+ addDummyTask(mainThread, 100000);
+ }
+ });
+
+ const interactiveTimestampsMap = getInteractiveTimestamps(model);
+ assert.deepEqual(interactiveTimestampsMap.get(101), [3000, 12000]);
+ assert.deepEqual(interactiveTimestampsMap.get(102), [4000, 22000]);
+ assert.deepEqual(interactiveTimestampsMap.get(103), [5000, 32000]);
+ assert.deepEqual(interactiveTimestampsMap.get(104), [6000, 42000]);
+ assert.deepEqual(interactiveTimestampsMap.get(105), [7000, 52000]);
+ });
+
+ test('singlePostInteractiveWindow', () => {
+ const interactiveTimestamps = [50];
+ const navStartTimestamps = [0];
+ const traceEndTimestamp = [100];
+ const windows = getPostInteractiveTaskWindows(
+ interactiveTimestamps, navStartTimestamps, traceEndTimestamp);
+ assert.strictEqual(windows.length, 1);
+ assertRangeEquals(windows[0], tr.b.math.Range.fromExplicitRange(50, 100));
+ });
+
+ test('multiplePostInteractiveWindows', () => {
+ const interactiveTimestamps = [50, 80];
+ const navStartTimestamps = [0, 70];
+ const traceEndTimestamp = [100];
+ const windows = getPostInteractiveTaskWindows(
+ interactiveTimestamps, navStartTimestamps, traceEndTimestamp);
+ assert.strictEqual(windows.length, 2);
+ assertRangeEquals(windows[0], tr.b.math.Range.fromExplicitRange(50, 70));
+ assertRangeEquals(windows[1], tr.b.math.Range.fromExplicitRange(80, 100));
+ });
+
+ test('postInteractiveWindowWithOneNavigationNeverReachingInteractive', () => {
+ const interactiveTimestamps = [50, 90];
+ const navStartTimestamps = [0, 60, 70];
+ const traceEndTimestamp = [100];
+ const windows = getPostInteractiveTaskWindows(
+ interactiveTimestamps, navStartTimestamps, traceEndTimestamp);
+ assert.strictEqual(windows.length, 2);
+ assertRangeEquals(windows[0], tr.b.math.Range.fromExplicitRange(50, 60));
+ assertRangeEquals(windows[1], tr.b.math.Range.fromExplicitRange(90, 100));
+ });
+
+ test('twoInteractiveTimeStampsWithNoNavStartInBetween', () => {
+ const interactiveTimestamps = [50, 75];
+ const navStartTimestamps = [0];
+ const traceEndTimestamp = [100];
+ assert.throws(() => getPostInteractiveTaskWindows(
+ interactiveTimestamps, navStartTimestamps, traceEndTimestamp));
+ });
+
+ test('expectedQueueingTime_noTasks', () => {
+ const window = tr.b.math.Range.fromExplicitRange(0, 1000);
+ assert.closeTo(0, expectedQueueingTime(window, []), 1e-6);
+ });
+
+ test('expectedQueueingTime_singleTask', () => {
+ const window = tr.b.math.Range.fromExplicitRange(0, 1000);
+ assert.closeTo(1000 / 2,
+ expectedQueueingTime(window, [{start: 0, end: 1000}]),
+ 1e-6);
+ });
+
+ test('expectedQueueingTime_singleTaskStartingBeforeWindow', () => {
+ const window = tr.b.math.Range.fromExplicitRange(0, 1000);
+ assert.closeTo(1000 / 2,
+ expectedQueueingTime(window, [{start: -1, end: 1000}]),
+ 1e-6);
+ });
+
+ test('expectedQueueingTime_singleTaskEndingAfterWindow', () => {
+ const window = tr.b.math.Range.fromExplicitRange(0, 1000);
+ assert.closeTo(1500,
+ expectedQueueingTime(window, [{start: 0, end: 2000}]),
+ 1e-6);
+ });
+
+ test('expectedQueueingTime_singleTaskInsideWindow', () => {
+ const window = tr.b.math.Range.fromExplicitRange(0, 1000);
+ assert.closeTo(10 / 1000 * 10 / 2,
+ expectedQueueingTime(window, [{start: 500, end: 510}]),
+ 1e-6);
+ });
+
+ test('expectedQueueingTime_twoTasksInsideWindow', () => {
+ const window = tr.b.math.Range.fromExplicitRange(0, 1000);
+ assert.closeTo(10 / 1000 * 10 / 2 + 100 / 1000 * 100 / 2,
+ expectedQueueingTime(window,
+ [{start: 500, end: 510}, {start: 600, end: 700}]),
+ 1e-6);
+ });
+
+ test('expectedQueueingTime_twoTasksPartiallyInsideWindow', () => {
+ const window = tr.b.math.Range.fromExplicitRange(0, 1000);
+ assert.closeTo(10 / 1000 * 10 / 2 + 100 / 1000 * (100 + 200) / 2,
+ expectedQueueingTime(window,
+ [{start: 500, end: 510}, {start: 900, end: 1100}]),
+ 1e-6);
+ });
+
+ test('weightedExpectedQueueingTime', () => {
+ const window = tr.b.math.Range.fromExplicitRange(0, 1000);
+ assert.closeTo(1000 / 2 * 0.7,
+ weightedExpectedQueueingTime(window,
+ [{start: 0, end: 1000, weight: 0.7}]),
+ 1e-6);
+ });
+
+ test('maxExpectedQueueingTimeInSlidingWindow_taskOutsideRange', () => {
+ assert.closeTo(0,
+ maxExpectedQueueingTimeInSlidingWindow(0, 1000, 100,
+ [{start: 2000, end: 3000}]),
+ 1e-6);
+ });
+
+ test('maxExpectedQueueingTimeInSlidingWindow_taskInsideRange', () => {
+ assert.closeTo(100 / 100 * 100 / 2,
+ maxExpectedQueueingTimeInSlidingWindow(0, 1000, 100,
+ [{start: 200, end: 300}]),
+ 1e-6);
+ });
+
+ test('maxExpectedQueueingTimeInSlidingWindow_longTask', () => {
+ assert.closeTo(100 / 100 * (100 + 200) / 2,
+ maxExpectedQueueingTimeInSlidingWindow(0, 1000, 100,
+ [{start: 200, end: 400}]),
+ 1e-6);
+ });
+
+ test('maxExpectedQueueingTimeInSlidingWindow_twoTasks', () => {
+ assert.closeTo(2 * 10 / 100 * 10 / 2,
+ maxExpectedQueueingTimeInSlidingWindow(0, 1000, 100,
+ [{start: 200, end: 210}, {start: 290, end: 300}]),
+ 1e-6);
+ });
+
+ test('maxExpectedQueueingTimeInSlidingWindow_taskLargerThanRange', () => {
+ assert.closeTo(100 / 100 * (1200 + 1100) / 2,
+ maxExpectedQueueingTimeInSlidingWindow(0, 1000, 100,
+ [{start: -200, end: 1200}]),
+ 1e-6);
+ });
+
+ test('maxExpectedQueueingTimeInSlidingWindow_multipleTasks', () => {
+ assert.closeTo(40 / 100 * 40 / 2,
+ maxExpectedQueueingTimeInSlidingWindow(0, 1000, 100, [
+ {start: 500, end: 510},
+ {start: 510, end: 520},
+ {start: 520, end: 530},
+ {start: 615, end: 655},
+ {start: 1000, end: 2000},
+ ]), 1e-6);
+ });
+
+ test('maxExpectedQueueingTimeInSlidingWindow_threeTasks', () => {
+ assert.closeTo(40 / 100 * 40 / 2 + 20 / 100 * (50 + 30) / 2,
+ maxExpectedQueueingTimeInSlidingWindow(0, 1000, 100, [
+ {start: 500, end: 510},
+ {start: 520, end: 560},
+ {start: 600, end: 650},
+ ]), 1e-6);
+ });
+
+ test('maxExpectedQueueingTimeInSlidingWindow_taskEndsAfterRange', () => {
+ assert.closeTo(1 / 100 * (200 + 199) / 2,
+ maxExpectedQueueingTimeInSlidingWindow(0, 1000, 100, [
+ {start: 500, end: 502},
+ {start: 999, end: 1199},
+ ]), 1e-6);
+ });
+
+ test('maxExpectedQueueingTimeInSlidingWindow_taskStartsBeforeRange', () => {
+ assert.closeTo(3 / 100 * 1 / 2 + 20 / 100 * 20 / 2,
+ maxExpectedQueueingTimeInSlidingWindow(0, 1000, 100, [
+ {start: -10, end: 1},
+ {start: 1, end: 2},
+ {start: 2, end: 3},
+ {start: 80, end: 100},
+ {start: 999, end: 1099},
+ ]), 1e-6);
+ });
+
+ test('maxExpectedQueueingTimeInSlidingWindow_nonFittingWindowThrows', () => {
+ assert.throws(() => maxExpectedQueueingTimeInSlidingWindow(0, 10, 100,
+ [{start: 0, end: 100}]),
+ 'The sliding window must fit in the specified time range'
+ );
+ });
+
+ test('maxExpectedQueueingTimeInSlidingWindow_emptyWindowThrows', () => {
+ assert.throws(() => maxExpectedQueueingTimeInSlidingWindow(0, 10, 0,
+ [{start: 0, end: 100}]),
+ 'The window size must be positive number'
+ );
+ });
+
+ test('maxExpectedQueueingTimeInSlidingWindow_smallOverlapIsTolerated', () => {
+ // Allow small floating-point precision error when comparing task
+ // end-points for overlapping.
+ assert.closeTo((100.01 + 0.01) / 2,
+ maxExpectedQueueingTimeInSlidingWindow(0, 1000, 100,
+ [{start: 0, end: 100.02}, {start: 100.0, end: 200}]),
+ 1e-6);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/event_finder_utils.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/event_finder_utils.html
new file mode 100644
index 00000000000..01281cc7cf7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/event_finder_utils.html
@@ -0,0 +1,234 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/metrics/system_health/utils.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.chrome', function() {
+ // We want to ignore chrome internal URLs when computing metrics.
+ const CHROME_INTERNAL_URLS = [
+ // Blank URLs are usually initial empty document.
+ '',
+ 'about:blank',
+ // Chrome on Android creates main frames with the below URL for plugins.
+ 'data:text/html,pluginplaceholderdata',
+ // Special URL used to start a navigation to an unreachable error page.
+ 'chrome-error://chromewebdata/'
+ ];
+
+
+ // Title for top level tasks in the scheduler. Any input event queued during a
+ // top level scheduler task cannot be handled until the end of that task.
+ const SCHEDULER_TOP_LEVEL_TASK_TITLE = 'ThreadControllerImpl::RunTask';
+
+ const SCHEDULOER_TOP_LEVEL_TASKS = new Set([
+ // Current title for the scheduler top level task.
+ SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ // Previous names scheduler top level tasks, kept for backwards
+ // compatibility.
+ 'ThreadControllerImpl::DoWork',
+ 'TaskQueueManager::ProcessTaskFromWorkQueue'
+ ]);
+
+ /**
+ * Utility class providing methods to efficiently find events.
+ * TODO(4023) This should be merged with thread/process helper.
+ */
+ class EventFinderUtils {
+ /**
+ * Returns true if |category| is one of the categories of |event|, and
+ * |event| has title |title|.
+ *
+ * TODO(dproy): Make this a method on a suitable parent class of the
+ * event/slice classes that are used with this function.
+ */
+ static hasCategoryAndName(event, category, title) {
+ return event.title === title && event.category &&
+ tr.b.getCategoryParts(event.category).includes(category);
+ }
+
+ /**
+ * Returns the list of main thread slices of |rendererHelper|
+ * with title |eventTitle| and category |eventCategory|.
+ * Returned slices do not include telemetry internal events.
+ *
+ * @param {tr.model.helpers.ChromeRendererHelper} rendererHelper
+ * @param {string} eventTitle
+ * @param {string} eventCategory
+ * @returns {Array<!tr.model.ThreadSlice>}
+ */
+ static* getMainThreadEvents(
+ rendererHelper, eventTitle, eventCategory) {
+ if (!rendererHelper.mainThread) return;
+ // Events yielded by childEvents() are sorted by start time.
+ for (const ev of rendererHelper.mainThread.sliceGroup.childEvents()) {
+ if (rendererHelper.isTelemetryInternalEvent(ev)) continue;
+ if (!this.hasCategoryAndName(ev, eventCategory, eventTitle)) {
+ continue;
+ }
+ yield ev;
+ }
+ }
+
+ /**
+ * Returns a map of frame id to main thread slices of |rendererHelper| with
+ * title |eventTitle| and categry |eventCategory|, sorted by start
+ * time. Returned slices do not include telemetry internal events.
+ *
+ * @param {tr.model.helpers.ChromeRendererHelper} rendererHelper
+ * @param {string} eventTitle
+ * @param {string} eventCategory
+ * @returns {Map.<string, Array<!tr.model.ThreadSlice>>}
+ */
+ static getSortedMainThreadEventsByFrame(
+ rendererHelper, eventTitle, eventCategory) {
+ const eventsByFrame = new Map();
+ const events = this.getMainThreadEvents(
+ rendererHelper, eventTitle, eventCategory);
+ for (const ev of events) {
+ const frameIdRef = ev.args.frame;
+ if (frameIdRef === undefined) continue;
+ if (!eventsByFrame.has(frameIdRef)) {
+ eventsByFrame.set(frameIdRef, []);
+ }
+ eventsByFrame.get(frameIdRef).push(ev);
+ }
+
+ return eventsByFrame;
+ }
+
+ /**
+ * Returns a map of navigation id to main thread slices of |rendererHelper|
+ * with title |eventTitle| and categry |eventCategory|.
+ * Returned slices do not include telemetry internal events.
+ *
+ * @param {tr.model.helpers.ChromeRendererHelper} rendererHelper
+ * @param {string} eventTitle
+ * @param {string} eventCategory
+ * @returns {Map.<string, tr.model.ThreadSlice>}
+ */
+ static getSortedMainThreadEventsByNavId(
+ rendererHelper, eventTitle, eventCategory) {
+ const eventsByNavId = new Map();
+ const events = this.getMainThreadEvents(
+ rendererHelper, eventTitle, eventCategory);
+ for (const ev of events) {
+ if (ev.args.data === undefined) continue;
+ const navIdRef = ev.args.data.navigationId;
+ if (navIdRef === undefined) continue;
+ eventsByNavId.set(navIdRef, ev);
+ }
+
+ return eventsByNavId;
+ }
+
+ /**
+ * Returns latest event in |sortedEvents| that starts on or before
+ * |timestamp|, or undefined if no such event exists.
+ *
+ * @param {!Array<!tr.model.TimedEvent>} sortedEvents - events sorted by
+ * start time.
+ * @param {number} timestamp
+ * @returns {tr.model.TimedEvent|undefined}
+ */
+ static findLastEventStartingOnOrBeforeTimestamp(sortedEvents, timestamp) {
+ const firstIndexAfterTimestamp =
+ tr.b.findFirstTrueIndexInSortedArray(
+ sortedEvents, e => e.start > timestamp);
+ // We found the first index after the timestamp, so the index immediately
+ // before it is the index we're looking for. If the first index after
+ // timestamp is 0, then there is no index on or before timestamp.
+ if (firstIndexAfterTimestamp === 0) return undefined;
+ return sortedEvents[firstIndexAfterTimestamp - 1];
+ }
+
+ /**
+ * Returns latest event in |sortedEvents| that starts before
+ * |timestamp|, or undefined if no such event exists.
+ *
+ * @param {!Array<!tr.model.TimedEvent>} sortedEvents - events sorted by
+ * start time.
+ * @param {number} timestamp
+ * @returns {tr.model.TimedEvent|undefined}
+ */
+ static findLastEventStartingBeforeTimestamp(sortedEvents, timestamp) {
+ const firstIndexAfterTimestamp =
+ tr.b.findFirstTrueIndexInSortedArray(
+ sortedEvents, e => e.start >= timestamp);
+ // We found the first index after the timestamp, so the index immediately
+ // before it is the index we're looking for. If the first index after
+ // timestamp is 0, then there is no index on or before timestamp.
+ if (firstIndexAfterTimestamp === 0) return undefined;
+ return sortedEvents[firstIndexAfterTimestamp - 1];
+ }
+
+
+ /**
+ * Returns earliest event in |sortedEvents| that starts on or after
+ * |timestamp|, or undefined if no such event exists.
+ *
+ * @param {!Array<!tr.model.TimedEvent>} sortedEvents - events sorted by
+ * start time.
+ * @param {number} timestamp
+ * @returns {tr.model.Event|undefined}
+ */
+ static findNextEventStartingOnOrAfterTimestamp(sortedEvents, timestamp) {
+ const firstIndexOnOrAfterTimestamp =
+ tr.b.findFirstTrueIndexInSortedArray(
+ sortedEvents, e => e.start >= timestamp);
+
+ if (firstIndexOnOrAfterTimestamp === sortedEvents.length) {
+ return undefined;
+ }
+ return sortedEvents[firstIndexOnOrAfterTimestamp];
+ }
+
+ /**
+ * Returns earliest event in |sortedEvents| that starts after |timestamp|,
+ * or undefined if no such event exists.
+ *
+ * @param {!Array<!tr.model.TimedEvent>} sortedEvents - events sorted by
+ * start time.
+ * @param {number} timestamp
+ * @returns {tr.model.Event|undefined}
+ */
+ static findNextEventStartingAfterTimestamp(sortedEvents, timestamp) {
+ const firstIndexOnOrAfterTimestamp =
+ tr.b.findFirstTrueIndexInSortedArray(
+ sortedEvents, e => e.start > timestamp);
+
+ if (firstIndexOnOrAfterTimestamp === sortedEvents.length) {
+ return undefined;
+ }
+ return sortedEvents[firstIndexOnOrAfterTimestamp];
+ }
+
+ /**
+ * Returns a list of top level scheduler tasks.
+ * It is used by TTI and EQT metrics.
+ * @param {!tr.model.Thread} mainThread - the main thead of the renderer.
+ * @returns {!Array<tr.model.Slice>}
+ */
+ static findToplevelSchedulerTasks(mainThread) {
+ const tasks = [];
+ tasks.push(...mainThread.findTopmostSlices(
+ slice => slice.category === 'toplevel' &&
+ SCHEDULOER_TOP_LEVEL_TASKS.has(slice.title)));
+ return tasks;
+ }
+ }
+
+ return {
+ EventFinderUtils,
+ CHROME_INTERNAL_URLS,
+ SCHEDULER_TOP_LEVEL_TASK_TITLE,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/event_finder_utils_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/event_finder_utils_test.html
new file mode 100644
index 00000000000..f3d078cceff
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/event_finder_utils_test.html
@@ -0,0 +1,256 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/event_finder_utils.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+
+<script>
+'use strict';
+
+const EventFinderUtils = tr.e.chrome.EventFinderUtils;
+
+tr.b.unittest.testSuite(function() {
+ test('getSortedMainThreadEventsByFrame', () => {
+ const model = tr.c.TestUtils.newModel(model => {
+ const rendererProcess = model.getOrCreateProcess(1);
+ const mainThread = rendererProcess.getOrCreateThread(2);
+ mainThread.name = 'CrRendererMain';
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0x0'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 300,
+ duration: 0.0,
+ args: {frame: '0x1'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 400,
+ duration: 0.0,
+ args: {frame: '0x0'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'devtools.timeline',
+ title: 'navigationStart',
+ start: 500,
+ duration: 0.0,
+ args: {frame: '0x0'}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'firstPaint',
+ start: 600,
+ duration: 0.0,
+ args: {frame: '0x0'}
+ }));
+ });
+
+
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ const rendererHelper = chromeHelper.rendererHelpers[1];
+ const frameToSlices = EventFinderUtils.getSortedMainThreadEventsByFrame(
+ rendererHelper, 'navigationStart', 'blink.user_timing');
+
+ assert.strictEqual(frameToSlices.size, 2);
+ assert.strictEqual(frameToSlices.get('0x0').length, 2);
+ assert.strictEqual(frameToSlices.get('0x0')[0].start, 200);
+ assert.strictEqual(frameToSlices.get('0x0')[1].start, 400);
+ assert.strictEqual(frameToSlices.get('0x1').length, 1);
+ assert.strictEqual(frameToSlices.get('0x1')[0].start, 300);
+ });
+
+ test('getSortedMainThreadEventsByNavId', () => {
+ const model = tr.c.TestUtils.newModel(model => {
+ const rendererProcess = model.getOrCreateProcess(1);
+ const mainThread = rendererProcess.getOrCreateThread(2);
+ mainThread.name = 'CrRendererMain';
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0x0', data: { navigationId: '0xa' }}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 300,
+ duration: 0.0,
+ args: {frame: '0x1', data: { navigationId: '0xb' }}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: 400,
+ duration: 0.0,
+ args: {frame: '0x0', data: { navigationId: '0xc' }}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'devtools.timeline',
+ title: 'navigationStart',
+ start: 500,
+ duration: 0.0,
+ args: {frame: '0x0', data: { navigationId: '0xd' }}
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'firstPaint',
+ start: 600,
+ duration: 0.0,
+ args: {frame: '0x0'}
+ }));
+ });
+
+
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ const rendererHelper = chromeHelper.rendererHelpers[1];
+ const navIdToSlices = EventFinderUtils.getSortedMainThreadEventsByNavId(
+ rendererHelper, 'navigationStart', 'blink.user_timing');
+
+ assert.strictEqual(navIdToSlices.size, 3);
+ assert.strictEqual(navIdToSlices.get('0xa').start, 200);
+ assert.strictEqual(navIdToSlices.get('0xb').start, 300);
+ assert.strictEqual(navIdToSlices.get('0xc').start, 400);
+ });
+
+ test('multipleCategoriesOnAnEvent', () => {
+ const model = tr.c.TestUtils.newModel(model => {
+ const rendererProcess = model.getOrCreateProcess(1);
+ const mainThread = rendererProcess.getOrCreateThread(2);
+ mainThread.name = 'CrRendererMain';
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing,rail',
+ title: 'navigationStart',
+ start: 200,
+ duration: 0.0,
+ args: {frame: '0x0'}
+ }));
+ });
+
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ const rendererHelper = chromeHelper.rendererHelpers[1];
+ const frameToSlices = EventFinderUtils.getSortedMainThreadEventsByFrame(
+ rendererHelper, 'navigationStart', 'blink.user_timing');
+
+ assert.strictEqual(frameToSlices.get('0x0')[0].start, 200);
+ });
+
+ test('findLastEventStartingOnOrBeforeTimestamp', () => {
+ const sortedEvents = [50, 100, 150, 200].map(ts =>
+ tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: ts,
+ duration: 40.0}));
+
+ assert.strictEqual(
+ EventFinderUtils.findLastEventStartingOnOrBeforeTimestamp(
+ sortedEvents, 100).start,
+ 100);
+ assert.strictEqual(
+ EventFinderUtils.findLastEventStartingOnOrBeforeTimestamp(
+ sortedEvents, 99).start,
+ 50);
+ assert.isUndefined(
+ EventFinderUtils.findLastEventStartingOnOrBeforeTimestamp(
+ sortedEvents, 49));
+ });
+
+ test('findNextEventStartingAfterTimestamp', () => {
+ const sortedEvents = [50, 50, 50, 200].map(ts =>
+ tr.c.TestUtils.newSliceEx({
+ cat: 'blink.user_timing',
+ title: 'navigationStart',
+ start: ts,
+ duration: 40.0}));
+
+ assert.strictEqual(
+ EventFinderUtils.findNextEventStartingAfterTimestamp(
+ sortedEvents, 49).start,
+ 50);
+ assert.strictEqual(
+ EventFinderUtils.findNextEventStartingAfterTimestamp(
+ sortedEvents, 50).start,
+ 200);
+ assert.isUndefined(
+ EventFinderUtils.findNextEventStartingAfterTimestamp(
+ sortedEvents, 201));
+ });
+
+ test('findToplevelSchedulerTasks', () => {
+ const model = tr.c.TestUtils.newModel(model => {
+ const rendererProcess = model.getOrCreateProcess(1);
+ const mainThread = rendererProcess.getOrCreateThread(2);
+ mainThread.name = 'CrRendererMain';
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: 'ThreadControllerImpl::RunTask',
+ start: 10,
+ duration: 1,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: 'ThreadControllerImpl::DoWork',
+ start: 20,
+ duration: 2,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: 'TaskQueueManager::ProcessTaskFromWorkQueue',
+ start: 30,
+ duration: 3,
+ }));
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'toplevel',
+ title: 'MessageLoop::RunTask',
+ start: 40,
+ duration: 4,
+ }));
+ // The category of 'ThreadControllerImpl::DoWork' slice was changed to
+ // 'sequence_manager' because it is not longer a scheduler top level task.
+ // See crbug.com/871204 for context.
+ mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
+ cat: 'sequence_manager',
+ title: 'ThreadControllerImpl::DoWork',
+ start: 50,
+ duration: 5,
+ }));
+ });
+
+ const chromeHelper = model.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ const rendererHelper = chromeHelper.rendererHelpers[1];
+ const tasks = EventFinderUtils.findToplevelSchedulerTasks(
+ rendererHelper.mainThread);
+
+ assert.strictEqual(tasks.length, 3);
+ assert.strictEqual(tasks[0].title,
+ 'ThreadControllerImpl::RunTask');
+ assert.strictEqual(tasks[0].start, 10);
+ assert.strictEqual(tasks[0].duration, 1);
+ assert.strictEqual(tasks[1].title,
+ 'ThreadControllerImpl::DoWork');
+ assert.strictEqual(tasks[1].start, 20);
+ assert.strictEqual(tasks[1].duration, 2);
+ assert.strictEqual(tasks[2].title,
+ 'TaskQueueManager::ProcessTaskFromWorkQueue');
+ assert.strictEqual(tasks[2].start, 30);
+ assert.strictEqual(tasks[2].duration, 3);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/gpu_async_slice.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/gpu_async_slice.html
new file mode 100644
index 00000000000..bfea13a7166
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/gpu_async_slice.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/model/async_slice.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.gpu', function() {
+ const AsyncSlice = tr.model.AsyncSlice;
+
+ function GpuAsyncSlice() {
+ AsyncSlice.apply(this, arguments);
+ }
+
+ GpuAsyncSlice.prototype = {
+ __proto__: AsyncSlice.prototype,
+
+ get viewSubGroupTitle() {
+ if (this.args.channel) {
+ if (this.category === 'disabled-by-default-gpu.device') {
+ return 'Device.' + this.args.channel;
+ }
+ return 'Service.' + this.args.channel;
+ }
+ return this.title;
+ }
+ };
+
+ AsyncSlice.subTypes.register(
+ GpuAsyncSlice,
+ {
+ categoryParts: ['disabled-by-default-gpu.device',
+ 'disabled-by-default-gpu.service']
+ });
+
+ return {
+ GpuAsyncSlice,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/gpu_async_slice_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/gpu_async_slice_test.html
new file mode 100644
index 00000000000..53edf5ded5f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/gpu_async_slice_test.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/gpu/gpu_async_slice.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const AsyncSlice = tr.model.AsyncSlice;
+ const GpuAsyncSlice = tr.e.gpu.GpuAsyncSlice;
+
+ test('construct', function() {
+ assert.strictEqual(AsyncSlice.subTypes.getConstructor(
+ 'disabled-by-default-gpu.device', 'gpu1'),
+ GpuAsyncSlice);
+ assert.strictEqual(AsyncSlice.subTypes.getConstructor(
+ 'disabled-by-default-gpu.service', 'gpu2'),
+ GpuAsyncSlice);
+ });
+
+ test('subgroup', function() {
+ const sDevice = new GpuAsyncSlice('disabled-by-default-gpu.device', 'gpu1',
+ 7, 0, {'channel': 'test_channel1'});
+
+ const sService = new GpuAsyncSlice(
+ 'disabled-by-default-gpu.service', 'gpu2', 7, 0,
+ {'channel': 'test_channel2'});
+
+ assert.strictEqual(sDevice.viewSubGroupTitle, 'Device.test_channel1');
+ assert.strictEqual(sService.viewSubGroupTitle, 'Service.test_channel2');
+ });
+
+ test('import', function() {
+ const events = [
+ {name: 'trace1', args: {}, pid: 1, ts: 100,
+ cat: 'disabled-by-default-gpu.service', tid: 2, ph: 'b', id: 71},
+ {name: 'trace1', args: {}, pid: 1, ts: 200,
+ cat: 'disabled-by-default-gpu.service', tid: 2, ph: 'e', id: 71},
+ {name: 'trace2', args: {'channel': 'test_channel'}, pid: 1, ts: 100,
+ cat: 'disabled-by-default-gpu.service', tid: 2, ph: 'b', id: 72},
+ {name: 'trace2', args: {'channel': 'test_channel'}, pid: 1, ts: 200,
+ cat: 'disabled-by-default-gpu.service', tid: 2, ph: 'e', id: 72},
+ {name: 'trace3', args: {'channel': 'test_channel'}, pid: 1, ts: 100,
+ cat: 'disabled-by-default-gpu.device', tid: 2, ph: 'b', id: 73},
+ {name: 'trace3', args: {'channel': 'test_channel'}, pid: 1, ts: 200,
+ cat: 'disabled-by-default-gpu.device', tid: 2, ph: 'e', id: 73}
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([events]);
+ const t2 = m.getOrCreateProcess(1).getOrCreateThread(2);
+ assert.strictEqual(t2.asyncSliceGroup.length, 3);
+
+ const slice1 = t2.asyncSliceGroup.slices[0];
+ const slice2 = t2.asyncSliceGroup.slices[1];
+ const slice3 = t2.asyncSliceGroup.slices[2];
+
+ assert.instanceOf(slice1, GpuAsyncSlice);
+ assert.instanceOf(slice2, GpuAsyncSlice);
+ assert.instanceOf(slice3, GpuAsyncSlice);
+
+ assert.strictEqual(slice1.viewSubGroupTitle, 'trace1');
+ assert.strictEqual(slice2.viewSubGroupTitle, 'Service.test_channel');
+ assert.strictEqual(slice3.viewSubGroupTitle, 'Device.test_channel');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/state.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/state.html
new file mode 100644
index 00000000000..b82c85abdf9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/state.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+<link rel="import" href="/tracing/model/object_instance.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.gpu', function() {
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+
+ /**
+ * @constructor
+ */
+ function StateSnapshot() {
+ ObjectSnapshot.apply(this, arguments);
+ }
+
+ StateSnapshot.prototype = {
+ __proto__: ObjectSnapshot.prototype,
+
+ preInitialize() {
+ this.screenshot_ = undefined;
+ },
+
+ initialize() {
+ if (this.args.screenshot) {
+ this.screenshot_ = this.args.screenshot;
+ }
+ },
+
+ /**
+ * @return {String} a base64 encoded screenshot if available.
+ */
+ get screenshot() {
+ return this.screenshot_;
+ }
+ };
+
+ ObjectSnapshot.subTypes.register(
+ StateSnapshot,
+ {typeName: 'gpu::State'});
+
+ return {
+ StateSnapshot,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/state_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/state_test.html
new file mode 100644
index 00000000000..ac5f34dfe8c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/state_test.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/gpu/state.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script src="/tracing/extras/chrome/gpu/state_test_data.js"></script>
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('basic', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_gpuStateTrace]);
+ const p = Object.values(m.processes)[0];
+
+ const instance = p.objects.getAllInstancesNamed('gpu::State')[0];
+ const snapshot = instance.snapshots[0];
+
+ assert.instanceOf(snapshot, tr.e.gpu.StateSnapshot);
+ assert.typeOf(snapshot.screenshot, 'string');
+ instance.wasDeleted(150);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/state_test_data.js b/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/state_test_data.js
new file mode 100644
index 00000000000..9327b49e8eb
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/gpu/state_test_data.js
@@ -0,0 +1,22 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+const g_gpuStateTrace = [
+ {
+ 'cat': 'disabled-by-default-gpu.debug',
+ 'pid': 23969,
+ 'tid': 1799,
+ 'ts': 1427012847340,
+ 'ph': 'O',
+ 'name': 'gpu::State',
+ 'args': {
+ 'snapshot': {
+ 'screenshot': 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB90JCREbHlyxtxQAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAqUlEQVRIx+2WiwrAIAhF/f9vHmywQTMft8w2CJKIoPDozR50fmy0AcsAiMjs9UDOJgG4jwG8SMuUXoMAZcVYBmgPqDYNwLo3JHqcHufbRETZKireOSbDQAA+zgKE7lyiCQDtcQygS6PKYIp3vZ5MvgB0mhmQu8kcgAXE6bYBoZYFmPnhgg5n4En/h0RmvdmX3eKAMYZ3HtGD0+NU3w2BR795dPe/aANuuwDyW5SiCgNBiQAAAABJRU5ErkJggg==' // @suppress longLineCheck
+ }
+ },
+ 'id': '0x7d229bc0'
+ }
+];
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/layout_object.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/layout_object.html
new file mode 100644
index 00000000000..60911317e14
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/layout_object.html
@@ -0,0 +1,207 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.chrome', function() {
+ const KNOWN_PROPERTIES = {
+ absX: 1,
+ absY: 1,
+ address: 1,
+ anonymous: 1,
+ childNeeds: 1,
+ children: 1,
+ classNames: 1,
+ col: 1,
+ colSpan: 1,
+ float: 1,
+ height: 1,
+ htmlId: 1,
+ name: 1,
+ posChildNeeds: 1,
+ positioned: 1,
+ positionedMovement: 1,
+ relX: 1,
+ relY: 1,
+ relativePositioned: 1,
+ row: 1,
+ rowSpan: 1,
+ selfNeeds: 1,
+ stickyPositioned: 1,
+ tag: 1,
+ width: 1
+ };
+
+ function LayoutObject(snapshot, args) {
+ this.snapshot_ = snapshot;
+ this.id_ = args.address;
+ this.name_ = args.name;
+ this.childLayoutObjects_ = [];
+ this.otherProperties_ = {};
+ this.tag_ = args.tag;
+ this.relativeRect_ = tr.b.math.Rect.fromXYWH(
+ args.relX, args.relY, args.width, args.height);
+ this.absoluteRect_ = tr.b.math.Rect.fromXYWH(
+ args.absX, args.absY, args.width, args.height);
+ this.isFloat_ = args.float;
+ this.isStickyPositioned_ = args.stickyPositioned;
+ this.isPositioned_ = args.positioned;
+ this.isRelativePositioned_ = args.relativePositioned;
+ this.isAnonymous_ = args.anonymous;
+ this.htmlId_ = args.htmlId;
+ this.classNames_ = args.classNames;
+ this.needsLayoutReasons_ = [];
+ if (args.selfNeeds) {
+ this.needsLayoutReasons_.push('self');
+ }
+ if (args.childNeeds) {
+ this.needsLayoutReasons_.push('child');
+ }
+ if (args.posChildNeeds) {
+ this.needsLayoutReasons_.push('positionedChild');
+ }
+ if (args.positionedMovement) {
+ this.needsLayoutReasons_.push('positionedMovement');
+ }
+ this.tableRow_ = args.row;
+ this.tableCol_ = args.col;
+ this.tableRowSpan_ = args.rowSpan;
+ this.tableColSpan_ = args.colSpan;
+
+ if (args.children) {
+ args.children.forEach(function(child) {
+ this.childLayoutObjects_.push(new LayoutObject(snapshot, child));
+ }.bind(this));
+ }
+
+ for (const property in args) {
+ if (!KNOWN_PROPERTIES[property]) {
+ this.otherProperties_[property] = args[property];
+ }
+ }
+ }
+
+ LayoutObject.prototype = {
+ get snapshot() {
+ return this.snapshot_;
+ },
+
+ get id() {
+ return this.id_;
+ },
+
+ get name() {
+ return this.name_;
+ },
+
+ get tag() {
+ return this.tag_;
+ },
+
+ get relativeRect() {
+ return this.relativeRect_;
+ },
+
+ get absoluteRect() {
+ return this.absoluteRect_;
+ },
+
+ get isPositioned() {
+ return this.isPositioned_;
+ },
+
+ get isFloat() {
+ return this.isFloat_;
+ },
+
+ get isStickyPositioned() {
+ return this.isStickyPositioned_;
+ },
+
+ get isRelativePositioned() {
+ return this.isRelativePositioned_;
+ },
+
+ get isAnonymous() {
+ return this.isAnonymous_;
+ },
+
+ get tableRow() {
+ return this.tableRow_;
+ },
+
+ get tableCol() {
+ return this.tableCol_;
+ },
+
+ get tableRowSpan() {
+ return this.tableRowSpan_;
+ },
+
+ get tableColSpan() {
+ return this.tableColSpan_;
+ },
+
+ get htmlId() {
+ return this.htmlId_;
+ },
+
+ get classNames() {
+ return this.classNames_;
+ },
+
+ get needsLayoutReasons() {
+ return this.needsLayoutReasons_;
+ },
+
+ get hasChildLayoutObjects() {
+ return this.childLayoutObjects_.length > 0;
+ },
+
+ get childLayoutObjects() {
+ return this.childLayoutObjects_;
+ },
+
+ traverseTree(cb, opt_this) {
+ cb.call(opt_this, this);
+ if (!this.hasChildLayoutObjects) return;
+ this.childLayoutObjects.forEach(function(child) {
+ child.traverseTree(cb, opt_this);
+ });
+ },
+
+ get otherPropertyNames() {
+ const names = [];
+ for (const name in this.otherProperties_) {
+ names.push(name);
+ }
+ return names;
+ },
+
+ getProperty(name) {
+ return this.otherProperties_[name];
+ },
+
+ get previousSnapshotLayoutObject() {
+ if (!this.snapshot.previousSnapshot) return undefined;
+ return this.snapshot.previousSnapshot.getLayoutObjectById(this.id);
+ },
+
+ get nextSnapshotLayoutObject() {
+ if (!this.snapshot.nextSnapshot) return undefined;
+ return this.snapshot.nextSnapshot.getLayoutObjectById(this.id);
+ }
+ };
+
+ return {
+ LayoutObject,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/layout_object_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/layout_object_test.html
new file mode 100644
index 00000000000..d1c888f17ef
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/layout_object_test.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/layout_object.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function FakeSnapshot() {
+ this.layoutObjectsById = {};
+ }
+ FakeSnapshot.prototype = {
+ getLayoutObjectById(id) {
+ return this.layoutObjectsById[id];
+ }
+ };
+
+ test('instantiate', function() {
+ const snapshot = new FakeSnapshot();
+ const layoutObject = new tr.e.chrome.LayoutObject(snapshot, {
+ address: 'deadbeef',
+ name: 'LayoutView',
+ other: 1,
+ children: [
+ {
+ address: 'beef2',
+ name: 'LayoutBlockFlow',
+ other: 2
+ }
+ ]
+ });
+ snapshot.layoutObjectsById[layoutObject.id] = layoutObject;
+
+ assert.strictEqual(snapshot, layoutObject.snapshot);
+ assert.strictEqual('LayoutView', layoutObject.name);
+ assert.strictEqual('deadbeef', layoutObject.id);
+ assert.strictEqual(1, layoutObject.otherPropertyNames.length);
+ assert.strictEqual(1, layoutObject.getProperty('other'));
+ assert.isTrue(layoutObject.hasChildLayoutObjects);
+ const child = layoutObject.childLayoutObjects[0];
+ assert.strictEqual('LayoutBlockFlow', child.name);
+ assert.strictEqual('beef2', child.id);
+ assert.strictEqual(1, child.otherPropertyNames.length);
+ assert.strictEqual(2, child.getProperty('other'));
+
+ assert.isUndefined(layoutObject.previousSnapshotLayoutObject);
+ assert.isUndefined(layoutObject.nextSnapshotLayoutObject);
+
+ let count = 0;
+ layoutObject.traverseTree(function(node) {
+ count += 1;
+ });
+ assert.strictEqual(2, count);
+
+ const nextSnapshot = new FakeSnapshot();
+ nextSnapshot.previousSnapshot = snapshot;
+ snapshot.nextSnapshot = nextSnapshot;
+
+ const nextLayoutObject = new tr.e.chrome.LayoutObject(nextSnapshot, {
+ address: 'deadbeef',
+ name: 'LayoutView'
+ });
+ nextSnapshot.layoutObjectsById[nextLayoutObject.id] = nextLayoutObject;
+
+ assert.strictEqual(layoutObject,
+ nextLayoutObject.previousSnapshotLayoutObject);
+ assert.strictEqual(nextLayoutObject, layoutObject.nextSnapshotLayoutObject);
+ assert.isUndefined(layoutObject.previousSnapshotLayoutObject);
+ assert.isUndefined(nextLayoutObject.nextSnapshotLayoutObject);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/layout_tree.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/layout_tree.html
new file mode 100644
index 00000000000..94a2f505d3c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/layout_tree.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/model/event_registry.html">
+<link rel="import" href="/tracing/model/object_instance.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.chrome', function() {
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+ const ObjectInstance = tr.model.ObjectInstance;
+
+ function LayoutTreeInstance() {
+ ObjectInstance.apply(this, arguments);
+ }
+
+ LayoutTreeInstance.prototype = {
+ __proto__: ObjectInstance.prototype,
+ };
+
+ ObjectInstance.subTypes.register(
+ LayoutTreeInstance, {typeName: 'LayoutTree'});
+
+ function LayoutTreeSnapshot() {
+ ObjectSnapshot.apply(this, arguments);
+ this.rootLayoutObject = new tr.e.chrome.LayoutObject(this, this.args);
+ }
+
+ LayoutTreeSnapshot.prototype = {
+ __proto__: ObjectSnapshot.prototype,
+ };
+
+ ObjectSnapshot.subTypes.register(
+ LayoutTreeSnapshot, {typeName: 'LayoutTree'});
+
+ return {
+ LayoutTreeInstance,
+ LayoutTreeSnapshot,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/slice_title_fixer.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/slice_title_fixer.html
new file mode 100644
index 00000000000..5a4bc02f396
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/slice_title_fixer.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/event.html">
+<link rel="import" href="/tracing/base/utils.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Some generic slice titles appear very frequently in traces,
+ * like MessageLoop::RunTask. Some of these slices have arguments that give
+ * useful context. This class combine such arguments with the slice title to
+ * generate a more useful title.
+ */
+tr.exportTo('tr.e.chrome', function() {
+ function SliceTitleFixer() {
+ }
+
+ // AsyncSlice uses virtual functions to accomplish something similar to what
+ // we're doing here. If this function ever becomes too complex we may consider
+ // using a similar pattern.
+ SliceTitleFixer.fromEvent = function(event) {
+ if (event.args && event.args.src_func) {
+ return event.title + ' <- ' + event.args.src_func;
+ }
+ return event.title;
+ };
+
+ return {
+ SliceTitleFixer,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/slice_title_fixer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/slice_title_fixer_test.html
new file mode 100644
index 00000000000..8b8ef3c9554
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/slice_title_fixer_test.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/slice_title_fixer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const stfFromEvent = tr.e.chrome.SliceTitleFixer.fromEvent;
+
+ test('sliceTitleFixer', function() {
+ assert.strictEqual(stfFromEvent({
+ title: 'SomeDummy:event'
+ }), 'SomeDummy:event');
+
+ assert.strictEqual(stfFromEvent({
+ title: 'EventWith:sourceFunction',
+ args: {'src_func': 'theSource'}
+ }), 'EventWith:sourceFunction <- theSource');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/time_to_interactive.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/time_to_interactive.html
new file mode 100644
index 00000000000..ea025e67697
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/time_to_interactive.html
@@ -0,0 +1,397 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.chrome', function() {
+ // Required quiet window size for Time to Interactive.
+ const TIME_TO_INTERACTIVE_WINDOW_SIZE_MS = 5000;
+
+ // Number of requests tolerated before network is considered busy.
+ const ACTIVE_REQUEST_TOLERANCE = 2;
+
+ // Minimum gap between task clusters for determining First CPU Idle.
+ const FCI_MIN_CLUSTER_SEPARATION_MS = 1000;
+
+ // Minimum duration of a task cluster to consider it heavy.
+ const TASK_CLUSTER_HEAVINESS_THRESHOLD_MS = 250;
+
+ /**
+ * Enum for endpoint types.
+ * @enum {string}
+ */
+ const ENDPOINT_TYPES = {
+ LONG_TASK_START: 'LONG_TASK_START',
+ LONG_TASK_END: 'LONG_TASK_END',
+ REQUEST_START: 'REQUEST_START',
+ REQUEST_END: 'REQUEST_END'
+ };
+
+ /**
+ * @typedef {Object} Endpoint
+ * @property {number} time - timestamp of endpoint.
+ * @property {string} type - A type defined in |ENDPOINT_TYPES|.
+ * originates from.
+ */
+
+ /**
+ * Returns an array of endpoints. An endpoint is either the start or end
+ * timestamp of a TimedEvent.
+ *
+ * @param {Array.<!tr.model.TimedEvent>} events - Events to extract endpoints
+ * from.
+ * @param {string} startType - Endpoint type for a start time endpoint.
+ * @param {string} endType - Endpoint type for an end time endpoint.
+ * @returns {Array.<!Endpoint>}
+ */
+ function getEndpoints_(events, startType, endType) {
+ const endpoints = [];
+ for (const event of events) {
+ endpoints.push({
+ time: event.start,
+ type: startType
+ });
+ endpoints.push({
+ time: event.end,
+ type: endType
+ });
+ }
+
+ return endpoints;
+ }
+
+ /**
+ * Returns true if at time |timestamp| we have a long enough quiet window for
+ * determining Time to Interactive.
+ *
+ * @param {number} timestamp - Timestamp of interest.
+ * @param {number} networkQuietWindowStart - Most recent timestamp when the
+ * renderer became network 2-quiet. Undefined if renderer is not network
+ * 2-quiet at |timestamp|.
+ * @param {number} mainThreadQuietWindowStart - End of most recent long task.
+ * Undefined if a long task is in progress at |timestamp|.
+ * @returns {boolean}
+ */
+ function reachedTTIQuiscence_(timestamp,
+ networkQuietWindowStart, mainThreadQuietWindowStart) {
+ if (networkQuietWindowStart === undefined ||
+ mainThreadQuietWindowStart === undefined) {
+ return false;
+ }
+
+ const mainThreadQuietForLongEnough =
+ timestamp - mainThreadQuietWindowStart >=
+ TIME_TO_INTERACTIVE_WINDOW_SIZE_MS;
+
+ const networkQuietForLongEnough =
+ timestamp - networkQuietWindowStart >=
+ TIME_TO_INTERACTIVE_WINDOW_SIZE_MS;
+
+ return mainThreadQuietForLongEnough && networkQuietForLongEnough;
+ }
+
+ /**
+ * Returns the timestamp for when the page first becomes Interactive.
+ *
+ * See https://goo.gl/IN68jL for detailed explanation of definition and
+ * motivations. This metric was previously known as "Consistently
+ * Interactive". To summarize, we look for a 5 second window starting from
+ * between |searchBegin| and |searchEnd| such that there is no long task
+ * overlapping with that window, and the entire window is network 2-quiet. A
+ * long task is defined as a main thread task with wall duration longer than
+ * 50ms, and network 2-quiet means the window never has more than 2 concurrent
+ * in-flight network requests. Interactive 'candidate' is defined as end of
+ * the last long task before this quiet window, or as |searchBegin| if there
+ * is no long task before that window. We define Interactive as max of
+ * Interactive candidate and domContentLoadedEnd.
+ *
+ * Returns undefined if no suitable quiet window is found.
+ *
+ * @param {number} searchBegin - The left boundary for our search for quiet
+ * window. Ideally, this is First Meaningful Paint, but if FMP is not
+ * available, First Contentful Paint or domContentLoadedEnd may be used as an
+ * approximation.
+ * @param {number} searchEnd - The right boundary for our search for quiet
+ * window. This is either the start of next navigationStart request, or end of
+ * traces.
+ * @param {number} domContentLoadedEnd - Timestamp of domContentLoadedEnd
+ * event for the main loading frame.
+ * @param {Array.<!tr.model.TimedEvent>} longTasks - Main thread tasks longer
+ * than 50ms overlapping with the search window.
+ * @param {Array.<!tr.model.TimedEvent>} networkRequests - Resource
+ * requests overlapping with the search window.
+ * @returns {number|undefined}
+ */
+ function findInteractiveTime(searchBegin, searchEnd,
+ domContentLoadedEnd, longTasksInWindow, networkRequests) {
+ // It is sufficient to only look at the end points of long tasks and network
+ // requests. These are the points where any meaningful changes happen.
+ const longTaskEndpoints = getEndpoints_(longTasksInWindow,
+ ENDPOINT_TYPES.LONG_TASK_START, ENDPOINT_TYPES.LONG_TASK_END);
+ const networkRequestEndpoints = getEndpoints_(networkRequests,
+ ENDPOINT_TYPES.REQUEST_START, ENDPOINT_TYPES.REQUEST_END);
+ const endpoints = longTaskEndpoints.concat(networkRequestEndpoints);
+ endpoints.sort((a, b) => a.time - b.time);
+
+ let networkQuietWindowStart = searchBegin;
+ let mainThreadQuietWindowStart = searchBegin;
+ let interactiveCandidate = undefined;
+ let activeRequests = 0;
+
+ for (const endpoint of endpoints) {
+ if (reachedTTIQuiscence_(endpoint.time,
+ networkQuietWindowStart, mainThreadQuietWindowStart)) {
+ interactiveCandidate = mainThreadQuietWindowStart;
+ break;
+ }
+
+ switch (endpoint.type) {
+ case ENDPOINT_TYPES.LONG_TASK_START:
+ mainThreadQuietWindowStart = undefined;
+ break;
+ case ENDPOINT_TYPES.LONG_TASK_END:
+ mainThreadQuietWindowStart = endpoint.time;
+ break;
+ case ENDPOINT_TYPES.REQUEST_START:
+ activeRequests++;
+ if (activeRequests > ACTIVE_REQUEST_TOLERANCE) {
+ networkQuietWindowStart = undefined;
+ }
+ break;
+ case ENDPOINT_TYPES.REQUEST_END:
+ activeRequests--;
+ if (activeRequests === ACTIVE_REQUEST_TOLERANCE) {
+ // Just became network 2-quiet.
+ networkQuietWindowStart = endpoint.time;
+ }
+ break;
+ default:
+ // This should never happen.
+ throw new Error('Internal Error: Unhandled endpoint type.');
+ }
+ }
+
+ if (interactiveCandidate === undefined &&
+ reachedTTIQuiscence_(searchEnd,
+ networkQuietWindowStart, mainThreadQuietWindowStart)) {
+ interactiveCandidate = mainThreadQuietWindowStart;
+ }
+
+ if (interactiveCandidate === undefined) return undefined;
+
+ return Math.max(interactiveCandidate, domContentLoadedEnd);
+ }
+
+ /**
+ * Returns the required quiet window size for First CPU Idle at
+ * |timeSinceSearchBeginMs| after searchBegin.
+ *
+ * Required quiet window size is modeled as an exponential decay. See
+ * https://goo.gl/kQXGLW for development of the exact equation used here.
+ *
+ * @param {number} timeSinceSearchBeginMs - Time since the beginning of search
+ * window for First CPU Idle.
+ */
+ function requiredFCIWindowSizeMs(timeSinceSearchBeginMs) {
+ const timeCoefficient = 1 / 15 * Math.log(2);
+
+ const timeSinceSearchBeginSeconds = tr.b.convertUnit(timeSinceSearchBeginMs,
+ tr.b.UnitPrefixScale.METRIC.MILLI, tr.b.UnitPrefixScale.METRIC.NONE);
+ const windowSizeSeconds =
+ 4 * Math.exp(- timeCoefficient * timeSinceSearchBeginSeconds) + 1;
+ return tr.b.convertUnit(windowSizeSeconds,
+ tr.b.UnitPrefixScale.METRIC.NONE, tr.b.UnitPrefixScale.METRIC.MILLI);
+ }
+
+ /**
+ * TaskCluster represents a group of long tasks such that they are at least 1s
+ * away from any other long task that is not in the group.
+ *
+ * A TaskCluster instance is meant to be immutable once constructed.
+ */
+ class TaskCluster {
+ /**
+ * Create a TaskCluster.
+ * @param {Array.<!tr.model.TimedEvent>} tasksInClusterSorted - Tasks in the
+ * cluster. Assumed sorted by start time.
+ */
+ constructor(tasksInClusterSorted) {
+ if (tasksInClusterSorted.length === 0) {
+ throw new Error('Internal Error: TaskCluster must have non zero tasks');
+ }
+
+ for (let i = 0; i < tasksInClusterSorted.length - 1; i++) {
+ const durationBetweenTasks = tasksInClusterSorted[i + 1].start -
+ tasksInClusterSorted[i].end;
+ if (durationBetweenTasks >= FCI_MIN_CLUSTER_SEPARATION_MS) {
+ throw new Error('Internal Error: Tasks in a TaskCluster cannot be ' +
+ 'more than ' + FCI_MIN_CLUSTER_SEPARATION_MS +
+ ' miliseconds apart');
+ }
+
+ // Ideally the condition below should be durationBetweenTasks < 0, but
+ // sometimes, for rounding errors, the end time of one task may very
+ // slightly extend past the start time of the next.
+ if (durationBetweenTasks < -1e7) {
+ throw new Error('Internal Error: List of tasks used to construct ' +
+ 'TaskCluster must be sorted.');
+ }
+ }
+
+ this._clusterTasks = tasksInClusterSorted;
+ }
+
+ /**
+ * @type{number}
+ */
+ get start() {
+ return this._clusterTasks[0].start;
+ }
+
+ /**
+ * @type{number}
+ */
+ get end() {
+ return this._clusterTasks[this._clusterTasks.length - 1].end;
+ }
+
+ /**
+ * @returns {boolean}
+ */
+ isHeavy() {
+ return this.end - this.start > TASK_CLUSTER_HEAVINESS_THRESHOLD_MS;
+ }
+ }
+
+
+ /**
+ * Returns all the task clusters of |sortedLongTasks|.
+ *
+ * @param {Array.<!tr.model.TimedEvent>} sortedLongTasks - Main thread tasks
+ * longer than 50ms, sorted by task start time.
+ * @returns {Array.<!TaskCluster>}
+ */
+ function findFCITaskClusters(sortedLongTasks) {
+ const clusters = [];
+ if (sortedLongTasks.length === 0) return clusters;
+
+ const firstTask = sortedLongTasks[0];
+ const restOfTasks = sortedLongTasks.slice(1);
+
+ let currentClusterTasks = [firstTask];
+
+ for (const currTask of restOfTasks) {
+ const prevTask = currentClusterTasks[currentClusterTasks.length - 1];
+ if (currTask.start - prevTask.end < FCI_MIN_CLUSTER_SEPARATION_MS) {
+ // Add task to current task cluster.
+ currentClusterTasks.push(currTask);
+ } else {
+ // Wrap up the current cluster and add task to a fresh cluster.
+ clusters.push(new TaskCluster(currentClusterTasks));
+ currentClusterTasks = [currTask];
+ }
+ }
+
+ clusters.push(new TaskCluster(currentClusterTasks));
+
+ return clusters;
+ }
+
+ /**
+ * Returns true if at time |timestamp|, assuming |timestamp| is not in the
+ * middle of a heavy task cluster, we have a long enough quiet window for
+ * determining First CPU Idle.
+ *
+ * @param {number} timestamp - Timestamp of interest. We assume |timestamp| is
+ * not in the middle of a heavy task cluster.
+ * @param {number} mainThreadQuietWindowStart - Most recent timestamp when we
+ * considered the main thread to be quiet. Usually end of most recent
+ * heavy task cluster or |searchBegin| where there are no heavy task
+ * cluster.
+ * @param {number} searchBegin - Left boundary for quiet window search.
+ * @returns {boolean}
+ */
+ function reachedFCIQuiescence_(timestamp, mainThreadQuietWindowStart,
+ searchBegin) {
+ const quietWindowSize = timestamp - mainThreadQuietWindowStart;
+ const timeSinceSearchBegin = mainThreadQuietWindowStart - searchBegin;
+ const requiredWindowSize = requiredFCIWindowSizeMs(timeSinceSearchBegin);
+ return quietWindowSize > requiredWindowSize;
+ }
+
+ /**
+ * Returns the timestamp for First CPU Idle for a page.
+ *
+ * See https://goo.gl/1a1XwZ for definition, and https://goo.gl/IN68jL for an
+ * explanation of how the definition was developed. This metric was previously
+ * known as "First Interactive". To summarize, in order to find First
+ * Interactive we look for a long enough window between |searchBegin| and
+ * |searchEnd| such that it doesn't contain a heavy task cluster. The required
+ * length of the window decreases the further we move away from |searchBegin|.
+ * A "task cluster" is defined as a group of long tasks such that they are at
+ * least 1s away from any other long task that is not in the group. A task
+ * cluster is considered "heavy" if the duration of the task cluster (the time
+ * interval from the beginning of first task to the end of last task) is
+ * longer than 250ms. We call the beginning of the quiet window FCI candidate,
+ * and define First Cpu Idle as max of FCI candidate and
+ * domContentLoadedEnd.
+ *
+ * Returns undefined if no suitable quiet window is found.
+ *
+ * @param {number} searchBegin - The left boundary for our search for quiet
+ * window. Ideally, this is First Meaningful Paint, but if FMP is not
+ * available, First Contentful Paint or domContentLoadedEnd may be used as
+ * an approximation.
+ * @param {number} searchEnd - The right boundary for our search for quiet
+ * window. This is either the start of next navigationStart request, or
+ * end of traces.
+ * @param {number} domContentLoadedEnd - Timestamp of domContentLoadedEnd
+ * event for the main loading frame.
+ * @param {Array.<!tr.model.TimedEvent>} longTasks - Main thread tasks longer
+ * than 50ms overlapping with the search window.
+ * @returns {number|undefined}
+ */
+ function findFirstCpuIdleTime(searchBegin, searchEnd,
+ domContentLoadedEnd, longTasksInWindow) {
+ const sortedLongTasks = longTasksInWindow.sort((a, b) => a.start - b.start);
+ const taskClusters = findFCITaskClusters(sortedLongTasks);
+ const heavyTaskClusters = taskClusters.filter(cluster => cluster.isHeavy());
+
+ let quietWindowBegin = searchBegin;
+ let fiCandidate = undefined;
+ for (const cluster of heavyTaskClusters) {
+ if (reachedFCIQuiescence_(cluster.start, quietWindowBegin, searchBegin)) {
+ fiCandidate = quietWindowBegin;
+ break;
+ }
+ quietWindowBegin = cluster.end;
+ }
+
+ if (fiCandidate === undefined) {
+ // Check if quiescence was reached at the end of the search window.
+ if (reachedFCIQuiescence_(searchEnd, quietWindowBegin, searchBegin)) {
+ fiCandidate = quietWindowBegin;
+ } else {
+ // We never reached quiescence.
+ return undefined;
+ }
+ }
+
+ return Math.max(fiCandidate, domContentLoadedEnd);
+ }
+
+ return {
+ findInteractiveTime,
+ findFirstCpuIdleTime,
+ requiredFCIWindowSizeMs,
+ findFCITaskClusters,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome/time_to_interactive_test.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome/time_to_interactive_test.html
new file mode 100644
index 00000000000..655bd23fc25
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome/time_to_interactive_test.html
@@ -0,0 +1,329 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/time_to_interactive.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const findInteractiveTime = tr.e.chrome.findInteractiveTime;
+ const findFirstCpuIdleTime = tr.e.chrome.findFirstCpuIdleTime;
+ const requiredFCIWindowSizeMs = tr.e.chrome.requiredFCIWindowSizeMs;
+ const findFCITaskClusters = tr.e.chrome.findFCITaskClusters;
+
+ test('findInteractiveTime_' +
+ 'doesNotWaitForTasksFarAwayWithoutNetworkRequests', () => {
+ const longTasks = [
+ {start: 50, end: 200},
+ {start: 300, end: 400},
+ // More than 5000ms gap here.
+ {start: 7000, end: 7200}
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ assert.closeTo(
+ findInteractiveTime(0, 10000, 50, longTasks, []),
+ 400, 1e-7);
+ });
+
+ test('findInteractiveTime_' +
+ 'waitsForTasksFarAwayWhenNetworkIsBusy', () => {
+ const longTasks = [
+ {start: 50, end: 200},
+ {start: 300, end: 400},
+ // More than 5000ms gap here.
+ {start: 7000, end: 7200}
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ const networkRequests = [
+ {start: 300, end: 3000},
+ {start: 500, end: 2900},
+ {start: 600, end: 3100},
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ // Last network 2-quiet region starts at 2900, while the next long task
+ // starts at 7000. We don't have 5 seconds of quiet window before that long
+ // task.
+ assert.closeTo(findInteractiveTime(
+ 0, 20000, 50, longTasks, networkRequests), 7200, 1e-7);
+ });
+
+ test('findInteractiveTime_' +
+ 'noQuietWindowBecauseOfLongTask', () => {
+ const longTasks = [
+ {start: 50, end: 200},
+ {start: 300, end: 400},
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ // windowEnd is 2400, so we don't get 5 seconds on quiet window after 400.
+ assert.isUndefined(findInteractiveTime(
+ 0, 2400, 50, longTasks, []));
+ });
+
+ test('findInteractiveTime_' +
+ 'noQuietWindowBecauseOfNetwork', () => {
+ const networkRequests = [
+ {start: 50, end: 200},
+ {start: 51, end: 300},
+ {start: 52, end: 400},
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ // windowEnd is 2200, and beginning of last network 2-quiet region is 200.
+ // We don't have a 5 second quiet window.
+ assert.isUndefined(findInteractiveTime(
+ 0, 2200, 50, [], networkRequests));
+ });
+
+ test('findInteractiveTime_' +
+ 'network2QuietHappensAfterLastLongTask', () => {
+ const longTasks = [
+ {start: 50, end: 200},
+ {start: 300, end: 400},
+ // More than 5000ms gap here.
+ {start: 7000, end: 7200}
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ const networkRequests = [
+ {start: 300, end: 8000},
+ {start: 500, end: 8100},
+ {start: 600, end: 8300},
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ assert.closeTo(findInteractiveTime(
+ 0, 20000, 50, longTasks, networkRequests), 7200, 1e-7);
+ });
+
+
+ test('findInteractiveTime_' +
+ 'longEnoughNetwork2QuietBeforeLastLongTask', () => {
+ const longTasks = [
+ {start: 50, end: 200},
+ {start: 300, end: 400},
+ // More than 5000ms gap here.
+ {start: 7000, end: 7200}
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ const networkRequests = [
+ {start: 300, end: 1000},
+ {start: 500, end: 1100},
+ {start: 600, end: 1200},
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+
+ // 1000ms is the beginning of last network 2-quiet region, and we have a 5s
+ // window between that and the start of next long task.
+ assert.closeTo(findInteractiveTime(
+ 0, 20000, 50, longTasks, networkRequests), 400, 1e-7);
+ });
+
+ test('findInteractiveTime_' +
+ 'isLowerBoundedAtDCL', () => {
+ const longTasks = [
+ {start: 50, end: 200}
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ // End of last long task is 200, but FCI is still 5000 because of
+ // DOMContentLoadedEnd being at 5000.
+ assert.closeTo(findInteractiveTime(
+ 0, 20000, 5000, longTasks, []), 5000, 1e-7);
+ });
+
+
+ test('findInteractiveTime_' +
+ 'returnsWindowStartWhenNoLongTasks', () => {
+ // Note that it is possible for DOMContentLoadedEnd to happen before
+ // windowStart, since windowStart is usually First Meaningful Paint. If
+ // there are no long tasks, and DCL is before windowStart, FCI should be
+ // windowStart.
+ assert.closeTo(findInteractiveTime(
+ 1000, 20000, 500, [], []), 1000, 1e-7);
+ });
+
+ test('findInteractiveTime' +
+ 'networkRequestStartsBeforeWindowStart', () => {
+ const longTasks = [
+ {start: 7000, end: 7200}
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ const networkRequests = [
+ {start: 0, end: 6000},
+ {start: 10, end: 6100},
+ {start: 20, end: 6200}
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ // windowStart is now 1000. All three network requests start before
+ // windowStart, but should still manage to block netowrk 2-quiet.
+ assert.closeTo(findInteractiveTime(
+ 1000, 20000, 1050, longTasks, networkRequests), 7200, 1e-7);
+ });
+
+ test('requiredFCIWindowSizeMs', () => {
+ // The required window size function is an exponential decay, and is
+ // uniquely determined by values at three points.
+ assert.closeTo(requiredFCIWindowSizeMs(0), 5000, 1e-7);
+ assert.closeTo(requiredFCIWindowSizeMs(15000), 3000, 1e-7);
+ assert.closeTo(requiredFCIWindowSizeMs(Infinity), 1000, 1e-7);
+ });
+
+ test('findFCITaskClusters_MultipleLongTasks', () => {
+ const longTasks = [
+ {start: 50, end: 200},
+ {start: 500, end: 700},
+ {start: 1710, end: 1800},
+ {start: 1900, end: 2000},
+ {start: 2200, end: 2300},
+ {start: 5000, end: 5100},
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ const clusters = findFCITaskClusters(longTasks);
+ assert.strictEqual(clusters.length, 3);
+ assert.strictEqual(clusters[0].start, 50);
+ assert.strictEqual(clusters[0].end, 700);
+ assert.strictEqual(clusters[1].start, 1710);
+ assert.strictEqual(clusters[1].end, 2300);
+ assert.strictEqual(clusters[2].start, 5000);
+ assert.strictEqual(clusters[2].end, 5100);
+ });
+
+ test('findFCITaskClusters_NoLongTasks', () => {
+ const clusters = findFCITaskClusters([]);
+ assert.strictEqual(clusters.length, 0);
+ });
+
+ test('findFirstInteractiveTimestamp_noLongTasks', () => {
+ const searchBegin = 0;
+ const searchEnd = 20000;
+ const dclEnd = 0;
+ const longTasks = [];
+
+ assert.closeTo(findFirstCpuIdleTime(searchBegin, searchEnd, dclEnd,
+ longTasks), 0, 1e-7);
+ });
+
+ test('findFirstCpuIdleTime_' +
+ 'firstInteractiveReachedInBetweenTaskClusters', () => {
+ const searchBegin = 0;
+ const searchEnd = 20000;
+ const dclEnd = 0;
+ const longTasks = [
+ // Cluster 1.
+ {start: 50, end: 150},
+ {start: 200, end: 300},
+ {start: 500, end: 700},
+ // Cluster 2.
+ {start: 2000, end: 2100},
+ {start: 2900, end: 3000},
+ // Cluster 3.
+ {start: 8000, end: 8100},
+ {start: 8200, end: 8500},
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ assert.closeTo(findFirstCpuIdleTime(searchBegin, searchEnd, dclEnd,
+ longTasks), 3000, 1e-7);
+ });
+
+ test('findFirstCpuIdleTime_' +
+ 'FirstInteractiveReachedAfterLastHeavyCluster', () => {
+ const searchBegin = 0;
+ const searchEnd = 20000;
+ const dclEnd = 0;
+ const longTasks = [
+ // Cluster 1.
+ {start: 1000, end: 1100},
+ {start: 1150, end: 1350},
+ // Cluster 2.
+ {start: 4000, end: 4400},
+ // Cluster 3.
+ {start: 7000, end: 7400}
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ assert.closeTo(findFirstCpuIdleTime(searchBegin, searchEnd, dclEnd,
+ longTasks), 7400, 1e-7);
+ });
+
+
+ test('findFirstInteractiveTimestamp_noFirstInteractiveReached', () => {
+ const searchBegin = 0;
+ const searchEnd = 10000;
+ const dclEnd = 0;
+ const longTasks = [
+ // Four spaced out heavy task clusters.
+ {start: 2000, end: 2300},
+ {start: 4000, end: 4300},
+ {start: 6000, end: 6300},
+ {start: 8000, end: 8300}
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ assert.isUndefined(findFirstCpuIdleTime(searchBegin, searchEnd,
+ dclEnd, longTasks));
+ });
+
+ test('findFirstInteractiveTimestamp_lowerBoundedAtDCL', () => {
+ const searchBegin = 0;
+ const searchEnd = 10000;
+ const dclEnd = 8500;
+ const longTasks = [
+ {start: 2000, end: 2100},
+ {start: 4000, end: 4100}
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ assert.closeTo(findFirstCpuIdleTime(searchBegin, searchEnd, dclEnd,
+ longTasks), dclEnd, 1e-7);
+ });
+
+ test('findFirstInteractiveTimestamp_doesNotAssumeLongTasksAreSorted', () => {
+ const searchBegin = 0;
+ const searchEnd = 20000;
+ const dclEnd = 0;
+ const longTasks = [
+ {start: 2000, end: 2400},
+ {start: 1000, end: 1400}
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ assert.closeTo(findFirstCpuIdleTime(searchBegin, searchEnd, dclEnd,
+ longTasks), 2400, 1e-7);
+ });
+
+ test('findFirstCpuIdleTime_' +
+ 'lightTaskClustersDoesNotBlockFirstInteractive', () => {
+ const searchBegin = 0;
+ const searchEnd = 20000;
+ const dclEnd = 0;
+ const longTasks = [
+ // Cluster 1 - heavy (duration 350ms).
+ {start: 1000, end: 1100},
+ {start: 1150, end: 1350},
+ // Cluster 2 - light (duration 200ms).
+ {start: 4000, end: 4200},
+ // Cluster 3 - heavy (duration 400ms).
+ {start: 7000, end: 7400}
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ assert.closeTo(findFirstCpuIdleTime(searchBegin, searchEnd, dclEnd,
+ longTasks), 1350, 1e-7);
+ });
+
+ test('findFirstCpuIdleTime_' +
+ 'requiredWindowSizeIsAtLeast5sAtTheBeginning', () => {
+ // Search begin intentionally not 0 to catch errors in calculating
+ // timeSinceSearchBegin.
+ const searchBegin = 10000;
+ const searchEnd = 30000;
+ const dclEnd = 5000;
+ const longTasks = [
+ // Heavy Task Cluster (duration 501ms). Starts 4999 ms after
+ // |searchBegin|.
+ {start: 14999, end: 15100},
+ {start: 15200, end: 15500},
+ ].map(options => tr.c.TestUtils.newSliceEx(options));
+
+ assert.closeTo(findFirstCpuIdleTime(searchBegin, searchEnd, dclEnd,
+ longTasks), 15500, 1e-7);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/chrome_config.html b/chromium/third_party/catapult/tracing/tracing/extras/chrome_config.html
new file mode 100644
index 00000000000..f6969a549cc
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/chrome_config.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<!--
+The chrome config is heavily used:
+ - chrome://tracing,
+ - trace2html, which in turn implies
+ - adb_profile_chrome
+ - telemetry
+-->
+
+<link rel="import" href="/tracing/extras/android/android_auditor.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/blame_context.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/frame_tree_node.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/render_frame.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/top_level.html">
+<link rel="import" href="/tracing/extras/chrome/blink/blink_scheduler_async_slice.html">
+<link rel="import" href="/tracing/extras/chrome/chrome_auditor.html">
+<link rel="import" href="/tracing/extras/chrome/layout_object.html">
+<link rel="import" href="/tracing/extras/chrome/layout_tree.html">
+<link rel="import" href="/tracing/extras/cpu/cpu_usage_auditor.html">
+<link rel="import" href="/tracing/extras/importer/etw/etw_importer.html">
+<link rel="import" href="/tracing/extras/importer/fuchsia_importer.html">
+<link rel="import" href="/tracing/extras/importer/gzip_importer.html">
+<link rel="import" href="/tracing/extras/importer/trace2html_importer.html">
+<link rel="import" href="/tracing/extras/importer/v8/v8_log_importer.html">
+<link rel="import" href="/tracing/extras/importer/zip_importer.html">
+<link rel="import" href="/tracing/extras/lean_config.html">
+<link rel="import" href="/tracing/extras/measure/measure.html">
+<link rel="import" href="/tracing/extras/net/net.html">
+<link rel="import" href="/tracing/extras/systrace_config.html">
+<link rel="import" href="/tracing/extras/v8_config.html">
+<link rel="import" href="/tracing/extras/vsync/vsync_auditor.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/cpu/cpu_usage_auditor.html b/chromium/third_party/catapult/tracing/tracing/extras/cpu/cpu_usage_auditor.html
new file mode 100644
index 00000000000..d32e69cc998
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/cpu/cpu_usage_auditor.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/math/math.html">
+<link rel="import" href="/tracing/core/auditor.html">
+<link rel="import" href="/tracing/model/resource_usage_series.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.audits', function() {
+ /**
+ * Auditor that analyzes the model and, if possible, adds data to it
+ * showing CPU usage.
+ */
+ class CpuUsageAuditor extends tr.c.Auditor {
+ constructor(model) {
+ super();
+ this.model_ = model;
+ }
+
+ runAnnotate() {
+ this.model_.device.cpuUsageSeries = this.computeCpuUsageSeries_(
+ this.model_.bounds.min, this.model_.bounds.max,
+ this.computeCpuUsage_());
+ }
+
+ /**
+ * Compute the bin size given the start and end times of the trace.
+ */
+ computeBinSize_(start, end) {
+ const MIN_BIN_SIZE_MS = 1.0;
+ const MAX_NUM_BINS = 100000;
+ const interval = end - start;
+ let binSize = MIN_BIN_SIZE_MS;
+ while (binSize * MAX_NUM_BINS < interval) binSize *= 2;
+ return binSize;
+ }
+
+ /**
+ * Returns a CPU usage series from a given set of CPU usage slices.
+ * Slices are in the format created by getCpuUsage below.
+ */
+ computeCpuUsageSeries_(start, end, usageRecords) {
+ const series = new tr.model.ResourceUsageSeries();
+ if (start === undefined || usageRecords.length === 0) return series;
+ const binSize = this.computeBinSize_(start, end);
+ const numBins = Math.ceil((end - start) / binSize);
+ const arr = new Array(numBins).fill(0);
+ for (const record of usageRecords) {
+ const firstIndex = Math.ceil((record.start - start) / binSize);
+ const lastIndex = Math.floor((record.end - start) / binSize);
+ for (let i = firstIndex; i <= lastIndex; i++) arr[i] += record.usage;
+ }
+ for (let i = 0; i < numBins; i++) {
+ series.addUsageSample(start + (i * binSize), arr[i]);
+ }
+ return series;
+ }
+
+ /**
+ * Returns a list of CPU usage slices based on tracing data. Thus, this
+ * effectively counts only processes that are traced (will not count
+ * e.g. background processes)
+ */
+ computeCpuUsage_() {
+ const model = this.model_;
+ const result = [];
+ for (const pid in model.processes) {
+ for (const e of model.processes[pid].getDescendantEvents()) {
+ if (!(e instanceof tr.model.ThreadSlice) || e.duration === 0 ||
+ e.cpuDuration === undefined) {
+ continue;
+ }
+
+ // This slice contains the most fine-grained CPU usage information
+ // for the area of the trace that it covers but that is not covered
+ // by its subslices.
+ // The math goes this way:
+ // s.selfTime : duration of slice s not spent in its subslices.
+ // s.cpuSelfTime : cpuDuration over slice s but not its subslices.
+ //
+ // We're looking for
+ // s.cpuSelfTimeRatio: average CPU usage over the area covered by
+ // s but not any of its subslices.
+ // = s.cpuSelfTime / s.selfTime
+ if (e.selfTime === 0 || e.selfTime === undefined ||
+ e.cpuSelfTime === undefined) {
+ continue;
+ }
+ const usage = tr.b.math.clamp(e.cpuSelfTime / e.selfTime, 0, 1);
+
+ // Go through the area covered by this slice but not its subslices
+ // and add the cpuSelfTimeRatio contribution over this area.
+ let lastTime = e.start;
+ for (const subslice of e.subSlices) {
+ result.push({usage, start: lastTime, end: subslice.start});
+ lastTime = subslice.end;
+ }
+ result.push({usage, start: lastTime, end: e.end});
+ }
+ }
+ return result;
+ }
+ }
+
+ tr.c.Auditor.register(CpuUsageAuditor);
+
+ return {
+ CpuUsageAuditor
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/full_config.html b/chromium/third_party/catapult/tracing/tracing/extras/full_config.html
new file mode 100644
index 00000000000..2f157189922
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/full_config.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<!-- The full config is all the configs slammed together. -->
+<link rel="import" href="/tracing/extras/chrome_config.html">
+<link rel="import" href="/tracing/extras/importer/gcloud_trace/gcloud_trace_importer.html">
+<link rel="import" href="/tracing/extras/lean_config.html">
+<link rel="import" href="/tracing/extras/systrace_config.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/android/atrace_process_dump_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/android/atrace_process_dump_importer.html
new file mode 100644
index 00000000000..518adfc2f35
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/android/atrace_process_dump_importer.html
@@ -0,0 +1,216 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/vm_region.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.importer.android.atrace_process_dump', function() {
+ const IMPORT_PRIORITY = tr.e.importer.linux_perf.IMPORT_PRIORITY + 1;
+ const HEADER = 'ATRACE_PROCESS_DUMP';
+
+ const PROTECTION_FLAG_LETTERS = {
+ '-': 0,
+ 'r': tr.model.VMRegion.PROTECTION_FLAG_READ,
+ 'w': tr.model.VMRegion.PROTECTION_FLAG_WRITE,
+ 'x': tr.model.VMRegion.PROTECTION_FLAG_EXECUTE,
+ 's': tr.model.VMRegion.PROTECTION_FLAG_MAYSHARE,
+ };
+
+ class AtraceProcessDumpImporter extends tr.importer.Importer {
+ constructor(model, data) {
+ super(model, data);
+ this.importPriority = IMPORT_PRIORITY;
+ this.model_ = model;
+ this.raw_data_ = data;
+ this.clock_sync_markers_ = {};
+ this.snapshots_ = [];
+ this.processes_ = {};
+ }
+
+ static canImport(events) {
+ if (!(typeof(events) === 'string' || events instanceof String)) {
+ return false;
+ }
+ return events.startsWith(HEADER);
+ }
+
+ get importerName() {
+ return 'AtraceProcessDumpImporter';
+ }
+
+ get model() {
+ return this.model_;
+ }
+
+ lazyParseData() {
+ if (this.raw_data_ === undefined) {
+ return;
+ }
+ const dump = JSON.parse(this.raw_data_.slice(HEADER.length + 1));
+ this.clock_sync_markers_ = dump.clock_sync_markers;
+ this.snapshots_ = dump.dump.snapshots;
+ this.processes_ = dump.dump.processes;
+ this.raw_data_ = undefined;
+ }
+
+ importClockSyncMarkers() {
+ this.lazyParseData();
+ for (const syncId in this.clock_sync_markers_) {
+ const ts = parseInt(this.clock_sync_markers_[syncId]);
+ this.model_.clockSyncManager.addClockSyncMarker(
+ tr.model.ClockDomainId.LINUX_CLOCK_MONOTONIC, syncId, ts);
+ }
+ }
+
+ setProcessMemoryDumpTotals_(pmd, processInfo) {
+ pmd.totals = {
+ 'residentBytes': processInfo.rss * 1024,
+ 'platformSpecific': {
+ 'vm': processInfo.vm * 1024
+ }
+ // TODO(kraynov): Add OOM scores and make UI able to show it.
+ };
+ const totals = pmd.totals.platformSpecific;
+
+ function importGpuMetric(name) {
+ if (processInfo[name] !== undefined && processInfo[name] > 0) {
+ totals[name] = processInfo[name] * 1024;
+ totals[name + '_pss'] = processInfo[name + '_pss'] * 1024;
+ }
+ }
+ importGpuMetric('gpu_egl');
+ importGpuMetric('gpu_gl');
+ importGpuMetric('gpu_etc');
+
+ if (processInfo.pss !== undefined) {
+ // Full stats.
+ totals.pss = processInfo.pss * 1024;
+ totals.swp = processInfo.swp * 1024;
+ totals.pc = processInfo.pc * 1024;
+ totals.pd = processInfo.pd * 1024;
+ totals.sc = processInfo.sc * 1024;
+ totals.sd = processInfo.sd * 1024;
+ }
+ }
+
+ setProcessMemoryDumpVmRegions_(pmd, processInfo) {
+ if (processInfo.mmaps === undefined) {
+ return;
+ }
+ const vmRegions = [];
+ for (const memoryMap of processInfo.mmaps) {
+ const addr = memoryMap.vm.split('-').map(x => parseInt(x, 16));
+ let flags = 0;
+ for (const letter of memoryMap.flags) {
+ flags |= PROTECTION_FLAG_LETTERS[letter];
+ }
+ const totals = {
+ 'proportionalResident': memoryMap.pss * 1024,
+ 'privateCleanResident': memoryMap.pc * 1024,
+ 'privateDirtyResident': memoryMap.pd * 1024,
+ 'sharedCleanResident': memoryMap.sc * 1024,
+ 'sharedDirtyResident': memoryMap.sd * 1024,
+ 'swapped': memoryMap.swp * 1024,
+ };
+ vmRegions.push(new tr.model.VMRegion(
+ addr[0], addr[1] - addr[0], flags, memoryMap.file, totals));
+ }
+ pmd.vmRegions =
+ tr.model.VMRegionClassificationNode.fromRegions(vmRegions);
+ }
+
+ importEvents() {
+ this.lazyParseData();
+ // Assign process and thread names.
+ for (const [pid, process] of Object.entries(this.processes_)) {
+ const modelProcess = this.model_.getProcess(pid);
+ if (modelProcess === undefined) {
+ continue;
+ }
+ modelProcess.name = process.name;
+
+ const threads = process.threads;
+ if (threads === undefined) {
+ continue;
+ }
+ for (const [tid, thread] of Object.entries(threads)) {
+ const modelThread = modelProcess.threads[tid];
+ if (modelThread === undefined) {
+ continue;
+ }
+ modelThread.name = thread.name;
+ }
+ }
+
+ // Memory dumps.
+ const memCounter =
+ this.model_.kernel.getOrCreateCounter('global', 'SystemMemory');
+ const memUsedSeries = new tr.model.CounterSeries('Used (KB)', 0);
+ const memSwappedSeries = new tr.model.CounterSeries('Swapped (KB)', 0);
+ memCounter.addSeries(memUsedSeries);
+ memCounter.addSeries(memSwappedSeries);
+
+ for (const snapshot of this.snapshots_) {
+ const ts = parseInt(snapshot.ts);
+ const memoryDump = snapshot.memdump;
+
+ if (memoryDump === undefined) {
+ const memInfo = snapshot.meminfo;
+ if (memInfo === undefined) {
+ continue;
+ }
+
+ // See Android com.android.server.am.ActivityManagerService class
+ // for calculation formula in 'dumpsys meminfo'.
+ //
+ // The formula below excludes Cached PSS because it's too expensive
+ // to calculate and it's not volatile during short systrace run.
+ // Cached PSS is a total PSS of apps being primary targets for
+ // OOM killer and treated by 'dumpsys meminfo' as a free memory.
+ const memCaches = memInfo.Buffers + memInfo.Cached - memInfo.Mapped;
+ const memUsed = memInfo.MemTotal - memInfo.MemFree - memCaches;
+ const memSwapped = memInfo.SwapTotal - memInfo.SwapFree;
+
+ memUsedSeries.addCounterSample(ts, memUsed);
+ memSwappedSeries.addCounterSample(ts, memSwapped);
+ continue;
+ }
+
+ const gmd = new tr.model.GlobalMemoryDump(this.model_, ts);
+ this.model_.globalMemoryDumps.push(gmd);
+
+ for (const [pid, processInfo] of Object.entries(memoryDump)) {
+ if (processInfo.rss === undefined) {
+ // Memory stats aren't available.
+ continue;
+ }
+ const modelProcess = this.model_.getProcess(pid);
+ if (modelProcess === undefined) {
+ continue;
+ }
+ const pmd = new tr.model.ProcessMemoryDump(gmd, modelProcess, ts);
+ gmd.processMemoryDumps[pid] = pmd;
+ modelProcess.memoryDumps.push(pmd);
+ this.setProcessMemoryDumpTotals_(pmd, processInfo);
+ this.setProcessMemoryDumpVmRegions_(pmd, processInfo);
+ }
+ }
+ }
+ }
+
+ tr.importer.Importer.register(AtraceProcessDumpImporter);
+
+ return {
+ AtraceProcessDumpImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/android/atrace_process_dump_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/android/atrace_process_dump_importer_test.html
new file mode 100644
index 00000000000..ecf3e8a1584
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/android/atrace_process_dump_importer_test.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/android/atrace_process_dump_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Importer =
+ tr.e.importer.android.atrace_process_dump.AtraceProcessDumpImporter;
+
+ const TEST_DATA =
+ 'ATRACE_PROCESS_DUMP\n' +
+ '{"clock_sync_markers": {"very-unique-id": 1}, "dump":' +
+ '{"start_ts": "21917568", "snapshots": [' +
+ '{"ts": "21917568", "memdump": {' +
+ ' "788": {' +
+ ' "vm": 2629440, "rss": 249348,' +
+ ' "oom_sc": 0, "oom_sc_adj": -900,' +
+ ' "min_flt": 15844983, "maj_flt": 54832,' +
+ ' "utime": 831123, "stime": 1117125,' +
+ ' "mmaps": [{"vm": "a1000-a5000", "file": "foo", "flags": "rw-s",' +
+ ' "pss": 100, "rss": 200, "swp": 10,' +
+ ' "pc": 50, "pd": 60, "sc": 40, "sd": 45}]' +
+ '}}}],' +
+ '"processes": {' +
+ '"37": {"name": "kworker/5:0H"},' +
+ '"788": {"name": "system_server", "exe": "/system/bin/app_process64",' +
+ ' "threads": {' +
+ ' "788": {"name": "system_server"},' +
+ ' "793": {"name": "Signal Catcher"}' +
+ '}}}}}';
+
+ test('canImport', function() {
+ const canImport = Importer.canImport;
+ assert.isTrue(canImport(TEST_DATA));
+ assert.isFalse(canImport('\n'));
+ });
+
+ test('lazyParseData', function() {
+ const importer = new Importer(null, TEST_DATA);
+ importer.lazyParseData();
+ assert.isUndefined(importer.raw_data_);
+ assert.lengthOf(Object.keys(importer.clock_sync_markers_), 1);
+ assert.lengthOf(importer.snapshots_, 1);
+ assert.lengthOf(Object.keys(importer.processes_), 2);
+ assert.lengthOf(Object.keys(importer.processes_['37']), 1);
+ assert.lengthOf(Object.keys(importer.processes_['788']), 3);
+ assert.lengthOf(Object.keys(importer.processes_['788'].threads), 2);
+
+ const snapshot = importer.snapshots_[0];
+ assert.strictEqual(snapshot.ts, '21917568');
+ assert.lengthOf(Object.keys(snapshot.memdump), 1);
+ assert.lengthOf(Object.keys(snapshot.memdump['788']), 9);
+ });
+
+ test('importEvents', function() {
+ const model = new tr.Model();
+ const proc788 = model.getOrCreateProcess(788);
+ const importer = new Importer(model, TEST_DATA);
+ importer.importEvents();
+ assert.strictEqual(proc788.name, 'system_server');
+ assert.lengthOf(model.globalMemoryDumps, 1);
+
+ const gmd = model.globalMemoryDumps[0];
+ assert.lengthOf(Object.keys(gmd.processMemoryDumps), 1);
+ const pmd = gmd.processMemoryDumps['788'];
+ assert.strictEqual(pmd.totals.residentBytes, 249348 * 1024);
+
+ const vmRegions = pmd.vmRegions.allRegionsForTesting;
+ assert.lengthOf(vmRegions, 1);
+ const vmRegion = vmRegions[0];
+ assert.deepEqual(vmRegion.byteStats, pmd.vmRegions.byteStats);
+ assert.strictEqual(vmRegion.byteStats.swapped, 10240);
+ assert.strictEqual(vmRegion.mappedFile, 'foo');
+ assert.strictEqual(vmRegion.protectionFlags, 134);
+ assert.strictEqual(vmRegion.sizeInBytes, 16384);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/android/event_log_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/android/event_log_importer.html
new file mode 100644
index 00000000000..677ad5f9912
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/android/event_log_importer.html
@@ -0,0 +1,324 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/importer/simple_line_reader.html">
+<link rel="import" href="/tracing/model/activity.html">
+<link rel="import" href="/tracing/model/model.html">
+
+
+<script>
+/**
+ * @fileoverview Imports android event log data into the trace model.
+ * Android event log data contains information about activities that
+ * are launched/paused, processes that are started, memory usage, etc.
+ *
+ * The current implementation only parses activity events, with the goal of
+ * determining which Activity is running in the foreground for a process.
+ *
+ * This importer assumes the events arrive as a string. The unit tests provide
+ * examples of the trace format.
+ */
+'use strict';
+
+tr.exportTo('tr.e.importer.android', function() {
+ const Importer = tr.importer.Importer;
+
+ const ACTIVITY_STATE = {
+ NONE: 'none',
+ CREATED: 'created',
+ STARTED: 'started',
+ RESUMED: 'resumed',
+ PAUSED: 'paused',
+ STOPPED: 'stopped',
+ DESTROYED: 'destroyed'
+ };
+
+ const activityMap = {};
+
+ /**
+ * Imports android event log data (adb logcat -b events)
+ * @constructor
+ */
+ function EventLogImporter(model, events) {
+ this.model_ = model;
+ this.events_ = events;
+ this.importPriority = 3;
+ }
+
+ // Generic format of event log entries.
+ // Sample event log entry that this matches (split over 2 lines):
+ // 08-11 13:12:31.405 880 2645 I am_focused_activity: [0,com.google.android.googlequicksearchbox/com.google.android.launcher.GEL] // @suppress longLineCheck
+ const eventLogActivityRE = new RegExp(
+ '(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d+)' +
+ '\\s+(\\d+)\\s+(\\d+)\\s+([A-Z])\\s*' +
+ '(am_\\w+)\\s*:(.*)');
+
+ // 08-28 03:58:21.834 888 3177 I am_create_activity: [0,5972200,30,com.nxp.taginfolite/.activities.MainView,android.intent.action.MAIN,NULL,NULL,270532608] // @suppress longLineCheck
+ // Store the name of the created activity only
+ const amCreateRE = new RegExp('\s*\\[.*,.*,.*,(.*),.*,.*,.*,.*\\]');
+
+ // 07-22 12:22:19.504 920 2504 I am_focused_activity: [0,com.android.systemui/.recents.RecentsActivity] // @suppress longLineCheck
+ // Store the name of the focused activity only
+ const amFocusedRE = new RegExp('\s*\\[\\d+,(.*)\\]');
+
+ // 07-21 19:56:12.315 920 2261 I am_proc_start: [0,19942,10062,com.google.android.talk,broadcast,com.google.android.talk/com.google.android.apps.hangouts.realtimechat.RealTimeChatService$AlarmReceiver] // @suppress longLineCheck
+ // We care about proc starts on behalf of activities, and store the activity
+ const amProcStartRE = new RegExp('\s*\\[\\d+,\\d+,\\d+,.*,activity,(.*)\\]');
+
+ // 07-22 12:21:43.490 2893 2893 I am_on_resume_called: [0,com.google.android.launcher.GEL] // @suppress longLineCheck
+ // Store the activity name only
+ const amOnResumeRE = new RegExp('\s*\\[\\d+,(.*)\\]');
+
+ // 07-22 12:22:19.545 2893 2893 I am_on_paused_called: [0,com.google.android.launcher.GEL] // @suppress longLineCheck
+ // Store the activity name only
+ const amOnPauseRE = new RegExp('\s*\\[\\d+,(.*)\\]');
+
+ // 08-28 03:51:54.456 888 907 I am_activity_launch_time: [0,185307115,com.google.android.googlequicksearchbox/com.google.android.launcher.GEL,1174,1174] // @suppress longLineCheck
+ // Store the activity name and launch times
+ const amLaunchTimeRE = new RegExp('\s*\\[\\d+,\\d+,(.*),(\\d+),(\\d+)');
+
+ // 08-28 03:58:15.854 888 902 I am_destroy_activity: [0,203516597,29,com.android.chrome/com.google.android.apps.chrome.Main,finish-idle] // @suppress longLineCheck
+ // Store the activity name only
+ const amDestroyRE = new RegExp('\s*\\[\\d+,\\d+,\\d+,(.*)\\]');
+
+ /**
+ * @return {boolean} True when events is an android event log array.
+ */
+ EventLogImporter.canImport = function(events) {
+ if (!(typeof(events) === 'string' || events instanceof String)) {
+ return false;
+ }
+
+ // Prevent the importer from matching this file in vulcanized traces.
+ if (/^<!DOCTYPE html>/.test(events)) return false;
+
+ return eventLogActivityRE.test(events);
+ };
+
+ EventLogImporter.prototype = {
+ __proto__: Importer.prototype,
+
+ get importerName() {
+ return 'EventLogImporter';
+ },
+
+ get model() {
+ return this.model_;
+ },
+
+ /**
+ * @return {string} the full activity name (including package) from
+ * a component
+ */
+ getFullActivityName(component) {
+ const componentSplit = component.split('/');
+ if (componentSplit[1].startsWith('.')) {
+ return componentSplit[0] + componentSplit[1];
+ }
+
+ return componentSplit[1];
+ },
+
+ /**
+ * @return {string} the process name of a component
+ */
+ getProcName(component) {
+ const componentSplit = component.split('/');
+ return componentSplit[0];
+ },
+
+ findOrCreateActivity(activityName) {
+ if (activityName in activityMap) {
+ return activityMap[activityName];
+ }
+ const activity = {
+ state: ACTIVITY_STATE.NONE,
+ name: activityName
+ };
+ activityMap[activityName] = activity;
+ return activity;
+ },
+
+ deleteActivity(activityName) {
+ delete activityMap[activityName];
+ },
+
+ handleCreateActivity(ts, activityName) {
+ const activity = this.findOrCreateActivity(activityName);
+ activity.state = ACTIVITY_STATE.CREATED;
+ activity.createdTs = ts;
+ },
+
+ handleFocusActivity(ts, procName, activityName) {
+ const activity = this.findOrCreateActivity(activityName);
+ activity.lastFocusedTs = ts;
+ },
+
+ handleProcStartForActivity(ts, activityName) {
+ const activity = this.findOrCreateActivity(activityName);
+ activity.procStartTs = ts;
+ },
+
+ handleOnResumeCalled(ts, pid, activityName) {
+ const activity = this.findOrCreateActivity(activityName);
+ activity.state = ACTIVITY_STATE.RESUMED;
+ activity.lastResumeTs = ts;
+ // on_resume_called shows the actual PID; use this
+ // to link the activity up with a process later
+ activity.pid = pid;
+ },
+
+ handleOnPauseCalled(ts, activityName) {
+ const activity = this.findOrCreateActivity(activityName);
+ activity.state = ACTIVITY_STATE.PAUSED;
+ activity.lastPauseTs = ts;
+ // Create a new AndroidActivity representing the foreground state,
+ // but only if the pause happened within the model bounds
+ if (ts > this.model_.bounds.min && ts < this.model_.bounds.max) {
+ this.addActivityToProcess(activity);
+ }
+ },
+
+ handleLaunchTime(ts, activityName, launchTime) {
+ const activity = this.findOrCreateActivity(activityName);
+ activity.launchTime = launchTime;
+ },
+
+ handleDestroyActivity(ts, activityName) {
+ this.deleteActivity(activityName);
+ },
+
+ addActivityToProcess(activity) {
+ if (activity.pid === undefined) return;
+ const process = this.model_.getOrCreateProcess(activity.pid);
+ // The range of the activity is the time from resume to time
+ // of pause; limit the start time to the beginning of the model
+ const range = tr.b.math.Range.fromExplicitRange(
+ Math.max(this.model_.bounds.min, activity.lastResumeTs),
+ activity.lastPauseTs);
+ const newActivity = new tr.model.Activity(activity.name,
+ 'Android Activity', range,
+ {created: activity.createdTs,
+ procstart: activity.procStartTs,
+ lastfocus: activity.lastFocusedTs});
+ process.activities.push(newActivity);
+ },
+
+ parseAmLine_(line) {
+ let match = eventLogActivityRE.exec(line);
+ if (!match) return;
+
+ // Possible activity life-cycles:
+ // 1) Launch from scratch:
+ // - am_create_activity
+ // - am_focused_activity
+ // - am_proc_start
+ // - am_proc_bound
+ // - am_restart_activity
+ // - am_on_resume_called
+ // 2) Re-open existing activity
+ // - am_focused_activity
+ // - am_on_resume_called
+
+ // HACK: event log date format is "MM-DD" and doesn't contain the year;
+ // to figure out the year, take the min bound of the model, convert
+ // to real-time and use that as the year.
+ // The Android event log will eventually contain the year once this
+ // CL is in a release:
+ // https://android-review.googlesource.com/#/c/168900
+ const firstRealtimeTs = this.model_.bounds.min -
+ this.model_.realtime_to_monotonic_offset_ms;
+ const year = new Date(firstRealtimeTs).getFullYear();
+ const ts = match[1].substring(0, 5) + '-' + year + ' ' +
+ match[1].substring(5, match[1].length);
+
+ const monotonicTs = Date.parse(ts) +
+ this.model_.realtime_to_monotonic_offset_ms;
+
+ const pid = match[2];
+ const action = match[5];
+ const data = match[6];
+
+ if (action === 'am_create_activity') {
+ match = amCreateRE.exec(data);
+ if (match && match.length >= 2) {
+ this.handleCreateActivity(monotonicTs,
+ this.getFullActivityName(match[1]));
+ }
+ } else if (action === 'am_focused_activity') {
+ match = amFocusedRE.exec(data);
+ if (match && match.length >= 2) {
+ this.handleFocusActivity(monotonicTs,
+ this.getProcName(match[1]), this.getFullActivityName(match[1]));
+ }
+ } else if (action === 'am_proc_start') {
+ match = amProcStartRE.exec(data);
+ if (match && match.length >= 2) {
+ this.handleProcStartForActivity(monotonicTs,
+ this.getFullActivityName(match[1]));
+ }
+ } else if (action === 'am_on_resume_called') {
+ match = amOnResumeRE.exec(data);
+ if (match && match.length >= 2) {
+ this.handleOnResumeCalled(monotonicTs, pid, match[1]);
+ }
+ } else if (action === 'am_on_paused_called') {
+ match = amOnPauseRE.exec(data);
+ if (match && match.length >= 2) {
+ this.handleOnPauseCalled(monotonicTs, match[1]);
+ }
+ } else if (action === 'am_activity_launch_time') {
+ match = amLaunchTimeRE.exec(data);
+ this.handleLaunchTime(monotonicTs,
+ this.getFullActivityName(match[1]), match[2]);
+ } else if (action === 'am_destroy_activity') {
+ match = amDestroyRE.exec(data);
+ if (match && match.length === 2) {
+ this.handleDestroyActivity(monotonicTs,
+ this.getFullActivityName(match[1]));
+ }
+ }
+ },
+
+ importEvents() {
+ // Check if we have a mapping from real-time to CLOCK_MONOTONIC
+ if (isNaN(this.model_.realtime_to_monotonic_offset_ms)) {
+ this.model_.importWarning({
+ type: 'eveng_log_clock_sync',
+ message: 'Need a trace_event_clock_sync to map realtime to import.'
+ });
+ return;
+ }
+ // Since the event log typically spans a much larger timeframe
+ // than the ftrace data, we want to calculate the bounds of the existing
+ // model, and dump all event log data outside of those bounds
+ this.model_.updateBounds();
+
+ const lines = this.events_.split('\n');
+ lines.forEach(this.parseAmLine_, this);
+
+ // Iterate over all created activities that are not destroyed yet
+ for (const activityName in activityMap) {
+ const activity = activityMap[activityName];
+ // If we're still in the foreground, store the activity anyway
+ if (activity.state === ACTIVITY_STATE.RESUMED) {
+ // Set the pause timestamp to the end of the model bounds
+ activity.lastPauseTs = this.model_.bounds.max;
+ this.addActivityToProcess(activity);
+ }
+ }
+ }
+ };
+
+ Importer.register(EventLogImporter);
+
+ return {
+ EventLogImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/android/process_data_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/android/process_data_importer.html
new file mode 100644
index 00000000000..7c9cdf5e89f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/android/process_data_importer.html
@@ -0,0 +1,178 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/importer/simple_line_reader.html">
+<link rel="import" href="/tracing/model/activity.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+/**
+ * @fileoverview
+ */
+'use strict';
+
+tr.exportTo('tr.e.importer.android.process_data', function() {
+ const Importer = tr.importer.Importer;
+
+ const PROCESS_DUMP_HEADER = 'PROCESS DUMP';
+
+ /**
+ * Imports android process data
+ * @constructor
+ */
+ function ProcessDataImporter(model, processData) {
+ this.model_ = model;
+ this.processDataLines = processData.split('\n');
+ this.importPriority = 3;
+ }
+
+ /**
+ * @return {boolean}
+ */
+ ProcessDataImporter.canImport = function(events) {
+ if (!(typeof(events) === 'string' || events instanceof String)) {
+ return false;
+ }
+
+ if (events.split('\n')[0] === PROCESS_DUMP_HEADER) {
+ return true;
+ }
+
+ return false;
+ };
+
+ ProcessDataImporter.prototype = {
+ __proto__: Importer.prototype,
+
+ get importerName() {
+ return 'ProcessDataImporter';
+ },
+
+ get model() {
+ return this.model_;
+ },
+
+ parseEventData(data) {
+ const allDumpedProcesses = {};
+
+ let parseProcesses = false;
+ let parseThreads = false;
+ let legacy = false;
+
+ // Skip header on line 1.
+ for (let i = 1; i < data.length; i++) {
+ const cols = data[i].split(/\s+/);
+ if (cols[0].startsWith('USER')) {
+ if (parseProcesses) {
+ parseProcesses = false;
+ parseThreads = true;
+ } else {
+ parseThreads = false;
+ parseProcesses = true;
+ }
+
+ const colCount = cols.length;
+ if (parseProcesses && colCount === 9) {
+ legacy = false;
+ } else if (parseProcesses && colCount === 8) {
+ legacy = true;
+ }
+ continue;
+ }
+
+ if (parseProcesses) {
+ const pid = Number(cols[1]);
+ if (allDumpedProcesses[pid] === undefined) {
+ allDumpedProcesses[pid] = {};
+ }
+ allDumpedProcesses[pid] = {
+ 'name': cols[8], pid, 'comm': cols[9]
+ };
+ continue;
+ }
+
+ if (parseThreads) {
+ let pid;
+ let tid;
+ let name;
+
+ // In legacy ps dumps, the PID is actually shown in the PPID column
+ // for thread lines, but PID column for the process line
+ if (legacy) {
+ pid = Number(cols[1]);
+ if (allDumpedProcesses[pid] !== undefined) {
+ // this is a process line in the dump.
+ tid = pid;
+ } else {
+ // Thread line, so swap.
+ tid = pid;
+ pid = Number(cols[2]); // i.e. the PPID column.
+ }
+ name = cols.slice(8).join(' ');
+ } else {
+ pid = Number(cols[1]);
+ tid = Number(cols[2]);
+ name = cols.slice(3).join(' ');
+ }
+
+ // Thread data for a process we didn't identify, skip.
+ if (allDumpedProcesses[pid] === undefined) continue;
+
+ if (allDumpedProcesses[pid].threads === undefined) {
+ allDumpedProcesses[pid].threads = {};
+ }
+ allDumpedProcesses[pid].threads[tid] = {tid, name};
+ continue;
+ }
+ }
+ return allDumpedProcesses;
+ },
+
+ importEvents() {
+ // Merge the snapshots into a single object, indexable by PID.
+
+ const allDumpedProcesses = this.parseEventData(this.processDataLines);
+
+ // Update the model with the new process / thread name data.
+ const modelProcesses = this.model_.getAllProcesses();
+ for (let i = 0; i < modelProcesses.length; i++) {
+ const modelProcess = modelProcesses[i];
+ const pid = modelProcess.pid;
+ const dumpedProcess = allDumpedProcesses[pid];
+ if (dumpedProcess === undefined) {
+ // There's a process in the model that the ps dump doesn't know about,
+ // nothing we can do so skip it.
+ continue;
+ }
+
+ modelProcess.name = dumpedProcess.name;
+ const processDumpThreads = dumpedProcess.threads;
+ if (processDumpThreads !== undefined) {
+ for (const tid in modelProcess.threads) {
+ const modelThread = modelProcess.threads[tid];
+ if (Number(pid) === Number(tid)) {
+ modelThread.name = 'UI thread';
+ } else if (modelThread.name === '<...>') {
+ if (processDumpThreads[tid] !== undefined) {
+ // Use the stored thread name that we have.
+ modelThread.name = processDumpThreads[tid].name;
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+
+ Importer.register(ProcessDataImporter);
+
+ return {
+ ProcessDataImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/android/process_data_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/android/process_data_importer_test.html
new file mode 100644
index 00000000000..f902b06229c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/android/process_data_importer_test.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/android/process_data_importer.html">
+
+<script>
+'use strict';
+
+const TEST_DATA =
+ 'PROCESS DUMP\n' +
+ 'USER PID PPID VSZ RSS WCHAN PC S NAME COMM\n' +
+ 'root 1 0 0 0 SyS_epoll_wait ffffff S test1 init\n' +
+ 'root 2 0 0 0 kthreadd 0 S test2 2\n' +
+ 'root 3 2 0 0 smpboot_thread_fn 0 S test3 3\n' +
+ 'USER PID TID CMD\n' +
+ 'root 1 1 test1\n' +
+ 'root 2 2 test2\n' +
+ 'root 3 3 test3\n' +
+ 'u0_a48 1 4 test1_t1\n' +
+ 'u0_a10 2 5 test2_t1\n' +
+ 'u0_a10 3 6 test3_t1\n' +
+ 'u0_a10 1 7 test1_t2\n' +
+ 'u0_a10 2 8 test2_t2\n' +
+ 'u0_a10 3 9 test3_t2\n' +
+ 'u0_a10 3 10 test3_t3\n';
+
+const LEGACY_TEST_DATA =
+ 'PROCESS DUMP\n' +
+ 'USER PID PPID VSIZE RSS WCHAN PC NAME\n' +
+ 'root 1 0 1324 856 SyS_epoll_ 00070cd8 S test1\n' +
+ 'root 2 0 0 0 kthreadd 00000000 S test2\n' +
+ 'root 3 0 0 0 smpboot_th 00000000 S test3\n' +
+ 'USER PID PPID VSIZE RSS WCHAN PC NAME\n' +
+ 'root 1 0 1324 856 SyS_epoll_ 00070cd8 S test1\n' +
+ 'root 2 0 0 0 kthreadd 00000000 S test2\n' +
+ 'root 3 0 0 0 smpboot_th 00000000 S test3\n' +
+ 'u0_a35 4 1 726452 34352 SyS_epoll_ ffffffff S test1_t1\n' +
+ 'u0_a35 5 2 726452 34352 SyS_epoll_ ffffffff S test2_t1\n' +
+ 'u0_a53 6 3 846540 54996 SyS_epoll_ ffffffff S test3_t1\n' +
+ 'u0_a53 7 1 846540 54996 futex_wait ffffffff S test1_t2\n' +
+ 'u0_a53 8 2 846540 54996 futex_wait ffffffff S test2_t2\n' +
+ 'u0_a53 9 3 846540 54996 futex_wait ffffffff S test3_t2\n' +
+ 'root 10 3 4692 244 poll_sched ffffffff S test3_t3\n';
+
+tr.b.unittest.testSuite(function() {
+ test('canImport', function() {
+ const canImport =
+ tr.e.importer.android.process_data.ProcessDataImporter.canImport;
+ assert.isTrue(canImport(TEST_DATA));
+ assert.isTrue(canImport(LEGACY_TEST_DATA));
+ assert.isFalse(canImport(''));
+ assert.isFalse(canImport('\n'));
+ });
+
+ test('procinfoImport', function() {
+ const importer = new tr.e.importer.android.process_data.ProcessDataImporter(
+ null, TEST_DATA);
+ const result = importer.parseEventData(importer.processDataLines);
+ verifyParsedDump(result);
+ });
+
+ test('procinfoImportLegacy', function() {
+ const importer = new tr.e.importer.android.process_data.ProcessDataImporter(
+ null, LEGACY_TEST_DATA);
+ const result = importer.parseEventData(importer.processDataLines);
+ verifyParsedDump(result);
+ });
+
+ function verifyParsedDump(result) {
+ assert.isDefined(result);
+
+ // indexable by PID, so invalid PIDs should be undefined.
+ assert.isUndefined(result[0]);
+ assert.isUndefined(result[100]);
+
+ let proc = result[1];
+ assert.isDefined(proc);
+ assert.strictEqual(proc.name, 'test1');
+ assert.strictEqual(proc.pid, 1);
+ assert.isDefined(proc.threads);
+ let thread = proc.threads[1];
+ assert.isDefined(thread);
+ assert.strictEqual(thread.name, 'test1');
+ assert.strictEqual(thread.tid, 1);
+ thread = proc.threads[4];
+ assert.isDefined(thread);
+ assert.strictEqual(thread.name, 'test1_t1');
+ assert.strictEqual(thread.tid, 4);
+ thread = proc.threads[7];
+ assert.isDefined(thread);
+ assert.strictEqual(thread.name, 'test1_t2');
+ assert.strictEqual(thread.tid, 7);
+
+ proc = result[2];
+ assert.isDefined(proc);
+ assert.strictEqual(proc.name, 'test2');
+ assert.strictEqual(proc.pid, 2);
+ assert.isDefined(proc.threads);
+ thread = proc.threads[2];
+ assert.isDefined(thread);
+ assert.strictEqual(thread.name, 'test2');
+ assert.strictEqual(thread.tid, 2);
+ thread = proc.threads[5];
+ assert.isDefined(thread);
+ assert.strictEqual(thread.name, 'test2_t1');
+ assert.strictEqual(thread.tid, 5);
+ thread = proc.threads[8];
+ assert.isDefined(thread);
+ assert.strictEqual(thread.name, 'test2_t2');
+ assert.strictEqual(thread.tid, 8);
+
+ proc = result[3];
+ assert.isDefined(proc);
+ assert.strictEqual(proc.name, 'test3');
+ assert.strictEqual(proc.pid, 3);
+ assert.isDefined(proc.threads);
+ thread = proc.threads[3];
+ assert.isDefined(thread);
+ assert.strictEqual(thread.name, 'test3');
+ assert.strictEqual(thread.tid, 3);
+ thread = proc.threads[6];
+ assert.isDefined(thread);
+ assert.strictEqual(thread.name, 'test3_t1');
+ assert.strictEqual(thread.tid, 6);
+ thread = proc.threads[9];
+ assert.isDefined(thread);
+ assert.strictEqual(thread.name, 'test3_t2');
+ assert.strictEqual(thread.tid, 9);
+ thread = proc.threads[10];
+ assert.isDefined(thread);
+ assert.strictEqual(thread.name, 'test3_t3');
+ assert.strictEqual(thread.tid, 10);
+ }
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/battor_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/battor_importer.html
new file mode 100644
index 00000000000..1235a4e41d5
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/battor_importer.html
@@ -0,0 +1,186 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/unit_scale.html">
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/power_series.html">
+
+<script>
+/**
+ * @fileoverview Imports text files in the BattOr format into the
+ * Model. This format is output by the battor_agent executable and library.
+ *
+ * This importer assumes the events arrive as a string. The unit tests provide
+ * examples of the trace format.
+ */
+'use strict';
+
+tr.exportTo('tr.e.importer.battor', function() {
+ /**
+ * Imports a BattOr power trace into a specified model.
+ * @constructor
+ */
+ function BattorImporter(model, events) {
+ this.importPriority = 3; // runs after the linux_perf importer
+ this.model_ = model;
+
+ // The list of power samples contained within the trace.
+ this.samples_ = [];
+ // The clock sync markers contained within the trace.
+ this.syncTimestampsById_ = new Map();
+
+ this.parseTrace_(events);
+ }
+
+ const battorDataLineRE = new RegExp(
+ '^(-?\\d+\\.\\d+)\\s+(-?\\d+\\.\\d+)\\s+(-?\\d+\\.\\d+)' +
+ '(?:\\s+<(\\S+)>)?$'
+ );
+ const battorHeaderLineRE = /^# BattOr/;
+
+ /**
+ * Guesses whether the provided events is a BattOr string.
+ * Looks for the magic string "# BattOr" at the start of the file,
+ *
+ * @return {boolean} True when events is a BattOr array.
+ */
+ BattorImporter.canImport = function(events) {
+ if (!(typeof(events) === 'string' || events instanceof String)) {
+ return false;
+ }
+
+ return battorHeaderLineRE.test(events);
+ };
+
+ BattorImporter.prototype = {
+ __proto__: tr.importer.Importer.prototype,
+
+ get importerName() {
+ return 'BattorImporter';
+ },
+
+ get model() {
+ return this.model_;
+ },
+
+ /**
+ * Imports clock sync markers from the trace into into this.model_.
+ */
+ importClockSyncMarkers() {
+ for (const [syncId, ts] of this.syncTimestampsById_) {
+ this.model_.clockSyncManager.addClockSyncMarker(
+ tr.model.ClockDomainId.BATTOR, syncId, ts);
+ }
+ },
+
+ /**
+ * Imports the events from the trace into this.model_.
+ */
+ importEvents() {
+ if (this.model_.device.powerSeries) {
+ this.model_.importWarning({
+ type: 'import_error',
+ message: 'Power counter exists, can not import BattOr power trace.'
+ });
+ return;
+ }
+
+ const modelTimeTransformer =
+ this.model_.clockSyncManager.getModelTimeTransformer(
+ tr.model.ClockDomainId.BATTOR);
+
+ const powerSeries = this.model_.device.powerSeries =
+ new tr.model.PowerSeries(this.model_.device);
+ for (let i = 0; i < this.samples_.length; i++) {
+ const sample = this.samples_[i];
+ powerSeries.addPowerSample(
+ modelTimeTransformer(sample.ts), sample.powerInW);
+ }
+ },
+
+ /**
+ * Given the BattOr trace as a string, parse it and store the results in
+ * this.samples_ and this.syncTimestampsById_.
+ */
+ parseTrace_(trace) {
+ const lines = trace.split('\n');
+
+ for (let line of lines) {
+ line = line.trim();
+
+ if (line.length === 0) continue;
+
+ if (line.startsWith('#')) continue;
+
+ // Parse power sample.
+ const groups = battorDataLineRE.exec(line);
+ if (!groups) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Unrecognized line in BattOr trace: ' + line
+ });
+ continue;
+ }
+
+ const ts = parseFloat(groups[1]);
+ const voltageInV = tr.b.convertUnit(parseFloat(groups[2]),
+ tr.b.UnitPrefixScale.METRIC.MILLI,
+ tr.b.UnitPrefixScale.METRIC.NONE);
+ const currentInA = tr.b.convertUnit(parseFloat(groups[3]),
+ tr.b.UnitPrefixScale.METRIC.MILLI,
+ tr.b.UnitPrefixScale.METRIC.NONE);
+ const syncId = groups[4];
+
+ if (syncId) {
+ this.syncTimestampsById_.set(syncId, ts);
+ }
+
+ if (voltageInV < 0 || currentInA < 0) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'The following line in the BattOr trace has a negative ' +
+ 'voltage or current, neither of which are allowed: ' + line +
+ '. A common cause of this is that the device is charging ' +
+ 'while the trace is being recorded.'
+ });
+ continue;
+ }
+
+ this.samples_.push(new Sample(ts, voltageInV, currentInA));
+ }
+ }
+ };
+
+ /**
+ * A sample recorded by a BattOr.
+ *
+ * @param {number} ts The timestamp (in milliseconds) of the sample.
+ * @param {number} voltage The voltage (in volts) at the specified time.
+ * @param {number} current The current (in amps) at the specified time.
+ *
+ * @constructor
+ */
+ function Sample(ts, voltageInV, currentInA) {
+ this.ts = ts;
+ this.voltageInV = voltageInV;
+ this.currentInA = currentInA;
+ }
+
+ Sample.prototype = {
+ /** Returns the instantaneous power consumption (in Watts). */
+ get powerInW() { return this.voltageInV * this.currentInA; }
+ };
+
+ tr.importer.Importer.register(BattorImporter);
+
+ return {
+ BattorImporter,
+ };
+});
+
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/battor_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/battor_importer_test.html
new file mode 100644
index 00000000000..ba9e091716d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/battor_importer_test.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/battor_importer.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const CHROMIUM_EVENTS = [
+ {
+ name: 'a', args: {}, pid: 52, ts: 0,
+ cat: 'foo', tid: 53, ph: 'B'
+ },
+ {
+ pid: 94936, tid: 5643, ts: 15000,
+ ph: 'c', cat: '__metadata', name: 'clock_sync',
+ args: {sync_id: 'ABCDEF-01234-5678-0A1B2C3D', issue_ts: 10000},
+ tts: 16496444
+ }
+ ];
+
+ const BATTOR_LINES = [
+ '# BattOr',
+ '# voltage_range [0.0, 6144.0] mV',
+ '# current_range [0.0, 2275.5] mA',
+ '# sample_rate 2000 Hz, gain 5.0x',
+ '0.000000 0.000000 4000.000000',
+ '0.500000 0.000000 4000.000000',
+ '1.000000 0.000000 4000.000000',
+ '1.500000 0.000000 4000.000000',
+ '2.000000 1.000000 4000.000000'
+ ];
+
+ test('canImport', function() {
+ assert.isFalse(tr.e.importer.battor.BattorImporter.canImport('string'));
+ assert.isFalse(tr.e.importer.battor.BattorImporter.canImport([]));
+ assert.isTrue(tr.e.importer.battor.BattorImporter.canImport(
+ BATTOR_LINES.join('\n')));
+ });
+
+ test('importExplicitClockSync', function() {
+ // Add a BattOr sample with an explicit clock sync.
+ const battorLinesWithExplicitSync = BATTOR_LINES.slice();
+ battorLinesWithExplicitSync.push(
+ '2.500000 1.000000 4000.000000 <ABCDEF-01234-5678-0A1B2C3D>');
+
+ const m = tr.c.TestUtils.newModelWithEvents(
+ [CHROMIUM_EVENTS, battorLinesWithExplicitSync.join('\n')]);
+
+ // Check to see if power samples were imported successfully.
+ assert.isDefined(m.device.powerSeries);
+
+ assert.strictEqual(m.device.powerSeries.samples[0].start, 7.5);
+ assert.strictEqual(m.device.powerSeries.samples[5].start, 10.0);
+ });
+
+ test('importExplicitClockSync_syncOnNegativeVoltageLine', function() {
+ // Add a BattOr sample with an explicit clock sync that occurs on a line
+ // with a negative voltage measurement. The sample should be ignored, but
+ // the clock sync should be counted.
+ const battorLinesWithExplicitSync = BATTOR_LINES.slice();
+ battorLinesWithExplicitSync.push(
+ '2.500000 -1.000000 4000.000000 <ABCDEF-01234-5678-0A1B2C3D>');
+
+ const m = tr.c.TestUtils.newModelWithEvents(
+ [CHROMIUM_EVENTS, battorLinesWithExplicitSync.join('\n')]);
+
+ // Check to see if power samples were imported successfully.
+ assert.isDefined(m.device.powerSeries);
+
+ assert.lengthOf(m.device.powerSeries.samples, 5);
+ assert.strictEqual(m.device.powerSeries.samples[0].start, 7.5);
+ });
+
+ test('explicitClockSyncWithoutSyncMarkers', function() {
+ // Create an empty model.
+ let m = new tr.Model();
+ const io = new tr.importer.ImportOptions();
+ io.showImportWarnings = false;
+ m.importOptions = io;
+
+ // Add a BattOr sample with an explicit clock sync.
+ const battorLinesWithExplicitSync = BATTOR_LINES.slice();
+ battorLinesWithExplicitSync.push(
+ '2.500000 1.000000 4000.000000 <ABCDEF-01234-5678-0A1B2C3D>');
+
+ m = tr.c.TestUtils.newModelWithEvents(
+ [battorLinesWithExplicitSync.join('\n')]);
+
+ assert.lengthOf(m.device.powerSeries.samples, 6);
+ assert.strictEqual(m.device.powerSeries.samples[0].start, 0);
+ assert.strictEqual(m.device.powerSeries.samples[5].start, 2.5);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/ddms_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/ddms_importer.html
new file mode 100644
index 00000000000..73ffc41b4c1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/ddms_importer.html
@@ -0,0 +1,223 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+/**
+ * @fileoverview Blah.
+ */
+'use strict';
+
+tr.exportTo('tr.e.importer.ddms', function() {
+ const kPid = 0;
+ const kCategory = 'java';
+ const kMethodLutEndMarker = '\n*end\n';
+ const kThreadsStart = '\n*threads\n';
+ const kMethodsStart = '\n*methods\n';
+
+ const kTraceMethodEnter = 0x00; // method entry
+ const kTraceMethodExit = 0x01; // method exit
+ const kTraceUnroll = 0x02; // method exited by exception unrolling
+ // 0x03 currently unused
+ const kTraceMethodActionMask = 0x03; // two bits
+
+ const kTraceHeaderLength = 32;
+ const kTraceMagicValue = 0x574f4c53;
+ const kTraceVersionSingleClock = 2;
+ const kTraceVersionDualClock = 3;
+ const kTraceRecordSizeSingleClock = 10; // using v2
+ const kTraceRecordSizeDualClock = 14; // using v3 with two timestamps
+
+ function Reader(stringPayload) {
+ this.position_ = 0;
+ this.data_ = new Uint8Array(stringPayload.length);
+ for (let i = 0; i < stringPayload.length; ++i) {
+ this.data_[i] = stringPayload.charCodeAt(i);
+ }
+ }
+
+ Reader.prototype = {
+ __proto__: Object.prototype,
+
+ uint8() {
+ const result = this.data_[this.position_];
+ this.position_ += 1;
+ return result;
+ },
+
+ uint16() {
+ let result = 0;
+ result += this.uint8();
+ result += this.uint8() << 8;
+ return result;
+ },
+
+ uint32() {
+ let result = 0;
+ result += this.uint8();
+ result += this.uint8() << 8;
+ result += this.uint8() << 16;
+ result += this.uint8() << 24;
+ return result;
+ },
+
+ uint64() {
+ // Javascript isn't able to manage 64-bit numeric values.
+ const low = this.uint32();
+ const high = this.uint32();
+ const lowStr = ('0000000' + low.toString(16)).substr(-8);
+ const highStr = ('0000000' + high.toString(16)).substr(-8);
+ const result = highStr + lowStr;
+ return result;
+ },
+
+ seekTo(position) {
+ this.position_ = position;
+ },
+
+ hasMore() {
+ return this.position_ < this.data_.length;
+ }
+ };
+
+ /**
+ * Imports DDMS method tracing events into a specified model.
+ * @constructor
+ */
+ function DdmsImporter(model, data) {
+ this.importPriority = 3;
+ this.model_ = model;
+ this.data_ = data;
+ }
+
+ /**
+ * Guesses whether the provided events is from a DDMS method trace.
+ * @return {boolean} True when events is a DDMS method trace.
+ */
+ DdmsImporter.canImport = function(data) {
+ if (typeof(data) === 'string' || data instanceof String) {
+ const header = data.slice(0, 1000);
+ return header.startsWith('*version\n') &&
+ header.indexOf('\nvm=') >= 0 &&
+ header.indexOf(kThreadsStart) >= 0;
+ }
+ /* key bit */
+ return false;
+ };
+
+ DdmsImporter.prototype = {
+ __proto__: tr.importer.Importer.prototype,
+
+ get importerName() {
+ return 'DdmsImporter';
+ },
+
+ get model() {
+ return this.model_;
+ },
+
+ /**
+ * Imports the data in this.data_ into this.model_.
+ */
+ importEvents() {
+ const divider = this.data_.indexOf(kMethodLutEndMarker) +
+ kMethodLutEndMarker.length;
+ this.metadata_ = this.data_.slice(0, divider);
+ this.methods_ = {};
+ this.parseThreads();
+ this.parseMethods();
+
+ const traceReader = new Reader(this.data_.slice(divider));
+ const magic = traceReader.uint32();
+ if (magic !== kTraceMagicValue) {
+ throw Error('Failed to match magic value');
+ }
+ this.version_ = traceReader.uint16();
+ if (this.version_ !== kTraceVersionDualClock) {
+ throw Error('Unknown version');
+ }
+ const dataOffest = traceReader.uint16();
+ const startDateTime = traceReader.uint64();
+ const recordSize = traceReader.uint16();
+
+ traceReader.seekTo(dataOffest);
+
+ while (traceReader.hasMore()) {
+ this.parseTraceEntry(traceReader);
+ }
+ },
+
+ parseTraceEntry(reader) {
+ const tid = reader.uint16();
+ const methodPacked = reader.uint32();
+ const cpuSinceStart = reader.uint32();
+ const wallClockSinceStart = reader.uint32();
+ let method = methodPacked & ~kTraceMethodActionMask;
+ const action = methodPacked & kTraceMethodActionMask;
+ const thread = this.getTid(tid);
+ method = this.getMethodName(method);
+ if (action === kTraceMethodEnter) {
+ thread.sliceGroup.beginSlice(kCategory, method, wallClockSinceStart,
+ undefined, cpuSinceStart);
+ } else if (thread.sliceGroup.openSliceCount) {
+ thread.sliceGroup.endSlice(wallClockSinceStart, cpuSinceStart);
+ }
+ },
+
+ parseThreads() {
+ let threads = this.metadata_.slice(this.metadata_.indexOf(kThreadsStart) +
+ kThreadsStart.length);
+ threads = threads.slice(0, threads.indexOf('\n*'));
+ threads = threads.split('\n');
+ threads.forEach(this.parseThread.bind(this));
+ },
+
+ parseThread(threadLine) {
+ const tid = threadLine.slice(0, threadLine.indexOf('\t'));
+ const thread = this.getTid(parseInt(tid));
+ thread.name = threadLine.slice(threadLine.indexOf('\t') + 1);
+ },
+
+ getTid(tid) {
+ return this.model_.getOrCreateProcess(kPid)
+ .getOrCreateThread(tid);
+ },
+
+ parseMethods() {
+ let methods = this.metadata_.slice(this.metadata_.indexOf(kMethodsStart) +
+ kMethodsStart.length);
+ methods = methods.slice(0, methods.indexOf('\n*'));
+ methods = methods.split('\n');
+ methods.forEach(this.parseMethod.bind(this));
+ },
+
+ parseMethod(methodLine) {
+ const data = methodLine.split('\t');
+ const methodId = parseInt(data[0]);
+ const methodName = data[1] + '.' + data[2] + data[3];
+ this.addMethod(methodId, methodName);
+ },
+
+ addMethod(methodId, methodName) {
+ this.methods_[methodId] = methodName;
+ },
+
+ getMethodName(methodId) {
+ return this.methods_[methodId];
+ }
+ };
+
+ // Register the DdmsImporter to the Importer.
+ tr.importer.Importer.register(DdmsImporter);
+
+ return {
+ DdmsImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/ddms_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/ddms_importer_test.html
new file mode 100644
index 00000000000..a516751e47b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/ddms_importer_test.html
@@ -0,0 +1,188 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base64.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/ddms_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Base64 = tr.b.Base64;
+
+ test('canImport', function() {
+ assert.isFalse(tr.e.importer.ddms.DdmsImporter.canImport('string'));
+ assert.isFalse(tr.e.importer.ddms.DdmsImporter.canImport([]));
+ assert.isTrue(tr.e.importer.ddms.DdmsImporter.canImport(TEST_DATA));
+ });
+
+ test('parseThreads', function() {
+ const m = tr.c.TestUtils.newModelWithEvents(TEST_DATA, {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ let threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 2);
+ threads = m.findAllThreadsNamed('main');
+ assert.strictEqual(threads.length, 1);
+ const thread = threads[0];
+ assert.strictEqual(thread.tid, 2703);
+ });
+
+ test('parseMethods', function() {
+ const m = tr.c.TestUtils.newModelWithEvents(TEST_DATA, {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.findAllThreadsNamed('Binder_1');
+ assert.strictEqual(threads.length, 1);
+ const thread = threads[0];
+ assert.strictEqual(thread.sliceGroup.length, 22);
+ assert.strictEqual('android.os.Binder.execTransact(IJJI)Z',
+ thread.sliceGroup.slices[0].title);
+ });
+
+ const TEST_DATA = Base64.atob(
+ 'KnZlcnNpb24KMwpkYXRhLWZpbGUtb3ZlcmZsb3c9ZmFsc2UKY2' +
+ 'xvY2s9ZHVhbAplbGFwc2VkLXRpbWUtdXNlYz02MzMwNzc5Cm51' +
+ 'bS1tZXRob2QtY2FsbHM9NzYKY2xvY2stY2FsbC1vdmVyaGVhZC' +
+ '1uc2VjPTMzNDMKdm09YXJ0Cip0aHJlYWRzCjI3MDMJbWFpbgoy' +
+ 'NzEwCUhlYXAgdGhyZWFkIHBvb2wgd29ya2VyIHRocmVhZCAxCj' +
+ 'I3MDkJSGVhcCB0aHJlYWQgcG9vbCB3b3JrZXIgdGhyZWFkIDAK' +
+ 'MjcxMQlIZWFwIHRocmVhZCBwb29sIHdvcmtlciB0aHJlYWQgMg' +
+ 'oyNzEyCVNpZ25hbCBDYXRjaGVyCjI3MTMJSkRXUAoyNzE0CVJl' +
+ 'ZmVyZW5jZVF1ZXVlRGFlbW9uCjI3MTUJRmluYWxpemVyRGFlbW' +
+ '9uCjI3MTYJRmluYWxpemVyV2F0Y2hkb2dEYWVtb24KMjcxNwlI' +
+ 'ZWFwVHJpbW1lckRhZW1vbgoyNzE4CUdDRGFlbW9uCjI3MTkJQm' +
+ 'luZGVyXzEKMjcyMAlCaW5kZXJfMgoyNzI3CVJlbmRlclRocmVh' +
+ 'ZAoyNzI4CUFzeW5jVGFzayAjMQoyNzI5CUFzeW5jVGFzayAjMg' +
+ 'oyNzMwCUJpbmRlcl8zCjExNTk4CWh3dWlUYXNrMQoxMTU5OQlo' +
+ 'd3VpVGFzazIKMTE2MDAJQXN5bmNUYXNrICMzCjExNjAxCUFzeW' +
+ '5jVGFzayAjNAoxMTY3MwlBc3luY1Rhc2sgIzUKKm1ldGhvZHMK' +
+ 'MHg3MGZiNzc1OAlkYWx2aWsuc3lzdGVtLkNsb3NlR3VhcmQJY2' +
+ 'xvc2UJKClWCUNsb3NlR3VhcmQuamF2YQoweDcwZmI4NDE4CWRh' +
+ 'bHZpay5zeXN0ZW0uVk1EZWJ1ZwlzdGFydE1ldGhvZFRyYWNpbm' +
+ 'cJKExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2lvL0ZpbGVEZXNj' +
+ 'cmlwdG9yO0lJWkkpVglWTURlYnVnLmphdmEKMHg3MGZlYTcyOA' +
+ 'lqYXZhLnV0aWwuQXJyYXlMaXN0CXNpemUJKClJCUFycmF5TGlz' +
+ 'dC5qYXZhCjB4NzEwMTNmYTgJbGliY29yZS5pby5CbG9ja0d1YX' +
+ 'JkT3MJY2xvc2UJKExqYXZhL2lvL0ZpbGVEZXNjcmlwdG9yOylW' +
+ 'CUJsb2NrR3VhcmRPcy5qYXZhCjB4NzEwNTc3NDgJYW5kcm9pZC' +
+ '5vcy5CaW5kZXIJZXhlY1RyYW5zYWN0CShJSkpJKVoJQmluZGVy' +
+ 'LmphdmEKMHg3MTA3MTdlOAlhbmRyb2lkLmFwcC5BY3Rpdml0eV' +
+ 'RocmVhZCRBcHBsaWNhdGlvblRocmVhZAlwcm9maWxlckNvbnRy' +
+ 'b2wJKFpMYW5kcm9pZC9hcHAvUHJvZmlsZXJJbmZvO0kpVglBY3' +
+ 'Rpdml0eVRocmVhZC5qYXZhCjB4NzEwNzJhNDgJYW5kcm9pZC5h' +
+ 'cHAuQXBwbGljYXRpb25UaHJlYWROYXRpdmUJb25UcmFuc2FjdA' +
+ 'koSUxhbmRyb2lkL29zL1BhcmNlbDtMYW5kcm9pZC9vcy9QYXJj' +
+ 'ZWw7SSlaCUFwcGxpY2F0aW9uVGhyZWFkTmF0aXZlLmphdmEKMH' +
+ 'g3MTA3MmYyOAlhbmRyb2lkLm9zLkhhbmRsZXIJZW5xdWV1ZU1l' +
+ 'c3NhZ2UJKExhbmRyb2lkL29zL01lc3NhZ2VRdWV1ZTtMYW5kcm' +
+ '9pZC9vcy9NZXNzYWdlO0opWglIYW5kbGVyLmphdmEKMHg3MTA3' +
+ 'MmZlOAlhbmRyb2lkLm9zLkhhbmRsZXIJZGlzcGF0Y2hNZXNzYW' +
+ 'dlCShMYW5kcm9pZC9vcy9NZXNzYWdlOylWCUhhbmRsZXIuamF2' +
+ 'YQoweDcxMDczNTI4CWFuZHJvaWQub3MuSGFuZGxlcglzZW5kTW' +
+ 'Vzc2FnZQkoTGFuZHJvaWQvb3MvTWVzc2FnZTspWglIYW5kbGVy' +
+ 'LmphdmEKMHg3MTA3MzU4OAlhbmRyb2lkLm9zLkhhbmRsZXIJc2' +
+ 'VuZE1lc3NhZ2VBdFRpbWUJKExhbmRyb2lkL29zL01lc3NhZ2U7' +
+ 'SilaCUhhbmRsZXIuamF2YQoweDcxMDczNWI4CWFuZHJvaWQub3' +
+ 'MuSGFuZGxlcglzZW5kTWVzc2FnZURlbGF5ZWQJKExhbmRyb2lk' +
+ 'L29zL01lc3NhZ2U7SilaCUhhbmRsZXIuamF2YQoweDcxMDczNj' +
+ 'Q4CWFuZHJvaWQuYXBwLkFjdGl2aXR5VGhyZWFkJEgJaGFuZGxl' +
+ 'TWVzc2FnZQkoTGFuZHJvaWQvb3MvTWVzc2FnZTspVglBY3Rpdm' +
+ 'l0eVRocmVhZC5qYXZhCjB4NzEwNzM3NjgJYW5kcm9pZC5hcHAu' +
+ 'QWN0aXZpdHlUaHJlYWQkUHJvZmlsZXIJc3RhcnRQcm9maWxpbm' +
+ 'cJKClWCUFjdGl2aXR5VGhyZWFkLmphdmEKMHg3MTA3Mzc5OAlh' +
+ 'bmRyb2lkLmFwcC5BY3Rpdml0eVRocmVhZCRQcm9maWxlcglzdG' +
+ '9wUHJvZmlsaW5nCSgpVglBY3Rpdml0eVRocmVhZC5qYXZhCjB4' +
+ 'NzEwYzQ2ZDgJYW5kcm9pZC5vcy5NZXNzYWdlCW9idGFpbgkoKU' +
+ 'xhbmRyb2lkL29zL01lc3NhZ2U7CU1lc3NhZ2UuamF2YQoweDcx' +
+ 'MGM0YTM4CWFuZHJvaWQub3MuTWVzc2FnZQlpc0luVXNlCSgpWg' +
+ 'lNZXNzYWdlLmphdmEKMHg3MTBjNGE2OAlhbmRyb2lkLm9zLk1l' +
+ 'c3NhZ2UJbWFya0luVXNlCSgpVglNZXNzYWdlLmphdmEKMHg3MT' +
+ 'BjNGFmOAlhbmRyb2lkLm9zLk1lc3NhZ2UJcmVjeWNsZVVuY2hl' +
+ 'Y2tlZAkoKVYJTWVzc2FnZS5qYXZhCjB4NzEwYzRmMTgJYW5kcm' +
+ '9pZC5vcy5QYXJjZWwJaW5pdAkoSilWCVBhcmNlbC5qYXZhCjB4' +
+ 'NzEwYzRmYTgJYW5kcm9pZC5vcy5QYXJjZWwJb2J0YWluCShKKU' +
+ 'xhbmRyb2lkL29zL1BhcmNlbDsJUGFyY2VsLmphdmEKMHg3MTBj' +
+ 'NTQyOAlhbmRyb2lkLm9zLlBhcmNlbAllbmZvcmNlSW50ZXJmYW' +
+ 'NlCShMamF2YS9sYW5nL1N0cmluZzspVglQYXJjZWwuamF2YQow' +
+ 'eDcxMGM1OWY4CWFuZHJvaWQub3MuUGFyY2VsCXJlYWRJbnQJKC' +
+ 'lJCVBhcmNlbC5qYXZhCjB4NzEwYzZhNDgJYW5kcm9pZC5vcy5Q' +
+ 'YXJjZWxGaWxlRGVzY3JpcHRvcgljbG9zZVdpdGhTdGF0dXMJKE' +
+ 'lMamF2YS9sYW5nL1N0cmluZzspVglQYXJjZWxGaWxlRGVzY3Jp' +
+ 'cHRvci5qYXZhCjB4NzEwYzZkNzgJYW5kcm9pZC5vcy5QYXJjZW' +
+ 'xGaWxlRGVzY3JpcHRvcgl3cml0ZUNvbW1TdGF0dXNBbmRDbG9z' +
+ 'ZQkoSUxqYXZhL2xhbmcvU3RyaW5nOylWCVBhcmNlbEZpbGVEZX' +
+ 'NjcmlwdG9yLmphdmEKMHg3MTBjNmUwOAlhbmRyb2lkLm9zLlBh' +
+ 'cmNlbEZpbGVEZXNjcmlwdG9yCWNsb3NlCSgpVglQYXJjZWxGaW' +
+ 'xlRGVzY3JpcHRvci5qYXZhCjB4NzEwYzZmZTgJYW5kcm9pZC5v' +
+ 'cy5QYXJjZWxGaWxlRGVzY3JpcHRvcglyZWxlYXNlUmVzb3VyY2' +
+ 'VzCSgpVglQYXJjZWxGaWxlRGVzY3JpcHRvci5qYXZhCjB4NzEx' +
+ 'NWIwZjgJYW5kcm9pZC5vcy5NZXNzYWdlUXVldWUJZW5xdWV1ZU' +
+ '1lc3NhZ2UJKExhbmRyb2lkL29zL01lc3NhZ2U7SilaCU1lc3Nh' +
+ 'Z2VRdWV1ZS5qYXZhCjB4NzExNWIyMTgJYW5kcm9pZC5vcy5NZX' +
+ 'NzYWdlUXVldWUJbmV4dAkoKUxhbmRyb2lkL29zL01lc3NhZ2U7' +
+ 'CU1lc3NhZ2VRdWV1ZS5qYXZhCjB4NzE3ZTM2MzAJZGFsdmlrLn' +
+ 'N5c3RlbS5WTURlYnVnCXN0YXJ0TWV0aG9kVHJhY2luZ0ZkCShM' +
+ 'amF2YS9sYW5nL1N0cmluZztMamF2YS9pby9GaWxlRGVzY3JpcH' +
+ 'RvcjtJSVpJKVYJVk1EZWJ1Zy5qYXZhCjB4NzE4MTBhMjAJamF2' +
+ 'YS5pby5GaWxlRGVzY3JpcHRvcglpc1NvY2tldAkoKVoJRmlsZU' +
+ 'Rlc2NyaXB0b3IuamF2YQoweDcxODEwYWUwCWphdmEuaW8uRmls' +
+ 'ZURlc2NyaXB0b3IJdmFsaWQJKClaCUZpbGVEZXNjcmlwdG9yLm' +
+ 'phdmEKMHg3MTgxYmI4MAlsaWJjb3JlLmlvLklvVXRpbHMJY2xv' +
+ 'c2UJKExqYXZhL2lvL0ZpbGVEZXNjcmlwdG9yOylWCUlvVXRpbH' +
+ 'MuamF2YQoweDcxODFiYmIwCWxpYmNvcmUuaW8uSW9VdGlscwlj' +
+ 'bG9zZVF1aWV0bHkJKExqYXZhL2lvL0ZpbGVEZXNjcmlwdG9yOy' +
+ 'lWCUlvVXRpbHMuamF2YQoweDcxODIxZWUwCWFuZHJvaWQuYXBw' +
+ 'LkFjdGl2aXR5VGhyZWFkCWFjY2VzcyQzMDAJKExhbmRyb2lkL2' +
+ 'FwcC9BY3Rpdml0eVRocmVhZDtJTGphdmEvbGFuZy9PYmplY3Q7' +
+ 'SUkpVglBY3Rpdml0eVRocmVhZC5qYXZhCjB4NzE4MjJhZTAJYW' +
+ '5kcm9pZC5hcHAuQWN0aXZpdHlUaHJlYWQJc2VuZE1lc3NhZ2UJ' +
+ 'KElMamF2YS9sYW5nL09iamVjdDtJSSlWCUFjdGl2aXR5VGhyZW' +
+ 'FkLmphdmEKMHg3MTgyMmIxMAlhbmRyb2lkLmFwcC5BY3Rpdml0' +
+ 'eVRocmVhZAlzZW5kTWVzc2FnZQkoSUxqYXZhL2xhbmcvT2JqZW' +
+ 'N0O0lJWilWCUFjdGl2aXR5VGhyZWFkLmphdmEKMHg3MTgyMzIz' +
+ 'MAlhbmRyb2lkLmFwcC5BY3Rpdml0eVRocmVhZAloYW5kbGVQcm' +
+ '9maWxlckNvbnRyb2wJKFpMYW5kcm9pZC9hcHAvUHJvZmlsZXJJ' +
+ 'bmZvO0kpVglBY3Rpdml0eVRocmVhZC5qYXZhCjB4NzE4MzY3MD' +
+ 'AJYW5kcm9pZC5vcy5EZWJ1ZwlzdG9wTWV0aG9kVHJhY2luZwko' +
+ 'KVYJRGVidWcuamF2YQoqZW5kClNMT1cDACAASxycoWkAAAAOAA' +
+ 'AAAAAAAAAAAAAAAAAAjwoxNn5xxtotAKISAgCPChmE+3CT4y0A' +
+ '/hsCAI8KaTcHcbbkLQAtNQIAjwoIbgxx0eQtAEc1AgCPCkhqDH' +
+ 'Hk5C0AWjUCAI8KWHf7cPLkLQBoNQIAjwpZd/tw/eQtAHM1AgCP' +
+ 'CnhtDHED5S0AeTUCAI8KeW0McQjlLQB+NQIAjwqwu4FxFOUtAI' +
+ 'o1AgCPCoC7gXEa5S0AkDUCAI8K4AqBcSPlLQCaNQIAjwrhCoFx' +
+ 'KuUtAKE1AgCPCqg/AXE65S0AsTUCAI8KIAqBcUDlLQC2NQIAjw' +
+ 'ohCoFxTuUtAMU1AgCPCqk/AXFn5S0A3jUCAI8KgbuBcWzlLQDi' +
+ 'NQIAjwqxu4Fxb+UtAOU1AgCPCuhvDHF15S0A6zUCAI8K6W8McX' +
+ 'nlLQDvNQIAjwpJagxxfOUtAPI1AgCPCgluDHGB5S0A9zUCAI8K' +
+ 'MTKCcYXlLQD6NQIAjwpJNgdxjOUtAAM2AgCPCukvB3GS5S0ACT' +
+ 'YCAI8K+EoMcaTlLQAbNgIAjwr5SgxxwOUtADY2AgCPChiyFXHH' +
+ '5S0APTYCAI8KKKf+cPHlLQBnNgIAjwopp/5w++UtAHE2AgCfCk' +
+ 'h3BXHy2wAAu5RgAJ8KqE8McSvcAADwlGAAnwoYTwxxaNwAACyV' +
+ 'YACfChlPDHGD3AAARpVgAJ8KqU8McZbcAABZlWAAnwqoTwxxrN' +
+ 'wAAG6VYACfChhPDHHL3AAAjpVgAJ8KGU8MceDcAACjlWAAnwqp' +
+ 'Twxx8twAALSVYACfCkgqB3Ea3QAA3ZVgAJ8KKFQMcUHdAAAFlm' +
+ 'AAnwopVAxxjN0AAFGWYACfCvhZDHHD3QAAiJZgAJ8K+VkMcejd' +
+ 'AACslmAAnwr4WQxxAN4AAMOWYACfCvlZDHEa3gAA3ZZgAJ8K+F' +
+ 'kMcS/eAADylmAAnwr5WQxxR94AAAqXYACfCugXB3Fv3gAAM5dg' +
+ 'AJ8K4B6CcYveAABPl2AAnwrgKoJxrN4AAHCXYACfChArgnHQ3g' +
+ 'AAkpdgAJ8K2EYMceTeAACml2AAnwrZRgxxDd8AANCXYACfCig1' +
+ 'B3Er3wAA7pdgAJ8KuDUHcT7fAAAAmGAAnwqINQdxa98AAC+YYA' +
+ 'CfCigvB3GD3wAASJhgAJ8K+LAVcaDfAABjmGAAnwo4Sgxxtt8A' +
+ 'AHiYYACfCjlKDHHL3wAAkJhgAJ8KaEoMce3fAACxmGAAnwppSg' +
+ 'xxAuAAAMWYYACfCvmwFXFz4AAAOJlgAI8KGbIVcU3mLQBAmWAA' +
+ 'jwroLwdxVuYtAEmZYACfCikvB3GG4AAASZlgAI8KSDYHcVzmLQ' +
+ 'BPmWAAjwowMoJxZuYtAFmZYACfCok1B3GW4AAAWJlgAI8KmDcH' +
+ 'cXTmLQBnmWAAnwq5NQdxr+AAAHGZYACPCgBng3GC5i0AdplgAJ' +
+ '8KKTUHcb3gAAB/mWAAnwoRK4JxzOAAAI6ZYAA=');
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/etw_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/etw_importer.html
new file mode 100644
index 00000000000..c0e928ebdc4
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/etw_importer.html
@@ -0,0 +1,483 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base64.html">
+<link rel="import" href="/tracing/extras/importer/etw/eventtrace_parser.html">
+<link rel="import" href="/tracing/extras/importer/etw/process_parser.html">
+<link rel="import" href="/tracing/extras/importer/etw/thread_parser.html">
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+/**
+ * @fileoverview Imports JSON file with the raw payloads from a Windows event
+ * trace into the Model. This format is outputted by Chrome running
+ * on a Windows system.
+ *
+ * This importer assumes the events arrived as a JSON file and the payloads are
+ * undecoded sequence of bytes in hex format string. The unit tests provide
+ * examples of the trace format.
+ *
+ * The format of the system trace is
+ * {
+ * name: 'ETW',
+ * content: [ <events> ]
+ * }
+ *
+ * where the <events> are dictionary values with fields.
+ *
+ * {
+ * guid: "1234-...", // The unique GUID for the event.
+ * op: 12, // The opcode of the event.
+ * ver: 1, // The encoding version of the event.
+ * cpu: 0, // The cpu id on which the event was captured.
+ * ts: 1092, // The thread id on which the event was captured.
+ * payload: "aaaa" // A base64 encoded string of the raw payload.
+ * }
+ *
+ * The payload is an undecoded version of the raw event sent by ETW.
+ * This importer uses specific parsers to decode recognized events.
+ * A parser need to register the recognized event by calling
+ * registerEventHandler(guid, opcode, handler). The parser is responsible to
+ * decode the payload and update the Model.
+ *
+ * The payload formats are described there:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa364085(v=vs.85).aspx
+ *
+ */
+'use strict';
+
+tr.exportTo('tr.e.importer.etw', function() {
+ // GUID and opcode of a Thread DCStart event, as defined at the link above.
+ const kThreadGuid = '3D6FA8D1-FE05-11D0-9DDA-00C04FD7BA7C';
+ const kThreadDCStartOpcode = 3;
+
+ /**
+ * Represents the raw bytes payload decoder.
+ * @constructor
+ */
+ function Decoder() {
+ this.payload_ = new DataView(new ArrayBuffer(256));
+ }
+
+ Decoder.prototype = {
+ __proto__: Object.prototype,
+
+ reset(base64Payload) {
+ const decodedSize = tr.b.Base64.getDecodedBufferLength(base64Payload);
+ if (decodedSize > this.payload_.byteLength) {
+ this.payload_ = new DataView(new ArrayBuffer(decodedSize));
+ }
+
+ tr.b.Base64.DecodeToTypedArray(base64Payload, this.payload_);
+ this.position_ = 0;
+ },
+
+ skip(length) {
+ this.position_ += length;
+ },
+
+ decodeUInt8() {
+ const result = this.payload_.getUint8(this.position_, true);
+ this.position_ += 1;
+ return result;
+ },
+
+ decodeUInt16() {
+ const result = this.payload_.getUint16(this.position_, true);
+ this.position_ += 2;
+ return result;
+ },
+
+ decodeUInt32() {
+ const result = this.payload_.getUint32(this.position_, true);
+ this.position_ += 4;
+ return result;
+ },
+
+ decodeUInt64ToString() {
+ // Javascript isn't able to manage 64-bit numeric values.
+ const low = this.decodeUInt32();
+ const high = this.decodeUInt32();
+ const lowStr = ('0000000' + low.toString(16)).substr(-8);
+ const highStr = ('0000000' + high.toString(16)).substr(-8);
+ const result = highStr + lowStr;
+ return result;
+ },
+
+ decodeInt8() {
+ const result = this.payload_.getInt8(this.position_, true);
+ this.position_ += 1;
+ return result;
+ },
+
+ decodeInt16() {
+ const result = this.payload_.getInt16(this.position_, true);
+ this.position_ += 2;
+ return result;
+ },
+
+ decodeInt32() {
+ const result = this.payload_.getInt32(this.position_, true);
+ this.position_ += 4;
+ return result;
+ },
+
+ decodeInt64ToString() {
+ // Javascript isn't able to manage 64-bit numeric values.
+ // Fallback to unsigned 64-bit hexa value.
+ return this.decodeUInt64ToString();
+ },
+
+ decodeUInteger(is64) {
+ if (is64) {
+ return this.decodeUInt64ToString();
+ }
+ return this.decodeUInt32();
+ },
+
+ decodeString() {
+ let str = '';
+ while (true) {
+ const c = this.decodeUInt8();
+ if (!c) return str;
+ str = str + String.fromCharCode(c);
+ }
+ },
+
+ decodeW16String() {
+ let str = '';
+ while (true) {
+ const c = this.decodeUInt16();
+ if (!c) return str;
+ str = str + String.fromCharCode(c);
+ }
+ },
+
+ decodeFixedW16String(length) {
+ const oldPosition = this.position_;
+ let str = '';
+ for (let i = 0; i < length; i++) {
+ const c = this.decodeUInt16();
+ if (!c) break;
+ str = str + String.fromCharCode(c);
+ }
+
+ // Move the position after the fixed buffer (i.e. wchar[length]).
+ this.position_ = oldPosition + 2 * length;
+ return str;
+ },
+
+ decodeBytes(length) {
+ const bytes = [];
+ for (let i = 0; i < length; ++i) {
+ const c = this.decodeUInt8();
+ bytes.push(c);
+ }
+ return bytes;
+ },
+
+ decodeSID(is64) {
+ // Decode the TOKEN_USER structure.
+ const pSid = this.decodeUInteger(is64);
+ const attributes = this.decodeUInt32();
+
+ // Skip padding.
+ if (is64) {
+ this.decodeUInt32();
+ }
+
+ // Decode the SID structure.
+ const revision = this.decodeUInt8();
+ const subAuthorityCount = this.decodeUInt8();
+ this.decodeUInt16();
+ this.decodeUInt32();
+
+ if (revision !== 1) {
+ throw new Error(
+ 'Invalid SID revision: could not decode the SID structure.');
+ }
+
+ const sid = this.decodeBytes(4 * subAuthorityCount);
+
+ return {
+ pSid,
+ attributes,
+ sid
+ };
+ },
+
+ decodeSystemTime() {
+ // Decode the SystemTime structure.
+ const wYear = this.decodeInt16();
+ const wMonth = this.decodeInt16();
+ const wDayOfWeek = this.decodeInt16();
+ const wDay = this.decodeInt16();
+ const wHour = this.decodeInt16();
+ const wMinute = this.decodeInt16();
+ const wSecond = this.decodeInt16();
+ const wMilliseconds = this.decodeInt16();
+ return {
+ wYear,
+ wMonth,
+ wDayOfWeek,
+ wDay,
+ wHour,
+ wMinute,
+ wSecond,
+ wMilliseconds
+ };
+ },
+
+ decodeTimeZoneInformation() {
+ // Decode the TimeZoneInformation structure.
+ const bias = this.decodeUInt32();
+ const standardName = this.decodeFixedW16String(32);
+ const standardDate = this.decodeSystemTime();
+ const standardBias = this.decodeUInt32();
+ const daylightName = this.decodeFixedW16String(32);
+ const daylightDate = this.decodeSystemTime();
+ const daylightBias = this.decodeUInt32();
+ return {
+ bias,
+ standardName,
+ standardDate,
+ standardBias,
+ daylightName,
+ daylightDate,
+ daylightBias
+ };
+ }
+
+ };
+
+ /**
+ * Imports Windows ETW kernel events into a specified model.
+ * @constructor
+ */
+ function EtwImporter(model, events) {
+ this.importPriority = 3;
+ this.model_ = model;
+ this.events_ = events;
+ this.handlers_ = {};
+ this.decoder_ = new Decoder();
+ this.walltime_ = undefined;
+ this.ticks_ = undefined;
+ this.is64bit_ = undefined;
+
+ // A map of tids to their process pid. On Windows, the tid is global to
+ // the system and doesn't need to belong to a process. As many events
+ // only provide tid, this map allows to retrieve the parent process.
+ this.tidsToPid_ = {};
+
+ // Instantiate the parsers; this will register handlers for known events.
+ const allTypeInfos = tr.e.importer.etw.Parser.getAllRegisteredTypeInfos();
+ this.parsers_ = allTypeInfos.map(
+ function(typeInfo) {
+ return new typeInfo.constructor(this);
+ }, this);
+ }
+
+ /**
+ * Guesses whether the provided events is from a Windows ETW trace.
+ * The object must has a property named 'name' with the value 'ETW' and
+ * a property 'content' with all the undecoded events.
+ *
+ * @return {boolean} True when events is a Windows ETW array.
+ */
+ EtwImporter.canImport = function(events) {
+ if (!events.hasOwnProperty('name') ||
+ !events.hasOwnProperty('content') ||
+ events.name !== 'ETW') {
+ return false;
+ }
+
+ return true;
+ };
+
+ EtwImporter.prototype = {
+ __proto__: tr.importer.Importer.prototype,
+
+ get importerName() {
+ return 'EtwImporter';
+ },
+
+ get model() {
+ return this.model_;
+ },
+
+ createThreadIfNeeded(pid, tid) {
+ this.tidsToPid_[tid] = pid;
+ },
+
+ removeThreadIfPresent(tid) {
+ this.tidsToPid_[tid] = undefined;
+ },
+
+ getPidFromWindowsTid(tid) {
+ if (tid === 0) return 0;
+ const pid = this.tidsToPid_[tid];
+ if (pid === undefined) {
+ // Kernel threads are not defined.
+ return 0;
+ }
+ return pid;
+ },
+
+ getThreadFromWindowsTid(tid) {
+ const pid = this.getPidFromWindowsTid(tid);
+ const process = this.model_.getProcess(pid);
+ if (!process) return undefined;
+ return process.getThread(tid);
+ },
+
+ /*
+ * Retrieve the Cpu for a given cpuNumber.
+ * @return {Cpu} A Cpu corresponding to the given cpuNumber.
+ */
+ getOrCreateCpu(cpuNumber) {
+ const cpu = this.model_.kernel.getOrCreateCpu(cpuNumber);
+ return cpu;
+ },
+
+ /**
+ * Imports the data in this.events_ into this.model_.
+ */
+ importEvents() {
+ this.events_.content.forEach(this.parseInfo.bind(this));
+
+ if (this.walltime_ === undefined || this.ticks_ === undefined) {
+ throw Error('Cannot find clock sync information in the system trace.');
+ }
+
+ if (this.is64bit_ === undefined) {
+ throw Error('Cannot determine pointer size of the system trace.');
+ }
+
+ this.events_.content.forEach(this.parseEvent.bind(this));
+ },
+
+ importTimestamp(timestamp) {
+ const ts = parseInt(timestamp, 16);
+ return (ts - this.walltime_ + this.ticks_) / 1000.;
+ },
+
+ parseInfo(event) {
+ // Retrieve clock sync information.
+ if (event.hasOwnProperty('guid') &&
+ event.hasOwnProperty('walltime') &&
+ event.hasOwnProperty('tick') &&
+ event.guid === 'ClockSync') {
+ this.walltime_ = parseInt(event.walltime, 16);
+ this.ticks_ = parseInt(event.tick, 16);
+ }
+
+ // Retrieve pointer size information from a Thread.DCStart event.
+ if (this.is64bit_ === undefined &&
+ event.hasOwnProperty('guid') &&
+ event.hasOwnProperty('op') &&
+ event.hasOwnProperty('ver') &&
+ event.hasOwnProperty('payload') &&
+ event.guid === kThreadGuid &&
+ event.op === kThreadDCStartOpcode) {
+ const decodedSize = tr.b.Base64.getDecodedBufferLength(event.payload);
+
+ if (event.ver === 1) {
+ if (decodedSize >= 52) {
+ this.is64bit_ = true;
+ } else {
+ this.is64bit_ = false;
+ }
+ } else if (event.ver === 2) {
+ if (decodedSize >= 64) {
+ this.is64bit_ = true;
+ } else {
+ this.is64bit_ = false;
+ }
+ } else if (event.ver === 3) {
+ if (decodedSize >= 60) {
+ this.is64bit_ = true;
+ } else {
+ this.is64bit_ = false;
+ }
+ }
+ }
+
+ return true;
+ },
+
+ parseEvent(event) {
+ if (!event.hasOwnProperty('guid') ||
+ !event.hasOwnProperty('op') ||
+ !event.hasOwnProperty('ver') ||
+ !event.hasOwnProperty('cpu') ||
+ !event.hasOwnProperty('ts') ||
+ !event.hasOwnProperty('payload')) {
+ return false;
+ }
+
+ const timestamp = this.importTimestamp(event.ts);
+
+ // Create the event header.
+ const header = {
+ guid: event.guid,
+ opcode: event.op,
+ version: event.ver,
+ cpu: event.cpu,
+ timestamp,
+ is64: this.is64bit_
+ };
+
+ // Set the payload to decode.
+ const decoder = this.decoder_;
+ decoder.reset(event.payload);
+
+ // Retrieve the handler to decode the payload.
+ const handler = this.getEventHandler(header.guid, header.opcode);
+ if (!handler) return false;
+
+ if (!handler(header, decoder)) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Malformed ' + header.guid + ' event (' + event.payload + ')'
+ });
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Registers a windows ETW event handler used by parseEvent().
+ */
+ registerEventHandler(guid, opcode, handler) {
+ if (this.handlers_[guid] === undefined) {
+ this.handlers_[guid] = [];
+ }
+ this.handlers_[guid][opcode] = handler;
+ },
+
+ /**
+ * Retrieves a registered event handler.
+ */
+ getEventHandler(guid, opcode) {
+ if (this.handlers_[guid] === undefined) {
+ return undefined;
+ }
+ return this.handlers_[guid][opcode];
+ }
+
+ };
+
+ // Register the EtwImporter to the Importer.
+ tr.importer.Importer.register(EtwImporter);
+
+ return {
+ EtwImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/etw_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/etw_importer_test.html
new file mode 100644
index 00000000000..c071b13ff5b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/etw_importer_test.html
@@ -0,0 +1,293 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base64.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/etw/etw_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Base64 = tr.b.Base64;
+
+ test('canImport', function() {
+ assert.isFalse(tr.e.importer.etw.EtwImporter.canImport('string'));
+ assert.isFalse(tr.e.importer.etw.EtwImporter.canImport([]));
+
+ // Must not parse an invalid name.
+ const dummy = { name: 'dummy', content: [] };
+ assert.isFalse(tr.e.importer.etw.EtwImporter.canImport(dummy));
+
+ // Must parse an empty valid trace.
+ const valid = { name: 'ETW', content: [] };
+ assert.isTrue(tr.e.importer.etw.EtwImporter.canImport(valid));
+ });
+
+ test('getModel', function() {
+ const model = 'dummy';
+ const events = [];
+ const importer = new tr.e.importer.etw.EtwImporter(model, events);
+ assert.strictEqual(importer.model, model);
+ });
+
+ test('registerEventHandler', function() {
+ // Create a dummy EtwImporter.
+ const model = 'dummy';
+ const events = ['events'];
+ const importer = new tr.e.importer.etw.EtwImporter(model, events);
+ const dummyHandler = function() {};
+
+ // The handler must not exists.
+ assert.isUndefined(importer.getEventHandler('ABCDEF', 2));
+
+ // Register an event handler for guid: ABCDEF and opcode: 2.
+ importer.registerEventHandler('ABCDEF', 2, dummyHandler);
+
+ // The handler exists now, must find it.
+ assert.isDefined(importer.getEventHandler('ABCDEF', 2));
+
+ // Must be able to manage an invalid handler.
+ assert.isUndefined(importer.getEventHandler('zzzzzz', 2));
+ });
+
+ test('parseEvent', function() {
+ const model = 'dummy';
+ const events = [];
+ const importer = new tr.e.importer.etw.EtwImporter(model, events);
+ let handlerCalled = false;
+ const dummyHandler = function() { handlerCalled = true; return true; };
+
+ // Register a valid handler.
+ importer.registerEventHandler('aaaa', 42, dummyHandler);
+
+ // Try to parse an invalid event with missing fields.
+ const incompleteEvent = { 'guid': 'aaaa', 'op': 42, 'ver': 0 };
+ assert.isFalse(importer.parseEvent(incompleteEvent));
+ assert.isFalse(handlerCalled);
+
+ // Try to parse a valid event.
+ const validEvent = {
+ 'guid': 'aaaa', 'op': 42, 'ver': 0, 'cpu': 0, 'ts': 0,
+ 'payload': Base64.btoa('0')
+ };
+ assert.isTrue(importer.parseEvent(validEvent));
+ assert.isTrue(handlerCalled);
+ });
+
+ test('resetTooSmall', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+
+ const oldByteLength = decoder.payload_.byteLength;
+ // Decode a payload too big for the actual buffer.
+ decoder.reset('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==');
+ const newByteLength = decoder.payload_.byteLength;
+
+ // Validate the buffer has been resized.
+ assert.isBelow(oldByteLength, newByteLength);
+ });
+
+ test('decode', function() {
+ const model = 'dummy';
+ const events = [];
+ const importer = new tr.e.importer.etw.EtwImporter(model, events);
+
+ const decoder = importer.decoder_;
+
+ decoder.reset('YQBiYw==');
+ assert.strictEqual(decoder.decodeInt32(), 0x63620061);
+
+ // Decode unsigned numbers.
+ decoder.reset('AQ==');
+ assert.strictEqual(decoder.decodeUInt8(), 0x01);
+
+ decoder.reset('AQI=');
+ assert.strictEqual(decoder.decodeUInt16(), 0x0201);
+
+ decoder.reset('AQIDBA==');
+ assert.strictEqual(decoder.decodeUInt32(), 0x04030201);
+
+ decoder.reset('AQIDBAUGBwg=');
+ assert.strictEqual(decoder.decodeUInt64ToString(), '0807060504030201');
+
+ // Decode signed numbers.
+ decoder.reset('AQ==');
+ assert.strictEqual(decoder.decodeInt8(), 0x01);
+
+ decoder.reset('AQI=');
+ assert.strictEqual(decoder.decodeInt16(), 0x0201);
+
+ decoder.reset('AQIDBA==');
+ assert.strictEqual(decoder.decodeInt32(), 0x04030201);
+
+ decoder.reset('AQIDBAUGBwg=');
+ assert.strictEqual(decoder.decodeInt64ToString(), '0807060504030201');
+
+ // Last value before being a signed number.
+ decoder.reset('fw==');
+ assert.strictEqual(decoder.decodeInt8(), 127);
+
+ // Decode negative numbers.
+ decoder.reset('1g==');
+ assert.strictEqual(decoder.decodeInt8(), -42);
+
+ decoder.reset('gA==');
+ assert.strictEqual(decoder.decodeInt8(), -128);
+
+ decoder.reset('hYI=');
+ assert.strictEqual(decoder.decodeInt16(), -32123);
+
+ decoder.reset('hYL//w==');
+ assert.strictEqual(decoder.decodeInt32(), -32123);
+
+ decoder.reset('Lv1ptv////8=');
+ assert.strictEqual(decoder.decodeInt32(), -1234567890);
+
+ // Decode number with zero (nul) in the middle of the string.
+ decoder.reset('YQBiYw==');
+ assert.strictEqual(decoder.decodeInt32(), 0x63620061);
+ });
+
+ test('decodeUInteger', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+
+ decoder.reset('AQIDBAUGBwg=');
+ assert.strictEqual(decoder.decodeUInteger(false), 0x04030201);
+
+ decoder.reset('AQIDBAUGBwg=');
+ assert.strictEqual(decoder.decodeUInteger(true), '0807060504030201');
+ });
+
+ test('decodeString', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+
+ decoder.reset('dGVzdAA=');
+ assert.strictEqual(decoder.decodeString(), 'test');
+
+ decoder.reset('VGhpcyBpcyBhIHRlc3Qu');
+ assert.strictEqual(decoder.decodeString(), 'This is a test.');
+ });
+
+ test('decodeW16String', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+ decoder.reset('dABlAHMAdAAAAA==');
+ assert.strictEqual(decoder.decodeW16String(), 'test');
+ });
+
+ test('decodeFixedW16String', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+ decoder.reset('dABlAHMAdAAAAA==');
+ assert.strictEqual(decoder.decodeFixedW16String(32), 'test');
+ assert.strictEqual(decoder.position_, 64);
+
+ decoder.reset('dABlAHMAdAAAAA==');
+ assert.strictEqual(decoder.decodeFixedW16String(1), 't');
+ assert.strictEqual(decoder.position_, 2);
+ });
+
+ test('decodeBytes', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+ decoder.reset('AAECAwQFBgc=');
+ const bytes = decoder.decodeBytes(8);
+ for (let i = 0; i < bytes.length; ++i) {
+ assert.strictEqual(bytes[i], i);
+ }
+ });
+
+ test('decodeSID', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+
+ // Decode a SID structure with 64-bit pointer.
+ decoder.reset(
+ 'AQIDBAECAwQFBAMCAAAAAAEFAAAAAAAFFQAAAAECAwQFBgcICQoLDA0DAAA=');
+ const sid = decoder.decodeSID(true);
+
+ assert.strictEqual(sid.pSid, '0403020104030201');
+ assert.strictEqual(sid.attributes, 0x02030405);
+ assert.strictEqual(sid.sid.length, 20);
+ });
+
+ test('decodeSystemTime', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+
+ // Decode a SystemTime structure.
+ decoder.reset('AQACAAMABAAFAAYABwAIAA==');
+ const time = decoder.decodeSystemTime();
+ assert.strictEqual(time.wYear, 1);
+ assert.strictEqual(time.wMonth, 2);
+ assert.strictEqual(time.wDayOfWeek, 3);
+ assert.strictEqual(time.wDay, 4);
+ assert.strictEqual(time.wHour, 5);
+ assert.strictEqual(time.wMinute, 6);
+ assert.strictEqual(time.wSecond, 7);
+ assert.strictEqual(time.wMilliseconds, 8);
+ });
+
+ test('decodeTimeZoneInformation', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+
+ // Decode a TimeZoneInformation structure.
+ decoder.reset('AQIDBGEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAIAAwAEAAUABgAHAAgABA' +
+ 'MCAWIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAIAAwAEAAUABgAHAAgACAgI' +
+ 'CA==');
+ const time = decoder.decodeTimeZoneInformation();
+
+ assert.strictEqual(time.bias, 0x04030201);
+ assert.strictEqual(time.standardBias, 0x01020304);
+ assert.strictEqual(time.daylightBias, 0x08080808);
+ assert.strictEqual(time.standardName, 'a');
+ assert.strictEqual(time.daylightName, 'b');
+ });
+
+ test('manageThreads', function() {
+ const events = [];
+ const model = 'dummy';
+ const importer = new tr.e.importer.etw.EtwImporter(model, events);
+
+ // After initialisation, no threads must exists.
+ assert.strictEqual(
+ Object.getOwnPropertyNames(importer.tidsToPid_).length, 0);
+
+ // Add some threads.
+ const thread10 = importer.createThreadIfNeeded(1, 10);
+ const thread11 = importer.createThreadIfNeeded(1, 11);
+ const thread20 = importer.createThreadIfNeeded(2, 20);
+
+ assert.strictEqual(
+ Object.getOwnPropertyNames(importer.tidsToPid_).length, 3);
+ assert.isTrue(importer.tidsToPid_.hasOwnProperty(10));
+ assert.isTrue(importer.tidsToPid_.hasOwnProperty(11));
+ assert.isTrue(importer.tidsToPid_.hasOwnProperty(20));
+
+ // Retrieve existing threads and processes.
+ const pid10 = importer.getPidFromWindowsTid(10);
+ const pid11 = importer.getPidFromWindowsTid(11);
+ const pid20 = importer.getPidFromWindowsTid(20);
+
+ assert.strictEqual(pid10, 1);
+ assert.strictEqual(pid11, 1);
+ assert.strictEqual(pid20, 2);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/eventtrace_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/eventtrace_parser.html
new file mode 100644
index 00000000000..5dd1bcec174
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/eventtrace_parser.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/etw/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses EventTrace events in the Windows event trace format.
+ */
+
+tr.exportTo('tr.e.importer.etw', function() {
+ const Parser = tr.e.importer.etw.Parser;
+
+ // Constants for EventTrace events.
+ const guid = '68FDD900-4A3E-11D1-84F4-0000F80464E3';
+ const kEventTraceHeaderOpcode = 0;
+
+ /**
+ * Parses Windows EventTrace trace events.
+ * @constructor
+ */
+ function EventTraceParser(importer) {
+ Parser.call(this, importer);
+
+ // Register handlers.
+ importer.registerEventHandler(guid, kEventTraceHeaderOpcode,
+ EventTraceParser.prototype.decodeHeader.bind(this));
+ }
+
+ EventTraceParser.prototype = {
+ __proto__: Parser.prototype,
+
+ decodeFields(header, decoder) {
+ if (header.version !== 2) {
+ throw new Error('Incompatible EventTrace event version.');
+ }
+
+ const bufferSize = decoder.decodeUInt32();
+ const version = decoder.decodeUInt32();
+ const providerVersion = decoder.decodeUInt32();
+ const numberOfProcessors = decoder.decodeUInt32();
+ const endTime = decoder.decodeUInt64ToString();
+ const timerResolution = decoder.decodeUInt32();
+ const maxFileSize = decoder.decodeUInt32();
+ const logFileMode = decoder.decodeUInt32();
+ const buffersWritten = decoder.decodeUInt32();
+ const startBuffers = decoder.decodeUInt32();
+ const pointerSize = decoder.decodeUInt32();
+ const eventsLost = decoder.decodeUInt32();
+ const cpuSpeed = decoder.decodeUInt32();
+ const loggerName = decoder.decodeUInteger(header.is64);
+ const logFileName = decoder.decodeUInteger(header.is64);
+ const timeZoneInformation = decoder.decodeTimeZoneInformation();
+ const padding = decoder.decodeUInt32();
+ const bootTime = decoder.decodeUInt64ToString();
+ const perfFreq = decoder.decodeUInt64ToString();
+ const startTime = decoder.decodeUInt64ToString();
+ const reservedFlags = decoder.decodeUInt32();
+ const buffersLost = decoder.decodeUInt32();
+ const sessionNameString = decoder.decodeW16String();
+ const logFileNameString = decoder.decodeW16String();
+
+ return {
+ bufferSize,
+ version,
+ providerVersion,
+ numberOfProcessors,
+ endTime,
+ timerResolution,
+ maxFileSize,
+ logFileMode,
+ buffersWritten,
+ startBuffers,
+ pointerSize,
+ eventsLost,
+ cpuSpeed,
+ loggerName,
+ logFileName,
+ timeZoneInformation,
+ bootTime,
+ perfFreq,
+ startTime,
+ reservedFlags,
+ buffersLost,
+ sessionNameString,
+ logFileNameString
+ };
+ },
+
+ decodeHeader(header, decoder) {
+ const fields = this.decodeFields(header, decoder);
+ // TODO(etienneb): Update the Model with |fields|.
+ return true;
+ }
+
+ };
+
+ Parser.register(EventTraceParser);
+
+ return {
+ EventTraceParser,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/eventtrace_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/eventtrace_parser_test.html
new file mode 100644
index 00000000000..bfa2caed994
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/eventtrace_parser_test.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/etw/etw_importer.html">
+<link rel="import" href="/tracing/extras/importer/etw/eventtrace_parser.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ // Constants for EventTrace events.
+ const guid = '68FDD900-4A3E-11D1-84F4-0000F80464E3';
+ const kEventTraceHeaderOpcode = 0;
+
+ const kEventTraceHeaderPayload32bitV2 =
+ 'AAABAAYBAQWwHQAAEAAAABEs1WHICMwBYWECAGQAAAABAAAAAwAAAAEAAAAEAAAAAAAAA' +
+ 'FoJAAAFAAAABgAAACwBAABAAHQAegByAGUAcwAuAGQAbABsACwALQAxADEAMgAAAAAAAA' +
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAQACAAAAAAAAAAAAAABAAHQ' +
+ 'AegByAGUAcwAuAGQAbABsACwALQAxADEAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAADAAAAAgACAAAAAAAAAMT///8AAAAAf0Ob368FzAGdrCMAAAAAACw0o' +
+ '2DICMwBAQAAAAAAAABNAGEAawBlACAAVABlAHMAdAAgAEQAYQB0AGEAIABTAGUAcwBzAG' +
+ 'kAbwBuAAAAYwA6AFwAcwByAGMAXABzAGEAdwBiAHUAYwBrAFwAdAByAHUAbgBrAFwAcwB' +
+ 'yAGMAXABzAGEAdwBiAHUAYwBrAFwAbABvAGcAXwBsAGkAYgBcAHQAZQBzAHQAXwBkAGEA' +
+ 'dABhAFwAaQBtAGEAZwBlAF8AZABhAHQAYQBfADMAMgBfAHYAMAAuAGUAdABsAAAA';
+
+ const kEventTraceHeaderPayload64bitV2 =
+ 'AAABAAYBAQWxHQAABAAAADsuzRRYLM8BYWECAAAAAAABAAEAtgEAAAEAAAAIAAAAHwAAA' +
+ 'KAGAAAAAAAAAAAAAAAAAAAAAAAALAEAAEAAdAB6AHIAZQBzAC4AZABsAGwALAAtADEAMQ' +
+ 'AyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAA' +
+ 'AAAAAAEAAdAB6AHIAZQBzAC4AZABsAGwALAAtADEAMQAxAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///wAAAABZQyWiwCvPAX1GG' +
+ 'QAAAAAALWSZBFgszwEBAAAAAAAAAFIAZQBsAG8AZwBnAGUAcgAAAEMAOgBcAGsAZQByAG' +
+ '4AZQBsAC4AZQB0AGwAAAA=';
+
+ test('DecodeFields', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+ const parser = new tr.e.importer.etw.EventTraceParser(importer);
+ let header;
+ let fields;
+
+ // Validate a version 2 32-bit payload.
+ header = {
+ guid, opcode: kEventTraceHeaderOpcode, version: 2, is64: 0
+ };
+ decoder.reset(kEventTraceHeaderPayload32bitV2);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.bufferSize, 65536);
+ assert.strictEqual(fields.version, 83951878);
+ assert.strictEqual(fields.providerVersion, 7600);
+ assert.strictEqual(fields.numberOfProcessors, 16);
+ assert.strictEqual(fields.endTime, '01cc08c861d52c11');
+ assert.strictEqual(fields.timerResolution, 156001);
+ assert.strictEqual(fields.maxFileSize, 100);
+ assert.strictEqual(fields.logFileMode, 1);
+ assert.strictEqual(fields.buffersWritten, 3);
+ assert.strictEqual(fields.startBuffers, 1);
+ assert.strictEqual(fields.pointerSize, 4);
+ assert.strictEqual(fields.eventsLost, 0);
+ assert.strictEqual(fields.cpuSpeed, 2394);
+ assert.strictEqual(fields.loggerName, 5);
+ assert.strictEqual(fields.logFileName, 6);
+ assert.strictEqual(fields.timeZoneInformation.standardName,
+ '@tzres.dll,-112');
+ assert.strictEqual(fields.timeZoneInformation.daylightName,
+ '@tzres.dll,-111');
+ assert.strictEqual(fields.bootTime, '01cc05afdf9b437f');
+ assert.strictEqual(fields.perfFreq, '000000000023ac9d');
+ assert.strictEqual(fields.startTime, '01cc08c860a3342c');
+ assert.strictEqual(fields.reservedFlags, 1);
+ assert.strictEqual(fields.buffersLost, 0);
+ assert.strictEqual(fields.sessionNameString, 'Make Test Data Session');
+ assert.strictEqual(fields.logFileNameString,
+ 'c:\\src\\sawbuck\\trunk\\src\\sawbuck\\log_lib\\' +
+ 'test_data\\image_data_32_v0.etl');
+
+ // Validate a version 2 64-bit payload.
+ header = {
+ guid, opcode: kEventTraceHeaderOpcode, version: 2, is64: 1
+ };
+ decoder.reset(kEventTraceHeaderPayload64bitV2);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.bufferSize, 65536);
+ assert.strictEqual(fields.version, 83951878);
+ assert.strictEqual(fields.providerVersion, 7601);
+ assert.strictEqual(fields.numberOfProcessors, 4);
+ assert.strictEqual(fields.endTime, '01cf2c5814cd2e3b');
+ assert.strictEqual(fields.timerResolution, 156001);
+ assert.strictEqual(fields.maxFileSize, 0);
+ assert.strictEqual(fields.logFileMode, 0x10001);
+ assert.strictEqual(fields.buffersWritten, 438);
+ assert.strictEqual(fields.startBuffers, 1);
+ assert.strictEqual(fields.pointerSize, 8);
+ assert.strictEqual(fields.eventsLost, 31);
+ assert.strictEqual(fields.cpuSpeed, 1696);
+ assert.strictEqual(fields.loggerName, '0000000000000000');
+ assert.strictEqual(fields.logFileName, '0000000000000000');
+ assert.strictEqual(fields.timeZoneInformation.standardName,
+ '@tzres.dll,-112');
+ assert.strictEqual(fields.timeZoneInformation.daylightName,
+ '@tzres.dll,-111');
+ assert.strictEqual(fields.bootTime, '01cf2bc0a2254359');
+ assert.strictEqual(fields.perfFreq, '000000000019467d');
+ assert.strictEqual(fields.startTime, '01cf2c580499642d');
+ assert.strictEqual(fields.reservedFlags, 1);
+ assert.strictEqual(fields.buffersLost, 0);
+ assert.strictEqual(fields.sessionNameString, 'Relogger');
+ assert.strictEqual(fields.logFileNameString, 'C:\\kernel.etl');
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/parser.html
new file mode 100644
index 00000000000..95f9d079e23
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/parser.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/base/base.html">
+<link rel="import" href="/tracing/base/extension_registry.html">
+<script>
+'use strict';
+
+/**
+ * @fileoverview Base class for Windows ETW event parsers.
+ *
+ * The ETW trace event importer depends on subclasses of
+ * Parser to parse event data. Each subclass corresponds
+ * to a group of trace events; e.g. Thread and Process implements
+ * decoding of scheduling events. Parser subclasses must
+ * call Parser.register to arrange to be instantiated
+ * and their constructor must register their event handlers with the
+ * importer. For example,
+ *
+ * var Parser = tr.e.importer.etw.Parser;
+ *
+ * function ThreadParser(importer) {
+ * Parser.call(this, importer);
+ *
+ * importer.registerEventHandler(guid, kThreadStartOpcode,
+ * ThreadParser.prototype.decodeStart.bind(this));
+ * importer.registerEventHandler(guid, kThreadEndOpcode,
+ * ThreadParser.prototype.decodeEnd.bind(this));
+ * }
+ *
+ * Parser.register(ThreadParser);
+ *
+ * When a registered event is found, the associated event handler is invoked:
+ *
+ * decodeStart: function(header, decoder) {
+ * [...]
+ * return true;
+ * },
+ *
+ * If the routine returns false the caller will generate an import error
+ * saying there was a problem parsing it. Handlers can also emit import
+ * messages using this.importer.model.importWarning. If this is done in lieu of
+ * the generic import error it may be desirable for the handler to return
+ * true.
+ *
+ */
+tr.exportTo('tr.e.importer.etw', function() {
+ /**
+ * Parses Windows ETW events.
+ * @constructor
+ */
+ function Parser(importer) {
+ this.importer = importer;
+ this.model = importer.model;
+ }
+
+ Parser.prototype = {
+ __proto__: Object.prototype
+ };
+
+ const options = new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);
+ options.mandatoryBaseClass = Parser;
+ tr.b.decorateExtensionRegistry(Parser, options);
+
+
+ return {
+ Parser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/process_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/process_parser.html
new file mode 100644
index 00000000000..bb857a9a33d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/process_parser.html
@@ -0,0 +1,190 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/etw/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses processes events in the Windows event trace format.
+ *
+ * The Windows process events are:
+ *
+ * - DCStart: Describes a process that was already running when the trace
+ * started. ETW automatically generates these events for all running
+ * processes at the beginning of the trace.
+ * - Start: Describes a process launched during the tracing session.
+ * - End: Describes a process that ended during the tracing session.
+ * - DCEnd: Describes a process that was still running when the trace ended.
+ *
+ * See http://msdn.microsoft.com/library/windows/desktop/aa364092.aspx
+ */
+tr.exportTo('tr.e.importer.etw', function() {
+ const Parser = tr.e.importer.etw.Parser;
+
+ // Constants for Process events.
+ const guid = '3D6FA8D0-FE05-11D0-9DDA-00C04FD7BA7C';
+ const kProcessStartOpcode = 1;
+ const kProcessEndOpcode = 2;
+ const kProcessDCStartOpcode = 3;
+ const kProcessDCEndOpcode = 4;
+ const kProcessDefunctOpcode = 39;
+
+ /**
+ * Parses Windows process trace events.
+ * @constructor
+ */
+ function ProcessParser(importer) {
+ Parser.call(this, importer);
+
+ // Register handlers.
+ importer.registerEventHandler(guid, kProcessStartOpcode,
+ ProcessParser.prototype.decodeStart.bind(this));
+ importer.registerEventHandler(guid, kProcessEndOpcode,
+ ProcessParser.prototype.decodeEnd.bind(this));
+ importer.registerEventHandler(guid, kProcessDCStartOpcode,
+ ProcessParser.prototype.decodeDCStart.bind(this));
+ importer.registerEventHandler(guid, kProcessDCEndOpcode,
+ ProcessParser.prototype.decodeDCEnd.bind(this));
+ importer.registerEventHandler(guid, kProcessDefunctOpcode,
+ ProcessParser.prototype.decodeDefunct.bind(this));
+ }
+
+ ProcessParser.prototype = {
+ __proto__: Parser.prototype,
+
+ decodeFields(header, decoder) {
+ if (header.version > 5) {
+ throw new Error('Incompatible Process event version.');
+ }
+
+ let pageDirectoryBase;
+ if (header.version === 1) {
+ pageDirectoryBase = decoder.decodeUInteger(header.is64);
+ }
+
+ let uniqueProcessKey;
+ if (header.version >= 2) {
+ uniqueProcessKey = decoder.decodeUInteger(header.is64);
+ }
+
+ const processId = decoder.decodeUInt32();
+ const parentId = decoder.decodeUInt32();
+
+ let sessionId;
+ let exitStatus;
+ if (header.version >= 1) {
+ sessionId = decoder.decodeUInt32();
+ exitStatus = decoder.decodeInt32();
+ }
+
+ let directoryTableBase;
+ if (header.version >= 3) {
+ directoryTableBase = decoder.decodeUInteger(header.is64);
+ }
+
+ let flags;
+ if (header.version >= 4) {
+ flags = decoder.decodeUInt32();
+ }
+
+ const userSID = decoder.decodeSID(header.is64);
+
+ let imageFileName;
+ if (header.version >= 1) {
+ imageFileName = decoder.decodeString();
+ }
+
+ let commandLine;
+ if (header.version >= 2) {
+ commandLine = decoder.decodeW16String();
+ }
+
+ let packageFullName;
+ let applicationId;
+ if (header.version >= 4) {
+ packageFullName = decoder.decodeW16String();
+ applicationId = decoder.decodeW16String();
+ }
+
+ let exitTime;
+ if (header.version === 5 && header.opcode === kProcessDefunctOpcode) {
+ exitTime = decoder.decodeUInt64ToString();
+ }
+
+ return {
+ pageDirectoryBase,
+ uniqueProcessKey,
+ processId,
+ parentId,
+ sessionId,
+ exitStatus,
+ directoryTableBase,
+ flags,
+ userSID,
+ imageFileName,
+ commandLine,
+ packageFullName,
+ applicationId,
+ exitTime
+ };
+ },
+
+ decodeStart(header, decoder) {
+ const fields = this.decodeFields(header, decoder);
+ const process = this.model.getOrCreateProcess(fields.processId);
+ if (process.hasOwnProperty('has_ended')) {
+ // On Windows, a process ID used by a process could be reused as soon as
+ // the process ends (there is no pid cycling like on Linux). However, in
+ // a short trace, this is unlikely to happen.
+ throw new Error('Process clash detected.');
+ }
+ process.name = fields.imageFileName;
+ return true;
+ },
+
+ decodeEnd(header, decoder) {
+ const fields = this.decodeFields(header, decoder);
+ const process = this.model.getOrCreateProcess(fields.processId);
+ process.has_ended = true;
+ return true;
+ },
+
+ decodeDCStart(header, decoder) {
+ const fields = this.decodeFields(header, decoder);
+ const process = this.model.getOrCreateProcess(fields.processId);
+ if (process.hasOwnProperty('has_ended')) {
+ throw new Error('Process clash detected.');
+ }
+ process.name = fields.imageFileName;
+ return true;
+ },
+
+ decodeDCEnd(header, decoder) {
+ const fields = this.decodeFields(header, decoder);
+ const process = this.model.getOrCreateProcess(fields.processId);
+ process.has_ended = true;
+ return true;
+ },
+
+ decodeDefunct(header, decoder) {
+ const fields = this.decodeFields(header, decoder);
+ // TODO(etienneb): Update the Model with |fields|.
+ return true;
+ }
+
+ };
+
+ Parser.register(ProcessParser);
+
+ return {
+ ProcessParser,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/process_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/process_parser_test.html
new file mode 100644
index 00000000000..aeeafc15dd2
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/process_parser_test.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/etw/etw_importer.html">
+<link rel="import" href="/tracing/extras/importer/etw/process_parser.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ // Constants for Process events.
+ const guid = '3D6FA8D0-FE05-11D0-9DDA-00C04FD7BA7C';
+ const kProcessStartOpcode = 1;
+ const kProcessDefunctOpcode = 39;
+
+ const kProcessStartPayload32bitV1 =
+ 'AAAAAPAGAADcAwAAAQAAAAMBAAAAAAAAAAAAAAEFAAAAAAAFFQAAAJYs7Cxo/TEG8dyk0' +
+ '+gDAABub3RlcGFkLmV4ZQA=';
+
+ const kProcessStartPayload32bitV2 =
+ 'AAAAAPAGAADcAwAAAQAAAAMBAAAAAAAAAAAAAAEFAAAAAAAFFQAAAJYs7Cxo/TEG8dyk0' +
+ '+gDAABub3RlcGFkLmV4ZQAiAEMAOgBcAFcAaQBuAGQAbwB3AHMAXABzAHkAcwB0AGUAbQ' +
+ 'AzADIAXABuAG8AdABlAHAAYQBkAC4AZQB4AGUAIgAgAAAA';
+
+ const kProcessStartPayload32bitV3 =
+ 'AAAAAPAGAADcAwAAAQAAAAMBAAAAAAAAAAAAAAAAAAABBQAAAAAABRUAAACWLOwsaP0xB' +
+ 'vHcpNPoAwAAbm90ZXBhZC5leGUAIgBDADoAXABXAGkAbgBkAG8AdwBzAFwAcwB5AHMAdA' +
+ 'BlAG0AMwAyAFwAbgBvAHQAZQBwAGEAZAAuAGUAeABlACIAIAAAAA==';
+
+ const kProcessStartPayload64bitV3 =
+ 'YIBiD4D6//8AGgAAoBwAAAEAAAADAQAAAPBDHQEAAAAwVlMVoPj//wAAAACg+P//AQUAA' +
+ 'AAAAAUVAAAAAgMBAgMEBQYHCAkKCwwAAHhwZXJmLmV4ZQB4AHAAZQByAGYAIAAgAC0AZA' +
+ 'AgAG8AdQB0AC4AZQB0AGwAAAA=';
+
+ const kProcessStartPayload64bitV4 =
+ 'gED8GgDg//+MCgAACBcAAAUAAAADAQAAALCiowAAAAAAAAAAkPBXBADA//8AAAAAAAAAA' +
+ 'AEFAAAAAAAFFQAAAAECAwQFBgcICQoLBukDAAB4cGVyZi5leGUAeABwAGUAcgBmACAAIA' +
+ 'AtAHMAdABvAHAAAAAAAAAA';
+
+ const kProcessDefunctPayload64bitV5 =
+ 'wMXyBgDg//9IGQAAEAgAAAEAAAAAAAAAAGDLTwAAAAAAAAAA8OU7AwDA//8AAAAAAAAMA' +
+ 'AEFAAAAAAAFFQAAAMDBwsPExcbH0NHS09QDAABjaHJvbWUuZXhlAAAAAAAAAI1Jovns+s' +
+ '4B';
+
+ test('DecodeFields', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+ const parser = new tr.e.importer.etw.ProcessParser(importer);
+ let header;
+ let fields;
+
+ // Validate a version 1 32-bit payload.
+ header = { guid, opcode: kProcessStartOpcode, version: 1, is64: 0 };
+ decoder.reset(kProcessStartPayload32bitV1);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.pageDirectoryBase, 0);
+ assert.strictEqual(fields.processId, 1776);
+ assert.strictEqual(fields.parentId, 988);
+ assert.strictEqual(fields.sessionId, 1);
+ assert.strictEqual(fields.exitStatus, 259);
+ assert.strictEqual(fields.imageFileName, 'notepad.exe');
+
+ // Validate a version 2 32-bit payload.
+ header = { guid, opcode: kProcessStartOpcode, version: 2, is64: 0 };
+ decoder.reset(kProcessStartPayload32bitV2);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.uniqueProcessKey, 0);
+ assert.strictEqual(fields.processId, 1776);
+ assert.strictEqual(fields.parentId, 988);
+ assert.strictEqual(fields.sessionId, 1);
+ assert.strictEqual(fields.exitStatus, 259);
+ assert.strictEqual(fields.imageFileName, 'notepad.exe');
+ assert.strictEqual(fields.commandLine,
+ '\"C:\\Windows\\system32\\notepad.exe\" ');
+
+ // Validate a version 3 32-bit payload.
+ header = { guid, opcode: kProcessStartOpcode, version: 3, is64: 0 };
+ decoder.reset(kProcessStartPayload32bitV3);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.uniqueProcessKey, 0);
+ assert.strictEqual(fields.processId, 1776);
+ assert.strictEqual(fields.parentId, 988);
+ assert.strictEqual(fields.sessionId, 1);
+ assert.strictEqual(fields.exitStatus, 259);
+ assert.strictEqual(fields.directoryTableBase, 0);
+ assert.strictEqual(fields.imageFileName, 'notepad.exe');
+ assert.strictEqual(fields.commandLine,
+ '\"C:\\Windows\\system32\\notepad.exe\" ');
+
+ // Validate a version 3 64-bit payload.
+ header = { guid, opcode: kProcessStartOpcode, version: 3, is64: 1 };
+ decoder.reset(kProcessStartPayload64bitV3);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.uniqueProcessKey, 'fffffa800f628060');
+ assert.strictEqual(fields.processId, 6656);
+ assert.strictEqual(fields.parentId, 7328);
+ assert.strictEqual(fields.sessionId, 1);
+ assert.strictEqual(fields.exitStatus, 259);
+ assert.strictEqual(fields.directoryTableBase, '000000011d43f000');
+ assert.strictEqual(fields.imageFileName, 'xperf.exe');
+ assert.strictEqual(fields.commandLine, 'xperf -d out.etl');
+
+ // Validate a version 4 64-bit payload.
+ header = { guid, opcode: kProcessStartOpcode, version: 4, is64: 1 };
+ decoder.reset(kProcessStartPayload64bitV4);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.uniqueProcessKey, 'ffffe0001afc4080');
+ assert.strictEqual(fields.processId, 2700);
+ assert.strictEqual(fields.parentId, 5896);
+ assert.strictEqual(fields.sessionId, 5);
+ assert.strictEqual(fields.exitStatus, 259);
+ assert.strictEqual(fields.directoryTableBase, '00000000a3a2b000');
+ assert.strictEqual(fields.flags, 0);
+ assert.strictEqual(fields.imageFileName, 'xperf.exe');
+ assert.strictEqual(fields.commandLine, 'xperf -stop');
+ assert.strictEqual(fields.packageFullName, '');
+ assert.strictEqual(fields.applicationId, '');
+
+ // Validate a version 5 64-bit payload.
+ header = { guid, opcode: kProcessDefunctOpcode, version: 5, is64: 1 };
+ decoder.reset(kProcessDefunctPayload64bitV5);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.uniqueProcessKey, 'ffffe00006f2c5c0');
+ assert.strictEqual(fields.processId, 6472);
+ assert.strictEqual(fields.parentId, 2064);
+ assert.strictEqual(fields.sessionId, 1);
+ assert.strictEqual(fields.exitStatus, 0);
+ assert.strictEqual(fields.directoryTableBase, '000000004fcb6000');
+ assert.strictEqual(fields.flags, 0);
+ assert.strictEqual(fields.imageFileName, 'chrome.exe');
+ assert.strictEqual(fields.commandLine, '');
+ assert.strictEqual(fields.packageFullName, '');
+ assert.strictEqual(fields.applicationId, '');
+ assert.strictEqual(fields.exitTime, '01cefaecf9a2498d');
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/thread_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/thread_parser.html
new file mode 100644
index 00000000000..5a28e9f8165
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/thread_parser.html
@@ -0,0 +1,256 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/etw/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses threads events in the Windows event trace format.
+ *
+ * The Windows thread events are:
+ *
+ * - DCStart: Describes a thread that was already running when the trace
+ * started. ETW automatically generates these events for all running
+ * threads at the beginning of the trace.
+ * - Start: Describes a thread that started during the tracing session.
+ * - End: Describes a thread that ended during the tracing session.
+ * - DCEnd: Describes a thread that was still alive when the trace ended.
+ *
+ * See http://msdn.microsoft.com/library/windows/desktop/aa364132.aspx
+ */
+tr.exportTo('tr.e.importer.etw', function() {
+ const Parser = tr.e.importer.etw.Parser;
+
+ // Constants for Thread events.
+ const guid = '3D6FA8D1-FE05-11D0-9DDA-00C04FD7BA7C';
+ const kThreadStartOpcode = 1;
+ const kThreadEndOpcode = 2;
+ const kThreadDCStartOpcode = 3;
+ const kThreadDCEndOpcode = 4;
+ const kThreadCSwitchOpcode = 36;
+
+ /**
+ * Parses Windows threads trace events.
+ * @constructor
+ */
+ function ThreadParser(importer) {
+ Parser.call(this, importer);
+
+ // Register handlers.
+ importer.registerEventHandler(guid, kThreadStartOpcode,
+ ThreadParser.prototype.decodeStart.bind(this));
+ importer.registerEventHandler(guid, kThreadEndOpcode,
+ ThreadParser.prototype.decodeEnd.bind(this));
+ importer.registerEventHandler(guid, kThreadDCStartOpcode,
+ ThreadParser.prototype.decodeDCStart.bind(this));
+ importer.registerEventHandler(guid, kThreadDCEndOpcode,
+ ThreadParser.prototype.decodeDCEnd.bind(this));
+ importer.registerEventHandler(guid, kThreadCSwitchOpcode,
+ ThreadParser.prototype.decodeCSwitch.bind(this));
+ }
+
+ ThreadParser.prototype = {
+ __proto__: Parser.prototype,
+
+ decodeFields(header, decoder) {
+ if (header.version > 3) {
+ throw new Error('Incompatible Thread event version ' +
+ header.version + '.');
+ }
+
+ // Common fields to all Thread events.
+ const processId = decoder.decodeUInt32();
+ const threadId = decoder.decodeUInt32();
+
+ // Extended fields.
+ let stackBase;
+ let stackLimit;
+ let userStackBase;
+ let userStackLimit;
+ let affinity;
+ let startAddr;
+ let win32StartAddr;
+ let tebBase;
+ let subProcessTag;
+ let basePriority;
+ let pagePriority;
+ let ioPriority;
+ let threadFlags;
+ let waitMode;
+
+ if (header.version === 1) {
+ // On version 1, only start events have extended information.
+ if (header.opcode === kThreadStartOpcode ||
+ header.opcode === kThreadDCStartOpcode) {
+ stackBase = decoder.decodeUInteger(header.is64);
+ stackLimit = decoder.decodeUInteger(header.is64);
+ userStackBase = decoder.decodeUInteger(header.is64);
+ userStackLimit = decoder.decodeUInteger(header.is64);
+ startAddr = decoder.decodeUInteger(header.is64);
+ win32StartAddr = decoder.decodeUInteger(header.is64);
+ waitMode = decoder.decodeInt8();
+ decoder.skip(3);
+ }
+ } else {
+ stackBase = decoder.decodeUInteger(header.is64);
+ stackLimit = decoder.decodeUInteger(header.is64);
+ userStackBase = decoder.decodeUInteger(header.is64);
+ userStackLimit = decoder.decodeUInteger(header.is64);
+
+ // Version 2 produces a field named 'startAddr'.
+ if (header.version === 2) {
+ startAddr = decoder.decodeUInteger(header.is64);
+ } else {
+ affinity = decoder.decodeUInteger(header.is64);
+ }
+
+ win32StartAddr = decoder.decodeUInteger(header.is64);
+ tebBase = decoder.decodeUInteger(header.is64);
+ subProcessTag = decoder.decodeUInt32();
+
+ if (header.version === 3) {
+ basePriority = decoder.decodeUInt8();
+ pagePriority = decoder.decodeUInt8();
+ ioPriority = decoder.decodeUInt8();
+ threadFlags = decoder.decodeUInt8();
+ }
+ }
+
+ return {
+ processId,
+ threadId,
+ stackBase,
+ stackLimit,
+ userStackBase,
+ userStackLimit,
+ affinity,
+ startAddr,
+ win32StartAddr,
+ tebBase,
+ subProcessTag,
+ waitMode,
+ basePriority,
+ pagePriority,
+ ioPriority,
+ threadFlags
+ };
+ },
+
+ decodeCSwitchFields(header, decoder) {
+ // The only difference in version 3 is that oldThreadWaitMode is renamed
+ // to ThreadFlags. There is no difference between version 3 and version 4.
+ // Therefore we can safely handle all of these versions with the same code
+ // since we don't interpret oldThreadWaitMode at all.
+ // To see the layout of the CSwitch_V4 class you can run wbemtest
+ // elevated, "Connect...", change root\cimv2 to root\WMI, "Connect",
+ // "Open Class...", "CSwitch_V4", "Show MOF".
+ if (header.version < 2 || header.version > 4) {
+ throw new Error('Incompatible cswitch event version ' +
+ header.version + '.');
+ }
+
+ // Decode CSwitch payload.
+ const newThreadId = decoder.decodeUInt32();
+ const oldThreadId = decoder.decodeUInt32();
+ const newThreadPriority = decoder.decodeInt8();
+ const oldThreadPriority = decoder.decodeInt8();
+ const previousCState = decoder.decodeUInt8();
+ const spareByte = decoder.decodeInt8();
+ const oldThreadWaitReason = decoder.decodeInt8();
+ // oldThreadWaitMode is called ThreadFlags in V3 and V4.
+ const oldThreadWaitMode = decoder.decodeInt8();
+ const oldThreadState = decoder.decodeInt8();
+ const oldThreadWaitIdealProcessor = decoder.decodeInt8();
+ const newThreadWaitTime = decoder.decodeUInt32();
+ const reserved = decoder.decodeUInt32();
+
+ return {
+ newThreadId,
+ oldThreadId,
+ newThreadPriority,
+ oldThreadPriority,
+ previousCState,
+ spareByte,
+ oldThreadWaitReason,
+ oldThreadWaitMode,
+ oldThreadState,
+ oldThreadWaitIdealProcessor,
+ newThreadWaitTime,
+ reserved
+ };
+ },
+
+ decodeStart(header, decoder) {
+ const fields = this.decodeFields(header, decoder);
+ this.importer.createThreadIfNeeded(fields.processId, fields.threadId);
+ return true;
+ },
+
+ decodeEnd(header, decoder) {
+ const fields = this.decodeFields(header, decoder);
+ this.importer.removeThreadIfPresent(fields.threadId);
+ return true;
+ },
+
+ decodeDCStart(header, decoder) {
+ const fields = this.decodeFields(header, decoder);
+ this.importer.createThreadIfNeeded(fields.processId, fields.threadId);
+ return true;
+ },
+
+ decodeDCEnd(header, decoder) {
+ const fields = this.decodeFields(header, decoder);
+ this.importer.removeThreadIfPresent(fields.threadId);
+ return true;
+ },
+
+ decodeCSwitch(header, decoder) {
+ const fields = this.decodeCSwitchFields(header, decoder);
+ const cpu = this.importer.getOrCreateCpu(header.cpu);
+ const newThread =
+ this.importer.getThreadFromWindowsTid(fields.newThreadId);
+
+ // Generate the new thread name. If some events were lost, it's possible
+ // that information about the new thread or process is not available.
+ let newThreadName;
+ if (newThread && newThread.userFriendlyName) {
+ newThreadName = newThread.userFriendlyName;
+ } else {
+ const newProcessId = this.importer.getPidFromWindowsTid(
+ fields.newThreadId);
+ const newProcess = this.model.getProcess(newProcessId);
+ let newProcessName;
+ if (newProcess) {
+ newProcessName = newProcess.name;
+ } else {
+ newProcessName = 'Unknown process';
+ }
+
+ newThreadName =
+ newProcessName + ' (tid ' + fields.newThreadId + ')';
+ }
+
+ cpu.switchActiveThread(
+ header.timestamp,
+ {},
+ fields.newThreadId,
+ newThreadName,
+ fields);
+ return true;
+ }
+
+ };
+
+ Parser.register(ThreadParser);
+
+ return {
+ ThreadParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/thread_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/thread_parser_test.html
new file mode 100644
index 00000000000..a15e3235a25
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/etw/thread_parser_test.html
@@ -0,0 +1,176 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/etw/etw_importer.html">
+<link rel="import" href="/tracing/extras/importer/etw/thread_parser.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ // Constants for Thread events.
+ const guid = '3D6FA8D1-FE05-11D0-9DDA-00C04FD7BA7C';
+ const kThreadStartOpcode = 1;
+ const kThreadEndOpcode = 2;
+ const kThreadDCStartOpcode = 3;
+ const kThreadCSwitchOpcode = 36;
+
+ const kThreadStartPayload32bitV1 =
+ 'BAAAAEwHAAAAYLfzADC38wAAAAAAAAAAhdse9wAAAAD/AAAA';
+
+ const kThreadEndPayload32bitV1 = 'BAAAALQAAAA=';
+
+
+ const kThreadDCStartPayload64bitV2 =
+ 'AAAAAAAAAAAAYPUCAPj//wAA9QIA+P//AAAAAAAAAAAAAAAAAAAAAIAlxwEA+P//gCXHA' +
+ 'QD4//8AAAAAAAAAAAAAAAA=';
+
+ const kThreadStartPayload32bitV3 =
+ 'LAIAACwTAAAAUJixACCYsQAA1QAAwNQAAwAAAOkDq3cA4P1/AAAAAAkFAgA=';
+
+ const kThreadStartPayload64bitV3 =
+ 'eCEAAJQUAAAAMA4nAND//wDQDScA0P//MP0LBgAAAAAAgAsGAAAAAP8AAAAAAAAALP1YX' +
+ 'AAAAAAAwBL/AAAAAAAAAAAIBQIA';
+
+ const kThreadCSwitchPayload32bitV2 = 'AAAAACwRAAAACQAAFwABABIAAAAmSAAA';
+ const kThreadCSwitchPayload64bitV2 = 'zAgAAAAAAAAIAAEAAAACBAEAAACHbYg0';
+
+ test('DecodeFields', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+ const parser = new tr.e.importer.etw.ThreadParser(importer);
+ let header;
+ let fields;
+
+ // Validate a version 1 32-bit payload.
+ header = { guid, opcode: kThreadStartOpcode, version: 1, is64: 0 };
+ decoder.reset(kThreadStartPayload32bitV1);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.processId, 4);
+ assert.strictEqual(fields.threadId, 1868);
+ assert.strictEqual(fields.stackBase, 4088881152);
+ assert.strictEqual(fields.stackLimit, 4088868864);
+ assert.strictEqual(fields.userStackBase, 0);
+ assert.strictEqual(fields.userStackLimit, 0);
+ assert.strictEqual(fields.startAddr, 4145994629);
+ assert.strictEqual(fields.win32StartAddr, 0);
+ assert.strictEqual(fields.waitMode, -1);
+
+ // Validate an End version 1 32-bit payload.
+ header = { guid, opcode: kThreadEndOpcode, version: 1, is64: 0 };
+ decoder.reset(kThreadStartPayload32bitV1);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.processId, 4);
+ assert.strictEqual(fields.threadId, 1868);
+
+ // Validate a version 2 64-bit payload.
+ header = { guid, opcode: kThreadDCStartOpcode, version: 2, is64: 1 };
+ decoder.reset(kThreadDCStartPayload64bitV2);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.processId, 0);
+ assert.strictEqual(fields.threadId, 0);
+ assert.strictEqual(fields.stackBase, 'fffff80002f56000');
+ assert.strictEqual(fields.stackLimit, 'fffff80002f50000');
+ assert.strictEqual(fields.userStackBase, '0000000000000000');
+ assert.strictEqual(fields.userStackLimit, '0000000000000000');
+ assert.strictEqual(fields.startAddr, 'fffff80001c72580');
+ assert.strictEqual(fields.win32StartAddr, 'fffff80001c72580');
+ assert.strictEqual(fields.tebBase, '0000000000000000');
+ assert.strictEqual(fields.subProcessTag, 0);
+
+ // Validate a version 3 32-bit payload.
+ header = { guid, opcode: kThreadStartOpcode, version: 3, is64: 0 };
+ decoder.reset(kThreadStartPayload32bitV3);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.processId, 556);
+ assert.strictEqual(fields.threadId, 4908);
+ assert.strictEqual(fields.stackBase, 2979549184);
+ assert.strictEqual(fields.stackLimit, 2979536896);
+ assert.strictEqual(fields.userStackBase, 13959168);
+ assert.strictEqual(fields.userStackLimit, 13942784);
+ assert.strictEqual(fields.affinity, 3);
+ assert.strictEqual(fields.win32StartAddr, 2007696361);
+ assert.strictEqual(fields.tebBase, 2147344384);
+ assert.strictEqual(fields.subProcessTag, 0);
+ assert.strictEqual(fields.basePriority, 9);
+ assert.strictEqual(fields.pagePriority, 5);
+ assert.strictEqual(fields.ioPriority, 2);
+ assert.strictEqual(fields.threadFlags, 0);
+
+ // Validate a version 3 64-bit payload.
+ header = { guid, opcode: kThreadStartOpcode, version: 3, is64: 1 };
+ decoder.reset(kThreadStartPayload64bitV3);
+ fields = parser.decodeFields(header, decoder);
+
+ assert.strictEqual(fields.processId, 8568);
+ assert.strictEqual(fields.threadId, 5268);
+ assert.strictEqual(fields.stackBase, 'ffffd000270e3000');
+ assert.strictEqual(fields.stackLimit, 'ffffd000270dd000');
+ assert.strictEqual(fields.userStackBase, '00000000060bfd30');
+ assert.strictEqual(fields.userStackLimit, '00000000060b8000');
+ assert.strictEqual(fields.affinity, '00000000000000ff');
+ assert.strictEqual(fields.win32StartAddr, '000000005c58fd2c');
+ assert.strictEqual(fields.tebBase, '00000000ff12c000');
+ assert.strictEqual(fields.subProcessTag, 0);
+ assert.strictEqual(fields.basePriority, 8);
+ assert.strictEqual(fields.pagePriority, 5);
+ assert.strictEqual(fields.ioPriority, 2);
+ assert.strictEqual(fields.threadFlags, 0);
+ });
+
+ test('DecodeCSwitchFields', function() {
+ const importer = new tr.e.importer.etw.EtwImporter('dummy', []);
+ const decoder = importer.decoder_;
+ const parser = new tr.e.importer.etw.ThreadParser(importer);
+ let header;
+ let fields;
+
+
+ // Validate a version 2 CSwitch 32-bit payload.
+ header = { guid, opcode: kThreadCSwitchOpcode, version: 2, is64: 0 };
+ decoder.reset(kThreadCSwitchPayload32bitV2);
+ fields = parser.decodeCSwitchFields(header, decoder);
+
+ assert.strictEqual(fields.newThreadId, 0);
+ assert.strictEqual(fields.oldThreadId, 4396);
+ assert.strictEqual(fields.newThreadPriority, 0);
+ assert.strictEqual(fields.oldThreadPriority, 9);
+ assert.strictEqual(fields.previousCState, 0);
+ assert.strictEqual(fields.spareByte, 0);
+ assert.strictEqual(fields.oldThreadWaitReason, 23);
+ assert.strictEqual(fields.oldThreadWaitMode, 0);
+ assert.strictEqual(fields.oldThreadState, 1);
+ assert.strictEqual(fields.oldThreadWaitIdealProcessor, 0);
+ assert.strictEqual(fields.newThreadWaitTime, 18);
+ assert.strictEqual(fields.reserved, 18470);
+
+ // Validate a version 2 CSwitch 64-bit payload.
+ header = { guid, opcode: kThreadCSwitchOpcode, version: 2, is64: 1 };
+ decoder.reset(kThreadCSwitchPayload64bitV2);
+ fields = parser.decodeCSwitchFields(header, decoder);
+
+ assert.strictEqual(fields.newThreadId, 2252);
+ assert.strictEqual(fields.oldThreadId, 0);
+ assert.strictEqual(fields.newThreadPriority, 8);
+ assert.strictEqual(fields.oldThreadPriority, 0);
+ assert.strictEqual(fields.previousCState, 1);
+ assert.strictEqual(fields.spareByte, 0);
+ assert.strictEqual(fields.oldThreadWaitReason, 0);
+ assert.strictEqual(fields.oldThreadWaitMode, 0);
+ assert.strictEqual(fields.oldThreadState, 2);
+ assert.strictEqual(fields.oldThreadWaitIdealProcessor, 4);
+ assert.strictEqual(fields.newThreadWaitTime, 1);
+ assert.strictEqual(fields.reserved, 881356167);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/fuchsia_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/fuchsia_importer.html
new file mode 100644
index 00000000000..8fd2fcbf870
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/fuchsia_importer.html
@@ -0,0 +1,234 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/trace_stream.html">
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.importer.fuchsia', function() {
+ const IMPORT_PRIORITY = 0;
+ const IDLE_THREAD_THRESHOLD = 6444000000;
+
+ // Zircon thread state constants from:
+ // https://fuchsia.googlesource.com/zircon/+/master/docs/syscalls/object_get_info.md
+ const ZX_THREAD_STATE_NEW = 0;
+ const ZX_THREAD_STATE_RUNNING = 1;
+ const ZX_THREAD_STATE_SUSPENDED = 2;
+ const ZX_THREAD_STATE_BLOCKED = 3;
+ const ZX_THREAD_STATE_DYING = 4;
+ const ZX_THREAD_STATE_DEAD = 5;
+
+ class FuchsiaImporter extends tr.importer.Importer {
+ constructor(model, eventData) {
+ super(model, eventData);
+ this.importPriority = IMPORT_PRIORITY;
+ this.model_ = model;
+ this.events_ = eventData.events;
+ this.parsers_ = [];
+ this.threadInfo_ = new Map();
+ this.processNames_ = new Map();
+ this.threadStates_ = new Map();
+ }
+
+ static canImport(eventData) {
+ if (eventData instanceof tr.b.TraceStream) {
+ if (eventData.isBinary) return false;
+ eventData = eventData.header;
+ }
+
+ if (eventData instanceof Object && eventData.type === 'fuchsia') {
+ return true;
+ }
+
+ return false;
+ }
+
+ get importerName() {
+ return 'FuchsiaImporter';
+ }
+
+ get model() {
+ return this.model_;
+ }
+
+ importClockSyncMarkers() {
+ }
+
+ finalizeImport() {
+ }
+
+ isIdleThread(prio, tid) {
+ if (prio === undefined) {
+ // If the "prio" field is not available (if we were, for example,
+ // using an old trace), then fall back to the legacy heuristic of
+ // assuming that large numbered threads are idle ones.
+ return tid > IDLE_THREAD_THRESHOLD;
+ }
+ // A thread is idle iff its priority is set to 0.
+ return prio === 0;
+ }
+
+ recordThreadState_(tid, timestamp, state, prio) {
+ if (this.isIdleThread(prio, tid)) {
+ return;
+ }
+ const states =
+ this.threadStates_.has(tid) ? this.threadStates_.get(tid) : [];
+ states.push({'ts': timestamp, state});
+ this.threadStates_.set(tid, states);
+ }
+
+ // Context switch events take the form:
+ // {
+ // "ph": "k",
+ // "ts": 151981130.88743783,
+ // "cpu": 1,
+ // "out": {
+ // "pid": 25977,
+ // "tid": 28909,
+ // "state": 3,
+ // "prio": 20
+ // },
+ // "in": {
+ // "pid": 0,
+ // "tid": 6444931392,
+ // "prio": 0
+ // }
+ // },
+ processContextSwitchEvent_(event) {
+ let tid = event.in.tid;
+ let threadName = tid.toString();
+ let procName = '';
+ const prio = event.in.prio;
+
+ if (this.threadInfo_.has(tid)) {
+ const threadInfo = this.threadInfo_.get(tid);
+ threadName = threadInfo.name;
+ const pid = threadInfo.pid;
+ if (this.processNames_.has(pid)) {
+ procName = this.processNames_.get(pid) + ':';
+ }
+ }
+
+ const name = procName + threadName;
+
+ if (this.isIdleThread(prio, tid)) {
+ tid = undefined; // Fake kernel idle task
+ }
+
+ const cpu = this.model_.kernel.getOrCreateCpu(event.cpu);
+ const timestamp = tr.b.Unit.timestampFromUs(event.ts);
+ cpu.switchActiveThread(timestamp, {}, tid, name, tid);
+
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ this.recordThreadState_(tid, timestamp, SCHEDULING_STATE.RUNNING, prio);
+
+ let outState = SCHEDULING_STATE.UNKNOWN;
+
+ switch (event.out.state) {
+ case ZX_THREAD_STATE_NEW:
+ outState = SCHEDULING_STATE.RUNNABLE;
+ break;
+
+ case ZX_THREAD_STATE_RUNNING:
+ outState = SCHEDULING_STATE.RUNNABLE;
+ break;
+
+ case ZX_THREAD_STATE_BLOCKED:
+ outState = SCHEDULING_STATE.SLEEPING;
+ break;
+
+ case ZX_THREAD_STATE_SUSPENDED:
+ outState = SCHEDULING_STATE.STOPPED;
+ break;
+
+ case ZX_THREAD_STATE_DEAD:
+ outState = SCHEDULING_STATE.TASK_DEAD;
+ break;
+ }
+ this.recordThreadState_(event.out.tid, timestamp, outState,
+ event.out.prio);
+ }
+
+ processProcessInfoEvent_(event) {
+ const process = this.model_.getOrCreateProcess(event.pid);
+ process.name = event.name;
+ this.processNames_.set(event.pid, event.name);
+
+ if ('sort_index' in event) {
+ process.sortIndex = event.sort_index;
+ }
+ }
+
+ processThreadInfoEvent_(event) {
+ const thread = this.model_.getOrCreateProcess(event.pid).
+ getOrCreateThread(event.tid);
+ thread.name = event.name;
+ this.threadInfo_.set(event.tid, {'name': event.name, 'pid': event.pid});
+
+ if ('sort_index' in event) {
+ const thread = this.model_.getOrCreateProcess(event.pid).
+ getOrCreateThread(event.tid);
+ thread.sortIndex = event.sort_index;
+ }
+ }
+
+ processEvent_(event) {
+ switch (event.ph) {
+ case 'k':
+ this.processContextSwitchEvent_(event);
+ break;
+ case 'p':
+ this.processProcessInfoEvent_(event);
+ break;
+ case 't':
+ this.processThreadInfoEvent_(event);
+ break;
+ }
+ }
+
+ postProcessStates_() {
+ for (const [tid, states] of this.threadStates_) {
+ if (!this.threadInfo_.has(tid)) {
+ continue;
+ }
+ const pid = this.threadInfo_.get(tid).pid;
+ const thread = this.model_.getOrCreateProcess(
+ pid).getOrCreateThread(tid);
+ const slices = [];
+ for (let i = 0; i < states.length - 1; i++) {
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread, states[i].state, '',
+ states[i].ts, {}, states[i + 1].ts - states[i].ts));
+ }
+
+ thread.timeSlices = slices;
+ }
+ }
+
+ /**
+ * Imports the data in this.events_ into model_.
+ */
+ importEvents() {
+ for (const event of this.events_) {
+ this.processEvent_(event);
+ }
+ this.postProcessStates_();
+ }
+ }
+
+ tr.importer.Importer.register(FuchsiaImporter);
+
+ return {
+ FuchsiaImporter,
+ IMPORT_PRIORITY,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/fuchsia_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/fuchsia_importer_test.html
new file mode 100644
index 00000000000..e7494f0da82
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/fuchsia_importer_test.html
@@ -0,0 +1,183 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/fuchsia_importer.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ // Test that fuchsia process and thread info events properly annotate
+ // thread and process names.
+ test('processAndThreadInfo', function() {
+ const events = `{
+ "displayTimeUnit": "ns",
+ "traceEvents": [
+ {
+ "cat": "cat0",
+ "name": "name0",
+ "ts": 1.0,
+ "pid": 1,
+ "tid": 2,
+ "ph": "B"
+ },
+ {
+ "cat": "cat0",
+ "name": "name0",
+ "ts": 2.0,
+ "pid": 1,
+ "tid": 2,
+ "ph": "E"
+ }
+ ],
+ "systemTraceEvents": {
+ "type": "fuchsia",
+ "events": [
+ {
+ "ph": "p",
+ "pid": 1,
+ "name": "proc1"
+ },
+ {
+ "ph": "t",
+ "pid": 1,
+ "tid": 2,
+ "name": "thread2"
+ }
+ ]
+ }
+ }`;
+ const m = tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ const process = m.getProcess(1);
+ assert.isDefined(process);
+ assert.strictEqual(process.name, 'proc1');
+
+ const thread = process.getThread(2);
+ assert.isDefined(thread);
+ assert.strictEqual(thread.name, 'thread2');
+ });
+
+ // Test that context switch events create CPU slices.
+ test('cpuSlices', function() {
+ const events = `{
+ "displayTimeUnit": "ns",
+ "traceEvents": [
+ ],
+ "systemTraceEvents": {
+ "type": "fuchsia",
+ "events": [
+ {
+ "ph": "p",
+ "pid": 1,
+ "name": "proc1"
+ },
+ {
+ "ph": "t",
+ "pid": 1,
+ "tid": 2,
+ "name": "thread2"
+ },
+ {
+ "ph": "t",
+ "pid": 1,
+ "tid": 3,
+ "name": "thread3"
+ },
+ {
+ "ph": "k",
+ "ts": 1.0,
+ "cpu": 1,
+ "out": {
+ "pid": 1,
+ "tid": 2,
+ "state": 1
+ },
+ "in": {
+ "pid": 1,
+ "tid": 3
+ }
+ },
+ {
+ "ph": "k",
+ "ts": 2.0,
+ "cpu": 1,
+ "out": {
+ "pid": 1,
+ "tid": 3,
+ "state": 3
+ },
+ "in": {
+ "pid": 1,
+ "tid": 2
+ }
+ },
+ {
+ "ph": "k",
+ "ts": 3.0,
+ "cpu": 1,
+ "out": {
+ "pid": 1,
+ "tid": 2,
+ "state": 1
+ },
+ "in": {
+ "pid": 1,
+ "tid": 3
+ }
+ }
+ ]
+ }
+ }`;
+ const m = tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ const cpu = m.kernel.cpus[1];
+ assert.isDefined(cpu);
+ assert.strictEqual(cpu.slices.length, 2);
+
+ assert.strictEqual(cpu.slices[0].title, 'proc1:thread3');
+ assert.closeTo(cpu.slices[0].duration, 0.001, 1e-5);
+
+ assert.strictEqual(cpu.slices[1].title, 'proc1:thread2');
+ assert.closeTo(cpu.slices[1].duration, 0.001, 1e-5);
+
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+
+ const thread2Slices =
+ m.getOrCreateProcess(1).getOrCreateThread(2).timeSlices;
+ assert.strictEqual(thread2Slices[0].schedulingState,
+ SCHEDULING_STATE.RUNNABLE);
+ assert.closeTo(thread2Slices[0].start, 0.001, 1e-5);
+ assert.closeTo(thread2Slices[0].duration, 0.001, 1e-5);
+ assert.strictEqual(thread2Slices[1].schedulingState,
+ SCHEDULING_STATE.RUNNING);
+ assert.closeTo(thread2Slices[1].start, 0.002, 1e-5);
+ assert.closeTo(thread2Slices[1].duration, 0.001, 1e-5);
+
+ const thread3Slices =
+ m.getOrCreateProcess(1).getOrCreateThread(3).timeSlices;
+ assert.strictEqual(thread3Slices[0].schedulingState,
+ SCHEDULING_STATE.RUNNING);
+ assert.closeTo(thread3Slices[0].start, 0.001, 1e-5);
+ assert.closeTo(thread3Slices[0].duration, 0.001, 1e-5);
+ assert.strictEqual(thread3Slices[1].schedulingState,
+ SCHEDULING_STATE.SLEEPING);
+ assert.closeTo(thread3Slices[1].start, 0.002, 1e-5);
+ assert.closeTo(thread3Slices[1].duration, 0.001, 1e-5);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/gcloud_trace/gcloud_trace_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/gcloud_trace/gcloud_trace_importer.html
new file mode 100644
index 00000000000..bd3ce854503
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/gcloud_trace/gcloud_trace_importer.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/importer/importer.html">
+
+<script>
+
+'use strict';
+
+/**
+ * @fileoverview GcloudTraceImporter imports JSON data from Google Cloud Trace.
+ */
+tr.exportTo('tr.e.importer.gcloud_trace', function() {
+ function GcloudTraceImporter(model, eventData) {
+ this.importPriority = 2;
+ this.eventData_ = eventData;
+ }
+
+ /**
+ * @return {boolean} Whether obj looks like the JSON output from Cloud Trace.
+ */
+ GcloudTraceImporter.canImport = function(eventData) {
+ if (typeof(eventData) !== 'string' && !(eventData instanceof String)) {
+ return false;
+ }
+
+ // Slice the data so we don't potentially do a replace on a gigantic string.
+ const normalizedEventData = eventData.slice(0, 20).replace(/\s/g, '');
+ if (normalizedEventData.length < 14) return false;
+
+ return normalizedEventData.slice(0, 14) === '{"projectId":"';
+ };
+
+ GcloudTraceImporter.prototype = {
+
+ __proto__: tr.importer.Importer.prototype,
+
+ get importerName() {
+ return 'GcloudTraceImporter';
+ },
+
+ /**
+ * Called by the Model to extract subtraces from the event data. The
+ * subtraces are passed on to other importers that can recognize them.
+ */
+ extractSubtraces() {
+ const traceEvents = this.createEventsForTrace();
+ return traceEvents ? [traceEvents] : [];
+ },
+
+ createEventsForTrace() {
+ const events = [];
+ const trace = JSON.parse(this.eventData_);
+ const spanLength = trace.spans.length;
+ for (let i = 0; i < spanLength; i++) {
+ events.push(this.createEventForSpan(trace.traceId, trace.spans[i]));
+ }
+ return {
+ 'traceEvents': events
+ };
+ },
+
+ createEventForSpan(traceId, span) {
+ let newArgs = {};
+ if (span.labels) {
+ newArgs = JSON.parse(JSON.stringify(span.labels));
+ }
+ newArgs['Span ID'] = span.spanId;
+ newArgs['Start Time'] = span.startTime;
+ newArgs['End Time'] = span.endTime;
+ if (span.parentSpanId) {
+ newArgs['Parent Span ID'] = span.parentSpanId;
+ }
+ // The timestamps are ISO-standard strings, which are parsed to millis,
+ // then converted to the micros that the trace viewer expects.
+ return {
+ name: span.name,
+ args: newArgs,
+ pid: traceId,
+ ts: Date.parse(span.startTime) * 1000,
+ dur: (Date.parse(span.endTime) - Date.parse(span.startTime)) * 1000,
+ cat: 'tracespan',
+ tid: traceId,
+ ph: 'X'
+ };
+ }
+ };
+
+ tr.importer.Importer.register(GcloudTraceImporter);
+
+ return {
+ GcloudTraceImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/gcloud_trace/gcloud_trace_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/gcloud_trace/gcloud_trace_importer_test.html
new file mode 100644
index 00000000000..b902ff5fce9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/gcloud_trace/gcloud_trace_importer_test.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/gcloud_trace/gcloud_trace_importer.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const GcloudTraceImporter = tr.e.importer.gcloud_trace.GcloudTraceImporter;
+
+ test('noSpans', function() {
+ const trace = {projectId: 'My Project', traceId: '123', spans: []};
+
+ const model = new tr.Model();
+ const importer = new tr.e.importer.gcloud_trace.GcloudTraceImporter(model,
+ JSON.stringify(trace));
+ const subtraces = importer.extractSubtraces();
+ assert.strictEqual(1, subtraces.length);
+ // Note there are, in fact, use cases for traces with no events (spans).
+ assert.deepEqual([], subtraces[0].traceEvents);
+ });
+
+ test('typicalTrace', function() {
+ const span1 = {
+ 'spanId': '1',
+ 'kind': 'RPC_CLIENT',
+ 'name': '/first',
+ 'startTime': '2015-09-03T16:40:00.841654024Z',
+ 'endTime': '2015-09-03T16:40:00.856599389Z',
+ 'labels': {
+ 'key1': 'value1',
+ 'key2': 'value2'
+ }
+ };
+ const span2 = {
+ 'spanId': '2',
+ 'kind': 'RPC_SERVER',
+ 'name': '/second',
+ 'startTime': '2015-09-03T16:40:00.842880028Z',
+ 'endTime': '2015-09-03T16:40:00.851729538Z',
+ 'parentSpanId': '1',
+ 'labels': {
+ 'key1': 'value3',
+ 'key2': 'value4'
+ }
+ };
+ const trace = {projectId: 'My Project', traceId: '123',
+ spans: [span1, span2]};
+
+ const model = new tr.Model();
+ const importer = new tr.e.importer.gcloud_trace.GcloudTraceImporter(model,
+ JSON.stringify(trace));
+ const subtraces = importer.extractSubtraces();
+ assert.strictEqual(1, subtraces.length);
+ assert.strictEqual(2, subtraces[0].traceEvents.length);
+
+ const span1Event = subtraces[0].traceEvents[0];
+ assert.strictEqual('tracespan', span1Event.cat);
+ assert.strictEqual('/first', span1Event.name);
+ assert.strictEqual(1441298400841000, span1Event.ts);
+ assert.strictEqual(15000, span1Event.dur);
+ assert.strictEqual('123', span1Event.pid);
+ assert.strictEqual('123', span1Event.tid);
+ assert.strictEqual(
+ '2015-09-03T16:40:00.856599389Z', span1Event.args['End Time']);
+ assert.strictEqual('1', span1Event.args['Span ID']);
+ assert.strictEqual('value1', span1Event.args.key1);
+ assert.strictEqual('value2', span1Event.args.key2);
+ assert.strictEqual(
+ '2015-09-03T16:40:00.856599389Z', span1Event.args['End Time']);
+
+ const span2Event = subtraces[0].traceEvents[1];
+ assert.strictEqual('tracespan', span2Event.cat);
+ assert.strictEqual('/second', span2Event.name);
+ assert.strictEqual(1441298400842000, span2Event.ts);
+ assert.strictEqual(9000, span2Event.dur);
+ assert.strictEqual('123', span2Event.pid);
+ assert.strictEqual('123', span2Event.tid);
+ });
+
+ test('canImport', function() {
+ assert.isTrue(GcloudTraceImporter.canImport(
+ JSON.stringify({projectId: 'My Project'})));
+ assert.isTrue(GcloudTraceImporter.canImport(
+ JSON.stringify({projectId: '56', traceId: '34'})));
+ assert.isFalse(GcloudTraceImporter.canImport(
+ JSON.stringify({wrongjson: '33'})));
+ assert.isFalse(GcloudTraceImporter.canImport(''));
+ assert.isFalse(GcloudTraceImporter.canImport([]));
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/gzip_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/gzip_importer.html
new file mode 100644
index 00000000000..88b6edba0d6
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/gzip_importer.html
@@ -0,0 +1,252 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/in_memory_trace_stream.html">
+<link rel="import" href="/tracing/extras/importer/pako.html">
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview GzipImporter inflates gzip compressed data and passes it along
+ * to an actual importer.
+ */
+tr.exportTo('tr.e.importer', function() {
+ const GZIP_MEMBER_HEADER_ID_SIZE = 3;
+
+ const GZIP_HEADER_ID1 = 0x1f;
+ const GZIP_HEADER_ID2 = 0x8b;
+ const GZIP_DEFLATE_COMPRESSION = 8;
+
+ function _stringToUInt8Array(str) {
+ const array = new Uint8Array(str.length);
+ for (let i = 0; i < str.length; ++i) {
+ array[i] = str.charCodeAt(i);
+ }
+ return array;
+ }
+
+ function GzipImporter(model, eventData) {
+ // Normalize the data into an Uint8Array.
+ this.inflateAsTraceStream = false;
+ if (typeof(eventData) === 'string' || eventData instanceof String) {
+ eventData = _stringToUInt8Array(eventData);
+ } else if (eventData instanceof ArrayBuffer) {
+ eventData = new Uint8Array(eventData);
+ } else if (eventData instanceof tr.b.InMemoryTraceStream) {
+ // This importer does not support processing general TraceStreams, only
+ // InMemoryTraceStreams for now.
+ eventData = eventData.data;
+ this.inflateAsTraceStream_ = true;
+ } else {
+ throw new Error('Unknown gzip data format');
+ }
+ this.model_ = model;
+ this.gzipData_ = eventData;
+ }
+
+ /**
+ * @param {eventData} Possibly gzip compressed data as a string or an
+ * ArrayBuffer.
+ * @return {boolean} Whether obj looks like gzip compressed data.
+ */
+ GzipImporter.canImport = function(eventData) {
+ // This importer does not support processing general TraceStreams, only
+ // InMemoryTraceStreams for now.
+ if (eventData instanceof tr.b.InMemoryTraceStream) {
+ eventData = eventData.header;
+ }
+
+ let header;
+ if (eventData instanceof ArrayBuffer) {
+ header = new Uint8Array(eventData.slice(0, GZIP_MEMBER_HEADER_ID_SIZE));
+ } else if (typeof(eventData) === 'string' || eventData instanceof String) {
+ header = eventData.substring(0, GZIP_MEMBER_HEADER_ID_SIZE);
+ header = _stringToUInt8Array(header);
+ } else {
+ return false;
+ }
+ return header[0] === GZIP_HEADER_ID1 &&
+ header[1] === GZIP_HEADER_ID2 &&
+ header[2] === GZIP_DEFLATE_COMPRESSION;
+ };
+
+ /**
+ * Inflates (decompresses) the data stored in the given gzip bitstream.
+ * @return {string} Inflated data.
+ */
+ GzipImporter.inflateGzipData_ = function(data) {
+ let position = 0;
+
+ function getByte() {
+ if (position >= data.length) {
+ throw new Error('Unexpected end of gzip data');
+ }
+ return data[position++];
+ }
+
+ function getWord() {
+ const low = getByte();
+ const high = getByte();
+ return (high << 8) + low;
+ }
+
+ function skipBytes(amount) {
+ position += amount;
+ }
+
+ function skipZeroTerminatedString() {
+ while (getByte() !== 0) {}
+ }
+
+ const id1 = getByte();
+ const id2 = getByte();
+ if (id1 !== GZIP_HEADER_ID1 || id2 !== GZIP_HEADER_ID2) {
+ throw new Error('Not gzip data');
+ }
+ const compressionMethod = getByte();
+ if (compressionMethod !== GZIP_DEFLATE_COMPRESSION) {
+ throw new Error('Unsupported compression method: ' + compressionMethod);
+ }
+ const flags = getByte();
+ const haveHeaderCrc = flags & (1 << 1);
+ const haveExtraFields = flags & (1 << 2);
+ const haveFileName = flags & (1 << 3);
+ const haveComment = flags & (1 << 4);
+
+ // Skip modification time, extra flags and OS.
+ skipBytes(4 + 1 + 1);
+
+ // Skip remaining fields before compressed data.
+ if (haveExtraFields) {
+ const bytesToSkip = getWord();
+ skipBytes(bytesToSkip);
+ }
+ if (haveFileName) skipZeroTerminatedString();
+ if (haveComment) skipZeroTerminatedString();
+ if (haveHeaderCrc) getWord();
+
+ // Inflate the data using pako.
+ const inflatedData = pako.inflateRaw(data.subarray(position));
+
+ if (this.inflateAsTraceStream_) {
+ return GzipImporter.transformToStream(inflatedData);
+ }
+
+ let string;
+ try {
+ string = GzipImporter.transformToString(inflatedData);
+ } catch (err) {
+ // It may be the case that inflated data does not fit into a V8 string. In
+ // that case, try to transform to a trace stream.
+ return GzipImporter.transformToStream(inflatedData);
+ }
+
+ if (inflatedData.length > 0 && string.length === 0) {
+ // Try transforming to a trace stream.
+ return GzipImporter.transformToStream(inflatedData);
+ }
+
+ return string;
+ };
+
+ GzipImporter.transformToStream = function(data) {
+ if (data instanceof Uint8Array) {
+ return new tr.b.InMemoryTraceStream(data, false);
+ }
+
+ throw new Error(`Cannot transform ${type} to TraceStream.`);
+ };
+
+ /**
+ * Transforms an array-like object to a string.
+ *
+ * Note that the following two expressions yield identical results:
+ *
+ * GzipImporter.transformToString_(data)
+ * JSZip.utils.transformTo('string', data)
+ *
+ * We use a custom static method because it is faster and, more importantly,
+ * avoids OOMing on large traces. See
+ * https://github.com/catapult-project/catapult/issues/2051.
+ */
+ GzipImporter.transformToString = function(data) {
+ if (typeof(data) === 'string') return data; // We already have a string.
+
+ // Fall back to manual conversion if TextDecoder is not available.
+ if (typeof TextDecoder === 'undefined') {
+ if (data instanceof ArrayBuffer) {
+ data = new Uint8Array(data);
+ }
+
+ // Based on JSZip.Utils.stringToArrayLike
+ const result = [];
+ let chunk = 65536;
+ let k = 0;
+ const len = data.length;
+
+ while (k < len && chunk > 1) {
+ try {
+ const chunklen = Math.min(k + chunk, len);
+ let dataslice;
+ if (data instanceof Array) {
+ dataslice = data.slice(k, chunklen);
+ } else {
+ dataslice = data.subarray(k, chunklen);
+ }
+ result.push(String.fromCharCode.apply(null, dataslice));
+ k += chunk;
+ } catch (e) {
+ chunk = Math.floor(chunk / 2);
+ }
+ }
+
+ return result.join('');
+ }
+
+ if (data instanceof Array) {
+ // TextDecoder requires an ArrayBuffer or an ArrayBufferView.
+ data = new Uint8Array(data);
+ }
+
+ return new TextDecoder('utf-8').decode(data);
+ };
+
+ GzipImporter.prototype = {
+ __proto__: tr.importer.Importer.prototype,
+
+ get importerName() {
+ return 'GzipImporter';
+ },
+
+ /**
+ * Called by the Model to check whether the importer just encapsulates
+ * the actual trace data which needs to be imported by another importer.
+ */
+ isTraceDataContainer() {
+ return true;
+ },
+
+ /**
+ * Called by the Model to extract subtraces from the event data. The
+ * subtraces are passed on to other importers that can recognize them.
+ */
+ extractSubtraces() {
+ const eventData = GzipImporter.inflateGzipData_(this.gzipData_);
+ return eventData ? [eventData] : [];
+ }
+ };
+
+ tr.importer.Importer.register(GzipImporter);
+
+ return {
+ GzipImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/gzip_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/gzip_importer_test.html
new file mode 100644
index 00000000000..32fd72be45a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/gzip_importer_test.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base64.html">
+<link rel="import" href="/tracing/base/trace_stream.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/gzip_importer.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Base64 = tr.b.Base64;
+ const findSliceNamed = tr.c.TestUtils.findSliceNamed;
+ const ORIGINAL_DATA =
+ '[{"name":"a","args":{},"pid":52,"ts":520,"cat":"foo","tid":53,' +
+ '"ph":"B"},{"name":"a","args":{},"pid":52,"ts":520,"cat":"foo",' +
+ '"tid":53,"ph":"E"}]\n';
+ const GZIP_DATA_BASE_64 =
+ 'H4sICHr4HVIAA3RyYWNlAIuuVspLzE1VslJKVNJRSixKL1ayqq7VUSrITFGy' +
+ 'MjXSUSopBtEGOkrJiSVAVWn5+UB1JWBZY6CyDKCYk1KtDhWMcVWqjeUCALak' +
+ 'EH+QAAAA';
+
+ test('failImportEmpty', function() {
+ assert.isFalse(tr.e.importer.GzipImporter.canImport([]));
+ assert.isFalse(tr.e.importer.GzipImporter.canImport(''));
+ });
+
+ test('inflateString', function() {
+ // Test inflating the data from a string.
+ const gzipData = Base64.atob(GZIP_DATA_BASE_64);
+ const importer = new tr.e.importer.GzipImporter(null, gzipData);
+ assert.isTrue(tr.e.importer.GzipImporter.canImport(gzipData));
+ assert.strictEqual(
+ importer.extractSubtraces()[0].toString(), ORIGINAL_DATA);
+ });
+
+ test('inflateArrayBuffer', function() {
+ // Test inflating the data from an ArrayBuffer.
+ const gzipData = Base64.atob(GZIP_DATA_BASE_64);
+ const buffer = new ArrayBuffer(gzipData.length);
+ const view = new Uint8Array(buffer);
+ for (let i = 0; i < gzipData.length; i++) {
+ view[i] = gzipData.charCodeAt(i);
+ }
+ const importer = new tr.e.importer.GzipImporter(null, buffer);
+ assert.isTrue(tr.e.importer.GzipImporter.canImport(buffer));
+ assert.strictEqual(
+ importer.extractSubtraces()[0].toString(), ORIGINAL_DATA);
+ });
+
+ test('import', function() {
+ const gzipData = Base64.atob(GZIP_DATA_BASE_64);
+ assert.isTrue(tr.e.importer.GzipImporter.canImport(gzipData));
+
+ const model = tr.c.TestUtils.newModelWithEvents(gzipData);
+ const threads = model.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const slice = findSliceNamed(threads[0].sliceGroup, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ });
+
+ test('transformToString', function() {
+ function checkTransform(data, expectedString) {
+ assert.strictEqual(tr.e.importer.GzipImporter.transformToString(data),
+ expectedString);
+ }
+
+ function createArrayBuffer(values) {
+ const buffer = new ArrayBuffer(values.length);
+ const view = new Uint8Array(buffer);
+ view.set(values);
+ return buffer;
+ }
+
+ // If the browser supports TextDecoder, this will test our custom
+ // implementation. Otherwise, the jszip fallback will be tested.
+ checkTransform('abc012', 'abc012');
+ checkTransform([100, 101, 102, 51, 52, 53], 'def345');
+ checkTransform(createArrayBuffer([103, 104, 105, 54, 55, 56]), 'ghi678');
+ checkTransform(new Uint8Array([106, 107, 108, 57, 58, 59]), 'jkl9:;');
+
+ if (typeof TextDecoder === 'undefined') {
+ // The browser doesn't support TextDecoder, so we have already checked
+ // the jszip fallback.
+ return;
+ }
+
+ // The browser supports TextDecoder, so we now check the jszip fallback.
+ const oldTextDecoder = TextDecoder;
+ TextDecoder = undefined;
+ try {
+ checkTransform('abc012', 'abc012');
+ checkTransform([100, 101, 102, 51, 52, 53], 'def345');
+ checkTransform(createArrayBuffer([103, 104, 105, 54, 55, 56]), 'ghi678');
+ checkTransform(new Uint8Array([106, 107, 108, 57, 58, 59]), 'jkl9:;');
+ } finally {
+ TextDecoder = oldTextDecoder;
+ }
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/heap_dump_trace_event_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/heap_dump_trace_event_importer.html
new file mode 100644
index 00000000000..e22671b6359
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/heap_dump_trace_event_importer.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/model/heap_dump.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Logic for importing a Heap Dump.
+ */
+tr.exportTo('tr.e.importer', function() {
+ /**
+ * @constructor
+ */
+ function HeapDumpTraceEventImporter(
+ heapProfileExpander,
+ stackFrames,
+ processMemoryDump,
+ idPrefix,
+ model) {
+ this.expander = heapProfileExpander;
+ this.stackFrames = stackFrames;
+ this.processMemoryDump = processMemoryDump;
+ this.idPrefix = idPrefix;
+ this.model = model;
+ }
+
+ HeapDumpTraceEventImporter.prototype = {
+
+ getLeafStackFrame(stackFrameId) {
+ // Root.
+ if (stackFrameId === '') return undefined;
+ const parentId = this.idPrefix + stackFrameId;
+ const id = parentId + ':self';
+
+ // In the new format all values are 'self' values,
+ // we distingiush these from the totals in the UI via
+ // artificial '<self>' stack frames.
+ if (!this.stackFrames[id]) {
+ const parentStackFrame = this.stackFrames[parentId];
+ const stackFrame = new tr.model.StackFrame(
+ parentStackFrame, id, '<self>',
+ undefined /* colorId */);
+ this.model.addStackFrame(stackFrame);
+ }
+ return this.stackFrames[id];
+ },
+
+ parseEntry(entry, heapDump) {
+ const size = entry.size;
+ const count = entry.count;
+ const leafStackFrame = this.getLeafStackFrame(entry.node.id);
+ const objectTypeName = entry.type.name;
+ const valuesAreTotals = false;
+ if (objectTypeName === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Missing object type name (ID ' + typeId + ')',
+ });
+ }
+ heapDump.addEntry(
+ leafStackFrame, objectTypeName, size, count, valuesAreTotals);
+ },
+
+ parse() {
+ const heapDumps = {};
+ const inflated = this.expander.inflated;
+ for (const [allocatorName, entries] of Object.entries(inflated)) {
+ const heapDump =
+ new tr.model.HeapDump(this.processMemoryDump, allocatorName);
+ for (const entry of entries) {
+ this.parseEntry(entry, heapDump);
+ }
+ heapDump.isComplete = true;
+ heapDumps[allocatorName] = heapDump;
+ }
+ return heapDumps;
+ },
+
+ };
+
+ return {
+ HeapDumpTraceEventImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/jszip.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/jszip.html
new file mode 100644
index 00000000000..5db6c4bbfc3
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/jszip.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/base/base.html">
+
+<script>
+'use strict';
+// Vinn workaround for JSzip requiring window.
+if (tr.isVinn) {
+ /**
+ * Hack.
+ */
+ global.window = {};
+}
+</script>
+<script src="/jszip.min.js"></script>
+<script>
+'use strict';
+// Vinn workaround for JSzip requiring window.
+if (tr.isVinn) {
+ /**
+ * Hack.
+ */
+ global.JSZip = global.window.JSZip;
+ global.window = undefined;
+} else if (tr.isNode) {
+ const jsZipAbsPath = HTMLImportsLoader.hrefToAbsolutePath(
+ '/jszip.min.js');
+ const jsZipModule = require(jsZipAbsPath);
+ global.JSZip = jsZipModule;
+}
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/legacy_heap_dump_trace_event_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/legacy_heap_dump_trace_event_importer.html
new file mode 100644
index 00000000000..ee0097b903c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/legacy_heap_dump_trace_event_importer.html
@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/model/heap_dump.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Logic for importing a Heap Dump.
+ */
+tr.exportTo('tr.e.importer', function() {
+ /**
+ * @constructor
+ * @param {!tr.model.Model} model The model we are currently building.
+ * @param {!tr.model.ProcessMemoryDump} processMemoryDump
+ * The parent memory dump for this heap dump.
+ * @param {!Map|undefined} processObjectTypeNameMap
+ * A map from raw heap dump 'type' ids to human-readable names.
+ * @param {!string} idPrefix Process-specific prefix to prepend to a stack
+ * trace id before looking it up in the model.
+ * @param {!string} dumpId
+ * Raw heap dump id, used only for nice error messages.
+ * @param {!Object} rawHeapDumps
+ * Raw heap dump.
+ */
+ function LegacyHeapDumpTraceEventImporter(
+ model,
+ processMemoryDump,
+ processObjectTypeNameMap,
+ idPrefix,
+ dumpId,
+ rawHeapDumps) {
+ this.model_ = model;
+ this.processObjectTypeNameMap_ = processObjectTypeNameMap;
+ this.idPrefix_ = idPrefix;
+ this.processMemoryDump_ = processMemoryDump;
+ this.pid_ = this.processMemoryDump_.process.pid;
+ this.dumpId_ = dumpId;
+ this.rawHeapDumps_ = rawHeapDumps;
+ }
+
+ LegacyHeapDumpTraceEventImporter.prototype = {
+ /**
+ * Parse rawHeapDump and add entries to heapDump.
+ *
+ * @param {!{!entries:(!Array<!Object>|undefined)}} rawHeapDump
+ * The data we're going to parse.
+ * @param {!string} allocatorName e.g. malloc.
+ * @return {!tr.model.HeapDump} on success or undefined on an error.
+ */
+ parseRawHeapDump(rawHeapDump, allocatorName) {
+ const model = this.model_;
+ const processMemoryDump = this.processMemoryDump_;
+ const heapDump = new tr.model.HeapDump(processMemoryDump, allocatorName);
+
+ const entries = rawHeapDump.entries;
+ if (entries === undefined || entries.length === 0) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'No heap entries in a ' + allocatorName +
+ ' heap dump for PID=' + this.pid_ +
+ ' and dump ID=' + this.dumpId_ + '.'
+ });
+
+ return undefined;
+ }
+
+ // The old format always starts with a {size: <total>} entry.
+ // See https://goo.gl/WYStil
+ // TODO(petrcermak): Remove support for the old format once the new
+ // format has been around long enough.
+ const isOldFormat = entries[0].bt === undefined;
+ if (!isOldFormat && this.processObjectTypeNameMap_ === undefined) {
+ // Mapping from object type IDs to names must be provided in the new
+ // format.
+ return undefined;
+ }
+
+ for (let i = 0; i < entries.length; i++) {
+ const entry = entries[i];
+ const size = parseInt(entry.size, 16);
+ const leafStackFrameIndex = entry.bt;
+ let leafStackFrame;
+
+ // There are two possible mappings from leaf stack frame indices
+ // (provided in the trace) to the corresponding stack frames
+ // depending on the format.
+ if (isOldFormat) {
+ // Old format:
+ // Undefined index -> / (root)
+ // Defined index for /A/B -> /A/B/<self>
+ if (leafStackFrameIndex === undefined) {
+ leafStackFrame = undefined; /* root */
+ } else {
+ // Get the leaf stack frame corresponding to the provided index.
+ let leafStackFrameId = this.idPrefix_ + leafStackFrameIndex;
+ if (leafStackFrameIndex === '') {
+ leafStackFrame = undefined; /* root */
+ } else {
+ leafStackFrame = model.stackFrames[leafStackFrameId];
+ if (leafStackFrame === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Missing leaf stack frame (ID ' +
+ leafStackFrameId + ') of heap entry ' + i + ' (size ' +
+ size + ') in a ' + allocatorName +
+ ' heap dump for PID=' + this.pid_ + '.'
+ });
+ continue;
+ }
+ }
+
+ // Inject an artificial <self> leaf stack frame.
+ leafStackFrameId += ':self';
+ if (model.stackFrames[leafStackFrameId] !== undefined) {
+ // The frame might already exist if there are multiple process
+ // memory dumps (for the same process) in the trace.
+ leafStackFrame = model.stackFrames[leafStackFrameId];
+ } else {
+ leafStackFrame = new tr.model.StackFrame(
+ leafStackFrame, leafStackFrameId, '<self>',
+ undefined /* colorId */);
+ model.addStackFrame(leafStackFrame);
+ }
+ }
+ } else {
+ // New format:
+ // Undefined index -> (invalid value)
+ // Defined index for /A/B -> /A/B
+ if (leafStackFrameIndex === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Missing stack frame ID of heap entry ' + i +
+ ' (size ' + size + ') in a ' + allocatorName +
+ ' heap dump for PID=' + this.pid_ + '.'
+ });
+ continue;
+ }
+
+ // Get the leaf stack frame corresponding to the provided index.
+ const leafStackFrameId = this.idPrefix_ + leafStackFrameIndex;
+ if (leafStackFrameIndex === '') {
+ leafStackFrame = undefined; /* root */
+ } else {
+ leafStackFrame = model.stackFrames[leafStackFrameId];
+ if (leafStackFrame === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Missing leaf stack frame (ID ' + leafStackFrameId +
+ ') of heap entry ' + i + ' (size ' + size + ') in a ' +
+ allocatorName + ' heap dump for PID=' + this.pid_ + '.'
+ });
+ continue;
+ }
+ }
+ }
+
+ const objectTypeId = entry.type;
+ let objectTypeName;
+ if (objectTypeId === undefined) {
+ objectTypeName = undefined; /* total over all types */
+ } else if (this.processObjectTypeNameMap_ === undefined) {
+ // This can only happen when the old format is used.
+ continue;
+ } else {
+ objectTypeName = this.processObjectTypeNameMap_[objectTypeId];
+ if (objectTypeName === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Missing object type name (ID ' + objectTypeId +
+ ') of heap entry ' + i + ' (size ' + size + ') in a ' +
+ allocatorName + ' heap dump for PID=' + this.pid_ + '.'
+ });
+ continue;
+ }
+ }
+
+ const count = entry.count === undefined ? undefined :
+ parseInt(entry.count, 16);
+ heapDump.addEntry(leafStackFrame, objectTypeName, size, count);
+ }
+
+ return heapDump;
+ },
+
+ parse() {
+ const heapDumps = {};
+ for (const allocatorName in this.rawHeapDumps_) {
+ const rawHeapDump = this.rawHeapDumps_[allocatorName];
+ const heapDump = this.parseRawHeapDump(rawHeapDump, allocatorName);
+
+ // Throw away heap dumps with no entries. This can happen if all raw
+ // entries in the trace are skipped for some reason (e.g. invalid leaf
+ // stack frame ID).
+ if (heapDump !== undefined && heapDump.entries.length > 0) {
+ heapDumps[allocatorName] = heapDump;
+ }
+ }
+ return heapDumps;
+ },
+
+ };
+
+ return {
+ LegacyHeapDumpTraceEventImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/android_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/android_parser.html
new file mode 100644
index 00000000000..bf534205db6
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/android_parser.html
@@ -0,0 +1,239 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+<link rel="import" href="/tracing/model/counter_series.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses trace_marker events that were inserted in the trace by
+ * userland.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux trace mark events that were inserted in the trace by userland.
+ * @constructor
+ */
+ function AndroidParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('tracing_mark_write:android',
+ AndroidParser.prototype.traceMarkWriteAndroidEvent.bind(this));
+ importer.registerEventHandler('0:android',
+ AndroidParser.prototype.traceMarkWriteAndroidEvent.bind(this));
+
+ this.model_ = importer.model_;
+ this.ppids_ = {};
+ }
+
+ function parseArgs(argsString) {
+ const args = {};
+ if (argsString) {
+ const argsArray = argsString.split(';');
+ for (let i = 0; i < argsArray.length; ++i) {
+ const parts = argsArray[i].split('=');
+ if (parts[0]) {
+ args[parts.shift()] = parts.join('=');
+ }
+ }
+ }
+ return args;
+ }
+
+ AndroidParser.prototype = {
+ __proto__: Parser.prototype,
+
+ openAsyncSlice(thread, category, name, cookie, ts, args) {
+ const asyncSliceConstructor =
+ tr.model.AsyncSlice.subTypes.getConstructor(
+ category, name);
+ const slice = new asyncSliceConstructor(
+ category, name,
+ ColorScheme.getColorIdForGeneralPurposeString(name), ts, args);
+ const key = category + ':' + name + ':' + cookie;
+ slice.id = cookie;
+ slice.startThread = thread;
+
+ if (!this.openAsyncSlices) {
+ this.openAsyncSlices = { };
+ }
+ this.openAsyncSlices[key] = slice;
+ },
+
+ closeAsyncSlice(thread, category, name, cookie, ts, args) {
+ if (!this.openAsyncSlices) {
+ // No async slices have been started.
+ return;
+ }
+
+ const key = category + ':' + name + ':' + cookie;
+ const slice = this.openAsyncSlices[key];
+ if (!slice) {
+ // No async slices w/ this key have been started.
+ return;
+ }
+
+ for (const arg in args) {
+ if (slice.args[arg] !== undefined) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Both the S and F events of ' + slice.title +
+ ' provided values for argument ' + arg + '.' +
+ ' The value of the F event will be used.'
+ });
+ }
+ slice.args[arg] = args[arg];
+ }
+
+ slice.endThread = thread;
+ slice.duration = ts - slice.start;
+ slice.startThread.asyncSliceGroup.push(slice);
+ delete this.openAsyncSlices[key];
+ },
+
+ traceMarkWriteAndroidEvent(eventName, cpuNumber, pid, ts,
+ eventBase) {
+ const eventData = eventBase.details.split('|');
+ switch (eventData[0]) {
+ case 'B': {
+ const ppid = parseInt(eventData[1]);
+ const title = eventData[2];
+ const args = parseArgs(eventData[3]);
+ let category = eventData[4];
+ if (category === undefined) category = 'android';
+
+ const thread = this.model_.getOrCreateProcess(ppid)
+ .getOrCreateThread(pid);
+ thread.name = eventBase.threadName;
+ if (!thread.sliceGroup.isTimestampValidForBeginOrEnd(ts)) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Timestamps are moving backward.'
+ });
+ return false;
+ }
+
+ this.ppids_[pid] = ppid;
+ thread.sliceGroup.beginSlice(category, title, ts, args);
+
+ break;
+ }
+
+ case 'E': {
+ const ppid = this.ppids_[pid];
+ if (ppid === undefined) {
+ // Silently ignore unmatched E events.
+ break;
+ }
+
+ const thread = this.model_.getOrCreateProcess(ppid)
+ .getOrCreateThread(pid);
+ if (!thread.sliceGroup.openSliceCount) {
+ // Silently ignore unmatched E events.
+ break;
+ }
+
+ const slice = thread.sliceGroup.endSlice(ts);
+
+ const args = parseArgs(eventData[3]);
+ for (const arg in args) {
+ if (slice.args[arg] !== undefined) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Both the B and E events of ' + slice.title +
+ ' provided values for argument ' + arg + '.' +
+ ' The value of the E event will be used.'
+ });
+ }
+ slice.args[arg] = args[arg];
+ }
+
+ break;
+ }
+
+ case 'C': {
+ const ppid = parseInt(eventData[1]);
+ const name = eventData[2];
+ const value = parseInt(eventData[3]);
+ let category = eventData[4];
+ if (category === undefined) category = 'android';
+
+ const ctr = this.model_.getOrCreateProcess(ppid)
+ .getOrCreateCounter(category, name);
+ // Initialize the counter's series fields if needed.
+ if (ctr.numSeries === 0) {
+ ctr.addSeries(new tr.model.CounterSeries(value,
+ ColorScheme.getColorIdForGeneralPurposeString(
+ ctr.name + '.' + 'value')));
+ }
+
+ ctr.series.forEach(function(series) {
+ series.addCounterSample(ts, value);
+ });
+
+ break;
+ }
+
+ case 'S': {
+ const ppid = parseInt(eventData[1]);
+ const name = eventData[2];
+ const cookie = parseInt(eventData[3]);
+ const args = parseArgs(eventData[4]);
+ let category = eventData[5];
+ if (category === undefined) category = 'android';
+
+ const thread = this.model_.getOrCreateProcess(ppid)
+ .getOrCreateThread(pid);
+ thread.name = eventBase.threadName;
+
+ this.ppids_[pid] = ppid;
+ this.openAsyncSlice(thread, category, name, cookie, ts, args);
+
+ break;
+ }
+
+ case 'F': {
+ // Note: An async slice may end on a different thread from the one
+ // that started it so this thread may not have been seen yet.
+ const ppid = parseInt(eventData[1]);
+
+ const name = eventData[2];
+ const cookie = parseInt(eventData[3]);
+ const args = parseArgs(eventData[4]);
+ let category = eventData[5];
+ if (category === undefined) category = 'android';
+
+ const thread = this.model_.getOrCreateProcess(ppid)
+ .getOrCreateThread(pid);
+ thread.name = eventBase.threadName;
+
+ this.ppids_[pid] = ppid;
+ this.closeAsyncSlice(thread, category, name, cookie, ts, args);
+
+ break;
+ }
+
+ default:
+ return false;
+ }
+
+ return true;
+ }
+ };
+
+ Parser.register(AndroidParser);
+
+ return {
+ AndroidParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/android_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/android_parser_test.html
new file mode 100644
index 00000000000..0e53e7e5e65
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/android_parser_test.html
@@ -0,0 +1,233 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ test('androidUserlandImport', function() {
+ const lines = [
+ 'SurfaceFlinger-4831 [001] ...1 80909.598554: tracing_mark_write: B|4829|onMessageReceived', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.598572: tracing_mark_write: B|4829|handleMessageInvalidate', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.598590: tracing_mark_write: B|4829|latchBuffer', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.598604: tracing_mark_write: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598627: tracing_mark_write: B|4829|latchBuffer', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.598651: tracing_mark_write: B|4829|updateTexImage', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.598675: tracing_mark_write: B|4829|acquireBuffer', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.598695: tracing_mark_write: B|4829|' + // @suppress longLineCheck
+ 'com.android.launcher/com.android.launcher2.Launcher: 0',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598709: tracing_mark_write: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598733: tracing_mark_write: C|4829|' + // @suppress longLineCheck
+ 'com.android.launcher/com.android.launcher2.Launcher|0',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598746: tracing_mark_write: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598844: tracing_mark_write: B|4829|releaseBuffer', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.598862: tracing_mark_write: B|4829|' + // @suppress longLineCheck
+ 'com.android.launcher/com.android.launcher2.Launcher: 2',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598876: tracing_mark_write: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598892: tracing_mark_write: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598925: tracing_mark_write: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598955: tracing_mark_write: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598988: tracing_mark_write: B|4829|latchBuffer', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.599001: tracing_mark_write: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.599021: tracing_mark_write: B|4829|latchBuffer', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.599036: tracing_mark_write: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.599068: tracing_mark_write: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.599087: tracing_mark_write: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.599104: tracing_mark_write: E'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.parent.pid, 4829);
+ assert.strictEqual(thread.tid, 4831);
+ assert.strictEqual(thread.name, 'SurfaceFlinger');
+ assert.strictEqual(thread.sliceGroup.length, 11);
+ });
+
+ test('androidUserlandImportWithSpacesInThreadName', function() {
+ const lines = [
+ 'Surface Flinger -4831 [001] ...1 80909.598590: tracing_mark_write: B|4829|latchBuffer', // @suppress longLineCheck
+ 'Surface Flinger -4831 [001] ...1 80909.598604: tracing_mark_write: E' // @suppress longLineCheck
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.parent.pid, 4829);
+ assert.strictEqual(thread.tid, 4831);
+ assert.strictEqual(thread.name, 'Surface Flinger ');
+ assert.strictEqual(thread.sliceGroup.length, 1);
+ });
+
+ test('androidAsyncUserlandImport', function() {
+ const lines = [
+ 'ndroid.launcher-9649 ( 9649) [000] ...1 1990280.663276: ' +
+ 'tracing_mark_write: S|9649|animator:childrenOutlineAlpha|' +
+ '1113053968',
+ 'ndroid.launcher-9649 ( 9649) [000] ...1 1990280.781445: ' +
+ 'tracing_mark_write: F|9649|animator:childrenOutlineAlpha|' +
+ '1113053968'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.parent.pid, 9649);
+ assert.strictEqual(thread.tid, 9649);
+ assert.strictEqual(thread.name, 'ndroid.launcher');
+ assert.strictEqual(thread.sliceGroup.length, 0);
+ assert.strictEqual(thread.asyncSliceGroup.length, 1);
+
+ const slice = thread.asyncSliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'animator:childrenOutlineAlpha');
+ assert.closeTo(118.169, slice.duration, 1e-5);
+
+ // no subslice events created
+ assert.strictEqual(slice.subSlices.length, 0);
+ });
+
+ test('androidUserlandLegacyKernelImport', function() {
+ const lines = [
+ 'SurfaceFlinger-4831 [001] ...1 80909.598554: 0: B|4829|onMessageReceived', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.598572: 0: B|4829|handleMessageInvalidate', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.598590: 0: B|4829|latchBuffer',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598604: 0: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598627: 0: B|4829|latchBuffer',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598651: 0: B|4829|updateTexImage', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.598675: 0: B|4829|acquireBuffer', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.598695: 0: B|4829|' +
+ 'com.android.launcher/com.android.launcher2.Launcher: 0',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598709: 0: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598733: 0: C|4829|' +
+ 'com.android.launcher/com.android.launcher2.Launcher|0',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598746: 0: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598844: 0: B|4829|releaseBuffer', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.598862: 0: B|4829|' +
+ 'com.android.launcher/com.android.launcher2.Launcher: 2',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598876: 0: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598892: 0: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598925: 0: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598955: 0: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.598988: 0: B|4829|latchBuffer', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.599001: 0: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.599021: 0: B|4829|latchBuffer', // @suppress longLineCheck
+ 'SurfaceFlinger-4831 [001] ...1 80909.599036: 0: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.599068: 0: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.599087: 0: E',
+ 'SurfaceFlinger-4831 [001] ...1 80909.599104: 0: E'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.parent.pid, 4829);
+ assert.strictEqual(thread.tid, 4831);
+ assert.strictEqual(thread.name, 'SurfaceFlinger');
+ assert.strictEqual(thread.sliceGroup.length, 11);
+ });
+
+ test('androidUserlandChromiumImport', function() {
+ const lines = [
+ 'SandboxedProces-2894 [001] ...1 253.780659: tracing_mark_write: B|2867|DoWorkLoop|arg1=1|cat1', // @suppress longLineCheck
+ 'SandboxedProces-2894 [001] ...1 253.780671: tracing_mark_write: B|2867|DeferOrRunPendingTask|source=test=test;task=xyz|cat2', // @suppress longLineCheck
+ 'SandboxedProces-2894 [001] ...1 253.780671: tracing_mark_write: E|2867|DeferOrRunPendingTask||cat1', // @suppress longLineCheck
+ 'SandboxedProces-2894 [001] ...1 253.780686: tracing_mark_write: B|2867|MessageLoop::RunTask|source=ipc/ipc_sync_message_filter.cc:Send|cat2', // @suppress longLineCheck
+ 'SandboxedProces-2894 [001] ...1 253.780700: tracing_mark_write: E|2867|MessageLoop::RunTask||cat1', // @suppress longLineCheck
+ 'SandboxedProces-2894 [001] ...1 253.780750: tracing_mark_write: C|2867|counter1|10|cat1', // @suppress longLineCheck
+ 'SandboxedProces-2894 [001] ...1 253.780859: tracing_mark_write: E|2867|DoWorkLoop|arg2=2|cat2', // @suppress longLineCheck
+ 'SandboxedProces-2894 [000] ...1 255.663276: tracing_mark_write: S|2867|async|1113053968|arg1=1;arg2=2|cat1', // @suppress longLineCheck
+ 'SandboxedProces-2894 [000] ...1 255.663276: tracing_mark_write: F|2867|async|1113053968|arg3=3|cat1', // @suppress longLineCheck
+ 'SandboxedProces-2894 [000] ...1 255.663276: tracing_mark_write: trace_event_clock_sync: parent_ts=128' // @suppress longLineCheck
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.parent.pid, 2867);
+ assert.strictEqual(thread.tid, 2894);
+ assert.strictEqual(thread.name, 'SandboxedProces');
+ assert.strictEqual(thread.sliceGroup.length, 3);
+
+ assert.strictEqual(thread.sliceGroup.slices[0].args.arg1, '1');
+ assert.strictEqual(thread.sliceGroup.slices[0].args.arg2, '2');
+
+ assert.strictEqual(thread.sliceGroup.slices[1].args.source, 'test=test');
+ assert.strictEqual(thread.sliceGroup.slices[1].category, 'cat2');
+ assert.strictEqual('DeferOrRunPendingTask',
+ thread.sliceGroup.slices[1].title);
+ assert.strictEqual(thread.sliceGroup.slices[1].args.task, 'xyz');
+
+ assert.strictEqual('ipc/ipc_sync_message_filter.cc:Send',
+ thread.sliceGroup.slices[2].args.source);
+
+ assert.strictEqual(thread.asyncSliceGroup.length, 1);
+ assert.strictEqual(thread.asyncSliceGroup.slices[0].args.arg1, '1');
+ assert.strictEqual(thread.asyncSliceGroup.slices[0].args.arg2, '2');
+ assert.strictEqual(thread.asyncSliceGroup.slices[0].args.arg3, '3');
+
+ const counters = m.getAllCounters();
+ assert.strictEqual(counters.length, 1);
+ assert.strictEqual(counters[0].category, 'cat1');
+ assert.strictEqual(counters[0].name, 'counter1');
+
+ assert.strictEqual(counters[0].numSamples, 1);
+ assert.strictEqual(counters[0].getSeries(0).getSample(0).value, 10);
+
+ assert.strictEqual(Math.round((253.780659 - (255.663276 - 128)) * 1000),
+ Math.round(thread.sliceGroup.slices[0].start));
+ });
+
+ test('androidNeedReschedImport', function() {
+ const lines = [
+ 'RenderThread-3894 [001] ...1 253.780659: tracing_mark_write: B|3586|DrawFrame', // @suppress longLineCheck
+ 'RenderThread-3894 [001] ...1 253.780671: tracing_mark_write: B|3586|waitForTask', // @suppress longLineCheck
+ 'RenderThread-3894 [001] .N.1 253.780671: tracing_mark_write: E', // @suppress longLineCheck
+ 'RenderThread-3894 [001] ...1 253.780671: tracing_mark_write: B|3586|waitForTask', // @suppress longLineCheck
+ 'RenderThread-3894 [001] .n.1 253.780671: tracing_mark_write: E', // @suppress longLineCheck
+ 'RenderThread-3894 [001] ...1 253.780671: tracing_mark_write: B|3586|waitForTask', // @suppress longLineCheck
+ 'RenderThread-3894 [001] .p.1 253.780671: tracing_mark_write: E', // @suppress longLineCheck
+ 'RenderThread-3894 [001] ...1 253.780686: tracing_mark_write: E' // @suppress longLineCheck
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings, 'hasImportWarnings');
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.parent.pid, 3586);
+ assert.strictEqual(thread.tid, 3894);
+ assert.strictEqual(thread.name, 'RenderThread');
+ assert.strictEqual(thread.sliceGroup.length, 4);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/binder_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/binder_parser.html
new file mode 100644
index 00000000000..690d2dd6786
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/binder_parser.html
@@ -0,0 +1,732 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses Binder events
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ // Matches binder transactions:
+ // transaction=%d dest_node=%d dest_proc=%d dest_thread=%d reply=%d flags=0x%x
+ // code=0x%x
+ const binderTransRE = new RegExp('transaction=(\\d+) dest_node=(\\d+) ' +
+ 'dest_proc=(\\d+) dest_thread=(\\d+) ' +
+ 'reply=(\\d+) flags=(0x[0-9a-fA-F]+) ' +
+ 'code=(0x[0-9a-fA-F]+)');
+ // binder_transaction_alloc_buf: transaction=%d data_size=%d offsets_size=%d
+ const binderAllocRE = new RegExp('transaction=(\\d+) data_size=(\\d+) ' +
+ 'offsets_size=(\\d+)');
+
+ const binderTransReceivedRE = /transaction=(\d+)/;
+
+ function isBinderThread(name) {
+ return (name.indexOf('Binder') > -1);
+ }
+
+ // Taken from kernel source: include/uapi/linux/android/binder.h.
+ const TF_ONE_WAY = 0x01;
+ const TF_ROOT_OBJECT = 0x04;
+ const TF_STATUS_CODE = 0x08;
+ const TF_ACCEPT_FDS = 0x10;
+ const NO_FLAGS = 0;
+
+ function binderFlagsToHuman(num) {
+ const flag = parseInt(num, 16);
+ let str = '';
+
+ if (flag & TF_ONE_WAY) {
+ str += 'this is a one-way call: async, no return; ';
+ }
+ if (flag & TF_ROOT_OBJECT) {
+ str += 'contents are the components root object; ';
+ }
+ if (flag & TF_STATUS_CODE) {
+ str += 'contents are a 32-bit status code; ';
+ }
+ if (flag & TF_ACCEPT_FDS) {
+ str += 'allow replies with file descriptors; ';
+ }
+ if (flag === NO_FLAGS) {
+ str += 'No Flags Set';
+ }
+
+ return str;
+ }
+
+ function isReplyToOrigin(calling, called) {
+ return (called.dest_proc === calling.calling_pid ||
+ called.dest_thread === calling.calling_pid);
+ }
+
+ function binderCodeToHuman(code) {
+ return 'Java Layer Dependent';
+ }
+
+ function doInternalSlice(trans, slice, ts) {
+ if (slice.subSlices.length !== 0) {
+ /* We want to make sure we keep moving the small slice to the end of
+ the big slice or else the arrows will not point to the end.
+ */
+ slice.subSlices[0].start = ts;
+ return slice.subSlices[0];
+ }
+ const kthread = trans.calling_kthread.thread;
+ const internalSlice = kthread.sliceGroup.pushCompleteSlice('binder',
+ slice.title,
+ ts, .001, 0, 0,
+ slice.args);
+
+ internalSlice.title = slice.title;
+ internalSlice.id = slice.id;
+ internalSlice.colorId = slice.colorId;
+ slice.subSlices.push(internalSlice);
+ return internalSlice;
+ }
+
+ function generateBinderArgsForSlice(trans, cThreadName) {
+ return {
+ 'Transaction Id': trans.transaction_key,
+ 'Destination Node': trans.dest_node,
+ 'Destination Process': trans.dest_proc,
+ 'Destination Thread': trans.dest_thread,
+ 'Destination Name': cThreadName,
+ 'Reply transaction?': trans.is_reply_transaction,
+ 'Flags': trans.flags + ' ' +
+ binderFlagsToHuman(trans.flags),
+
+ 'Code': trans.code + ' ' +
+ binderCodeToHuman(trans.code),
+
+ 'Calling PID': trans.calling_pid,
+ 'Calling tgid': trans.calling_kthread.thread.parent.pid
+ };
+ }
+
+ /** @constructor */
+ function BinderTransaction(events, callingPid, callingTs, callingKthread) {
+ this.transaction_key = parseInt(events[1]);
+ this.dest_node = parseInt(events[2]);
+ this.dest_proc = parseInt(events[3]);
+ this.dest_thread = parseInt(events[4]);
+ this.is_reply_transaction = parseInt(events[5]) === 1 ? true : false;
+ this.expect_reply = ((this.is_reply_transaction === false) &&
+ (parseInt(events[6], 16) & TF_ONE_WAY) === 0);
+
+ this.flags = events[6];
+ this.code = events[7];
+ this.calling_pid = callingPid;
+ this.calling_ts = callingTs;
+ this.calling_kthread = callingKthread;
+ }
+
+
+ /** @constructor */
+ function BinderParser(importer) {
+ Parser.call(this, importer);
+ importer.registerEventHandler('binder_locked',
+ BinderParser.prototype.
+ binderLocked.bind(this));
+ importer.registerEventHandler('binder_unlock',
+ BinderParser.prototype.
+ binderUnlock.bind(this));
+ importer.registerEventHandler('binder_lock',
+ BinderParser.prototype.binderLock.bind(this));
+ importer.registerEventHandler('binder_transaction',
+ BinderParser.prototype.
+ binderTransaction.bind(this));
+ importer.registerEventHandler('binder_transaction_received',
+ BinderParser.prototype.
+ binderTransactionReceived.bind(this));
+ importer.registerEventHandler('binder_transaction_alloc_buf',
+ BinderParser.prototype.
+ binderTransactionAllocBuf.bind(this));
+
+ this.model_ = importer.model;
+ this.kthreadlookup = {};
+ this.importer_ = importer;
+ this.transWaitingRecv = {};
+ this.syncTransWaitingCompletion = {};
+ this.recursiveSyncTransWaitingCompletion_ByPID = {};
+ this.receivedTransWaitingConversion = {};
+ }
+
+ BinderParser.prototype = {
+ __proto__: Parser.prototype,
+
+ binderLock(eventName, cpuNumber, pid, ts, eventBase) {
+ const tgid = parseInt(eventBase.tgid);
+ if (isNaN(tgid)) return false;
+
+ this.doNameMappings(pid, tgid, eventName.threadName);
+
+ const kthread = this.importer_.
+ getOrCreateBinderKernelThread(eventBase.threadName, tgid, pid);
+
+ kthread.binderAttemptLockTS = ts;
+ kthread.binderOpenTsA = ts;
+ return true;
+ },
+
+ binderLocked(eventName, cpuNumber, pid, ts, eventBase) {
+ const tgid = parseInt(eventBase.tgid);
+ if (isNaN(tgid)) return false;
+
+ const binderThread = isBinderThread(eventBase.threadName);
+ const name = eventBase.threadName;
+
+ const kthread = this.importer_.
+ getOrCreateBinderKernelThread(eventBase.threadName, tgid, pid);
+
+ this.doNameMappings(pid, tgid, name);
+
+ const rthread = kthread.thread;
+ kthread.binderLockAquiredTS = ts;
+
+ if (kthread.binderAttemptLockTS === undefined) return false;
+
+ const args = this.generateArgsForSlice(tgid, pid, name, kthread);
+ rthread.sliceGroup.pushCompleteSlice('binder', 'binder lock waiting',
+ kthread.binderAttemptLockTS,
+ ts - kthread.binderAttemptLockTS,
+ 0, 0, args);
+
+ kthread.binderAttemptLockTS = undefined;
+ return true;
+ },
+
+ binderUnlock(eventName, cpuNumber, pid, ts, eventBase) {
+ const tgid = parseInt(eventBase.tgid);
+ if (isNaN(tgid)) return false;
+
+ const kthread = this.importer_.
+ getOrCreateBinderKernelThread(
+ eventBase.threadName, tgid, pid);
+
+ if (kthread.binderLockAquiredTS === undefined) return false;
+
+ const args = this.generateArgsForSlice(tgid, pid, eventBase.threadName,
+ kthread);
+ kthread.thread.sliceGroup.pushCompleteSlice(
+ 'binder',
+ 'binder lock held',
+ kthread.binderLockAquiredTS,
+ ts - kthread.binderLockAquiredTS,
+ 0, 0, args);
+
+ kthread.binderLockAquiredTS = undefined;
+ return true;
+ },
+
+ /** There are a few transaction status changes that signify
+ * progress through a binder transaction:
+ *
+ * Case One: Sync transaction.
+ * Thread A calls a blocking function on Thread B. We receive a
+ * binder_transaction msg From thread A stating that it is going to Call
+ * thread B. We create a slice and a binder object for this transaction and
+ * add it to addTransactionWaitingForRecv(transaction key, binder object)
+ * This notifies thread B and passes the slice, binder object and time
+ * stamp.
+ *
+ * Case Two: Async transaction.
+ * Thread A calls an async function on Thread B. Like above we receive a
+ * binder_transaction message, but the flags differ from above. The
+ * TF_ONEWAY flags are set so we know that when Thread B gets the
+ * binder_transaction_received with the same transaciton key the total
+ * transaction is complete.
+ *
+ * Case Three: 'Prior_receive'
+ * Prior_receive occurs when the thread being called (think A calls B),
+ * receives a binder_transaction_received message, but cannot correlate it
+ * to any current outstanding recursive transactions. That means the
+ * message it just received is the start of some communication, not some
+ * ongoing communication.
+ * Once the execution flow has been passed to thread B, from A:
+ * Thread B enters binder_transaction_received() we see that Thread A
+ * had notified us that it sent us a message by calling
+ * getTransactionWaitingForRecv(transaction key);
+ * What can happen now is either this was a simple Call reply,
+ * or this is a call -> recursion -> reply. We call modelPriorReceive()
+ * which sets up the slices accordingly.
+ * If this is a call -> recursion -> reply
+ * we will go to case 4 by calling addRecursiveSyncTransNeedingCompletion()
+ * The function takes B's PID, the binder object from A and the current
+ * binder object from B. This function adds outstanding non-complete
+ * transactions to a stack on thread B.
+ *
+ * Case Four: 'recursiveTrans'
+ * This case follows Like above:
+ * A sent binder_transaction
+ * B got binder_transaction_received
+ * B instead of replying to A can Call C or call 'into' A, ie recursion
+ * Case four also deals with setting up a large slice to 'contain'
+ * all the recursive transactions that happen until B finally replies to
+ * A.
+ *
+ *
+ * An example: A-> B-> C-> B-> A
+ *
+ * (1) A starts a synchronous transaction to B.
+ * (2) A enters binderTransaction() event handler, hits the else statement
+ * (3) A calls addTransactionWaitingForRecv(trans key, object) to notify
+ * Thread B.
+ * (4) B Enters binderTransactionReceived().
+ * (5) B hits the second if after calling
+ * getTransactionWaitingForRecv(trans key)
+ * This function returns us the object set up in step (3).
+ * (6) This is not an async transaction, B calls
+ * setCurrentReceiveOnPID(B's PID, [ts for (4), object from (3)]).
+ *
+ * (7) B enters binderTransaction() event handler, first if triggers after
+ * calling getPriorReceiveOnPID(B's PID) the tuple from (6) is returned.
+ *
+ * (8) Execution enters modelPriorReceive().
+ * (8a) A slice is setup for this binder transaction in B's timeline.
+ * (9) This is not a reply to thread A, B is going to call Thread C.
+ * (10) else statement is hit.
+ * (11) We set the tile from (8a) to be binder_reply this is the
+ * 'containg slice' for the recursion
+ * (12) We create a new slice 'binder_transaction' this slice is the
+ * recursive slice that will have arrows to Thread C's slice.
+ * (13) addRecursiveSyncTransNeedingCompletion(B's PID,
+ * [obj from (3), obj from 7])
+ * this sets up notification that B's pid has outstanding recursive
+ * transactions that need to be completed.
+ * (14) B notifies C that a transaction is waiting for it by calling
+ * addTransactionWaitingForRecv like in step (3).
+ * (15) C enters binderTransactionReceived() step 5 6 7 8 8a happen, but in
+ * the context of Thread C.
+ * (16) C is in modelPriorReceive(), it hits the first if statement,
+ * this transaction _IS_ a reply, and it is a reply to B.
+ * (17) C calls addSyncTransNeedingCompletion(trans key,
+ * [object from(3), object from 15-5])
+ * (18) B enters binderTransactionReceived() hits the first if after calling
+ * getSyncTransNeedingCompletion(trans key from (17)) the tuple from
+ * (17) gets returned.
+ *
+ * (19) B scales up the slice created in (12) and sets up flows from 15-8a
+ * slice.
+ * (20) B enters BinderTransaction() event handler and the second if is hit
+ * after calling getRecursiveTransactionNeedingCompletion(B's pid).
+ * (21) modelRecursiveTransactions() gets called, first if executes.
+ * (22) slice durations are fixed up.
+ * (23) B notifies A via
+ * addSyncTransNeedingCompletion(trans key, binder obj from 8a).
+ * (24) B deletes the outstanding asynctrans via
+ ( removeRecursiveTransaction(B's pid).
+ * (25) A enters binderTransactionReceived() event handler and finishes up
+ * some flows, and slices.
+ */
+ binderTransaction(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = binderTransRE.exec(eventBase.details);
+ if (event === undefined) return false;
+
+ const tgid = parseInt(eventBase.tgid);
+ if (isNaN(tgid)) return false;
+
+ this.doNameMappings(pid, tgid, eventBase.threadName);
+
+ const kthread = this.importer_.
+ getOrCreateBinderKernelThread(eventBase.threadName, tgid, pid);
+
+ const trans = new BinderTransaction(event, pid, ts, kthread);
+ const args = generateBinderArgsForSlice(trans, eventBase.threadName);
+ /**
+ * This thread previously ack'd the transaction with a
+ * transaction_received. That means someone sent us a message we processed
+ * it and are now sending a transaction.
+ * The transaction could be a response, or it could be recursive.
+ */
+ const priorReceive = this.getPriorReceiveOnPID(pid);
+
+ if (priorReceive !== false) {
+ return this.modelPriorReceive(priorReceive, ts, pid, tgid, kthread,
+ trans, args, event);
+ }
+ /**
+ * This Thread has an already established recursive slice. We will now
+ * either complete the entire transaction, OR do more recursive calls.
+ */
+ const recursiveTrans = this.getRecursiveTransactionNeedingCompletion(pid);
+
+ if (recursiveTrans !== false) {
+ return this.modelRecursiveTransactions(recursiveTrans, ts, pid,
+ kthread, trans, args);
+ }
+
+ /**
+ * Start of a Transaction. This thread is the initiator of either a call
+ * response, an async call -> ack, or a call -> recursion -> response.
+ * Note, we put a fake duration into this slice and patch it up later.
+ */
+ const slice = kthread.thread.sliceGroup.pushCompleteSlice('binder',
+ '', ts, .03, 0, 0, args);
+
+ slice.colorId = ColorScheme.getColorIdForGeneralPurposeString(
+ ts.toString());
+ trans.slice = slice;
+
+ if (trans.expect_reply) {
+ slice.title = 'binder transaction';
+ } else {
+ slice.title = 'binder transaction async';
+ }
+
+ this.addTransactionWaitingForRecv(trans.transaction_key, trans);
+
+ return true;
+ },
+
+ binderTransactionReceived(eventName, cpuNumber, pid, ts,
+ eventBase) {
+ const event = binderTransReceivedRE.exec(eventBase.details);
+ if (event === undefined) return false;
+ const tgid = parseInt(eventBase.tgid);
+ if (isNaN(tgid)) return false;
+
+ const transactionkey = parseInt(event[1]);
+ const kthread = this.importer_.
+ getOrCreateBinderKernelThread(eventBase.threadName, tgid, pid);
+
+ const syncComplete = this.getSyncTransNeedsCompletion(transactionkey);
+
+ if (syncComplete !== false) {
+ /* This recv is the completion of a synchronous transaction.
+ * We need to scale the slice up to the current ts and finish
+ * creating some flows.
+ */
+ const syncTrans = syncComplete[0];
+ const syncSlice = syncTrans.slice;
+ const responseTrans = syncComplete[1];
+ const responseSlice = responseTrans.slice;
+
+ syncSlice.duration = ts - syncSlice.start;
+ /** These calls are a little hack that places a very small slice at
+ * the end of the sync slice and the response slice. This allows us
+ * to hook flow events (arrows) from the start to the end of the
+ * slices.
+ */
+ const syncInternal = doInternalSlice(syncTrans, syncSlice, ts);
+ const responseTs = responseSlice.start + responseSlice.duration;
+ const responseInternal = doInternalSlice(responseTrans,
+ responseSlice, responseTs);
+
+ if (responseSlice.outFlowEvents.length === 0 ||
+ syncSlice.inFlowEvents.length === 0) {
+ const flow = this.generateFlow(responseInternal, syncInternal,
+ responseTrans, syncTrans);
+
+ syncSlice.inFlowEvents.push(flow);
+ responseSlice.outFlowEvents.push(flow);
+ this.model_.flowEvents.push(flow);
+ }
+ // Move flow arrows -- but not the first one.
+ for (let i = 1; i < syncSlice.inFlowEvents.length; i++) {
+ syncSlice.inFlowEvents[i].duration =
+ ts - syncSlice.inFlowEvents[i].start;
+ }
+ return true;
+ }
+
+ const trForRecv = this.getTransactionWaitingForRecv(transactionkey);
+
+ if (trForRecv !== false) {
+ if (!trForRecv.expect_reply) {
+ // This is an async call place an Async slice.
+ const args = generateBinderArgsForSlice(trForRecv,
+ eventBase.threadName);
+ const slice = kthread.thread.sliceGroup.
+ pushCompleteSlice('binder',
+ 'binder Async recv',
+ ts, .03, 0, 0,
+ args);
+
+ const fakeEvent = [0, 0, 0, 0, 0, 0, 0];
+ const fakeTrans = new BinderTransaction(fakeEvent, pid, ts, kthread);
+ const flow = this.generateFlow(trForRecv.slice, slice,
+ trForRecv, fakeTrans);
+
+ this.model_.flowEvents.push(flow);
+ trForRecv.slice.title = 'binder transaction async';
+ trForRecv.slice.duration = .03;
+ return true;
+ }
+ // Setup prior receive.
+ trForRecv.slice.title = 'binder transaction';
+ this.setCurrentReceiveOnPID(pid, [ts, trForRecv]);
+ return true;
+ }
+ /** This case is when we received an ack for a transaction we have
+ * never seen before. This usually happens at the start of a trace.
+ * We will get incomplete transactions that started before started
+ * tracing. Just discard them.
+ */
+ return false;
+ },
+
+ binderTransactionAllocBuf(eventName, cpuNumber, pid, ts,
+ eventBase) {
+ const event = binderAllocRE.exec(eventBase.details);
+ if (event === null) return false;
+ const tgid = parseInt(eventBase.tgid);
+ if (isNaN(tgid)) return false;
+
+ const transactionkey = parseInt(event[1]);
+ const kthread = this.importer_.
+ getOrCreateBinderKernelThread(eventBase.threadName, tgid, pid);
+
+ const trans = this.peekTransactionWaitingForRecv(transactionkey);
+ if (trans && trans.slice) {
+ trans.slice.args['Data Size'] = parseInt(event[2]);
+ trans.slice.args['Offsets Size'] = parseInt(event[3]);
+ return true;
+ }
+ return false;
+ },
+
+
+ // helper functions
+ modelRecursiveTransactions(recursiveTrans, ts, pid, kthread,
+ trans, args) {
+ const recursiveSlice = recursiveTrans[1].slice;
+ const origSlice = recursiveTrans[0].slice;
+ recursiveSlice.duration = ts - recursiveSlice.start;
+ recursiveSlice.args = args;
+ trans.slice = recursiveSlice;
+
+ if (trans.is_reply_transaction) {
+ /* Case one:
+ * This transaction is finally the reply of the recursion.
+ */
+ origSlice.duration = ts - origSlice.start;
+ this.addSyncTransNeedingCompletion(trans.transaction_key,
+ recursiveTrans);
+
+ if (isReplyToOrigin(recursiveTrans[0], trans)) {
+ this.removeRecursiveTransaction(pid);
+ }
+ } else {
+ /**
+ * Case two:
+ * This transaction is more recursive calls.
+ * This is a nested call within an already started transaction,
+ * it can either be a async or a normal sync transaction.
+ */
+ const slice = kthread.thread.sliceGroup.pushCompleteSlice('binder',
+ '', ts, .03, 0,
+ 0, args);
+
+ trans.slice = slice;
+ this.addTransactionWaitingForRecv(trans.transaction_key, trans);
+ }
+ return true;
+ },
+
+ modelPriorReceive(priorReceive, ts, pid, tgid, kthread, trans,
+ args, event) {
+ const calleeSlice = priorReceive[1].slice;
+ const calleeTrans = priorReceive[1];
+ const recvTs = priorReceive[0];
+ let slice = kthread.thread.sliceGroup.pushCompleteSlice('binder',
+ '', recvTs, ts - recvTs, 0, 0);
+
+ const flow = this.generateFlow(calleeSlice, slice, calleeTrans, trans);
+ this.model_.flowEvents.push(flow);
+ trans.slice = slice;
+
+ if (trans.is_reply_transaction) {
+ /* This is a response to a synchronous or a recursive sync
+ * transaction.
+ */
+ slice.title = 'binder reply';
+ slice.args = args;
+ /* Notify this transaction key that when it recv's it is completing
+ * a sync transaction.
+ */
+ this.addSyncTransNeedingCompletion(trans.transaction_key,
+ [calleeTrans, trans]);
+ } else {
+ /**
+ * Recursive calls and or calls around, either way it's not
+ * going to complete a transaction.
+ */
+ slice.title = 'binder reply';
+ /* Since this is a recursive transaction we want to create the main
+ * large slice which will contain all these recursive transactions.
+ * For that we created the main slice above and this is a recursive
+ * transaction that will be placed right below it. Note, that this
+ * is only for the first recursive transaction. If more come they will
+ * be handled below in the getRecursiveTransactionNeedingCompletion
+ */
+ const trans1 = new BinderTransaction(event, pid, ts, kthread);
+
+ slice = kthread.thread.sliceGroup.
+ pushCompleteSlice('binder',
+ 'binder transaction',
+ recvTs,
+ (ts - recvTs), 0,
+ 0, args);
+
+ /* could be a async trans if so set the length to be a small one */
+ if (!trans.expect_reply) {
+ slice.title = 'binder transaction async';
+ slice.duration = .03;
+ } else {
+ /* stupid hack to stop merging of AIDL slices and
+ * this slice. This is currently disabled, if AIDL tracing is on we
+ * will see merging of this slice and the AIDL slice. Once upstream
+ * has a solution for flow events to be placed in the middle of
+ * slices this part can be fixed.
+ *
+ * This is commented out because AIDL tracing doesn't exit yet.
+ */
+ // slice.start += .15;
+ }
+ trans1.slice = slice;
+ this.addRecursiveSyncTransNeedingCompletion(pid,
+ [calleeTrans, trans]);
+ this.addTransactionWaitingForRecv(trans.transaction_key, trans1);
+ }
+ return true;
+ },
+
+ getRecursiveTransactionNeedingCompletion(pid) {
+ if (this.recursiveSyncTransWaitingCompletion_ByPID[pid] === undefined) {
+ return false;
+ }
+
+ const len = this.recursiveSyncTransWaitingCompletion_ByPID[pid].length;
+ if (len === 0) return false;
+
+ return this.recursiveSyncTransWaitingCompletion_ByPID[pid][len - 1];
+ },
+
+ addRecursiveSyncTransNeedingCompletion(pid, tuple) {
+ if (this.recursiveSyncTransWaitingCompletion_ByPID[pid] === undefined) {
+ this.recursiveSyncTransWaitingCompletion_ByPID[pid] = [];
+ }
+
+ this.recursiveSyncTransWaitingCompletion_ByPID[pid].push(tuple);
+ },
+
+ removeRecursiveTransaction(pid) {
+ const len = this.recursiveSyncTransWaitingCompletion_ByPID[pid].length;
+ if (len === 0) {
+ delete this.recursiveSyncTransWaitingCompletion_ByPID[pid];
+ return;
+ }
+
+ this.recursiveSyncTransWaitingCompletion_ByPID[pid].splice(len - 1, 1);
+ },
+
+ setCurrentReceiveOnPID(pid, tuple) {
+ if (this.receivedTransWaitingConversion[pid] === undefined) {
+ this.receivedTransWaitingConversion[pid] = [];
+ }
+ this.receivedTransWaitingConversion[pid].push(tuple);
+ },
+
+ getPriorReceiveOnPID(pid) {
+ if (this.receivedTransWaitingConversion[pid] === undefined) {
+ return false;
+ }
+
+ const len = this.receivedTransWaitingConversion[pid].length;
+ if (len === 0) return false;
+
+ return this.receivedTransWaitingConversion[pid].splice(len - 1, 1)[0];
+ },
+
+ addSyncTransNeedingCompletion(transactionkey, tuple) {
+ const dict = this.syncTransWaitingCompletion;
+ dict[transactionkey] = tuple;
+ },
+
+ getSyncTransNeedsCompletion(transactionkey) {
+ const ret = this.syncTransWaitingCompletion[transactionkey];
+ if (ret === undefined) return false;
+
+ delete this.syncTransWaitingCompletion[transactionkey];
+ return ret;
+ },
+
+ getTransactionWaitingForRecv(transactionkey) {
+ const ret = this.transWaitingRecv[transactionkey];
+ if (ret === undefined) return false;
+
+ delete this.transWaitingRecv[transactionkey];
+ return ret;
+ },
+
+ peekTransactionWaitingForRecv(transactionkey) {
+ const ret = this.transWaitingRecv[transactionkey];
+ if (ret === undefined) return false;
+ return ret;
+ },
+
+ addTransactionWaitingForRecv(transactionkey, transaction) {
+ this.transWaitingRecv[transactionkey] = transaction;
+ },
+
+ generateFlow(from, to, fromTrans, toTrans) {
+ const title = 'Transaction from : ' +
+ this.pid2name(fromTrans.calling_pid) +
+ ' From PID: ' + fromTrans.calling_pid + ' to pid: ' +
+ toTrans.calling_pid +
+ ' Thread Name: ' + this.pid2name(toTrans.calling_pid);
+
+ const ts = from.start;
+ const flow = new tr.model.FlowEvent('binder', 'binder',
+ title, 1, ts, []);
+ flow.startSlice = from;
+ flow.endSlice = to;
+ flow.start = from.start;
+ flow.duration = to.start - ts;
+
+ from.outFlowEvents.push(flow);
+ to.inFlowEvents.push(flow);
+
+ return flow;
+ },
+
+ generateArgsForSlice(tgid, pid, name, kthread) {
+ return {
+ 'Thread Name': name,
+ pid,
+ 'gid': tgid
+ };
+ },
+
+ pid2name(pid) {
+ return this.kthreadlookup[pid];
+ },
+
+ doNameMappings(pid, tgid, name) {
+ this.registerPidName(pid, name);
+ this.registerPidName(tgid, name);
+ },
+
+ registerPidName(pid, name) {
+ if (this.pid2name(pid) === undefined) {
+ this.kthreadlookup[pid] = name;
+ }
+ }
+ };
+
+ Parser.register(BinderParser);
+ return {
+ BinderParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/binder_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/binder_parser_test.html
new file mode 100644
index 00000000000..07a619b07cf
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/binder_parser_test.html
@@ -0,0 +1,273 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('binderParserImport', function() {
+ const lines = [
+ 'binderLibTest-10619 (10619) [001] ...1 25191.335749: ' +
+ 'binder_transaction: transaction=489053 dest_node=489048 ' +
+ 'dest_proc=10670 dest_thread=0 reply=0 flags=0x10 code=0xd',
+ 'binderLibTest-10670 (10670) [000] ...1 25191.335996:' +
+ ' binder_transaction_received: transaction=489053',
+ 'binderLibTest-10670 (10670) [000] ...1 25191.336167:' +
+ ' binder_transaction: transaction=489056 dest_node=161277 ' +
+ 'dest_proc=3454 dest_thread=0 reply=0 flags=0x10 code=0x2',
+ '/system/bin/servicemanager-3454 ( 3454) [000] ...1 25191.336199:' +
+ ' binder_transaction_received: transaction=489056',
+ '/system/bin/servicemanager-3454 ( 3454) [000] ...1 25191.336302:' +
+ ' binder_transaction: transaction=489057 dest_node=0 dest_proc=10670' +
+ ' dest_thread=10670 reply=1 flags=0x0 code=0x0',
+ 'binderLibTest-10670 (10670) [000] ...1 25191.336334:' +
+ ' binder_transaction_received: transaction=489057',
+ 'binderLibTest-10670 (10670) [000] ...1 25191.336655:' +
+ ' binder_transaction: transaction=489059 dest_node=488755' +
+ ' dest_proc=10622 dest_thread=0 reply=0 flags=0x10 code=0x1',
+ 'Binder_4-10645 (10622) [001] ...1 25191.336693:' +
+ ' binder_transaction_received: transaction=489059',
+ 'Binder_4-10645 (10622) [001] ...1 25191.336754: binder_transaction:' +
+ ' transaction=489060 dest_node=0 dest_proc=10670 dest_thread=10670' +
+ ' reply=1 flags=0x0 code=0x0',
+ 'binderLibTest-10670 (10670) [000] ...1 25191.337003:' +
+ ' binder_transaction_received: transaction=489060',
+ 'binderLibTest-10670 (10670) [000] ...1 25191.337052:' +
+ ' binder_transaction: transaction=489061 dest_node=0 dest_proc=10619' +
+ ' dest_thread=10619 reply=1 flags=0x8 code=0x0',
+ 'binderLibTest-10619 (10619) [001] ...1 25191.337085:' +
+ ' binder_transaction_received: transaction=489061',
+ 'atrace-10618 (10618) [000] ...1 25196.059954: tracing_mark_write:' +
+ ' trace_event_clock_sync: parent_ts=25196.050781'
+ ];
+
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shfitWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 4);
+ });
+
+ test('binderRecursiveTest', function() {
+ const lines = [
+ 'binderLibTest-10619 (10619) [001] ...1 25191.335749: ' +
+ 'binder_transaction: transaction=489053 dest_node=489048 ' +
+ 'dest_proc=10670 dest_thread=0 reply=0 flags=0x10 code=0xd',
+ 'binderLibTest-10670 (10670) [000] ...1 25191.335996:' +
+ ' binder_transaction_received: transaction=489053',
+ 'binderLibTest-10670 (10670) [000] ...1 25191.336167:' +
+ ' binder_transaction: transaction=489056 dest_node=161277 ' +
+ 'dest_proc=3454 dest_thread=0 reply=0 flags=0x10 code=0x2',
+ '/system/bin/servicemanager-3454 ( 3454) [000] ...1 25191.336199:' +
+ ' binder_transaction_received: transaction=489056',
+ '/system/bin/servicemanager-3454 ( 3454) [000] ...1 25191.336302:' +
+ ' binder_transaction: transaction=489057 dest_node=0 dest_proc=10670' +
+ ' dest_thread=10670 reply=1 flags=0x0 code=0x0',
+ 'binderLibTest-10670 (10670) [000] ...1 25191.336334:' +
+ ' binder_transaction_received: transaction=489057',
+ 'binderLibTest-10670 (10670) [000] ...1 25191.336655:' +
+ ' binder_transaction: transaction=489059 dest_node=488755' +
+ ' dest_proc=10622 dest_thread=0 reply=0 flags=0x10 code=0x1',
+ 'Binder_4-10645 (10622) [001] ...1 25191.336693:' +
+ ' binder_transaction_received: transaction=489059',
+ 'Binder_4-10645 (10622) [001] ...1 25191.336754: binder_transaction:' +
+ ' transaction=489060 dest_node=0 dest_proc=10670 dest_thread=10670' +
+ ' reply=1 flags=0x0 code=0x0',
+ 'binderLibTest-10670 (10670) [000] ...1 25191.337003:' +
+ ' binder_transaction_received: transaction=489060',
+ 'binderLibTest-10670 (10670) [000] ...1 25191.337052:' +
+ ' binder_transaction: transaction=489061 dest_node=0 dest_proc=10619' +
+ ' dest_thread=10619 reply=1 flags=0x8 code=0x0',
+ 'binderLibTest-10619 (10619) [001] ...1 25191.337085:' +
+ ' binder_transaction_received: transaction=489061',
+ 'atrace-10618 (10618) [000] ...1 25196.059954: tracing_mark_write:' +
+ ' trace_event_clock_sync: parent_ts=25196.050781'
+ ];
+
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shfitWorldToZero: false
+ });
+ const threads = m.getAllThreads();
+
+ let thread = threads[0];
+ assert.strictEqual(thread.parent.pid, 3454);
+ assert.strictEqual(thread.tid, 3454);
+ assert.strictEqual(thread.name, '/system/bin/servicemanager');
+ /* one main slice and one 'internal slice' for the end flow event' */
+ assert.strictEqual(thread.sliceGroup.length, 2);
+
+ /* check flow events for service manager */
+ let mainSlice = thread.sliceGroup.slices[0];
+ assert.strictEqual(mainSlice.inFlowEvents.length, 1);
+ assert.strictEqual(mainSlice.outFlowEvents.length, 1);
+ let internalSlice = thread.sliceGroup.slices[1];
+ assert.strictEqual(internalSlice.outFlowEvents.length, 1);
+
+ /* check name of slice */
+ assert.strictEqual(mainSlice.title, 'binder reply');
+ assert.strictEqual(mainSlice.args['Destination Process'], 10670);
+ assert.strictEqual(mainSlice.args['Destination Thread'], 10670);
+ assert.strictEqual(mainSlice.args['Reply transaction?'], true);
+ assert.strictEqual(mainSlice.args['Calling PID'], 3454);
+ assert.strictEqual(mainSlice.args['Calling tgid'], 3454);
+
+ /* check binderLibTest */
+ thread = threads[1];
+ assert.strictEqual(thread.parent.pid, 10619);
+ assert.strictEqual(thread.tid, 10619);
+ assert.strictEqual(thread.name, 'binderLibTest');
+
+ assert.strictEqual(2, thread.sliceGroup.length);
+
+ mainSlice = thread.sliceGroup.slices[0];
+ assert.strictEqual(mainSlice.inFlowEvents.length, 1);
+ assert.strictEqual(mainSlice.outFlowEvents.length, 1);
+ internalSlice = thread.sliceGroup.slices[1];
+ assert.strictEqual(internalSlice.inFlowEvents.length, 1);
+
+ assert.strictEqual(mainSlice.title, 'binder transaction');
+ assert.strictEqual(mainSlice.args['Destination Process'], 10670);
+ assert.strictEqual(mainSlice.args['Reply transaction?'], false);
+ assert.strictEqual(mainSlice.args['Calling PID'], 10619);
+ assert.strictEqual(mainSlice.args['Calling tgid'], 10619);
+
+ /* check Binder_4 */
+ thread = threads[2];
+ assert.strictEqual(thread.parent.pid, 10622);
+ assert.strictEqual(thread.tid, 10645);
+ assert.strictEqual(thread.name, 'Binder_4');
+ assert.strictEqual(2, thread.sliceGroup.length);
+
+ mainSlice = thread.sliceGroup.slices[0];
+ assert.strictEqual(mainSlice.inFlowEvents.length, 1);
+ assert.strictEqual(mainSlice.outFlowEvents.length, 1);
+ internalSlice = thread.sliceGroup.slices[1];
+ assert.strictEqual(internalSlice.outFlowEvents.length, 1);
+
+ assert.strictEqual(mainSlice.title, 'binder reply');
+ assert.strictEqual(mainSlice.args['Destination Process'], 10670);
+ assert.strictEqual(mainSlice.args['Destination Thread'], 10670);
+ assert.strictEqual(mainSlice.args['Reply transaction?'], true);
+ assert.strictEqual(mainSlice.args['Calling PID'], 10645);
+ assert.strictEqual(mainSlice.args['Calling tgid'], 10622);
+
+ /* check last binderLibTest with recursive slices */
+ thread = threads[3];
+ assert.strictEqual(thread.parent.pid, 10670);
+ assert.strictEqual(thread.tid, 10670);
+ assert.strictEqual(thread.name, 'binderLibTest');
+ assert.strictEqual(6, thread.sliceGroup.length);
+
+ mainSlice = thread.sliceGroup.slices[0];
+ assert.strictEqual(mainSlice.inFlowEvents.length, 1);
+ assert.strictEqual(mainSlice.outFlowEvents.length, 1);
+ internalSlice = thread.sliceGroup.slices[5];
+ assert.strictEqual(internalSlice.outFlowEvents.length, 1);
+
+ assert.strictEqual(mainSlice.title, 'binder reply');
+ assert.strictEqual(mainSlice.args['Destination Process'], 10619);
+ assert.strictEqual(mainSlice.args['Destination Thread'], 10619);
+ assert.strictEqual(mainSlice.args['Reply transaction?'], true);
+ assert.strictEqual(mainSlice.args['Calling PID'], 10670);
+ assert.strictEqual(mainSlice.args['Calling tgid'], 10670);
+
+ let recursive = thread.sliceGroup.slices[1];
+ let recursiveInternal = thread.sliceGroup.slices[2];
+ assert.strictEqual(recursive.inFlowEvents.length, 1);
+ assert.strictEqual(recursive.outFlowEvents.length, 1);
+ assert.strictEqual(recursiveInternal.inFlowEvents.length, 1);
+
+ assert.strictEqual(recursive.title, 'binder transaction');
+ assert.strictEqual(recursive.args['Destination Process'], 3454);
+ assert.strictEqual(recursive.args['Reply transaction?'], false);
+ assert.strictEqual(recursive.args['Calling PID'], 10670);
+ assert.strictEqual(recursive.args['Calling tgid'], 10670);
+
+ /* check second recursive slice and internal */
+
+ recursive = thread.sliceGroup.slices[3];
+ recursiveInternal = thread.sliceGroup.slices[4];
+ assert.strictEqual(recursive.inFlowEvents.length, 1);
+ assert.strictEqual(recursive.outFlowEvents.length, 1);
+ assert.strictEqual(recursiveInternal.inFlowEvents.length, 1);
+
+ assert.strictEqual(recursive.title, 'binder transaction');
+ assert.strictEqual(recursive.args['Destination Process'], 10622);
+ assert.strictEqual(recursive.args['Reply transaction?'], false);
+ assert.strictEqual(recursive.args['Calling PID'], 10670);
+ assert.strictEqual(recursive.args['Calling tgid'], 10670);
+ });
+
+ test('binderSimpleAsync', function() {
+ const lines = [
+ '/system/bin/surfaceflinger-3462 ( 3462) [000] ...1 108286.109437:' +
+ ' binder_transaction: transaction=923419 dest_node=175950' +
+ ' dest_proc=4044 dest_thread=0 reply=0 flags=0x11 code=0x1',
+ 'Binder_5-12869 ( 4044) [000] ...1 108286.109835:' +
+ ' binder_transaction_received: transaction=923419'
+ ];
+
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shfitWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads(0);
+ assert.strictEqual(threads.length, 2);
+
+ let thread = threads[0];
+ assert.strictEqual(thread.tid, 3462);
+ assert.strictEqual(thread.name, '/system/bin/surfaceflinger');
+ assert.strictEqual(thread.sliceGroup.length, 1);
+
+ let slice = thread.sliceGroup.slices[0];
+ assert.strictEqual(slice.outFlowEvents.length, 1);
+ assert.strictEqual(slice.inFlowEvents.length, 0);
+ assert.strictEqual(slice.title, 'binder transaction async');
+
+ thread = threads[1];
+ assert.strictEqual(thread.tid, 12869);
+ assert.strictEqual(thread.name, 'Binder_5');
+ assert.strictEqual(thread.sliceGroup.length, 1);
+
+ slice = thread.sliceGroup.slices[0];
+ assert.strictEqual(slice.inFlowEvents.length, 1);
+ assert.strictEqual(slice.outFlowEvents.length, 0);
+ assert.strictEqual(slice.title, 'binder Async recv');
+ });
+
+ test('binderAllocBuf', function() {
+ const lines = [
+ 'binderLibTest-10619 (10619) [001] ...1 25191.335749: ' +
+ 'binder_transaction: transaction=489053 dest_node=489048 ' +
+ 'dest_proc=10670 dest_thread=0 reply=0 flags=0x10 code=0xd',
+ 'binderLibTest-10619 (10619) [001] ...1 25191.335750: ' +
+ 'binder_transaction_alloc_buf: transaction=489053 ' +
+ 'data_size=1001 offsets_size=1002',
+ 'binderLibTest-10670 (10620) [000] ...1 25191.335751: ' +
+ 'binder_transaction_received: transaction=489053',
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shfitWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+ const threads = m.getAllThreads(0);
+ assert.strictEqual(threads.length, 1);
+ const thread = threads[0];
+ assert.strictEqual(thread.sliceGroup.length, 1);
+ const slice = thread.sliceGroup.slices[0];
+ assert.strictEqual(slice.args['Data Size'], 1001);
+ assert.strictEqual(slice.args['Offsets Size'], 1002);
+ });
+});
+
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/bus_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/bus_parser.html
new file mode 100644
index 00000000000..501fa83aea0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/bus_parser.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/unit_scale.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+<link rel="import" href="/tracing/model/counter_series.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses trace_marker events that were inserted in the trace by
+ * userland.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux trace mark events that were inserted in the trace by userland.
+ * @constructor
+ */
+ function BusParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('memory_bus_usage',
+ BusParser.prototype.traceMarkWriteBusEvent.bind(this));
+
+ this.model_ = importer.model_;
+ this.ppids_ = {};
+ }
+
+ BusParser.prototype = {
+ __proto__: Parser.prototype,
+
+ traceMarkWriteBusEvent(eventName, cpuNumber, pid, ts,
+ eventBase, threadName) {
+ const re = new RegExp('bus=(\\S+) rw_bytes=(\\d+) r_bytes=(\\d+) ' +
+ 'w_bytes=(\\d+) cycles=(\\d+) ns=(\\d+)');
+ const event = re.exec(eventBase.details);
+
+ const name = event[1];
+ const rwBytes = parseInt(event[2]);
+ const rBytes = parseInt(event[3]);
+ const wBytes = parseInt(event[4]);
+ const cycles = parseInt(event[5]);
+ const ns = parseInt(event[6]);
+
+ // BW in MiB/s.
+ const sec = tr.b.convertUnit(ns, tr.b.UnitPrefixScale.METRIC.NANO,
+ tr.b.UnitPrefixScale.METRIC.NONE);
+ const readBandwidthInBps = rBytes / sec;
+ const readBandwidthInMiBps = tr.b.convertUnit(readBandwidthInBps,
+ tr.b.UnitPrefixScale.BINARY.NONE,
+ tr.b.UnitPrefixScale.BINARY.MEBI);
+ const writeBandwidthInBps = wBytes / sec;
+ const writeBandwidthInMiBps = tr.b.convertUnit(writeBandwidthInBps,
+ tr.b.UnitPrefixScale.BINARY.NONE,
+ tr.b.UnitPrefixScale.BINARY.MEBI);
+
+ let ctr = this.model_.kernel
+ .getOrCreateCounter(null, 'bus ' + name + ' read');
+ if (ctr.numSeries === 0) {
+ ctr.addSeries(new tr.model.CounterSeries('value',
+ ColorScheme.getColorIdForGeneralPurposeString(
+ ctr.name + '.' + 'value')));
+ }
+ ctr.series.forEach(function(series) {
+ series.addCounterSample(ts, readBandwidthInMiBps);
+ });
+
+ ctr = this.model_.kernel
+ .getOrCreateCounter(null, 'bus ' + name + ' write');
+ if (ctr.numSeries === 0) {
+ ctr.addSeries(new tr.model.CounterSeries('value',
+ ColorScheme.getColorIdForGeneralPurposeString(
+ ctr.name + '.' + 'value')));
+ }
+ ctr.series.forEach(function(series) {
+ series.addCounterSample(ts, writeBandwidthInMiBps);
+ });
+
+ return true;
+ }
+ };
+
+ Parser.register(BusParser);
+
+ return {
+ BusParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/bus_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/bus_parser_test.html
new file mode 100644
index 00000000000..d2bfa35b1a5
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/bus_parser_test.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('exynos5Bus', function() {
+ const lines = [
+ 's3c-fb-vsync-85 [001] d..2 8116.730115: memory_bus_usage: ' +
+ 'bus=RIGHT rw_bytes=0 r_bytes=0 w_bytes=0 cycles=2681746 ns=16760792',
+
+ 's3c-fb-vsync-85 [001] d..2 8116.730118: memory_bus_usage: ' +
+ 'bus=CPU rw_bytes=2756608 r_bytes=2267328 w_bytes=491328 ' +
+ 'cycles=6705198 ns=16763375',
+
+ 's3c-fb-vsync-85 [001] d..2 8116.746788: memory_bus_usage: ' +
+ 'bus=DDR_C rw_bytes=2736128 r_bytes=2260864 w_bytes=479248 ' +
+ 'cycles=6670677 ns=16676375',
+
+ 's3c-fb-vsync-85 [001] d..2 8116.746790: memory_bus_usage: ' +
+ 'bus=DDR_R1 rw_bytes=31457280 r_bytes=31460912 w_bytes=0 ' +
+ 'cycles=6670521 ns=16676500',
+
+ 's3c-fb-vsync-85 [001] d..2 8116.746792: memory_bus_usage: ' +
+ 'bus=DDR_L rw_bytes=16953344 r_bytes=16731088 w_bytes=223664 ' +
+ 'cycles=6669885 ns=16674833',
+
+ 's3c-fb-vsync-85 [001] d..2 8116.746793: memory_bus_usage: ' +
+ 'bus=RIGHT rw_bytes=0 r_bytes=0 w_bytes=0 cycles=2667378 ns=16671250',
+
+ 's3c-fb-vsync-85 [001] d..2 8116.746798: memory_bus_usage: ' +
+ 'bus=CPU rw_bytes=2797568 r_bytes=2309424 w_bytes=491968 ' +
+ 'cycles=6672156 ns=16680458',
+
+ 's3c-fb-vsync-85 [001] d..2 8116.763521: memory_bus_usage: ' +
+ 'bus=DDR_C rw_bytes=2408448 r_bytes=1968448 w_bytes=441456 ' +
+ 'cycles=6689562 ns=16723458',
+
+ 's3c-fb-vsync-85 [001] d..2 8116.763523: memory_bus_usage: ' +
+ 'bus=DDR_R1 rw_bytes=31490048 r_bytes=31493360 w_bytes=0 ' +
+ 'cycles=6690012 ns=16725083',
+
+ 's3c-fb-vsync-85 [001] d..2 8116.763525: memory_bus_usage: ' +
+ 'bus=DDR_L rw_bytes=16941056 r_bytes=16719136 w_bytes=223472 ' +
+ 'cycles=6690156 ns=16725375'
+
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ const counters = m.getAllCounters();
+ assert.strictEqual(counters.length, 10);
+
+ assert.strictEqual(counters[0].series[0].samples.length, 2);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/clock_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/clock_parser.html
new file mode 100644
index 00000000000..eb290cccd96
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/clock_parser.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+<link rel="import" href="/tracing/model/counter_series.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses trace_marker events that were inserted in the trace by
+ * userland.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux trace mark events that were inserted in the trace by userland.
+ * @constructor
+ */
+ function ClockParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('clock_set_rate',
+ ClockParser.prototype.traceMarkWriteClockEvent.bind(this));
+ importer.registerEventHandler('clk_set_rate',
+ ClockParser.prototype.traceMarkWriteClkEvent.bind(this));
+ importer.registerEventHandler('clock_enable',
+ ClockParser.prototype.traceMarkWriteClockOnOffEvent.bind(this));
+ importer.registerEventHandler('clock_disable',
+ ClockParser.prototype.traceMarkWriteClockOnOffEvent.bind(this));
+ importer.registerEventHandler('clk_enable',
+ ClockParser.prototype.traceMarkWriteClkOnEvent.bind(this));
+ importer.registerEventHandler('clk_disable',
+ ClockParser.prototype.traceMarkWriteClkOffEvent.bind(this));
+ this.model_ = importer.model_;
+ this.ppids_ = {};
+ }
+
+ ClockParser.prototype = {
+ __proto__: Parser.prototype,
+
+ clockMark(name, subName, value, ts) {
+ const ctr = this.model_.kernel
+ .getOrCreateCounter(null, name + ' ' + subName);
+ // Initialize the counter's series fields if needed.
+ if (ctr.numSeries === 0) {
+ ctr.addSeries(new tr.model.CounterSeries('value',
+ ColorScheme.getColorIdForGeneralPurposeString(
+ ctr.name + '.' + 'value')));
+ }
+ ctr.series.forEach(function(series) {
+ series.addCounterSample(ts, value);
+ });
+ },
+
+ traceMarkWriteClockEvent(eventName, cpuNumber, pid, ts,
+ eventBase, threadName) {
+ const event = /(\S+) state=(\d+)/.exec(eventBase.details);
+ const name = event[1];
+ const rate = parseInt(event[2]);
+ this.clockMark(name, 'Frequency', rate, ts);
+ return true;
+ },
+
+ traceMarkWriteClkEvent(eventName, cpuNumber, pid, ts,
+ eventBase, threadName) {
+ const event = /(\S+) (\d+)/.exec(eventBase.details);
+ const name = event[1];
+ const rate = parseInt(event[2]);
+ this.clockMark(name, 'Frequency', rate, ts);
+ return true;
+ },
+
+ traceMarkWriteClockOnOffEvent(eventName, cpuNumber, pid, ts,
+ eventBase, threadName) {
+ const event = /(\S+) state=(\d+)/.exec(eventBase.details);
+ const name = event[1];
+ const state = parseInt(event[2]);
+ this.clockMark(name, 'State', state, ts);
+ return true;
+ },
+
+ traceMarkWriteClkOnEvent(eventName, cpuNumber, pid, ts,
+ eventBase, threadName) {
+ const event = /\S+/.exec(eventBase.details);
+ const name = event[0];
+ this.clockMark(name, 'State', 1, ts);
+ return true;
+ },
+
+ traceMarkWriteClkOffEvent(eventName, cpuNumber, pid, ts,
+ eventBase, threadName) {
+ const event = /\S+/.exec(eventBase.details);
+ const name = event[0];
+ this.clockMark(name, 'State', 0, ts);
+ return true;
+ }
+ };
+
+ Parser.register(ClockParser);
+
+ return {
+ ClockParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/clock_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/clock_parser_test.html
new file mode 100644
index 00000000000..3ce688c796b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/clock_parser_test.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('clock', function() {
+ const lines = [
+ 'cfinteractive-23 [000] d..2 8113.233768: clock_set_rate: ' +
+ 'fout_apll state=500000000 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.249509: clock_set_rate: ' +
+ 'fout_apll state=300000000 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.289796: clock_set_rate: ' +
+ 'fout_apll state=400000000 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.294568: clock_set_rate: ' +
+ 'fout_apll state=500000000 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.309509: clock_set_rate: ' +
+ 'fout_apll state=800000000 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.388732: clock_set_rate: ' +
+ 'fout_apll state=200000000 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.410182: clock_set_rate: ' +
+ 'fout_apll state=300000000 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.414872: clock_set_rate: ' +
+ 'fout_apll state=600000000 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.494455: clock_set_rate: ' +
+ 'fout_apll state=200000000 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.515254: clock_set_rate: ' +
+ 'fout_apll state=500000000 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.694455: clk_set_rate: ' +
+ 'fout_apll 200000000',
+
+ 'cfinteractive-23 [000] d..2 8113.715254: clk_set_rate: ' +
+ 'fout_apll 500000000'
+ ];
+
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ const counters = m.getAllCounters();
+ assert.strictEqual(counters.length, 1);
+
+ assert.strictEqual(counters[0].series[0].samples.length, 12);
+ });
+
+ test('clock state', function() {
+ const lines = [
+ 'cfinteractive-23 [000] d..2 8113.233768: clock_enable: ' +
+ 'fout_apll state=1 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.249509: clock_disable: ' +
+ 'fout_apll state=0 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.289796: clock_enable: ' +
+ 'fout_apll state=1 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.294568: clock_disable: ' +
+ 'fout_apll state=0 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.309509: clock_enable: ' +
+ 'fout_apll state=1 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.388732: clock_disable: ' +
+ 'fout_apll state=0 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.410182: clock_enable: ' +
+ 'fout_apll state=1 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.414872: clock_disable: ' +
+ 'fout_apll state=0 cpu_id=0',
+
+ 'cfinteractive-23 [000] d..2 8113.694455: clk_enable: ' +
+ 'fout_apll',
+
+ 'cfinteractive-23 [000] d..2 8113.715254: clk_disable: ' +
+ 'fout_apll'
+ ];
+
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ const counters = m.getAllCounters();
+ assert.strictEqual(counters.length, 1);
+
+ assert.strictEqual(counters[0].series[0].samples.length, 10);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/cpufreq_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/cpufreq_parser.html
new file mode 100644
index 00000000000..da112cb0e31
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/cpufreq_parser.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses cpufreq events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux cpufreq trace events.
+ * @constructor
+ */
+ function CpufreqParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('cpufreq_interactive_up',
+ CpufreqParser.prototype.cpufreqUpDownEvent.bind(this));
+ importer.registerEventHandler('cpufreq_interactive_down',
+ CpufreqParser.prototype.cpufreqUpDownEvent.bind(this));
+ importer.registerEventHandler('cpufreq_interactive_already',
+ CpufreqParser.prototype.cpufreqTargetEvent.bind(this));
+ importer.registerEventHandler('cpufreq_interactive_notyet',
+ CpufreqParser.prototype.cpufreqTargetEvent.bind(this));
+ importer.registerEventHandler('cpufreq_interactive_setspeed',
+ CpufreqParser.prototype.cpufreqTargetEvent.bind(this));
+ importer.registerEventHandler('cpufreq_interactive_target',
+ CpufreqParser.prototype.cpufreqTargetEvent.bind(this));
+ importer.registerEventHandler('cpufreq_interactive_boost',
+ CpufreqParser.prototype.cpufreqBoostUnboostEvent.bind(this));
+ importer.registerEventHandler('cpufreq_interactive_unboost',
+ CpufreqParser.prototype.cpufreqBoostUnboostEvent.bind(this));
+ }
+
+ function splitData(input) {
+ // TODO(sleffler) split by cpu
+ const data = {};
+ const args = input.split(/\s+/);
+ const len = args.length;
+ for (let i = 0; i < len; i++) {
+ const item = args[i].split('=');
+ data[item[0]] = parseInt(item[1]);
+ }
+ return data;
+ }
+
+ CpufreqParser.prototype = {
+ __proto__: Parser.prototype,
+
+ cpufreqSlice(ts, eventName, cpu, args) {
+ // TODO(sleffler) should be per-cpu
+ const kthread = this.importer.getOrCreatePseudoThread('cpufreq');
+ kthread.openSlice = eventName;
+ const slice = new tr.model.ThreadSlice('', kthread.openSlice,
+ ColorScheme.getColorIdForGeneralPurposeString(kthread.openSlice),
+ ts, args, 0);
+
+ kthread.thread.sliceGroup.pushSlice(slice);
+ },
+
+ cpufreqBoostSlice(ts, eventName, args) {
+ const kthread = this.importer.getOrCreatePseudoThread('cpufreq_boost');
+ kthread.openSlice = eventName;
+ const slice = new tr.model.ThreadSlice('', kthread.openSlice,
+ ColorScheme.getColorIdForGeneralPurposeString(kthread.openSlice),
+ ts, args, 0);
+
+ kthread.thread.sliceGroup.pushSlice(slice);
+ },
+
+ /**
+ * Parses cpufreq events and sets up state in the importer.
+ */
+ cpufreqUpDownEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const data = splitData(eventBase.details);
+ this.cpufreqSlice(ts, eventName, data.cpu, data);
+ return true;
+ },
+
+ cpufreqTargetEvent(eventName, cpuNumber, pid, ts,
+ eventBase) {
+ const data = splitData(eventBase.details);
+ this.cpufreqSlice(ts, eventName, data.cpu, data);
+ return true;
+ },
+
+ cpufreqBoostUnboostEvent(eventName, cpuNumber, pid, ts,
+ eventBase) {
+ this.cpufreqBoostSlice(ts, eventName,
+ {
+ type: eventBase.details
+ });
+ return true;
+ }
+ };
+
+ Parser.register(CpufreqParser);
+
+ return {
+ CpufreqParser,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/cpufreq_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/cpufreq_parser_test.html
new file mode 100644
index 00000000000..9c324a8346d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/cpufreq_parser_test.html
@@ -0,0 +1,173 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ test('cpuFreqTargetImport', function() {
+ const lines = [
+ '<idle>-0 [000] ..s3 1043.718825: cpufreq_interactive_target: ' +
+ 'cpu=0 load=2 cur=2000000 targ=300000\n',
+ '<idle>-0 [000] ..s3 1043.718825: cpufreq_interactive_target: ' +
+ 'cpu=0 load=12 cur=1000000 actual=1000000 targ=200000\n'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.sliceGroup.slices[0].args.cpu, 0);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.load, 2);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.cur, 2000000);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.targ, 300000);
+
+ assert.strictEqual(thread.sliceGroup.slices[1].args.cpu, 0);
+ assert.strictEqual(thread.sliceGroup.slices[1].args.load, 12);
+ assert.strictEqual(thread.sliceGroup.slices[1].args.cur, 1000000);
+ assert.strictEqual(thread.sliceGroup.slices[1].args.actual, 1000000);
+ assert.strictEqual(thread.sliceGroup.slices[1].args.targ, 200000);
+ });
+
+ test('cpuFreqNotYetImport', function() {
+ const lines = [
+ '<idle>-0 [001] ..s3 1043.718832: cpufreq_interactive_notyet: ' +
+ 'cpu=1 load=10 cur=700000 targ=200000\n',
+ '<idle>-0 [001] ..s3 1043.718832: cpufreq_interactive_notyet: ' +
+ 'cpu=1 load=10 cur=700000 actual=1000000 targ=200000\n'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.sliceGroup.slices[0].args.cpu, 1);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.load, 10);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.cur, 700000);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.targ, 200000);
+
+ assert.strictEqual(thread.sliceGroup.slices[1].args.cpu, 1);
+ assert.strictEqual(thread.sliceGroup.slices[1].args.load, 10);
+ assert.strictEqual(thread.sliceGroup.slices[1].args.cur, 700000);
+ assert.strictEqual(thread.sliceGroup.slices[1].args.actual, 1000000);
+ assert.strictEqual(thread.sliceGroup.slices[1].args.targ, 200000);
+ });
+
+ test('cpuFreqSetSpeedImport', function() {
+ const lines = [
+ 'cfinteractive-23 [001] ...1 1043.719688: ' +
+ 'cpufreq_interactive_setspeed: cpu=0 targ=200000 actual=700000\n'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.sliceGroup.slices[0].args.cpu, 0);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.targ, 200000);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.actual, 700000);
+ });
+
+ test('cpuFreqAlreadyImport', function() {
+ const lines = [
+ '<idle>-0 [000] ..s3 1043.738822: cpufreq_interactive_already: cpu=0 load=18 cur=200000 actual=700000 targ=200000\n' // @suppress longLineCheck
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.sliceGroup.slices[0].args.cpu, 0);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.load, 18);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.cur, 200000);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.actual, 700000);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.targ, 200000);
+ });
+
+ test('cpuFreqBoostImport', function() {
+ const lines = [
+ 'InputDispatcher-465 [001] ...1 1044.213948: ' +
+ 'cpufreq_interactive_boost: pulse\n'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.sliceGroup.slices[0].args.type, 'pulse');
+ });
+
+ test('cpuFreqUnBoostImport', function() {
+ const lines = [
+ 'InputDispatcher-465 [001] ...1 1044.213948: ' +
+ 'cpufreq_interactive_unboost: pulse\n'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.sliceGroup.slices[0].args.type, 'pulse');
+ });
+
+ test('cpuFreqUpImport', function() {
+ const lines = [
+ 'kinteractive-69 [003] .... 414324.164432: ' +
+ 'cpufreq_interactive_up: cpu=1 targ=1400000 actual=800000'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.sliceGroup.slices[0].args.cpu, 1);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.targ, 1400000);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.actual, 800000);
+ });
+
+ test('cpuFreqDownImport', function() {
+ const lines = [
+ 'kinteractive-69 [003] .... 414365.834193: ' +
+ 'cpufreq_interactive_down: cpu=3 targ=800000 actual=1000000'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+ assert.strictEqual(thread.sliceGroup.slices[0].args.cpu, 3);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.targ, 800000);
+ assert.strictEqual(thread.sliceGroup.slices[0].args.actual, 1000000);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/disk_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/disk_parser.html
new file mode 100644
index 00000000000..e44e29eb8cf
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/disk_parser.html
@@ -0,0 +1,302 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses filesystem and block device events in the Linux event
+ * trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux filesystem and block device trace events.
+ * @constructor
+ */
+ function DiskParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('f2fs_write_begin',
+ DiskParser.prototype.f2fsWriteBeginEvent.bind(this));
+ importer.registerEventHandler('f2fs_write_end',
+ DiskParser.prototype.f2fsWriteEndEvent.bind(this));
+ importer.registerEventHandler('f2fs_sync_file_enter',
+ DiskParser.prototype.f2fsSyncFileEnterEvent.bind(this));
+ importer.registerEventHandler('f2fs_sync_file_exit',
+ DiskParser.prototype.f2fsSyncFileExitEvent.bind(this));
+ importer.registerEventHandler('ext4_sync_file_enter',
+ DiskParser.prototype.ext4SyncFileEnterEvent.bind(this));
+ importer.registerEventHandler('ext4_sync_file_exit',
+ DiskParser.prototype.ext4SyncFileExitEvent.bind(this));
+ importer.registerEventHandler('ext4_da_write_begin',
+ DiskParser.prototype.ext4WriteBeginEvent.bind(this));
+ importer.registerEventHandler('ext4_da_write_end',
+ DiskParser.prototype.ext4WriteEndEvent.bind(this));
+ importer.registerEventHandler('block_rq_issue',
+ DiskParser.prototype.blockRqIssueEvent.bind(this));
+ importer.registerEventHandler('block_rq_complete',
+ DiskParser.prototype.blockRqCompleteEvent.bind(this));
+ }
+
+ DiskParser.prototype = {
+ __proto__: Parser.prototype,
+
+ openAsyncSlice(ts, category, threadName, pid, key, name) {
+ const kthread = this.importer.getOrCreateKernelThread(
+ category + ':' + threadName, pid);
+ const asyncSliceConstructor =
+ tr.model.AsyncSlice.subTypes.getConstructor(
+ category, name);
+ const slice = new asyncSliceConstructor(
+ category, name,
+ ColorScheme.getColorIdForGeneralPurposeString(name),
+ ts);
+ slice.startThread = kthread.thread;
+
+ if (!kthread.openAsyncSlices) {
+ kthread.openAsyncSlices = { };
+ }
+ kthread.openAsyncSlices[key] = slice;
+ },
+
+ closeAsyncSlice(ts, category, threadName, pid, key, args) {
+ const kthread = this.importer.getOrCreateKernelThread(
+ category + ':' + threadName, pid);
+ if (kthread.openAsyncSlices) {
+ const slice = kthread.openAsyncSlices[key];
+ if (slice) {
+ slice.duration = ts - slice.start;
+ slice.args = args;
+ slice.endThread = kthread.thread;
+ slice.subSlices = [
+ new tr.model.AsyncSlice(category, slice.title,
+ slice.colorId, slice.start, slice.args, slice.duration)
+ ];
+ kthread.thread.asyncSliceGroup.push(slice);
+ delete kthread.openAsyncSlices[key];
+ }
+ }
+ },
+
+ /**
+ * Parses events and sets up state in the importer.
+ */
+ f2fsWriteBeginEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /dev = \((\d+,\d+)\), ino = (\d+), pos = (\d+), len = (\d+), flags = (\d+)/. // @suppress longLineCheck
+ exec(eventBase.details);
+ if (!event) return false;
+ const device = event[1];
+ const inode = parseInt(event[2]);
+ const pos = parseInt(event[3]);
+ const len = parseInt(event[4]);
+ const key = device + '-' + inode + '-' + pos + '-' + len;
+ this.openAsyncSlice(ts, 'f2fs', eventBase.threadName, eventBase.pid,
+ key, 'f2fs_write');
+ return true;
+ },
+
+ f2fsWriteEndEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /dev = \((\d+,\d+)\), ino = (\d+), pos = (\d+), len = (\d+), copied = (\d+)/. // @suppress longLineCheck
+ exec(eventBase.details);
+ if (!event) return false;
+
+ const device = event[1];
+ const inode = parseInt(event[2]);
+ const pos = parseInt(event[3]);
+ const len = parseInt(event[4]);
+ const error = parseInt(event[5]) !== len;
+ const key = device + '-' + inode + '-' + pos + '-' + len;
+ this.closeAsyncSlice(ts, 'f2fs', eventBase.threadName, eventBase.pid,
+ key, {
+ device,
+ inode,
+ error
+ });
+ return true;
+ },
+
+ ext4WriteBeginEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /dev (\d+,\d+) ino (\d+) pos (\d+) len (\d+) flags (\d+)/.
+ exec(eventBase.details);
+ if (!event) return false;
+ const device = event[1];
+ const inode = parseInt(event[2]);
+ const pos = parseInt(event[3]);
+ const len = parseInt(event[4]);
+ const key = device + '-' + inode + '-' + pos + '-' + len;
+ this.openAsyncSlice(ts, 'ext4', eventBase.threadName, eventBase.pid,
+ key, 'ext4_write');
+ return true;
+ },
+
+ ext4WriteEndEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /dev (\d+,\d+) ino (\d+) pos (\d+) len (\d+) copied (\d+)/.
+ exec(eventBase.details);
+ if (!event) return false;
+
+ const device = event[1];
+ const inode = parseInt(event[2]);
+ const pos = parseInt(event[3]);
+ const len = parseInt(event[4]);
+ const error = parseInt(event[5]) !== len;
+ const key = device + '-' + inode + '-' + pos + '-' + len;
+ this.closeAsyncSlice(ts, 'ext4', eventBase.threadName, eventBase.pid,
+ key, {
+ device,
+ inode,
+ error
+ });
+ return true;
+ },
+
+ f2fsSyncFileEnterEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = new RegExp(
+ 'dev = \\((\\d+,\\d+)\\), ino = (\\d+), pino = (\\d+), i_mode = (\\S+), ' + // @suppress longLineCheck
+ 'i_size = (\\d+), i_nlink = (\\d+), i_blocks = (\\d+), i_advise = (\\d+)'). // @suppress longLineCheck
+ exec(eventBase.details);
+ if (!event) return false;
+
+ const device = event[1];
+ const inode = parseInt(event[2]);
+ const key = device + '-' + inode;
+ this.openAsyncSlice(ts, 'f2fs', eventBase.threadName, eventBase.pid,
+ key, 'fsync');
+ return true;
+ },
+
+ f2fsSyncFileExitEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = new RegExp('dev = \\((\\d+,\\d+)\\), ino = (\\d+), checkpoint is (\\S+), ' + // @suppress longLineCheck
+ 'datasync = (\\d+), ret = (\\d+)').
+ exec(eventBase.details.replace('not needed', 'not_needed'));
+ if (!event) return false;
+
+ const device = event[1];
+ const inode = parseInt(event[2]);
+ const error = parseInt(event[5]);
+ const key = device + '-' + inode;
+ this.closeAsyncSlice(ts, 'f2fs', eventBase.threadName, eventBase.pid,
+ key, {
+ device,
+ inode,
+ error
+ });
+ return true;
+ },
+
+ ext4SyncFileEnterEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /dev (\d+,\d+) ino (\d+) parent (\d+) datasync (\d+)/.
+ exec(eventBase.details);
+ if (!event) return false;
+
+ const device = event[1];
+ const inode = parseInt(event[2]);
+ const datasync = (event[4] === '1') || (event[4] === 1);
+ const key = device + '-' + inode;
+ const action = datasync ? 'fdatasync' : 'fsync';
+ this.openAsyncSlice(ts, 'ext4', eventBase.threadName, eventBase.pid,
+ key, action);
+ return true;
+ },
+
+ ext4SyncFileExitEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /dev (\d+,\d+) ino (\d+) ret (\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ const device = event[1];
+ const inode = parseInt(event[2]);
+ const error = parseInt(event[3]);
+ const key = device + '-' + inode;
+ this.closeAsyncSlice(ts, 'ext4', eventBase.threadName, eventBase.pid,
+ key, {
+ device,
+ inode,
+ error
+ });
+ return true;
+ },
+
+ blockRqIssueEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = new RegExp('(\\d+,\\d+) (F)?([DWRN])(F)?(A)?(S)?(M)? ' +
+ '\\d+ \\(.*\\) (\\d+) \\+ (\\d+) \\[.*\\]').exec(eventBase.details);
+ if (!event) return false;
+
+ let action;
+ switch (event[3]) {
+ case 'D':
+ action = 'discard';
+ break;
+ case 'W':
+ action = 'write';
+ break;
+ case 'R':
+ action = 'read';
+ break;
+ case 'N':
+ action = 'none';
+ break;
+ default:
+ action = 'unknown';
+ break;
+ }
+
+ if (event[2]) {
+ action += ' flush';
+ }
+ if (event[4] === 'F') {
+ action += ' fua';
+ }
+ if (event[5] === 'A') {
+ action += ' ahead';
+ }
+ if (event[6] === 'S') {
+ action += ' sync';
+ }
+ if (event[7] === 'M') {
+ action += ' meta';
+ }
+ const device = event[1];
+ const sector = parseInt(event[8]);
+ const numSectors = parseInt(event[9]);
+ const key = device + '-' + sector + '-' + numSectors;
+ this.openAsyncSlice(ts, 'block', eventBase.threadName, eventBase.pid,
+ key, action);
+ return true;
+ },
+
+ blockRqCompleteEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = new RegExp('(\\d+,\\d+) (F)?([DWRN])(F)?(A)?(S)?(M)? ' +
+ '\\(.*\\) (\\d+) \\+ (\\d+) \\[(.*)\\]').exec(eventBase.details);
+ if (!event) return false;
+
+ const device = event[1];
+ const sector = parseInt(event[8]);
+ const numSectors = parseInt(event[9]);
+ const error = parseInt(event[10]);
+ const key = device + '-' + sector + '-' + numSectors;
+ this.closeAsyncSlice(ts, 'block', eventBase.threadName, eventBase.pid,
+ key, {
+ device,
+ sector,
+ numSectors,
+ error
+ });
+ return true;
+ }
+ };
+
+ Parser.register(DiskParser);
+
+ return {
+ DiskParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/disk_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/disk_parser_test.html
new file mode 100644
index 00000000000..ac1795a7140
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/disk_parser_test.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('diskImport', function() {
+ const lines = [
+ // NB: spliced from different traces; mismatched timestamps don't matter
+ 'AsyncTask #2-18830 [000] ...1 154578.668286: ext4_sync_file_enter: ' +
+ 'dev 259,1 ino 81993 parent 81906 datasync 1',
+ 'Binder_A-3179 [001] ...1 1354.510088: f2fs_sync_file_enter: ' +
+ 'dev = (259,14), ino = 4882, pino = 313, i_mode = 0x81b0, i_size = ' +
+ '25136, i_nlink = 1, i_blocks = 8, i_advise = 0x0',
+ 'Binder_A-3179 [001] ...1 1354.514013: f2fs_sync_file_exit: ' +
+ 'dev = (259,14), ino = 4882, checkpoint is not needed, datasync = 1, ret = 0', // @suppress longLineCheck
+ 'mmcqd/0-81 [000] d..2 154578.668390: block_rq_issue: ' +
+ '179,0 WS 0 () 3427120 + 16 [mmcqd/0]',
+ 'mmcqd/0-81 [000] d..2 154578.669181: block_rq_complete: ' +
+ '179,0 WS () 3427120 + 16 [0]',
+ 'mmcqd/0-81 [001] d..2 154578.670853: block_rq_issue: ' +
+ '179,0 FWS 0 () 18446744073709551615 + 0 [mmcqd/0]',
+ 'mmcqd/0-81 [001] d..2 154578.670869: block_rq_complete: ' +
+ '179,0 FWS () 18446744073709551615 + 0 [0]',
+ 'AsyncTask #2-18830 [001] ...1 154578.670901: ext4_sync_file_exit: ' +
+ 'dev 259,1 ino 81993 ret 0',
+ 'mmcqd/0-81 [001] d..2 154578.877038: block_rq_issue: ' +
+ '179,0 R 0 () 3255256 + 8 [mmcqd/0]',
+ 'mmcqd/0-81 [001] d..2 154578.877110: block_rq_issue: ' +
+ '179,0 R 0 () 3255288 + 8 [mmcqd/0]',
+ 'mmcqd/0-81 [000] d..2 154578.877345: block_rq_complete: ' +
+ '179,0 R () 3255256 + 8 [0]',
+ 'mmcqd/0-81 [000] d..2 154578.877466: block_rq_complete: ' +
+ '179,0 R () 3255288 + 8 [0]',
+ 'ContactsProvide-1184 [000] ...1 66.613719: f2fs_write_begin: ' +
+ 'dev = (253,2), ino = 3342, pos = 0, len = 75, flags = 0',
+ 'ContactsProvide-1184 [000] ...1 66.613733: f2fs_write_end: ' +
+ 'dev = (253,2), ino = 3342, pos = 0, len = 75, copied = 75'
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ let blockThread = undefined;
+ let ext4Thread = undefined;
+ let f2fsSyncThread = undefined;
+ let f2fsWriteThread = undefined;
+
+ m.getAllThreads().forEach(function(t) {
+ switch (t.name) {
+ case 'block:mmcqd/0':
+ blockThread = t;
+ break;
+ case 'ext4:AsyncTask #2':
+ ext4Thread = t;
+ break;
+ case 'f2fs:Binder_A':
+ f2fsSyncThread = t;
+ break;
+ case 'f2fs:ContactsProvide':
+ f2fsWriteThread = t;
+ break;
+ default:
+ assert.fail(t, undefined, 'Unexpected thread named ' + t.name);
+ }
+ });
+ assert.isDefined(blockThread);
+ assert.isDefined(ext4Thread);
+ assert.isDefined(f2fsSyncThread);
+ assert.isDefined(f2fsWriteThread);
+
+ assert.strictEqual(blockThread.asyncSliceGroup.length, 4);
+
+ let slice = blockThread.asyncSliceGroup.slices[0];
+ assert.strictEqual(slice.category, 'block');
+ assert.strictEqual(slice.title, 'write sync');
+ assert.strictEqual(slice.args.device, '179,0');
+ assert.strictEqual(slice.args.error, 0);
+ assert.strictEqual(slice.args.numSectors, 16);
+ assert.strictEqual(slice.args.sector, 3427120);
+
+ assert.strictEqual(ext4Thread.asyncSliceGroup.length, 1);
+
+ slice = ext4Thread.asyncSliceGroup.slices[0];
+ assert.strictEqual(slice.category, 'ext4');
+ assert.strictEqual(slice.title, 'fdatasync');
+ assert.strictEqual(slice.args.device, '259,1');
+ assert.strictEqual(slice.args.error, 0);
+ assert.strictEqual(slice.args.inode, 81993);
+
+ assert.strictEqual(f2fsSyncThread.asyncSliceGroup.length, 1);
+
+ slice = f2fsSyncThread.asyncSliceGroup.slices[0];
+ assert.strictEqual(slice.category, 'f2fs');
+ assert.strictEqual(slice.title, 'fsync');
+ assert.strictEqual(slice.args.device, '259,14');
+ assert.strictEqual(slice.args.error, 0);
+ assert.strictEqual(slice.args.inode, 4882);
+
+ assert.strictEqual(f2fsWriteThread.asyncSliceGroup.length, 1);
+
+ slice = f2fsWriteThread.asyncSliceGroup.slices[0];
+ assert.strictEqual(slice.category, 'f2fs');
+ assert.strictEqual(slice.title, 'f2fs_write');
+ assert.strictEqual(slice.args.device, '253,2');
+ assert.strictEqual(slice.args.inode, 3342);
+ assert.strictEqual(slice.args.error, false);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/drm_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/drm_parser.html
new file mode 100644
index 00000000000..a7f02f23e93
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/drm_parser.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses drm driver events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux drm trace events.
+ * @constructor
+ */
+ function DrmParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('drm_vblank_event',
+ DrmParser.prototype.vblankEvent.bind(this));
+ }
+
+ DrmParser.prototype = {
+ __proto__: Parser.prototype,
+
+ drmVblankSlice(ts, eventName, args) {
+ const kthread = this.importer.getOrCreatePseudoThread('drm_vblank');
+ kthread.openSlice = eventName;
+ const slice = new tr.model.ThreadSlice('', kthread.openSlice,
+ ColorScheme.getColorIdForGeneralPurposeString(kthread.openSlice),
+ ts, args, 0);
+
+ kthread.thread.sliceGroup.pushSlice(slice);
+ },
+
+ /**
+ * Parses drm driver events and sets up state in the importer.
+ */
+ vblankEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /crtc=(\d+), seq=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ const crtc = parseInt(event[1]);
+ const seq = parseInt(event[2]);
+ this.drmVblankSlice(ts, 'vblank:' + crtc,
+ {
+ crtc,
+ seq
+ });
+ return true;
+ }
+ };
+
+ Parser.register(DrmParser);
+
+ return {
+ DrmParser,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/drm_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/drm_parser_test.html
new file mode 100644
index 00000000000..1369fa5ca9a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/drm_parser_test.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('drmImport', function() {
+ const lines = [
+ ' chrome-2465 [000] 71.653157: drm_vblank_event: crtc=0, seq=4233',
+ ' <idle>-0 [000] 71.669851: drm_vblank_event: crtc=0, seq=4234'
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const vblankThread = threads[0];
+ assert.strictEqual(vblankThread.name, 'drm_vblank');
+ assert.strictEqual(vblankThread.sliceGroup.length, 2);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/exynos_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/exynos_parser.html
new file mode 100644
index 00000000000..3f93d3bd6de
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/exynos_parser.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses exynos events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux exynos trace events.
+ * @constructor
+ */
+ function ExynosParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('exynos_busfreq_target_int',
+ ExynosParser.prototype.busfreqTargetIntEvent.bind(this));
+ importer.registerEventHandler('exynos_busfreq_target_mif',
+ ExynosParser.prototype.busfreqTargetMifEvent.bind(this));
+
+ importer.registerEventHandler('exynos_page_flip_state',
+ ExynosParser.prototype.pageFlipStateEvent.bind(this));
+ }
+
+ ExynosParser.prototype = {
+ __proto__: Parser.prototype,
+
+ exynosBusfreqSample(name, ts, frequency) {
+ const targetCpu = this.importer.getOrCreateCpu(0);
+ const counter = targetCpu.getOrCreateCounter('', name);
+ if (counter.numSeries === 0) {
+ counter.addSeries(new tr.model.CounterSeries('frequency',
+ ColorScheme.getColorIdForGeneralPurposeString(
+ counter.name + '.' + 'frequency')));
+ }
+ counter.series.forEach(function(series) {
+ series.addCounterSample(ts, frequency);
+ });
+ },
+
+ /**
+ * Parses exynos_busfreq_target_int events and sets up state.
+ */
+ busfreqTargetIntEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /frequency=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ this.exynosBusfreqSample('INT Frequency', ts, parseInt(event[1]));
+ return true;
+ },
+
+ /**
+ * Parses exynos_busfreq_target_mif events and sets up state.
+ */
+ busfreqTargetMifEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /frequency=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ this.exynosBusfreqSample('MIF Frequency', ts, parseInt(event[1]));
+ return true;
+ },
+
+ exynosPageFlipStateOpenSlice(ts, pipe, fb, state) {
+ const kthread = this.importer.getOrCreatePseudoThread(
+ 'exynos_flip_state (pipe:' + pipe + ', fb:' + fb + ')');
+ kthread.openSliceTS = ts;
+ kthread.openSlice = state;
+ },
+
+ exynosPageFlipStateCloseSlice(ts, pipe, fb, args) {
+ const kthread = this.importer.getOrCreatePseudoThread(
+ 'exynos_flip_state (pipe:' + pipe + ', fb:' + fb + ')');
+ if (kthread.openSlice) {
+ const slice = new tr.model.ThreadSlice('', kthread.openSlice,
+ ColorScheme.getColorIdForGeneralPurposeString(kthread.openSlice),
+ kthread.openSliceTS,
+ args,
+ ts - kthread.openSliceTS);
+ kthread.thread.sliceGroup.pushSlice(slice);
+ }
+ kthread.openSlice = undefined;
+ },
+
+ /**
+ * Parses page_flip_state events and sets up state in the importer.
+ */
+ pageFlipStateEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /pipe=(\d+), fb=(\d+), state=(.*)/.exec(eventBase.details);
+ if (!event) return false;
+
+ const pipe = parseInt(event[1]);
+ const fb = parseInt(event[2]);
+ const state = event[3];
+
+ this.exynosPageFlipStateCloseSlice(ts, pipe, fb,
+ {
+ pipe,
+ fb
+ });
+ if (state !== 'flipped') {
+ this.exynosPageFlipStateOpenSlice(ts, pipe, fb, state);
+ }
+ return true;
+ }
+ };
+
+ Parser.register(ExynosParser);
+
+ return {
+ ExynosParser,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/exynos_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/exynos_parser_test.html
new file mode 100644
index 00000000000..74cd74a7b4c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/exynos_parser_test.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ test('exynosBusfreqImport', function() {
+ const lines = [
+ ' kworker/1:0-4177 [001] .... 2803.129806: ' +
+ 'exynos_busfreq_target_int: frequency=200000',
+ ' kworker/1:0-4177 [001] .... 2803.229207: ' +
+ 'exynos_busfreq_target_int: frequency=267000',
+ ' kworker/1:0-4177 [001] .... 2803.329031: ' +
+ 'exynos_busfreq_target_int: frequency=160000',
+ ' kworker/1:0-4177 [001] .... 2805.729039: ' +
+ 'exynos_busfreq_target_mif: frequency=200000'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const c0 = m.kernel.cpus[0];
+ assert.strictEqual(c0.slices.length, 0);
+ assert.strictEqual(
+ c0.counters['.INT Frequency'].series[0].samples.length, 3);
+ assert.strictEqual(
+ c0.counters['.MIF Frequency'].series[0].samples.length, 1);
+ });
+
+ test('exynosPageFlipSlowRequestImport', function() {
+ const lines = [
+ ' <idle>-0 [000] d.h. 1000.000000: ' +
+ 'exynos_page_flip_state: pipe=0, fb=26, state=wait_kds',
+ ' Chrome_IOThread-21603 [000] d.h. 1000.000001: ' +
+ 'exynos_page_flip_state: pipe=0, fb=26, state=wait_apply',
+ ' kworker/0:1-25931 [000] .... 1000.000002: ' +
+ 'exynos_page_flip_state: pipe=0, fb=26, state=wait_flip',
+ ' kworker/0:1-25931 [000] .... 1000.000003: ' +
+ 'exynos_page_flip_state: pipe=0, fb=26, state=flipped',
+ ' <idle>-0 [000] d.h. 1000.000004: ' +
+ 'exynos_page_flip_state: pipe=0, fb=25, state=wait_kds',
+ ' Chrome_IOThread-21603 [000] d.h. 1000.000005: ' +
+ 'exynos_page_flip_state: pipe=0, fb=25, state=wait_apply',
+ ' kworker/0:1-25931 [000] .... 1000.000006: ' +
+ 'exynos_page_flip_state: pipe=0, fb=25, state=wait_flip',
+ ' kworker/0:1-25931 [000] .... 1000.000007: ' +
+ 'exynos_page_flip_state: pipe=0, fb=25, state=flipped'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ // there are 2 threads:
+ // (1) "exynos_flip_state (pipe:0, fb:25)"
+ // (2) "exynos_flip_state (pipe:0, fb:26)"
+ assert.strictEqual(threads.length, 2);
+
+ // in the test data, event of fb=26 occurs first, so it's thread[0]
+ const gfxFbId26Thread = threads[0]; // thread where fb === 26
+ const gfxFbId25Thread = threads[1]; // thread where fb === 25
+ assert.strictEqual(
+ gfxFbId25Thread.name, 'exynos_flip_state (pipe:0, fb:25)');
+ assert.strictEqual(
+ gfxFbId26Thread.name, 'exynos_flip_state (pipe:0, fb:26)');
+ // Every state (except for 'flipped') will start a new slice.
+ // The last event will not be closed, so it's not a slice
+ assert.strictEqual(gfxFbId25Thread.sliceGroup.length, 3);
+ assert.strictEqual(gfxFbId26Thread.sliceGroup.length, 3);
+ });
+
+ test('exynosPageFlipFastRequestImport', function() {
+ const lines = [
+ ' <idle>-0 [000] d.h. 1000.000000: ' +
+ 'exynos_page_flip_state: pipe=0, fb=26, state=wait_kds',
+ ' Chrome_IOThread-21603 [000] d.h. 1000.000001: ' +
+ 'exynos_page_flip_state: pipe=0, fb=25, state=wait_kds',
+ ' X-21385 [000] .... 1000.000002: ' +
+ 'exynos_page_flip_state: pipe=0, fb=26, state=wait_apply',
+ ' kworker/0:1-25931 [000] .... 1000.000003: ' +
+ 'exynos_page_flip_state: pipe=0, fb=26, state=wait_flip',
+ ' X-21385 [001] .... 1000.000004: ' +
+ 'exynos_page_flip_state: pipe=0, fb=25, state=wait_apply',
+ ' kworker/0:1-25931 [000] .... 1000.000005: ' +
+ 'exynos_page_flip_state: pipe=0, fb=26, state=flipped',
+ ' <idle>-0 [000] d.h. 1000.000006: ' +
+ 'exynos_page_flip_state: pipe=0, fb=26, state=wait_kds',
+ ' X-21385 [000] .... 1000.000007: ' +
+ 'exynos_page_flip_state: pipe=0, fb=25, state=wait_flip',
+ ' kworker/0:1-25931 [000] .... 1000.000008: ' +
+ 'exynos_page_flip_state: pipe=0, fb=25, state=flipped',
+ ' kworker/0:1-25931 [000] .... 1000.000009: ' +
+ 'exynos_page_flip_state: pipe=0, fb=25, state=wait_kds',
+ ' Chrome_IOThread-21603 [000] d.h. 1000.000010: ' +
+ 'exynos_page_flip_state: pipe=0, fb=25, state=wait_apply',
+ ' <idle>-0 [000] d.h. 1000.000011: ' +
+ 'exynos_page_flip_state: pipe=0, fb=26, state=wait_apply'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ // there are 2 threads:
+ // (1) "exynos_flip_state (pipe:0, fb:25)"
+ // (2) "exynos_flip_state (pipe:0, fb:26)"
+ assert.strictEqual(threads.length, 2);
+
+ // in the test data, event of fb=26 occurs first, so it's thread[0]
+ const gfxFbId26Thread = threads[0]; // thread where fb === 26
+ const gfxFbId25Thread = threads[1]; // thread where fb === 25
+ assert.strictEqual(
+ gfxFbId25Thread.name, 'exynos_flip_state (pipe:0, fb:25)');
+ assert.strictEqual(
+ gfxFbId26Thread.name, 'exynos_flip_state (pipe:0, fb:26)');
+ // Every state (except for 'flipped') will start a new slice.
+ // The last event will not be closed, so it's not a slice
+ assert.strictEqual(gfxFbId25Thread.sliceGroup.length, 4);
+ assert.strictEqual(gfxFbId26Thread.sliceGroup.length, 4);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/fence_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/fence_parser.html
new file mode 100644
index 00000000000..5745ff5e4a2
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/fence_parser.html
@@ -0,0 +1,163 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2018 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses fence events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux fence trace events.
+ * @constructor
+ */
+ function FenceParser(importer) {
+ Parser.call(this, importer);
+
+ this.model_ = importer.model_;
+ importer.registerEventHandler(
+ 'fence_init',
+ FenceParser.prototype.initEvent.bind(this));
+ importer.registerEventHandler(
+ 'fence_destroy',
+ FenceParser.prototype.fenceDestroyEvent.bind(this));
+ importer.registerEventHandler(
+ 'fence_enable_signal',
+ FenceParser.prototype.fenceEnableSignalEvent.bind(this));
+ importer.registerEventHandler(
+ 'fence_signaled',
+ FenceParser.prototype.fenceSignaledEvent.bind(this));
+ this.model_ = importer.model_;
+ }
+
+ const fenceRE = /driver=(\S+) timeline=(\S+) context=(\d+) seqno=(\d+)/;
+
+ FenceParser.prototype = {
+ __proto__: Parser.prototype,
+
+ /**
+ * Parses fence events and sets up state in the importer.
+ */
+ initEvent(eventName, cpuNumber, pid,
+ ts, eventBase) {
+ const event = fenceRE.exec(eventBase.details);
+ if (!event) return false;
+
+ if (eventBase.tgid === undefined) {
+ return false;
+ }
+
+ const thread = this.importer.getOrCreatePseudoThread(event[2]);
+ thread.lastActiveTs = ts;
+
+ return true;
+ },
+
+ fenceDestroyEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = fenceRE.exec(eventBase.details);
+ if (!event) return false;
+
+ if (eventBase.tgid === undefined) {
+ return false;
+ }
+ const thread = this.importer.getOrCreatePseudoThread(event[2]);
+ const name = 'fence_destroy(' + event[4] + ')';
+ const colorName = 'fence(' + event[4] + ')';
+
+ if (thread.lastActiveTs !== undefined) {
+ const duration = ts - thread.lastActiveTs;
+ const slice = new tr.model.ThreadSlice(
+ '', name,
+ ColorScheme.getColorIdForGeneralPurposeString(colorName),
+ thread.lastActiveTs, {
+ 'driver': event[1],
+ 'context': event[3]
+ },
+ duration);
+ thread.thread.sliceGroup.pushSlice(slice);
+ }
+ if (thread.thread.sliceGroup.openSliceCount > 0) {
+ thread.thread.sliceGroup.endSlice(ts);
+ }
+ thread.lastActiveTs = ts;
+ },
+
+ fenceEnableSignalEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = fenceRE.exec(eventBase.details);
+ if (!event) return false;
+
+ if (eventBase.tgid === undefined) {
+ return false;
+ }
+ const thread = this.importer.getOrCreatePseudoThread(event[2]);
+ const name = 'fence_enable(' + event[4] + ')';
+ const colorName = 'fence(' + event[4] + ')';
+
+ if (thread.lastActiveTs !== undefined) {
+ const duration = ts - thread.lastActiveTs;
+ const slice = new tr.model.ThreadSlice(
+ '', name,
+ ColorScheme.getColorIdForGeneralPurposeString(colorName),
+ thread.lastActiveTs, {
+ 'driver': event[1],
+ 'context': event[3]
+ },
+ duration);
+ thread.thread.sliceGroup.pushSlice(slice);
+ }
+ if (thread.thread.sliceGroup.openSliceCount > 0) {
+ thread.thread.sliceGroup.endSlice(ts);
+ }
+ thread.lastActiveTs = ts;
+ },
+
+ fenceSignaledEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = fenceRE.exec(eventBase.details);
+ if (!event) return false;
+
+ if (eventBase.tgid === undefined) {
+ return false;
+ }
+ const thread = this.importer.getOrCreatePseudoThread(event[2]);
+ const name = 'fence_signal(' + event[4] + ')';
+ const colorName = 'fence(' + event[4] + ')';
+
+ if (thread.lastActiveTs !== undefined) {
+ const duration = ts - thread.lastActiveTs;
+ const slice = new tr.model.ThreadSlice(
+ '', name,
+ ColorScheme.getColorIdForGeneralPurposeString(colorName),
+ thread.lastActiveTs, {
+ 'driver': event[1],
+ 'context': event[3]
+ },
+ duration);
+ thread.thread.sliceGroup.pushSlice(slice);
+ }
+ if (thread.thread.sliceGroup.openSliceCount > 0) {
+ thread.thread.sliceGroup.endSlice(ts);
+ }
+ thread.lastActiveTs = ts;
+
+ return true;
+ },
+
+ };
+
+ Parser.register(FenceParser);
+
+ return {
+ FenceParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/fence_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/fence_parser_test.html
new file mode 100644
index 00000000000..ae439e2c591
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/fence_parser_test.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2018 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('fenceEventImport', function() {
+ const lines = [
+ 'HwBinder:595_2-650 ( 595) [001] .... 584509.195956: ' +
+ 'fence_init: driver=sde_fence:crtc111:25602 timeline=crtc111 ' +
+ 'context=3 seqno=256022',
+ 'crtc_event:111-247 ( 247) [001] d..1 584509.213897: ' +
+ 'fence_signaled: driver=sde_fence:crtc111:25602 timeline=crtc111 ' +
+ 'context=3 seqno=256022'
+ ];
+
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ftrace_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ftrace_importer.html
new file mode 100644
index 00000000000..cedff930314
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ftrace_importer.html
@@ -0,0 +1,941 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/color_scheme.html">
+<link rel="import" href="/tracing/base/trace_stream.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/android_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/binder_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/bus_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/clock_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/cpufreq_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/disk_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/drm_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/exynos_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/fence_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/gesture_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/i2c_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/i915_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ion_heap_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/irq_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/kfunc_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/mali_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/memreclaim_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/power_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/regulator_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/rss_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/sched_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/sync_parser.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/workqueue_parser.html">
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/importer/simple_line_reader.html">
+<link rel="import" href="/tracing/model/clock_sync_manager.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+/**
+ * @fileoverview Imports text files in the Linux event trace format into the
+ * Model. This format is output both by sched_trace and by Linux's perf tool.
+ *
+ * This importer assumes the events arrive as a string. The unit tests provide
+ * examples of the trace format.
+ *
+ * Linux scheduler traces use a definition for 'pid' that is different than
+ * tracing uses. Whereas tracing uses pid to identify a specific process, a pid
+ * in a linux trace refers to a specific thread within a process. Within this
+ * file, we the definition used in Linux traces, as it improves the importing
+ * code's readability.
+ */
+'use strict';
+
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const MONOTONIC_TO_FTRACE_GLOBAL_SYNC_ID =
+ 'linux_clock_monotonic_to_ftrace_global';
+
+ const IMPORT_PRIORITY = 2;
+
+ /**
+ * Imports linux perf events into a specified model.
+ * @constructor
+ */
+ function FTraceImporter(model, events) {
+ this.importPriority = IMPORT_PRIORITY;
+ this.model_ = model;
+ this.events_ = events;
+ this.wakeups_ = [];
+ this.blockedReasons_ = [];
+ this.kernelThreadStates_ = {};
+ this.buildMapFromLinuxPidsToThreads_();
+ this.lines_ = [];
+ this.pseudoThreadCounter = 1;
+ this.parsers_ = [];
+ this.eventHandlers_ = {};
+ this.haveClockSyncedMonotonicToGlobal_ = false;
+ this.clockDomainId_ = tr.model.ClockDomainId.LINUX_FTRACE_GLOBAL;
+ }
+
+ const TestExports = {};
+
+ // Matches the trace record in 3.2 and later with the print-tgid option:
+ // <idle>-0 0 [001] d... 1.23: sched_switch
+ //
+ // A TGID (Thread Group ID) is basically what the Linux kernel calls what
+ // userland refers to as a process ID (as opposed to a Linux pid, which is
+ // what userland calls a thread ID).
+ const lineREWithTGID = new RegExp(
+ '^\\s*(.+)-(\\d+)\\s+\\(\\s*(\\d+|-+)\\)\\s\\[(\\d+)\\]' +
+ '\\s+[dX.][Nnp.][Hhs.][0-9a-f.]' +
+ '\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)$');
+ const lineParserWithTGID = function(line) {
+ const groups = lineREWithTGID.exec(line);
+ if (!groups) return groups;
+
+ let tgid = groups[3];
+ if (tgid[0] === '-') tgid = undefined;
+
+ return {
+ threadName: groups[1],
+ pid: groups[2],
+ tgid,
+ cpuNumber: groups[4],
+ timestamp: groups[5],
+ eventName: groups[6],
+ details: groups[7]
+ };
+ };
+ TestExports.lineParserWithTGID = lineParserWithTGID;
+
+ // Matches the default trace record in 3.2 and later (includes irq-info):
+ // <idle>-0 [001] d... 1.23: sched_switch
+ const lineREWithIRQInfo = new RegExp(
+ '^\\s*(.+)-(\\d+)\\s+\\[(\\d+)\\]' +
+ '\\s+[dX.][Nnp.][Hhs.][0-9a-f.]' +
+ '\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)$');
+ const lineParserWithIRQInfo = function(line) {
+ const groups = lineREWithIRQInfo.exec(line);
+ if (!groups) return groups;
+ return {
+ threadName: groups[1],
+ pid: groups[2],
+ cpuNumber: groups[3],
+ timestamp: groups[4],
+ eventName: groups[5],
+ details: groups[6]
+ };
+ };
+ TestExports.lineParserWithIRQInfo = lineParserWithIRQInfo;
+
+ // Matches the default trace record pre-3.2:
+ // <idle>-0 [001] 1.23: sched_switch
+ const lineREWithLegacyFmt =
+ /^\s*(.+)-(\d+)\s+\[(\d+)\]\s*(\d+\.\d+):\s+(\S+):\s(.*)$/;
+ const lineParserWithLegacyFmt = function(line) {
+ const groups = lineREWithLegacyFmt.exec(line);
+ if (!groups) {
+ return groups;
+ }
+ return {
+ threadName: groups[1],
+ pid: groups[2],
+ cpuNumber: groups[3],
+ timestamp: groups[4],
+ eventName: groups[5],
+ details: groups[6]
+ };
+ };
+ TestExports.lineParserWithLegacyFmt = lineParserWithLegacyFmt;
+
+ // Matches the trace_event_clock_sync marker:
+ // 0: trace_event_clock_sync: parent_ts=19581477508
+ const traceEventClockSyncRE = /trace_event_clock_sync: parent_ts=(\d+\.?\d*)/;
+ TestExports.traceEventClockSyncRE = traceEventClockSyncRE;
+
+ const realTimeClockSyncRE = /trace_event_clock_sync: realtime_ts=(\d+)/;
+ const genericClockSyncRE = /trace_event_clock_sync: name=([\w\-]+)/;
+
+ // Some kernel trace events are manually classified in slices and
+ // hand-assigned a pseudo PID.
+ const pseudoKernelPID = 0;
+
+ /**
+ * Deduce the format of trace data. Linux kernels prior to 3.3 used one
+ * format (by default); 3.4 and later used another. Additionally, newer
+ * kernels can optionally trace the TGID.
+ *
+ * @return {function} the function for parsing data when the format is
+ * recognized; otherwise undefined.
+ */
+ function autoDetectLineParser(line) {
+ if (line[0] === '{') return false;
+ if (lineREWithTGID.test(line)) return lineParserWithTGID;
+ if (lineREWithIRQInfo.test(line)) return lineParserWithIRQInfo;
+ if (lineREWithLegacyFmt.test(line)) return lineParserWithLegacyFmt;
+ return undefined;
+ }
+ TestExports.autoDetectLineParser = autoDetectLineParser;
+
+ /**
+ * Guesses whether the provided events is a Linux perf string.
+ * Looks for the magic string "# tracer" at the start of the file,
+ * or the typical task-pid-cpu-timestamp-function sequence of a typical
+ * trace's body.
+ *
+ * @return {boolean} True when events is a linux perf array.
+ */
+ FTraceImporter.canImport = function(events) {
+ if (events instanceof tr.b.TraceStream) events = events.header;
+
+ if (!(typeof(events) === 'string' || events instanceof String)) {
+ return false;
+ }
+
+ if (FTraceImporter._extractEventsFromSystraceHTML(events, false).ok) {
+ return true;
+ }
+
+ if (FTraceImporter._extractEventsFromSystraceMultiHTML(events, false).ok) {
+ return true;
+ }
+
+ if (/^# tracer:/.test(events)) return true;
+
+ const lineBreakIndex = events.indexOf('\n');
+ if (lineBreakIndex > -1) events = events.substring(0, lineBreakIndex);
+
+ if (autoDetectLineParser(events)) return true;
+
+ return false;
+ };
+
+ FTraceImporter._extractEventsFromSystraceHTML = function(
+ incomingEvents, produceResult) {
+ const failure = {ok: false};
+ if (produceResult === undefined) produceResult = true;
+
+ const header = incomingEvents instanceof tr.b.TraceStream ?
+ incomingEvents.header : incomingEvents;
+ if (!/^<!DOCTYPE html>/.test(header)) return failure;
+ const r = new tr.importer.SimpleLineReader(incomingEvents);
+
+ // Try to find the data...
+ if (!r.advanceToLineMatching(/^ <script>$/)) return failure;
+ if (!r.advanceToLineMatching(/^ var linuxPerfData = "\\$/)) return failure;
+
+ const eventsBeginAtLine = r.curLineNumber + 1;
+ r.beginSavingLines();
+ if (!r.advanceToLineMatching(/^ <\/script>$/)) return failure;
+
+ let rawEvents = r.endSavingLinesAndGetResult();
+
+ // Drop off first and last event as it contains the tag.
+ rawEvents = rawEvents.slice(1, rawEvents.length - 1);
+
+ if (!r.advanceToLineMatching(/^<\/body>$/)) return failure;
+ if (!r.advanceToLineMatching(/^<\/html>$/)) return failure;
+
+ function endsWith(str, suffix) {
+ return str.indexOf(suffix, str.length - suffix.length) !== -1;
+ }
+ function stripSuffix(str, suffix) {
+ if (!endsWith(str, suffix)) return str;
+ return str.substring(str, str.length - suffix.length);
+ }
+
+ // Strip off escaping in the file needed to preserve linebreaks.
+ let events = [];
+ if (produceResult) {
+ for (let i = 0; i < rawEvents.length; i++) {
+ let event = rawEvents[i];
+ event = stripSuffix(event, '\\n\\');
+ events.push(event);
+ }
+ } else {
+ events = [rawEvents[rawEvents.length - 1]];
+ }
+
+ // Last event ends differently. Strip that off too,
+ // treating absence of that trailing string as a failure.
+ const oldLastEvent = events[events.length - 1];
+ const newLastEvent = stripSuffix(oldLastEvent, '\\n";');
+ if (newLastEvent === oldLastEvent) return failure;
+ events[events.length - 1] = newLastEvent;
+
+ return {ok: true,
+ lines: produceResult ? events : undefined,
+ eventsBeginAtLine};
+ };
+
+ FTraceImporter._extractEventsFromSystraceMultiHTML = function(
+ incomingEvents, produceResult) {
+ const failure = {ok: false};
+ if (produceResult === undefined) produceResult = true;
+
+ const header = incomingEvents instanceof tr.b.TraceStream ?
+ incomingEvents.header : incomingEvents;
+ if (!(new RegExp('^<!DOCTYPE HTML>', 'i').test(header))) return failure;
+
+ const r = new tr.importer.SimpleLineReader(incomingEvents);
+
+ // Try to find the Linux perf trace in any of the trace-data tags
+ let events = [];
+ let eventsBeginAtLine;
+ while (!/^# tracer:/.test(events)) {
+ if (!r.advanceToLineMatching(
+ /^ <script class="trace-data" type="application\/text">$/)) {
+ return failure;
+ }
+
+ eventsBeginAtLine = r.curLineNumber + 1;
+
+ r.beginSavingLines();
+ if (!r.advanceToLineMatching(/^ <\/script>$/)) return failure;
+
+ events = r.endSavingLinesAndGetResult();
+
+ // Drop off first and last event as it contains the tag.
+ events = events.slice(1, events.length - 1);
+ }
+
+ if (!r.advanceToLineMatching(/^<\/body>$/)) return failure;
+ if (!r.advanceToLineMatching(/^<\/html>$/)) return failure;
+
+ return {
+ ok: true,
+ lines: produceResult ? events : undefined,
+ eventsBeginAtLine,
+ };
+ };
+
+ FTraceImporter.prototype = {
+ __proto__: tr.importer.Importer.prototype,
+
+ get importerName() {
+ return 'FTraceImporter';
+ },
+
+ get model() {
+ return this.model_;
+ },
+
+ /**
+ * Imports clock sync markers into model_.
+ */
+ importClockSyncMarkers() {
+ this.lazyInit_();
+ this.forEachLine_(function(text, eventBase, cpuNumber, pid, ts) {
+ const eventName = eventBase.eventName;
+ if (eventName !== 'tracing_mark_write' && eventName !== '0') return;
+
+ if (traceEventClockSyncRE.exec(eventBase.details) ||
+ genericClockSyncRE.exec(eventBase.details)) {
+ this.traceClockSyncEvent_(eventName, cpuNumber, pid, ts, eventBase);
+ } else if (realTimeClockSyncRE.exec(eventBase.details)) {
+ // TODO(charliea): Migrate this sync to ClockSyncManager.
+ // This entry syncs CLOCK_REALTIME with CLOCK_MONOTONIC. Store the
+ // offset between the two in the model so that importers parsing files
+ // with CLOCK_REALTIME timestamps can map back to CLOCK_MONOTONIC.
+ const match = realTimeClockSyncRE.exec(eventBase.details);
+ this.model_.realtime_to_monotonic_offset_ms = ts - match[1];
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Imports the data in this.events_ into model_.
+ */
+ importEvents() {
+ const modelTimeTransformer =
+ this.model_.clockSyncManager.getModelTimeTransformer(
+ this.clockDomainId_);
+
+ this.importCpuData_(modelTimeTransformer);
+ this.buildMapFromLinuxPidsToThreads_();
+ this.buildPerThreadCpuSlicesFromCpuState_();
+ },
+
+ /**
+ * Registers a linux perf event parser used by importCpuData_.
+ */
+ registerEventHandler(eventName, handler) {
+ // TODO(sleffler) how to handle conflicts?
+ this.eventHandlers_[eventName] = handler;
+ },
+
+ /**
+ * @return {Cpu} A Cpu corresponding to the given cpuNumber.
+ */
+ getOrCreateCpu(cpuNumber) {
+ return this.model_.kernel.getOrCreateCpu(cpuNumber);
+ },
+
+ /**
+ * @return {TimelineThread} A thread corresponding to the kernelThreadName.
+ */
+ getOrCreateKernelThread(kernelThreadName, pid, tid) {
+ if (!this.kernelThreadStates_[kernelThreadName]) {
+ const thread = this.model_.getOrCreateProcess(pid).getOrCreateThread(
+ tid);
+ thread.name = kernelThreadName;
+ this.kernelThreadStates_[kernelThreadName] = {
+ pid,
+ thread,
+ openSlice: undefined,
+ openSliceTS: undefined
+ };
+ this.threadsByLinuxPid[tid] = thread;
+ }
+ return this.kernelThreadStates_[kernelThreadName];
+ },
+
+ /**
+ * Processes can have multiple binder threads.
+ * Binder thread names are not unique across processes we therefore need to
+ * keep more information in order to return the correct threads.
+ */
+ getOrCreateBinderKernelThread(kernelThreadName, pid, tid) {
+ const key = kernelThreadName + pid + tid;
+ if (!this.kernelThreadStates_[key]) {
+ const thread = this.model_.getOrCreateProcess(pid).getOrCreateThread(
+ tid);
+ thread.name = kernelThreadName;
+ this.kernelThreadStates_[key] = {
+ pid,
+ thread,
+ openSlice: undefined,
+ openSliceTS: undefined
+ };
+ this.threadsByLinuxPid[tid] = thread;
+ }
+ return this.kernelThreadStates_[key];
+ },
+
+ /**
+ * @return {TimelineThread} A pseudo thread corresponding to the
+ * threadName. Pseudo threads are for events that we want to break
+ * out to a separate timeline but would not otherwise happen.
+ * These threads are assigned to pseudoKernelPID and given a
+ * unique (incrementing) TID.
+ */
+ getOrCreatePseudoThread(threadName) {
+ let thread = this.kernelThreadStates_[threadName];
+ if (!thread) {
+ thread = this.getOrCreateKernelThread(threadName, pseudoKernelPID,
+ this.pseudoThreadCounter);
+ this.pseudoThreadCounter++;
+ }
+ return thread;
+ },
+
+ /**
+ * Records the fact that a pid has become runnable. This data will
+ * eventually get used to derive each thread's timeSlices array.
+ */
+ markPidRunnable(ts, pid, comm, prio, fromPid) {
+ // The the pids that get passed in to this function are Linux kernel
+ // pids, which identify threads. The rest of trace-viewer refers to
+ // these as tids, so the change of nomenclature happens in the following
+ // construction of the wakeup object.
+ this.wakeups_.push({ts, tid: pid, fromTid: fromPid});
+ },
+
+ /**
+ * Records the reason why a pid has gone into uninterruptible sleep.
+ */
+ addPidBlockedReason(ts, pid, iowait, caller) {
+ // The the pids that get passed in to this function are Linux kernel
+ // pids, which identify threads. The rest of trace-viewer refers to
+ // these as tids, so the change of nomenclature happens in the following
+ // construction of the wakeup object.
+ this.blockedReasons_.push({ts, tid: pid, iowait,
+ caller});
+ },
+
+ /**
+ * Precomputes a lookup table from linux pids back to existing
+ * Threads. This is used during importing to add information to each
+ * thread about whether it was running, descheduled, sleeping, et
+ * cetera.
+ */
+ buildMapFromLinuxPidsToThreads_() {
+ this.threadsByLinuxPid = {};
+ this.model_.getAllThreads().forEach(
+ function(thread) {
+ this.threadsByLinuxPid[thread.tid] = thread;
+ }.bind(this));
+ },
+
+ /**
+ * Builds the timeSlices array on each thread based on our knowledge of what
+ * each Cpu is doing. This is done only for Threads that are
+ * already in the model, on the assumption that not having any traced data
+ * on a thread means that it is not of interest to the user.
+ */
+ buildPerThreadCpuSlicesFromCpuState_() {
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+
+ // Push the cpu slices to the threads that they run on.
+ for (const cpuNumber in this.model_.kernel.cpus) {
+ const cpu = this.model_.kernel.cpus[cpuNumber];
+
+ for (let i = 0; i < cpu.slices.length; i++) {
+ const cpuSlice = cpu.slices[i];
+
+ const thread = this.threadsByLinuxPid[cpuSlice.args.tid];
+ if (!thread) continue;
+
+ cpuSlice.threadThatWasRunning = thread;
+
+ if (!thread.tempCpuSlices) {
+ thread.tempCpuSlices = [];
+ }
+ thread.tempCpuSlices.push(cpuSlice);
+ }
+ }
+
+ for (const i in this.wakeups_) {
+ const wakeup = this.wakeups_[i];
+ const thread = this.threadsByLinuxPid[wakeup.tid];
+ if (!thread) continue;
+ thread.tempWakeups = thread.tempWakeups || [];
+ thread.tempWakeups.push(wakeup);
+ }
+ for (const i in this.blockedReasons_) {
+ const reason = this.blockedReasons_[i];
+ const thread = this.threadsByLinuxPid[reason.tid];
+ if (!thread) continue;
+ thread.tempBlockedReasons = thread.tempBlockedReasons || [];
+ thread.tempBlockedReasons.push(reason);
+ }
+
+ // Create slices for when the thread is not running.
+ this.model_.getAllThreads().forEach(function(thread) {
+ if (thread.tempCpuSlices === undefined) return;
+ const origSlices = thread.tempCpuSlices;
+ delete thread.tempCpuSlices;
+
+ origSlices.sort(function(x, y) {
+ return x.start - y.start;
+ });
+
+ const wakeups = thread.tempWakeups || [];
+ delete thread.tempWakeups;
+ wakeups.sort(function(x, y) {
+ return x.ts - y.ts;
+ });
+
+ const reasons = thread.tempBlockedReasons || [];
+ delete thread.tempBlockedReasons;
+ reasons.sort(function(x, y) {
+ return x.ts - y.ts;
+ });
+
+ // Walk the slice list and put slices between each original slice to
+ // show when the thread isn't running.
+ const slices = [];
+
+ if (origSlices.length) {
+ const slice = origSlices[0];
+
+ if (wakeups.length && wakeups[0].ts < slice.start) {
+ const wakeup = wakeups.shift();
+ const wakeupDuration = slice.start - wakeup.ts;
+ const args = {'wakeup from tid': wakeup.fromTid};
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.RUNNABLE, '',
+ wakeup.ts, args, wakeupDuration));
+ }
+
+ const runningSlice = new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.RUNNING, '',
+ slice.start, {}, slice.duration);
+ runningSlice.cpuOnWhichThreadWasRunning = slice.cpu;
+ slices.push(runningSlice);
+ }
+
+ for (let i = 1; i < origSlices.length; i++) {
+ let wakeup = undefined;
+ const prevSlice = origSlices[i - 1];
+ const nextSlice = origSlices[i];
+ let midDuration = nextSlice.start - prevSlice.end;
+ while (wakeups.length && wakeups[0].ts < nextSlice.start) {
+ const w = wakeups.shift();
+ if (wakeup === undefined && w.ts > prevSlice.end) {
+ wakeup = w;
+ }
+ }
+ let blockedReason = undefined;
+ while (reasons.length && reasons[0].ts < prevSlice.end) {
+ const r = reasons.shift();
+ }
+ if (wakeup !== undefined &&
+ reasons.length &&
+ reasons[0].ts < wakeup.ts) {
+ blockedReason = reasons.shift();
+ }
+
+ // Push a sleep slice onto the slices list, interrupting it with a
+ // wakeup if appropriate.
+ const pushSleep = function(state) {
+ if (wakeup !== undefined) {
+ midDuration = wakeup.ts - prevSlice.end;
+ }
+
+ if (blockedReason !== undefined) {
+ const args = {
+ 'kernel callsite when blocked:': blockedReason.caller
+ };
+ if (blockedReason.iowait) {
+ switch (state) {
+ case SCHEDULING_STATE.UNINTR_SLEEP:
+ state = SCHEDULING_STATE.UNINTR_SLEEP_IO;
+ break;
+ case SCHEDULING_STATE.UNINTR_SLEEP_WAKE_KILL:
+ state = SCHEDULING_STATE.UNINTR_SLEEP_WAKE_KILL_IO;
+ break;
+ case SCHEDULING_STATE.UNINTR_SLEEP_WAKING:
+ state = SCHEDULING_STATE.UNINTR_SLEEP_WAKE_KILL_IO;
+ break;
+ default:
+ }
+ }
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread,
+ state, '', prevSlice.end, args, midDuration));
+ } else {
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread,
+ state, '', prevSlice.end, {}, midDuration));
+ }
+ if (wakeup !== undefined) {
+ const wakeupDuration = nextSlice.start - wakeup.ts;
+ const args = {'wakeup from tid': wakeup.fromTid};
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.RUNNABLE, '',
+ wakeup.ts, args, wakeupDuration));
+ wakeup = undefined;
+ }
+ };
+
+ if (prevSlice.args.stateWhenDescheduled === 'S') {
+ pushSleep(SCHEDULING_STATE.SLEEPING);
+ } else if (prevSlice.args.stateWhenDescheduled === 'R' ||
+ prevSlice.args.stateWhenDescheduled === 'R+') {
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.RUNNABLE, '',
+ prevSlice.end, {}, midDuration));
+ } else if (prevSlice.args.stateWhenDescheduled === 'D') {
+ pushSleep(SCHEDULING_STATE.UNINTR_SLEEP);
+ } else if (prevSlice.args.stateWhenDescheduled === 'T') {
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.STOPPED, '',
+ prevSlice.end, {}, midDuration));
+ } else if (prevSlice.args.stateWhenDescheduled === 't') {
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.DEBUG, '',
+ prevSlice.end, {}, midDuration));
+ } else if (prevSlice.args.stateWhenDescheduled === 'Z') {
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.ZOMBIE, '',
+ prevSlice.end, {}, midDuration));
+ } else if (prevSlice.args.stateWhenDescheduled === 'X') {
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.EXIT_DEAD, '',
+ prevSlice.end, {}, midDuration));
+ } else if (prevSlice.args.stateWhenDescheduled === 'x') {
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.TASK_DEAD, '',
+ prevSlice.end, {}, midDuration));
+ } else if (prevSlice.args.stateWhenDescheduled === 'K') {
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.WAKE_KILL, '',
+ prevSlice.end, {}, midDuration));
+ } else if (prevSlice.args.stateWhenDescheduled === 'W') {
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.WAKING, '',
+ prevSlice.end, {}, midDuration));
+ } else if (prevSlice.args.stateWhenDescheduled === 'D|K') {
+ pushSleep(SCHEDULING_STATE.UNINTR_SLEEP_WAKE_KILL);
+ } else if (prevSlice.args.stateWhenDescheduled === 'D|W') {
+ pushSleep(SCHEDULING_STATE.UNINTR_SLEEP_WAKING);
+ } else {
+ slices.push(new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.UNKNOWN, '',
+ prevSlice.end, {}, midDuration));
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Unrecognized sleep state: ' +
+ prevSlice.args.stateWhenDescheduled
+ });
+ }
+
+ const runningSlice = new tr.model.ThreadTimeSlice(
+ thread, SCHEDULING_STATE.RUNNING, '',
+ nextSlice.start, {}, nextSlice.duration);
+ runningSlice.cpuOnWhichThreadWasRunning = prevSlice.cpu;
+ slices.push(runningSlice);
+ }
+ thread.timeSlices = slices;
+ }, this);
+ },
+
+ /**
+ * Creates an instance of each registered linux perf event parser.
+ * This allows the parsers to register handlers for the events they
+ * understand. We also register our own special handlers (for the
+ * timestamp synchronization markers).
+ */
+ createParsers_() {
+ // Instantiate the parsers; this will register handlers for known events
+ const allTypeInfos = tr.e.importer.linux_perf.
+ Parser.getAllRegisteredTypeInfos();
+ const parsers = allTypeInfos.map(
+ function(typeInfo) {
+ return new typeInfo.constructor(this);
+ }, this);
+
+ return parsers;
+ },
+
+ registerDefaultHandlers_() {
+ this.registerEventHandler('tracing_mark_write',
+ FTraceImporter.prototype.traceMarkingWriteEvent_.bind(this));
+ // NB: old-style trace markers; deprecated
+ this.registerEventHandler('0',
+ FTraceImporter.prototype.traceMarkingWriteEvent_.bind(this));
+ // Register dummy clock sync handlers to avoid warnings in the log.
+ this.registerEventHandler('tracing_mark_write:trace_event_clock_sync',
+ function() { return true; });
+ this.registerEventHandler('0:trace_event_clock_sync',
+ function() { return true; });
+ },
+
+ /**
+ * Processes a trace_event_clock_sync event.
+ */
+ traceClockSyncEvent_(eventName, cpuNumber, pid, ts, eventBase) {
+ // Check to see if we have a normal clock sync marker that contains a
+ // sync ID and the current time according to the "ftrace global" clock.
+ let event = /name=(\w+?)\s(.+)/.exec(eventBase.details);
+ if (event) {
+ // TODO(alexandermont): This section of code seems to be broken. It
+ // creates an "args" variable, but doesn't seem to do anything with it.
+ const name = event[1];
+ const pieces = event[2].split(' ');
+ const args = {
+ perfTs: ts
+ };
+ for (let i = 0; i < pieces.length; i++) {
+ const parts = pieces[i].split('=');
+ if (parts.length !== 2) {
+ throw new Error('omgbbq');
+ }
+ args[parts[0]] = parts[1];
+ }
+
+ this.model_.clockSyncManager.addClockSyncMarker(
+ this.clockDomainId_, name, ts);
+ return true;
+ }
+
+ // Check to see if we have a "new style" clock sync marker that contains
+ // only a sync ID.
+ event = /name=([\w\-]+)/.exec(eventBase.details);
+ if (event) {
+ this.model_.clockSyncManager.addClockSyncMarker(
+ this.clockDomainId_, event[1], ts);
+ return true;
+ }
+
+ // Check to see if we have a special clock sync marker that contains both
+ // the current "ftrace global" time and the current CLOCK_MONOTONIC time.
+ event = /parent_ts=(\d+\.?\d*)/.exec(eventBase.details);
+ if (!event) return false;
+
+ let monotonicTs = event[1] * 1000;
+ // A monotonic timestamp of zero is used as a sentinel value to indicate
+ // that CLOCK_MONOTONIC and the ftrace global clock are identical.
+ if (monotonicTs === 0) monotonicTs = ts;
+
+ if (this.haveClockSyncedMonotonicToGlobal_) {
+ // ftrace sometimes includes multiple clock syncs between the monotonic
+ // and global clocks within a single trace. We protect against this by
+ // only taking the first one into account.
+ return true;
+ }
+
+ // We have a clock sync event that contains two timestamps: a timestamp
+ // according to the ftrace 'global' clock, and that same timestamp
+ // according to clock_gettime(CLOCK_MONOTONIC).
+ this.model_.clockSyncManager.addClockSyncMarker(
+ this.clockDomainId_,
+ MONOTONIC_TO_FTRACE_GLOBAL_SYNC_ID, ts);
+ this.model_.clockSyncManager.addClockSyncMarker(
+ tr.model.ClockDomainId.LINUX_CLOCK_MONOTONIC,
+ MONOTONIC_TO_FTRACE_GLOBAL_SYNC_ID, monotonicTs);
+
+ this.haveClockSyncedMonotonicToGlobal_ = true;
+ return true;
+ },
+
+ /**
+ * Processes a trace_marking_write event.
+ */
+ traceMarkingWriteEvent_(eventName, cpuNumber, pid, ts, eventBase,
+ threadName) {
+ // Some profiles end up with a \n\ on the end of each line. Strip it
+ // before we do the comparisons.
+ eventBase.details = eventBase.details.replace(/\\n.*$/, '');
+
+ const event = /^\s*(\w+):\s*(.*)$/.exec(eventBase.details);
+ if (!event) {
+ // Check if the event matches events traced by the Android framework
+ const tag = eventBase.details.substring(0, 2);
+ if (tag === 'B|' || tag === 'E' || tag === 'E|' || tag === 'X|' ||
+ tag === 'C|' || tag === 'S|' || tag === 'F|') {
+ eventBase.subEventName = 'android';
+ } else {
+ return false;
+ }
+ } else {
+ eventBase.subEventName = event[1];
+ eventBase.details = event[2];
+ }
+
+ const writeEventName = eventName + ':' + eventBase.subEventName;
+ const handler = this.eventHandlers_[writeEventName];
+ if (!handler) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Unknown trace_marking_write event ' + writeEventName
+ });
+ return true;
+ }
+ return handler(writeEventName, cpuNumber, pid, ts, eventBase, threadName);
+ },
+
+ /**
+ * Walks the this.events_ structure and creates Cpu objects.
+ */
+ importCpuData_(modelTimeTransformer) {
+ this.forEachLine_(function(text, eventBase, cpuNumber, pid, ts) {
+ const eventName = eventBase.eventName;
+ const handler = this.eventHandlers_[eventName];
+ if (!handler) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Unknown event ' + eventName + ' (' + text + ')'
+ });
+ return;
+ }
+ ts = modelTimeTransformer(ts);
+ if (!handler(eventName, cpuNumber, pid, ts, eventBase)) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Malformed ' + eventName + ' event (' + text + ')'
+ });
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Walks the this.events_ structure and populates this.lines_.
+ */
+ parseLines_() {
+ let extractResult = FTraceImporter._extractEventsFromSystraceHTML(
+ this.events_, true);
+ if (!extractResult.ok) {
+ extractResult = FTraceImporter._extractEventsFromSystraceMultiHTML(
+ this.events_, true);
+ }
+ let lineParser = undefined;
+ if (extractResult.ok) {
+ for (const line of extractResult.lines) {
+ lineParser = this.parseLine_(line, lineParser);
+ }
+ } else {
+ const r = new tr.importer.SimpleLineReader(this.events_);
+ for (const line of r) {
+ lineParser = this.parseLine_(line, lineParser);
+ }
+ }
+ },
+
+ parseLine_(line, lineParser) {
+ line = line.trim();
+ if (line.length === 0) return lineParser;
+ if (/^#/.test(line)) {
+ const clockType = /^# clock_type=([A-Z_]+)$/.exec(line);
+ // This allows the clock domain to be specified through a comment,
+ // Ex. "# clock_type=LINUX_CLOCK_MONOTONIC".
+ // This is used in the WALT trace agent.
+ if (clockType) {
+ this.clockDomainId_ = clockType[1];
+ }
+ return lineParser;
+ }
+
+ if (!lineParser) {
+ lineParser = autoDetectLineParser(line);
+ if (!lineParser) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Cannot parse line: ' + line
+ });
+ return lineParser;
+ }
+ }
+
+ const eventBase = lineParser(line);
+ if (!eventBase) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Unrecognized line: ' + line
+ });
+ return lineParser;
+ }
+
+ this.lines_.push([
+ line,
+ eventBase,
+ parseInt(eventBase.cpuNumber),
+ parseInt(eventBase.pid),
+ parseFloat(eventBase.timestamp) * 1000
+ ]);
+ return lineParser;
+ },
+
+ /**
+ * Calls |handler| for every parsed line.
+ */
+ forEachLine_(handler) {
+ for (let i = 0; i < this.lines_.length; ++i) {
+ const line = this.lines_[i];
+ handler.apply(this, line);
+ }
+ },
+
+ /**
+ * Initializes the ftrace importer. This initialization can't be done in the
+ * constructor because all trace event handlers may not have been registered
+ * by that point.
+ */
+ lazyInit_() {
+ this.parsers_ = this.createParsers_();
+ this.registerDefaultHandlers_();
+ this.parseLines_();
+ }
+ };
+
+ tr.importer.Importer.register(FTraceImporter);
+
+ return {
+ FTraceImporter,
+ _FTraceImporterTestExports: TestExports,
+ IMPORT_PRIORITY,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ftrace_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ftrace_importer_test.html
new file mode 100644
index 00000000000..ee9382ab13f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ftrace_importer_test.html
@@ -0,0 +1,686 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/in_memory_trace_stream.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+<link rel="import" href="/tracing/importer/import.html">
+<link rel="import" href="/tracing/model/clock_sync_manager.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ClockDomainId = tr.model.ClockDomainId;
+ const FTraceImporter = tr.e.importer.linux_perf.FTraceImporter;
+ const FTraceImporterTestExports =
+ tr.e.importer.linux_perf._FTraceImporterTestExports;
+
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ test('lineParserWithLegacyFmt', function() {
+ const p = FTraceImporterTestExports.lineParserWithLegacyFmt;
+ let x = p(' <idle>-0 [001] 4467.843475: sched_switch: ' +
+ 'prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R ==> ' +
+ 'next_comm=SurfaceFlinger next_pid=178 next_prio=112');
+ assert.isNotNull(x);
+ assert.strictEqual(x.threadName, '<idle>');
+ assert.strictEqual(x.pid, '0');
+ assert.strictEqual(x.cpuNumber, '001');
+ assert.strictEqual(x.timestamp, '4467.843475');
+ assert.strictEqual(x.eventName, 'sched_switch');
+ assert.strictEqual(
+ 'prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R' +
+ ' ==> next_comm=SurfaceFlinger next_pid=178 next_prio=112', x.details);
+
+ x = p('Binder-Thread #-647 [001] 260.464294: sched_switch: ' +
+ 'prev_comm=Binder Thread # prev_pid=647 prev_prio=120 prev_state=D ' +
+ ' ==> next_comm=.android.chrome next_pid=1562 next_prio=120');
+ assert.isNotNull(x);
+ assert.strictEqual(x.threadName, 'Binder-Thread #');
+ assert.strictEqual(x.pid, '647');
+ });
+
+ test('lineParserWithIRQInfo', function() {
+ const p = FTraceImporterTestExports.lineParserWithIRQInfo;
+ const x = p(' systrace.sh-5441 [001] d... 1031.091570: ' +
+ 'sched_wakeup: comm=debugd pid=4978 prio=120 success=1 target_cpu=000');
+ assert.isNotNull(x);
+ assert.strictEqual(x.threadName, 'systrace.sh');
+ assert.strictEqual(x.pid, '5441');
+ assert.strictEqual(x.cpuNumber, '001');
+ assert.strictEqual(x.timestamp, '1031.091570');
+ assert.strictEqual(x.eventName, 'sched_wakeup');
+ assert.strictEqual(x.details, 'comm=debugd pid=4978 prio=120 success=1 target_cpu=000'); // @suppress longLineCheck
+ });
+
+ test('lineParserWithIRQInfoNeedResched', function() {
+ const p = FTraceImporterTestExports.lineParserWithIRQInfo;
+ let x = p(' systrace.sh-5441 [001] .N.. 1031.091570: ' +
+ 'sched_wakeup: comm=debugd pid=4978 prio=120 success=1 target_cpu=000');
+ assert.isNotNull(x);
+ assert.strictEqual(x.threadName, 'systrace.sh');
+ assert.strictEqual(x.pid, '5441');
+ assert.strictEqual(x.cpuNumber, '001');
+ assert.strictEqual(x.timestamp, '1031.091570');
+ assert.strictEqual(x.eventName, 'sched_wakeup');
+ assert.strictEqual(x.details, 'comm=debugd pid=4978 prio=120 success=1 target_cpu=000'); // @suppress longLineCheck
+
+ x = p(' systrace.sh-5441 [001] .n.. 1031.091570: ' +
+ 'sched_wakeup: comm=debugd pid=4978 prio=120 success=1 target_cpu=000');
+ assert.isNotNull(x);
+ assert.strictEqual(x.threadName, 'systrace.sh');
+
+ x = p(' systrace.sh-5441 [001] .p.. 1031.091570: ' +
+ 'sched_wakeup: comm=debugd pid=4978 prio=120 success=1 target_cpu=000');
+ assert.isNotNull(x);
+ assert.strictEqual(x.threadName, 'systrace.sh');
+ });
+
+ test('lineParserWithTGID', function() {
+ const p = FTraceImporterTestExports.lineParserWithTGID;
+ let x = p(' systrace.sh-5441 (54321) [001] d... 1031.091570: ' +
+ 'sched_wakeup: comm=debugd pid=4978 prio=120 success=1 target_cpu=000');
+ assert.isNotNull(x);
+ assert.strictEqual(x.threadName, 'systrace.sh');
+ assert.strictEqual(x.pid, '5441');
+ assert.strictEqual(x.tgid, '54321');
+ assert.strictEqual(x.cpuNumber, '001');
+ assert.strictEqual(x.timestamp, '1031.091570');
+ assert.strictEqual(x.eventName, 'sched_wakeup');
+ assert.strictEqual(x.details, 'comm=debugd pid=4978 prio=120 success=1 target_cpu=000'); // @suppress longLineCheck
+
+ x = p(' systrace.sh-5441 ( 321) [001] d... 1031.091570: ' +
+ 'sched_wakeup: comm=debugd pid=4978 prio=120 success=1 target_cpu=000');
+ assert.isNotNull(x);
+ assert.strictEqual(x.tgid, '321');
+
+ x = p(' systrace.sh-5441 (-----) [001] d... 1031.091570: ' +
+ 'sched_wakeup: comm=debugd pid=4978 prio=120 success=1 target_cpu=000');
+ assert.isNotNull(x);
+ assert.isUndefined(x.tgid);
+ });
+
+ test('lineParserWithTGIDNeedResched', function() {
+ const p = FTraceImporterTestExports.lineParserWithTGID;
+ let x = p(' systrace.sh-5441 (54321) [001] .N.. 1031.091570: ' +
+ 'sched_wakeup: comm=debugd pid=4978 prio=120 success=1 target_cpu=000');
+ assert.isNotNull(x);
+ assert.strictEqual(x.threadName, 'systrace.sh');
+ assert.strictEqual(x.pid, '5441');
+ assert.strictEqual(x.tgid, '54321');
+ assert.strictEqual(x.cpuNumber, '001');
+ assert.strictEqual(x.timestamp, '1031.091570');
+ assert.strictEqual(x.eventName, 'sched_wakeup');
+ assert.strictEqual(x.details, 'comm=debugd pid=4978 prio=120 success=1 target_cpu=000'); // @suppress longLineCheck
+
+ x = p(' systrace.sh-5441 ( 321) [001] .n.. 1031.091570: ' +
+ 'sched_wakeup: comm=debugd pid=4978 prio=120 success=1 target_cpu=000');
+ assert.isNotNull(x);
+ assert.strictEqual(x.tgid, '321');
+
+ x = p(' systrace.sh-5441 (-----) [001] .p.. 1031.091570: ' +
+ 'sched_wakeup: comm=debugd pid=4978 prio=120 success=1 target_cpu=000');
+ assert.isNotNull(x);
+ assert.isUndefined(x.tgid);
+ });
+
+ test('autodetectLineCornerCases', function() {
+ const detectParser = FTraceImporterTestExports.autoDetectLineParser;
+ const lineParserWithLegacyFmt =
+ FTraceImporterTestExports.lineParserWithLegacyFmt;
+ const lineParserWithIRQInfo =
+ FTraceImporterTestExports.lineParserWithIRQInfo;
+ const lineParserWithTGID = FTraceImporterTestExports.lineParserWithTGID;
+
+ const lineWithLegacyFmt =
+ 'systrace.sh-8170 [001] 15180.978813: sched_switch: ' +
+ 'prev_comm=systrace.sh prev_pid=8170 prev_prio=120 ' +
+ 'prev_state=x ==> next_comm=kworker/1:0 next_pid=7873 ' +
+ 'next_prio=120';
+ let detected = detectParser(lineWithLegacyFmt);
+ assert.strictEqual(lineParserWithLegacyFmt, detected);
+
+ const lineWithIRQInfo =
+ 'systrace.sh-8170 [001] d... 15180.978813: sched_switch: ' +
+ 'prev_comm=systrace.sh prev_pid=8170 prev_prio=120 ' +
+ 'prev_state=x ==> next_comm=kworker/1:0 next_pid=7873 ' +
+ 'next_prio=120';
+ detected = detectParser(lineWithIRQInfo);
+ assert.strictEqual(lineParserWithIRQInfo, detected);
+
+ const lineWithTGID =
+ 'systrace.sh-8170 (54321) [001] d... 15180.978813: sched_switch: ' +
+ 'prev_comm=systrace.sh prev_pid=8170 prev_prio=120 ' +
+ 'prev_state=x ==> next_comm=kworker/1:0 next_pid=7873 ' +
+ 'next_prio=120';
+ detected = detectParser(lineWithTGID);
+ assert.strictEqual(lineParserWithTGID, detected);
+ });
+
+ test('traceEventClockSyncRE', function() {
+ const re = FTraceImporterTestExports.traceEventClockSyncRE;
+ let x = re.exec('trace_event_clock_sync: parent_ts=19581477508');
+ assert.isNotNull(x);
+ assert.strictEqual(x[1], '19581477508');
+
+ x = re.exec('trace_event_clock_sync: parent_ts=123.456');
+ assert.isNotNull(x);
+ assert.strictEqual(x[1], '123.456');
+ });
+
+ test('genericClockSync', function() {
+ const lines = [
+ '# tracer: nop',
+ '#',
+ '# TASK-PID CPU# TIMESTAMP FUNCTION',
+ '# | | | | |',
+ 'sh-26121 [000] ...1 15.050: tracing_mark_write: trace_event_clock_sync: name=battor regulator=8941_smbb_boost' // @suppress longLineCheck
+ ];
+
+ const io = new tr.importer.ImportOptions();
+ const m = new tr.Model();
+ const i = new tr.importer.Import(m, io);
+
+ m.clockSyncManager.addClockSyncMarker(ClockDomainId.BATTOR, 'battor', 50);
+
+ i.importTraces([lines.join('\n')]);
+
+ assert.isFalse(m.hasImportWarnings);
+ // The clock sync happened at 15050 in the ftrace global domain and at 50
+ // in the BattOr domain. This means the ftrace global timestamps need 15000
+ // subtracted from them in order to be on the BattOr timeline.
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(ClockDomainId.BATTOR)(3),
+ 3);
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ ClockDomainId.LINUX_FTRACE_GLOBAL)(15003),
+ 3);
+ });
+
+ test('clockTypeMarker', function() {
+ const lines = [
+ '# tracer: nop',
+ '#',
+ '# clock_type=LINUX_CLOCK_MONOTONIC',
+ '# TASK-PID CPU# TIMESTAMP FUNCTION',
+ '# | | | | |',
+ '<0>-0 (-----) [001] ...1 15.050: tracing_mark_write: ' +
+ 'trace_event_clock_sync: name=some_sync_id'
+ ];
+
+ const io = new tr.importer.ImportOptions();
+ const m = new tr.Model();
+ const i = new tr.importer.Import(m, io);
+
+ m.clockSyncManager.addClockSyncMarker(
+ ClockDomainId.LINUX_FTRACE_GLOBAL, 'some_sync_id', 50);
+
+ i.importTraces([lines.join('\n')]);
+
+ assert.isFalse(m.hasImportWarnings);
+ // The clock sync happened at 15050 in the linux monotonic domain and at 50
+ // in the ftrace global domain. This means the ftrace global timestamps need
+ // 15000 added to them in order to be on the linux monotonic timeline.
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ ClockDomainId.LINUX_FTRACE_GLOBAL)(3),
+ 15003);
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ ClockDomainId.LINUX_CLOCK_MONOTONIC)(3),
+ 3);
+ });
+
+ test('canImport', function() {
+ let lines = [
+ '# tracer: nop',
+ '#',
+ '# TASK-PID CPU# TIMESTAMP FUNCTION',
+ '# | | | | |',
+ ' <idle>-0 [001] 4467.843475: sched_switch: ' +
+ 'prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R ==> ' +
+ 'next_comm=SurfaceFlinger next_pid=178 next_prio=112',
+
+ ' SurfaceFlinger-178 [001] 4467.843536: sched_switch: ' +
+ 'prev_comm=SurfaceFlinger prev_pid=178 prev_prio=112 prev_state=S ' +
+ '==> next_comm=kworker/u:2 next_pid=2844 next_prio=120',
+
+ ' kworker/u:2-2844 [001] 4467.843567: sched_switch: ' +
+ 'prev_comm=kworker/u:2 prev_pid=2844 prev_prio=120 prev_state=S ' +
+ '==> next_comm=swapper next_pid=0 next_prio=120',
+
+ ' <idle>-0 [001] 4467.844208: sched_switch: ' +
+ 'prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R ==> ' +
+ 'next_comm=kworker/u:2 next_pid=2844 next_prio=120'
+ ];
+ assert.isTrue(FTraceImporter.canImport(lines.join('\n')));
+
+ lines = [
+ ' <idle>-0 [001] 4467.843475: sched_switch: ' +
+ 'prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R ==> ' +
+ 'next_comm=SurfaceFlinger next_pid=178 next_prio=112'
+ ];
+ assert.isTrue(FTraceImporter.canImport(lines.join('\n')));
+
+ lines = [
+ ' <idle>-0 [001] 4467.843475: sched_switch: ' +
+ 'prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R ==> ' +
+ 'next_comm=SurfaceFlinger next_pid=178 next_prio=112',
+
+ ' SurfaceFlinger-178 [001] 4467.843536: sched_switch: ' +
+ 'prev_comm=SurfaceFlinger prev_pid=178 prev_prio=112 ' +
+ 'prev_state=S ==> next_comm=kworker/u:2 next_pid=2844 ' +
+ 'next_prio=120'
+ ];
+ assert.isTrue(FTraceImporter.canImport(lines.join('\n')));
+
+ lines = [
+ 'SomeRandomText',
+ 'More random text'
+ ];
+ assert.isFalse(FTraceImporter.canImport(lines.join('\n')));
+ });
+
+ test('canImport34AndLater', function() {
+ let lines = [
+ '# tracer: nop',
+ '#',
+ '# entries-in-buffer/entries-written: 55191/55191 #P:2',
+ '#',
+ '# _-----=> irqs-off',
+ '# / _----=> need-resched',
+ '# | / _---=> hardirq/softirq',
+ '# || / _--=> preempt-depth',
+ '# ||| / delay',
+ '# TASK-PID CPU# |||| TIMESTAMP FUNCTION',
+ '# | | | |||| | |',
+ ' systrace.sh-5441 [001] d... 1031.091570: sched_wakeup: ' +
+ 'comm=debugd pid=4978 prio=120 success=1 target_cpu=000',
+ ' systrace.sh-5441 [001] d... 1031.091584: sched_switch: ' +
+ 'prev_comm=systrace.sh prev_pid=5441 prev_prio=120 prev_state=x ' +
+ '==> next_comm=chrome next_pid=5418 next_prio=120'
+ ];
+ assert.isTrue(FTraceImporter.canImport(lines.join('\n')));
+
+ lines = [
+ ' systrace.sh-5441 [001] d... 1031.091570: sched_wakeup: ' +
+ 'comm=debugd pid=4978 prio=120 success=1 target_cpu=000',
+ ' systrace.sh-5441 [001] d... 1031.091584: sched_switch: ' +
+ 'prev_comm=systrace.sh prev_pid=5441 prev_prio=120 prev_state=x ' +
+ '==> next_comm=chrome next_pid=5418 next_prio=120'
+ ];
+ assert.isTrue(FTraceImporter.canImport(lines.join('\n')));
+ });
+
+ test('importOneSequence', function() {
+ const lines = [
+ ' <idle>-0 [001] 4467.843475: sched_switch: ' +
+ 'prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R ==> ' +
+ 'next_comm=SurfaceFlinger next_pid=178 next_prio=112',
+
+ ' SurfaceFlinger-178 [001] 4467.843536: sched_switch: ' +
+ 'prev_comm=SurfaceFlinger prev_pid=178 prev_prio=112 ' +
+ 'prev_state=S ==> next_comm=kworker/u:2 next_pid=2844 ' +
+ 'next_prio=120',
+
+ ' kworker/u:2-2844 [001] 4467.843567: sched_switch: ' +
+ 'prev_comm=kworker/u:2 prev_pid=2844 prev_prio=120 ' +
+ 'prev_state=S ==> next_comm=swapper next_pid=0 next_prio=120'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const c = m.kernel.cpus[1];
+ assert.strictEqual(c.slices.length, 2);
+
+ assert.strictEqual(c.slices[0].title, 'SurfaceFlinger');
+ assert.strictEqual(c.slices[0].start, 4467843.475);
+ assert.closeTo(.536 - .475, c.slices[0].duration, 1e-5);
+ });
+
+ test('importOneSequenceWithSpacyThreadName', function() {
+ const lines = [
+ ' <idle>-0 [001] 4467.843475: sched_switch: ' +
+ 'prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R ==> ' +
+ 'next_comm=Surface Flinger next_pid=178 next_prio=112',
+
+ 'Surface Flinger -178 [001] 4467.843536: sched_switch: ' +
+ 'prev_comm=Surface Flinger prev_pid=178 prev_prio=112 ' +
+ 'prev_state=S ==> next_comm=kworker/u:2 next_pid=2844 ' +
+ 'next_prio=120',
+
+ ' kworker/u:2-2844 [001] 4467.843567: sched_switch: ' +
+ 'prev_comm=kworker/u:2 prev_pid=2844 prev_prio=120 ' +
+ 'prev_state=S ==> next_comm=swapper next_pid=0 next_prio=120'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const c = m.kernel.cpus[1];
+ assert.strictEqual(c.slices.length, 2);
+
+ assert.strictEqual(c.slices[0].title, 'Surface Flinger ');
+ assert.strictEqual(c.slices[0].start, 4467843.475);
+ assert.closeTo(.536 - .475, c.slices[0].duration, 1e-5);
+ });
+
+ test('importWithNewline', function() {
+ const lines = [
+ ''
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+ });
+
+ test('importSystraceHtml', function() {
+ const p = tr.b.getAsync(
+ '/test_data/trivial_systrace.html');
+ return p.then(function(data) {
+ const m = newModel(data);
+ assert.isFalse(m.hasImportWarnings);
+
+ assert.isDefined(m.processes[124]);
+ assert.isDefined(m.processes[360]);
+
+ assert.isDefined(m.processes[124].counters['android.StatusBar']);
+ assert.strictEqual(
+ m.processes[124].counters['android.StatusBar'].numSamples, 1);
+ assert.isDefined(m.processes[124].counters['android.VSYNC']);
+ assert.strictEqual(
+ 2, m.processes[124].counters['android.VSYNC'].numSamples);
+ assert.isDefined(m.processes[360].counters['android.iq']);
+ assert.strictEqual(
+ 1, m.processes[360].counters['android.iq'].numSamples);
+ }, function(err) {
+ throw err;
+ });
+ });
+
+ test('importMultiTraceHtml', function() {
+ const lines = [
+ '<!DoCTYPE hTml>', // check must be case insensitive
+ '<body>',
+ ' <div class="view">',
+ ' <\/div>',
+ ' <script class="trace-data" type="application/text">',
+ 'test1',
+ 'test2',
+ ' <\/script>',
+ ' <script class="trace-data" type="application/text">',
+ '# tracer: nop',
+ '#',
+ '# TASK-PID CPU# TIMESTAMP FUNCTION',
+ '# | | | | |',
+ ' hwc_eventmon-336 [000] 50260.929925: 0: C|124|VSYNC|1',
+ ' Binder_1-340 [000] 50260.935656: 0: C|124|StatusBar|1',
+ ' hwc_eventmon-336 [000] 50260.946573: 0: C|124|VSYNC|0',
+ ' InputReader-419 [000] 50262.538578: 0: C|360|iq|1',
+ ' <\/script>',
+ '<\/body>',
+ '<\/html>'
+ ];
+
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ assert.isDefined(m.processes[124]);
+ assert.isDefined(m.processes[360]);
+
+ assert.isDefined(m.processes[124].counters['android.StatusBar']);
+ assert.strictEqual(
+ m.processes[124].counters['android.StatusBar'].numSamples, 1);
+ assert.isDefined(m.processes[124].counters['android.VSYNC']);
+ assert.strictEqual(
+ 2, m.processes[124].counters['android.VSYNC'].numSamples);
+ assert.isDefined(m.processes[360].counters['android.iq']);
+ assert.strictEqual(
+ 1, m.processes[360].counters['android.iq'].numSamples);
+ });
+
+ test('importTraceStream', function() {
+ const lines = [
+ '<!DoCTYPE hTml>', // check must be case insensitive
+ '<body>',
+ ' <div class="view">',
+ ' <\/div>',
+ ' <script class="trace-data" type="application/text">',
+ 'test1',
+ 'test2',
+ ' <\/script>',
+ ' <script class="trace-data" type="application/text">',
+ '# tracer: nop',
+ '#',
+ '# TASK-PID CPU# TIMESTAMP FUNCTION',
+ '# | | | | |',
+ ' hwc_eventmon-336 [000] 50260.929925: 0: C|124|VSYNC|1',
+ ' Binder_1-340 [000] 50260.935656: 0: C|124|StatusBar|1',
+ ' hwc_eventmon-336 [000] 50260.946573: 0: C|124|VSYNC|0',
+ ' InputReader-419 [000] 50262.538578: 0: C|360|iq|1',
+ ' <\/script>',
+ '<\/body>',
+ '<\/html>'
+ ];
+ const inputStr = lines.join('\n');
+ const buffer = new ArrayBuffer(inputStr.length);
+ const bufferView = new Uint8Array(buffer);
+ for (let i = 0; i < bufferView.length; i++) {
+ bufferView[i] = inputStr.charCodeAt(i);
+ }
+
+ const m = newModel(new tr.b.InMemoryTraceStream(bufferView));
+ assert.isFalse(m.hasImportWarnings);
+
+ assert.isDefined(m.processes[124]);
+ assert.isDefined(m.processes[360]);
+
+ assert.isDefined(m.processes[124].counters['android.StatusBar']);
+ assert.strictEqual(
+ m.processes[124].counters['android.StatusBar'].numSamples, 1);
+ assert.isDefined(m.processes[124].counters['android.VSYNC']);
+ assert.strictEqual(
+ 2, m.processes[124].counters['android.VSYNC'].numSamples);
+ assert.isDefined(m.processes[360].counters['android.iq']);
+ assert.strictEqual(1, m.processes[360].counters['android.iq'].numSamples);
+ });
+
+ test('clockSync', function() {
+ const lines = [
+ ' <idle>-0 [001] 4467.843475: sched_switch: ' +
+ 'prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R ' +
+ '==> next_comm=SurfaceFlinger next_pid=178 next_prio=112',
+ ' SurfaceFlinger-178 [001] 4467.843536: sched_switch: ' +
+ 'prev_comm=SurfaceFlinger prev_pid=178 prev_prio=112 ' +
+ 'prev_state=S ==> next_comm=kworker/u:2 next_pid=2844 ' +
+ 'next_prio=120',
+ ' kworker/u:2-2844 [001] 4467.843567: sched_switch: ' +
+ 'prev_comm=kworker/u:2 prev_pid=2844 prev_prio=120 ' +
+ 'prev_state=S ==> next_comm=swapper next_pid=0 ' +
+ 'next_prio=120',
+ ' kworker/u:2-2844 [001] 4467.843000: 0: ' +
+ 'trace_event_clock_sync: parent_ts=0.1'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const c = m.kernel.cpus[1];
+ assert.strictEqual(c.slices.length, 2);
+
+ assert.closeTo(
+ (467.843475 - (467.843 - 0.1)) * 1000,
+ c.slices[0].start,
+ 1e-5);
+ });
+
+ test('clockSyncMarkWrite', function() {
+ const lines = [
+ 'systrace.sh-8170 [001] 15180.978813: sched_switch: ' +
+ 'prev_comm=systrace.sh prev_pid=8170 prev_prio=120 ' +
+ 'prev_state=x ==> next_comm=kworker/1:0 next_pid=7873 ' +
+ 'next_prio=120',
+ ' kworker/1:0-7873 [001] 15180.978836: sched_switch: ' +
+ 'prev_comm=kworker/1:0 prev_pid=7873 prev_prio=120 ' +
+ 'prev_state=S ==> next_comm=debugd next_pid=4404 next_prio=120',
+ ' debugd-4404 [001] 15180.979010: sched_switch: prev_comm=debugd ' +
+ 'prev_pid=4404 prev_prio=120 prev_state=S ==> ' +
+ 'next_comm=dbus-daemon next_pid=510 next_prio=120',
+ 'systrace.sh-8182 [000] 15186.203900: tracing_mark_write: ' +
+ 'trace_event_clock_sync: parent_ts=0'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const c = m.kernel.cpus[1];
+ assert.strictEqual(c.slices.length, 2);
+
+ assert.closeTo((15180.978813 - 0) * 1000, c.slices[0].start, 1e-5);
+
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ tr.model.ClockDomainId.LINUX_FTRACE_GLOBAL)(100),
+ 100);
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ tr.model.ClockDomainId.LINUX_CLOCK_MONOTONIC)(100),
+ 100);
+ });
+
+ test('clockSyncMarkWriteSecondIgnored', function() {
+ const lines = [
+ 'systrace.sh-8182 [000] 15186.203900: tracing_mark_write: ' +
+ 'trace_event_clock_sync: parent_ts=0',
+ 'systrace.sh-8182 [000] 15187.203900: tracing_mark_write: ' +
+ 'trace_event_clock_sync: parent_ts=0'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ tr.model.ClockDomainId.LINUX_FTRACE_GLOBAL)(100),
+ 100);
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ tr.model.ClockDomainId.LINUX_CLOCK_MONOTONIC)(100),
+ 100);
+ });
+
+ test('tracingMarkWriteEOLCleanup', function() {
+ const lines = [
+ 'systrace.sh-8182 [001] ...1 2068001.677892: tracing_mark_write: ' +
+ 'B|9304|test\\n\\',
+ 'systrace.sh-8182 [002] ...1 2068991.686415: tracing_mark_write: E\\n\\'
+ ];
+
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const c = m.processes[9304].threads[8182].sliceGroup;
+ assert.strictEqual(c.slices.length, 1);
+
+ assert.closeTo((2068001.677892 - 0) * 1000, c.slices[0].start, 1e-5);
+ assert.closeTo(
+ (2068991.686415 - 2068001.677892) * 1000,
+ c.slices[0].duration,
+ 1e-5);
+ });
+
+ test('cpuCount', function() {
+ const lines = [
+ 'systrace.sh-8170 [001] 15180.978813: sched_switch: ' +
+ 'prev_comm=systrace.sh prev_pid=8170 prev_prio=120 ' +
+ 'prev_state=x ==> next_comm=kworker/1:0 next_pid=7873 ' +
+ 'next_prio=120',
+ ' kworker/1:0-7873 [001] 15180.978836: sched_switch: ' +
+ 'prev_comm=kworker/1:0 prev_pid=7873 prev_prio=120 ' +
+ 'prev_state=S ==> next_comm=debugd next_pid=4404 next_prio=120',
+ ' debugd-4404 [000] 15180.979010: sched_switch: prev_comm=debugd ' +
+ 'prev_pid=4404 prev_prio=120 prev_state=S ==> ' +
+ 'next_comm=dbus-daemon next_pid=510 next_prio=120'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ assert.lengthOf(Object.keys(m.kernel.cpus), 2);
+ assert.strictEqual(m.kernel.bestGuessAtCpuCount, 2);
+ });
+
+ test('noOverlap', function() {
+ const lines = [
+ '# tracer: nop',
+ '#',
+ '# entries-in-buffer/entries-written: 10/10 #P:1',
+ '#',
+ '# _-----=> irqs-off',
+ '# / _----=> need-resched',
+ '# | / _---=> hardirq/softirq',
+ '# || / _--=> preempt-depth',
+ '# ||| / delay',
+ '# TASK-PID CPU# |||| TIMESTAMP FUNCTION',
+ '# | | | |||| | |',
+ ' <idle>-0 [000] d..3 49.000000: sched_switch: ' +
+ 'prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=S ' +
+ '==> next_comm=Unity next_pid=29677 next_prio=120',
+ ' <...>-29677 [000] d..3 49.001000: sched_switch: ' +
+ 'prev_comm=Unity prev_pid=29677 prev_prio=120 prev_state=R+ ' +
+ '==> next_comm=swapper/3 next_pid=0 next_prio=120',
+ ' <...>-29678 [000] d..4 49.002000: sched_wakeup: ' +
+ 'comm=Unity pid=29677 prio=120 success=1 target_cpu=000',
+ ' <idle>-0 [000] d..3 49.003000: sched_switch: ' +
+ 'prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=R ' +
+ '==> next_comm=Unity next_pid=29677 next_prio=120',
+ ' <...>-29677 [000] ...1 49.004000: tracing_mark_write: ' +
+ 'B|7244|eglSwapBuffersWithDamageKHR',
+ ' <...>-29677 [000] d..3 49.005000: sched_switch: ' +
+ 'prev_comm=Unity prev_pid=29677 prev_prio=120 prev_state=S ' +
+ '==> next_comm=swapper/3 next_pid=0 next_prio=120',
+ ' <...>-2599 [000] d.h4 49.006000: sched_wakeup: ' +
+ 'comm=Unity pid=29677 prio=120 success=1 target_cpu=000',
+ ' <idle>-0 [000] d..3 49.007000: sched_switch: ' +
+ 'prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=R ' +
+ '==> next_comm=Unity next_pid=29677 next_prio=120',
+ ' <...>-29677 [000] ...1 49.008000: tracing_mark_write: ' +
+ 'B|7244|queueBuffer',
+ ' <...>-29677 [000] d..3 49.009000: sched_switch: ' +
+ 'prev_comm=Unity prev_pid=29677 prev_prio=120 prev_state=R ' +
+ '==> next_comm=swapper/3 next_pid=0 next_prio=120'
+ ];
+ const m = newModel(lines.join('\n'));
+ const thread = m.getAllThreads()[0];
+ let previousEnd = 0;
+ for (const slice of thread.timeSlices) {
+ assert.isTrue(slice.end >= slice.start);
+ assert.isTrue(slice.start >= previousEnd);
+ previousEnd = slice.end;
+ }
+ });
+
+ test('cpuSliceNaming', function() {
+ // Invoke the binder parser first on a thread(1002) in the process that is
+ // not the main thread(1001) and then use sched events to create a cpu
+ // slice.
+ const lines = [
+ 'bar-1002 (1001) [000] ...1 1.000000: binder_transaction_received: ' +
+ 'transaction=612345 ',
+ 'dont_care-2000 (2000) [001] d..3 2.000000: sched_switch: ' +
+ 'prev_comm=dont_care prev_pid=2000 prev_prio=100 prev_state=D ' +
+ '==> next_comm=foo next_pid=1001 next_prio=100',
+ 'foo-1001 (1001) [001] d..3 3.000000: sched_switch: ' +
+ 'prev_comm=foo prev_pid=1001 prev_prio=100 prev_state=S ' +
+ '==> next_comm=doesnt_matter next_pid=3000 next_prio=100'
+ ];
+ const m = newModel(lines.join('\n'));
+ const slice = m.kernel.getOrCreateCpu(1).slices[0];
+ assert.strictEqual(slice.title, 'foo');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/gesture_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/gesture_parser.html
new file mode 100644
index 00000000000..e06cfbe9ef4
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/gesture_parser.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses gesture events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses trace events generated by gesture library for touchpad.
+ * @constructor
+ */
+ function GestureParser(importer) {
+ Parser.call(this, importer);
+ importer.registerEventHandler('tracing_mark_write:log',
+ GestureParser.prototype.logEvent.bind(this));
+ importer.registerEventHandler('tracing_mark_write:SyncInterpret',
+ GestureParser.prototype.syncEvent.bind(this));
+ importer.registerEventHandler('tracing_mark_write:HandleTimer',
+ GestureParser.prototype.timerEvent.bind(this));
+ }
+
+ GestureParser.prototype = {
+ __proto__: Parser.prototype,
+
+ /**
+ * Parse events generate by gesture library.
+ * gestureOpenSlice and gestureCloseSlice are two common
+ * functions to store the begin time and end time for all
+ * events in gesture library
+ */
+ gestureOpenSlice(title, ts, opt_args) {
+ const thread = this.importer.getOrCreatePseudoThread('gesture').thread;
+ thread.sliceGroup.beginSlice(
+ 'touchpad_gesture', title, ts, opt_args);
+ },
+
+ gestureCloseSlice(title, ts) {
+ const thread = this.importer.getOrCreatePseudoThread('gesture').thread;
+ if (thread.sliceGroup.openSliceCount) {
+ const slice = thread.sliceGroup.mostRecentlyOpenedPartialSlice;
+ if (slice.title !== title) {
+ this.importer.model.importWarning({
+ type: 'title_match_error',
+ message: 'Titles do not match. Title is ' +
+ slice.title + ' in openSlice, and is ' +
+ title + ' in endSlice'
+ });
+ } else {
+ thread.sliceGroup.endSlice(ts);
+ }
+ }
+ },
+
+ /**
+ * For log events, events will come in pairs with a tag log:
+ * like this:
+ * tracing_mark_write: log: start: TimerLogOutputs
+ * tracing_mark_write: log: end: TimerLogOutputs
+ * which represent the start and the end time of certain log behavior
+ * Take these logs above for example, they are the start and end time
+ * of logging Output for HandleTimer function
+ */
+ logEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const innerEvent =
+ /^\s*(\w+):\s*(\w+)$/.exec(eventBase.details);
+ switch (innerEvent[1]) {
+ case 'start':
+ this.gestureOpenSlice('GestureLog', ts, {name: innerEvent[2]});
+ break;
+ case 'end':
+ this.gestureCloseSlice('GestureLog', ts);
+ }
+ return true;
+ },
+
+ /**
+ * For SyncInterpret events, events will come in pairs with
+ * a tag SyncInterpret:
+ * like this:
+ * tracing_mark_write: SyncInterpret: start: ClickWiggleFilterInterpreter
+ * tracing_mark_write: SyncInterpret: end: ClickWiggleFilterInterpreter
+ * which represent the start and the end time of SyncInterpret function
+ * inside the certain interpreter in the gesture library.
+ * Take the logs above for example, they are the start and end time
+ * of the SyncInterpret function inside ClickWiggleFilterInterpreter
+ */
+ syncEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const innerEvent = /^\s*(\w+):\s*(\w+)$/.exec(eventBase.details);
+ switch (innerEvent[1]) {
+ case 'start':
+ this.gestureOpenSlice('SyncInterpret', ts,
+ {interpreter: innerEvent[2]});
+ break;
+ case 'end':
+ this.gestureCloseSlice('SyncInterpret', ts);
+ }
+ return true;
+ },
+
+ /**
+ * For HandleTimer events, events will come in pairs with
+ * a tag HandleTimer:
+ * like this:
+ * tracing_mark_write: HandleTimer: start: LookaheadFilterInterpreter
+ * tracing_mark_write: HandleTimer: end: LookaheadFilterInterpreter
+ * which represent the start and the end time of HandleTimer function
+ * inside the certain interpreter in the gesture library.
+ * Take the logs above for example, they are the start and end time
+ * of the HandleTimer function inside LookaheadFilterInterpreter
+ */
+ timerEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const innerEvent = /^\s*(\w+):\s*(\w+)$/.exec(eventBase.details);
+ switch (innerEvent[1]) {
+ case 'start':
+ this.gestureOpenSlice('HandleTimer', ts,
+ {interpreter: innerEvent[2]});
+ break;
+ case 'end':
+ this.gestureCloseSlice('HandleTimer', ts);
+ }
+ return true;
+ }
+ };
+
+ Parser.register(GestureParser);
+
+ return {
+ GestureParser,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/gesture_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/gesture_parser_test.html
new file mode 100644
index 00000000000..95f5cb5a54b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/gesture_parser_test.html
@@ -0,0 +1,213 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ test('gestureImport', function() {
+ const lines = [
+ '<...>-1837 [000] ...1 875292.741648: tracing_mark_write: ' +
+ 'log: start: TimerLogOutputs', // 0
+ '<...>-1837 [000] ...1 875292.741651: tracing_mark_write: ' +
+ 'log: end: TimerLogOutputs',
+ '<...>-1837 [000] ...1 875292.742796: tracing_mark_write: ' +
+ 'log: start: LogTimerCallback',
+ '<...>-1837 [000] ...1 875292.742802: tracing_mark_write: ' +
+ 'log: end: LogTimerCallback',
+ '<...>-1837 [000] ...1 875292.742805: tracing_mark_write: ' +
+ 'HandleTimer: start: LoggingFilterInterpreter', // 2
+ '<...>-1837 [000] ...1 875292.742809: tracing_mark_write: ' +
+ 'HandleTimer: start: AppleTrackpadFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742814: tracing_mark_write: ' +
+ 'HandleTimer: start: Cr48ProfileSensorFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742818: tracing_mark_write: ' +
+ 'HandleTimer: start: T5R2CorrectingFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742822: tracing_mark_write: ' +
+ 'HandleTimer: start: StuckButtonInhibitorFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742825: tracing_mark_write: ' +
+ 'HandleTimer: start: IntegralGestureFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742829: tracing_mark_write: ' +
+ 'HandleTimer: start: ScalingFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742833: tracing_mark_write: ' +
+ 'HandleTimer: start: SplitCorrectingFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742836: tracing_mark_write: ' +
+ 'HandleTimer: start: AccelFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742840: tracing_mark_write: ' +
+ 'HandleTimer: start: SensorJumpFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742843: tracing_mark_write: ' +
+ 'HandleTimer: start: BoxFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742846: tracing_mark_write: ' +
+ 'HandleTimer: start: LookaheadFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742853: tracing_mark_write: ' +
+ 'SyncInterpret: start: IirFilterInterpreter', // 14
+ '<...>-1837 [000] ...1 875292.742861: tracing_mark_write: ' +
+ 'SyncInterpret: start: PalmClassifyingFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742872: tracing_mark_write: ' +
+ 'SyncInterpret: start: ClickWiggleFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742881: tracing_mark_write: ' +
+ 'SyncInterpret: start: FlingStopFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742887: tracing_mark_write: ' +
+ 'SyncInterpret: start: ImmediateInterpreter',
+ '<...>-1837 [000] ...1 875292.742906: tracing_mark_write: ' +
+ 'SyncInterpret: end: ImmediateInterpreter',
+ '<...>-1837 [000] ...1 875292.742910: tracing_mark_write: ' +
+ 'SyncInterpret: end: FlingStopFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742914: tracing_mark_write: ' +
+ 'SyncInterpret: end: ClickWiggleFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742917: tracing_mark_write: ' +
+ 'SyncInterpret: end: PalmClassifyingFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742921: tracing_mark_write: ' +
+ 'SyncInterpret: end: IirFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742926: tracing_mark_write: ' +
+ 'HandleTimer: end: LookaheadFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742929: tracing_mark_write: ' +
+ 'HandleTimer: end: BoxFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742932: tracing_mark_write: ' +
+ 'HandleTimer: end: SensorJumpFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742937: tracing_mark_write: ' +
+ 'HandleTimer: end: AccelFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742940: tracing_mark_write: ' +
+ 'HandleTimer: end: SplitCorrectingFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742944: tracing_mark_write: ' +
+ 'HandleTimer: end: ScalingFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742949: tracing_mark_write: ' +
+ 'HandleTimer: end: IntegralGestureFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742952: tracing_mark_write: ' +
+ 'HandleTimer: end: StuckButtonInhibitorFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742956: tracing_mark_write: ' +
+ 'HandleTimer: end: T5R2CorrectingFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742959: tracing_mark_write: ' +
+ 'HandleTimer: end: Cr48ProfileSensorFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742962: tracing_mark_write: ' +
+ 'HandleTimer: end: AppleTrackpadFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742966: tracing_mark_write: ' +
+ 'HandleTimer: end: LoggingFilterInterpreter',
+ '<...>-1837 [000] ...1 875292.742969: tracing_mark_write: ' +
+ 'log: start: TimerLogOutputs',
+ '<...>-1837 [000] ...1 875292.742973: tracing_mark_write: ' +
+ 'log: end: TimerLogOutputs',
+ '<...>-1837 [000] ...1 875292.795219: tracing_mark_write: ' +
+ 'log: start: LogHardwareState',
+ '<...>-1837 [000] ...1 875292.795231: tracing_mark_write: ' +
+ 'log: end: LogHardwareState'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const gestureThread = threads[0];
+ assert.strictEqual(gestureThread.name, 'gesture');
+ assert.strictEqual(gestureThread.sliceGroup.length, 21);
+ assert.strictEqual('touchpad_gesture',
+ gestureThread.sliceGroup.slices[0].category);
+ assert.strictEqual('GestureLog',
+ gestureThread.sliceGroup.slices[0].title);
+ assert.strictEqual('touchpad_gesture',
+ gestureThread.sliceGroup.slices[2].category);
+ assert.strictEqual('HandleTimer',
+ gestureThread.sliceGroup.slices[2].title);
+ assert.strictEqual('touchpad_gesture',
+ gestureThread.sliceGroup.slices[14].category);
+ assert.strictEqual('SyncInterpret',
+ gestureThread.sliceGroup.slices[14].title);
+ });
+
+ test('unusualStart', function() {
+ const lines = [
+ 'X-30368 [000] ...1 1819362.481867: tracing_mark_write: ' +
+ 'SyncInterpret: start: IirFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481881: tracing_mark_write: ' +
+ 'SyncInterpret: start: PalmClassifyingFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481894: tracing_mark_write: ' +
+ 'SyncInterpret: start: ClickWiggleFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481905: tracing_mark_write: ' +
+ 'SyncInterpret: start: FlingStopFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481912: tracing_mark_write: ' +
+ 'SyncInterpret: start: ImmediateInterpreter',
+ 'X-30368 [000] ...1 1819362.481933: tracing_mark_write: ' +
+ 'SyncInterpret: end: ImmediateInterpreter',
+ 'X-30368 [000] ...1 1819362.481938: tracing_mark_write: ' +
+ 'SyncInterpret: end: FlingStopFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481943: tracing_mark_write: ' +
+ 'SyncInterpret: end: ClickWiggleFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481947: tracing_mark_write: ' +
+ 'SyncInterpret: end: PalmClassifyingFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481952: tracing_mark_write: ' +
+ 'SyncInterpret: end: IirFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481958: tracing_mark_write: ' +
+ 'HandleTimer: end: LookaheadFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481962: tracing_mark_write: ' +
+ 'HandleTimer: end: BoxFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481967: tracing_mark_write: ' +
+ 'HandleTimer: end: SensorJumpFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481973: tracing_mark_write: ' +
+ 'HandleTimer: end: AccelFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481977: tracing_mark_write: ' +
+ 'HandleTimer: end: SplitCorrectingFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481982: tracing_mark_write: ' +
+ 'HandleTimer: end: ScalingFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481988: tracing_mark_write: ' +
+ 'HandleTimer: end: IntegralGestureFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481993: tracing_mark_write: ' +
+ 'HandleTimer: end: StuckButtonInhibitorFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481998: tracing_mark_write: ' +
+ 'HandleTimer: end: T5R2CorrectingFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.482033: tracing_mark_write: ' +
+ 'HandleTimer: end: Cr48ProfileSensorFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.482038: tracing_mark_write: ' +
+ 'HandleTimer: end: AppleTrackpadFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.482043: tracing_mark_write: ' +
+ 'HandleTimer: end: LoggingFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.482047: tracing_mark_write: ' +
+ 'log: start: TimerLogOutputs',
+ 'X-30368 [000] ...1 1819362.482053: tracing_mark_write: ' +
+ 'log: end: TimerLogOutputs'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+ });
+
+ test('importError', function() {
+ const lines = [
+ 'X-30368 [000] ...1 1819362.481912: tracing_mark_write: ' +
+ 'SyncInterpret: start: ImmediateInterpreter',
+ 'X-30368 [000] ...1 1819362.481958: tracing_mark_write: ' +
+ 'HandleTimer: end: LookaheadFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481962: tracing_mark_write: ' +
+ 'HandleTimer: end: BoxFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481967: tracing_mark_write: ' +
+ 'HandleTimer: end: SensorJumpFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481973: tracing_mark_write: ' +
+ 'HandleTimer: end: AccelFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481977: tracing_mark_write: ' +
+ 'HandleTimer: end: SplitCorrectingFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481982: tracing_mark_write: ' +
+ 'HandleTimer: end: ScalingFilterInterpreter',
+ 'X-30368 [000] ...1 1819362.481988: tracing_mark_write: ' +
+ 'HandleTimer: end: IntegralGestureFilterInterpreter'
+ ];
+ const m = newModel(lines.join('\n'));
+
+ assert.isTrue(m.hasImportWarnings);
+ assert.strictEqual(m.importWarnings.length, 7);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i2c_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i2c_parser.html
new file mode 100644
index 00000000000..60769de3ad2
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i2c_parser.html
@@ -0,0 +1,177 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses i2c driver events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux i2c trace events.
+ * @constructor
+ */
+ function I2cParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('i2c_write',
+ I2cParser.prototype.i2cWriteEvent.bind(this));
+ importer.registerEventHandler('i2c_read',
+ I2cParser.prototype.i2cReadEvent.bind(this));
+ importer.registerEventHandler('i2c_reply',
+ I2cParser.prototype.i2cReplyEvent.bind(this));
+ importer.registerEventHandler('i2c_result',
+ I2cParser.prototype.i2cResultEvent.bind(this));
+ }
+
+ // Matches the i2c_write and i2c_reply records
+ const i2cWriteReplyRE = new RegExp(
+ 'i2c-(\\d+) #(\\d+) a=([\\da-fA-F]+) f=([\\da-fA-F]+) l=(\\d+) ' +
+ '(\\[[\\da-fA-F\\-]+\\])');
+ // Matches the i2c_read record
+ const i2cReadRE = /i2c-(\d+) #(\d+) a=([\da-fA-F]+) f=([\da-fA-F]+) l=(\d+)/;
+ // Matches the i2c_result record
+ const i2cResultRE = /i2c-(\d+) n=(\d+) ret=(\d+)/;
+
+ I2cParser.prototype = {
+ __proto__: Parser.prototype,
+
+ /**
+ * Parses i2c events and sets up state in the importer.
+ */
+ i2cWriteEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = i2cWriteReplyRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const adapterNumber = parseInt(event[1]);
+ const messageNumber = event[2];
+ const address = event[3];
+ const flags = event[4];
+ const dataLength = event[5];
+ const data = event[6];
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'i2c adapter ' + adapterNumber);
+
+ pushLastSliceIfNeeded(thread, event[1], ts);
+
+ thread.lastEntryTitle = 'i2c write';
+ thread.lastEntryTs = ts;
+ thread.lastEntryArgs = {
+ 'Message number': messageNumber,
+ 'Address': address,
+ 'Flags': flags,
+ 'Data Length': dataLength,
+ 'Data': data
+ };
+
+ return true;
+ },
+
+ i2cReadEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = i2cReadRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const adapterNumber = parseInt(event[1]);
+ const messageNumber = event[2];
+ const address = event[3];
+ const flags = event[4];
+ const dataLength = event[5];
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'i2c adapter ' + adapterNumber);
+
+ pushLastSliceIfNeeded(thread, event[1], ts);
+
+ thread.lastEntryTitle = 'i2c read';
+ thread.lastEntryTs = ts;
+ thread.lastEntryArgs = {
+ 'Message number': messageNumber,
+ 'Address': address,
+ 'Flags': flags,
+ 'Data Length': dataLength
+ };
+
+ return true;
+ },
+
+ i2cReplyEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = i2cWriteReplyRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const adapterNumber = parseInt(event[1]);
+ const messageNumber = event[2];
+ const address = event[3];
+ const flags = event[4];
+ const dataLength = event[5];
+ const data = event[6];
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'i2c adapter ' + adapterNumber);
+
+ pushLastSliceIfNeeded(thread, event[1], ts);
+
+ thread.lastEntryTitle = 'i2c reply';
+ thread.lastEntryTs = ts;
+ thread.lastEntryArgs = {
+ 'Message number': messageNumber,
+ 'Address': address,
+ 'Flags': flags,
+ 'Data Length': dataLength,
+ 'Data': data
+ };
+
+ return true;
+ },
+
+ i2cResultEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = i2cResultRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const adapterNumber = parseInt(event[1]);
+ const numMessages = event[2];
+ const ret = event[3];
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'i2c adapter ' + adapterNumber);
+
+ const args = thread.lastEntryArgs;
+ if (args !== undefined) {
+ args['Number of messages'] = numMessages;
+ args.Return = ret;
+ }
+
+ pushLastSliceIfNeeded(thread, event[1], ts);
+
+ thread.lastEntryTitle = undefined;
+ thread.lastEntryTs = undefined;
+ thread.lastEntryArgs = undefined;
+
+ return true;
+ },
+ };
+
+ function pushLastSliceIfNeeded(thread, id, currentTs) {
+ if (thread.lastEntryTs !== undefined) {
+ const duration = currentTs - thread.lastEntryTs;
+ const slice = new tr.model.ThreadSlice(
+ '', thread.lastEntryTitle,
+ ColorScheme.getColorIdForGeneralPurposeString(id),
+ thread.lastEntryTs, thread.lastEntryArgs, duration);
+ thread.thread.sliceGroup.pushSlice(slice);
+ }
+ }
+
+ Parser.register(I2cParser);
+
+ return {
+ I2cParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i2c_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i2c_parser_test.html
new file mode 100644
index 00000000000..da031719eb1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i2c_parser_test.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('i2cImport', function() {
+ const lines = [
+
+ ' /system/bin/surfaceflinger-516 ( 516) [002] ...1 6481.291425: ' +
+ 'i2c_write: i2c-3 #0 a=020 f=0000 l=2 [15-00]',
+ ' /system/bin/surfaceflinger-516 ( 516) [002] ...1 6481.291602: ' +
+ 'i2c_result: i2c-3 n=1 ret=1',
+ ' kworker/u8:5-9309 ( 9309) [001] ...1 6481.365821: ' +
+ 'i2c_write: i2c-7 #0 a=01d f=0000 l=1 [00]',
+ ' kworker/u8:5-9309 ( 9309) [001] ...1 6481.365823: ' +
+ 'i2c_read: i2c-7 #1 a=01d f=0001 l=1',
+ ' kworker/u8:5-9309 ( 9309) [001] ...1 6481.366730: ' +
+ 'i2c_reply: i2c-7 #1 a=01d f=0001 l=1 [01]',
+ ' kworker/u8:5-9309 ( 9309) [001] ...1 6481.366732: ' +
+ 'i2c_result: i2c-7 n=2 ret=2'
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings,
+ `Expected no import warnings but got ${m.importWarnings}`);
+
+ let threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 2);
+
+ threads = m.findAllThreadsNamed('i2c adapter 3');
+ assert.strictEqual(threads.length, 1);
+ assert.strictEqual(threads[0].sliceGroup.length, 1);
+ let slice = threads[0].sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'i2c write');
+ assert.isDefined(slice.args);
+ assert.strictEqual(slice.args['Message number'], '0');
+ assert.strictEqual(slice.args.Address, '020');
+ assert.strictEqual(slice.args.Flags, '0000');
+ assert.strictEqual(slice.args['Data Length'], '2');
+ assert.strictEqual(slice.args.Data, '[15-00]');
+
+ threads = m.findAllThreadsNamed('i2c adapter 7');
+ assert.strictEqual(threads.length, 1);
+ assert.strictEqual(threads[0].sliceGroup.length, 3);
+
+ slice = threads[0].sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'i2c write');
+ assert.strictEqual(slice.args['Message number'], '0');
+ assert.strictEqual(slice.args.Address, '01d');
+ assert.strictEqual(slice.args.Flags, '0000');
+ assert.strictEqual(slice.args['Data Length'], '1');
+ assert.strictEqual(slice.args.Data, '[00]');
+
+ slice = threads[0].sliceGroup.slices[1];
+ assert.strictEqual(slice.title, 'i2c read');
+ assert.strictEqual(slice.args['Message number'], '1');
+ assert.strictEqual(slice.args.Address, '01d');
+ assert.strictEqual(slice.args.Flags, '0001');
+ assert.strictEqual(slice.args['Data Length'], '1');
+
+ slice = threads[0].sliceGroup.slices[2];
+ assert.strictEqual(slice.title, 'i2c reply');
+ assert.strictEqual(slice.args['Message number'], '1');
+ assert.strictEqual(slice.args.Address, '01d');
+ assert.strictEqual(slice.args.Flags, '0001');
+ assert.strictEqual(slice.args['Data Length'], '1');
+ assert.strictEqual(slice.args.Data, '[01]');
+ assert.strictEqual(slice.args['Number of messages'], '2');
+ assert.strictEqual(slice.args.Return, '2');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i915_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i915_parser.html
new file mode 100644
index 00000000000..3862b351652
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i915_parser.html
@@ -0,0 +1,363 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses i915 driver events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux i915 trace events.
+ * @constructor
+ */
+ function I915Parser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('i915_gem_object_create',
+ I915Parser.prototype.gemObjectCreateEvent.bind(this));
+ importer.registerEventHandler('i915_gem_object_bind',
+ I915Parser.prototype.gemObjectBindEvent.bind(this));
+ importer.registerEventHandler('i915_gem_object_unbind',
+ I915Parser.prototype.gemObjectBindEvent.bind(this));
+ importer.registerEventHandler('i915_gem_object_change_domain',
+ I915Parser.prototype.gemObjectChangeDomainEvent.bind(this));
+ importer.registerEventHandler('i915_gem_object_pread',
+ I915Parser.prototype.gemObjectPreadWriteEvent.bind(this));
+ importer.registerEventHandler('i915_gem_object_pwrite',
+ I915Parser.prototype.gemObjectPreadWriteEvent.bind(this));
+ importer.registerEventHandler('i915_gem_object_fault',
+ I915Parser.prototype.gemObjectFaultEvent.bind(this));
+ importer.registerEventHandler('i915_gem_object_clflush',
+ // NB: reuse destroy handler
+ I915Parser.prototype.gemObjectDestroyEvent.bind(this));
+ importer.registerEventHandler('i915_gem_object_destroy',
+ I915Parser.prototype.gemObjectDestroyEvent.bind(this));
+ importer.registerEventHandler('i915_gem_ring_dispatch',
+ I915Parser.prototype.gemRingDispatchEvent.bind(this));
+ importer.registerEventHandler('i915_gem_ring_flush',
+ I915Parser.prototype.gemRingFlushEvent.bind(this));
+ importer.registerEventHandler('i915_gem_request',
+ I915Parser.prototype.gemRequestEvent.bind(this));
+ importer.registerEventHandler('i915_gem_request_add',
+ I915Parser.prototype.gemRequestEvent.bind(this));
+ importer.registerEventHandler('i915_gem_request_complete',
+ I915Parser.prototype.gemRequestEvent.bind(this));
+ importer.registerEventHandler('i915_gem_request_retire',
+ I915Parser.prototype.gemRequestEvent.bind(this));
+ importer.registerEventHandler('i915_gem_request_wait_begin',
+ I915Parser.prototype.gemRequestEvent.bind(this));
+ importer.registerEventHandler('i915_gem_request_wait_end',
+ I915Parser.prototype.gemRequestEvent.bind(this));
+ importer.registerEventHandler('i915_gem_ring_wait_begin',
+ I915Parser.prototype.gemRingWaitEvent.bind(this));
+ importer.registerEventHandler('i915_gem_ring_wait_end',
+ I915Parser.prototype.gemRingWaitEvent.bind(this));
+ importer.registerEventHandler('i915_reg_rw',
+ I915Parser.prototype.regRWEvent.bind(this));
+ importer.registerEventHandler('i915_flip_request',
+ I915Parser.prototype.flipEvent.bind(this));
+ importer.registerEventHandler('i915_flip_complete',
+ I915Parser.prototype.flipEvent.bind(this));
+ importer.registerEventHandler('intel_gpu_freq_change',
+ I915Parser.prototype.gpuFrequency.bind(this));
+ }
+
+ I915Parser.prototype = {
+ __proto__: Parser.prototype,
+
+ i915FlipOpenSlice(ts, obj, plane) {
+ // use i915_flip_obj_plane?
+ const kthread = this.importer.getOrCreatePseudoThread('i915_flip');
+ kthread.openSliceTS = ts;
+ kthread.openSlice = 'flip:' + obj + '/' + plane;
+ },
+
+ i915FlipCloseSlice(ts, args) {
+ const kthread = this.importer.getOrCreatePseudoThread('i915_flip');
+ if (kthread.openSlice) {
+ const slice = new tr.model.ThreadSlice('', kthread.openSlice,
+ ColorScheme.getColorIdForGeneralPurposeString(kthread.openSlice),
+ kthread.openSliceTS,
+ args,
+ ts - kthread.openSliceTS);
+
+ kthread.thread.sliceGroup.pushSlice(slice);
+ }
+ kthread.openSlice = undefined;
+ },
+
+ i915GemObjectSlice(ts, eventName, obj, args) {
+ const kthread = this.importer.getOrCreatePseudoThread('i915_gem');
+ kthread.openSlice = eventName + ':' + obj;
+ const slice = new tr.model.ThreadSlice('', kthread.openSlice,
+ ColorScheme.getColorIdForGeneralPurposeString(kthread.openSlice),
+ ts, args, 0);
+
+ kthread.thread.sliceGroup.pushSlice(slice);
+ },
+
+ i915GemRingSlice(ts, eventName, dev, ring, args) {
+ const kthread = this.importer.getOrCreatePseudoThread('i915_gem_ring');
+ kthread.openSlice = eventName + ':' + dev + '.' + ring;
+ const slice = new tr.model.ThreadSlice('', kthread.openSlice,
+ ColorScheme.getColorIdForGeneralPurposeString(kthread.openSlice),
+ ts, args, 0);
+
+ kthread.thread.sliceGroup.pushSlice(slice);
+ },
+
+ i915RegSlice(ts, eventName, reg, args) {
+ const kthread = this.importer.getOrCreatePseudoThread('i915_reg');
+ kthread.openSlice = eventName + ':' + reg;
+ const slice = new tr.model.ThreadSlice('', kthread.openSlice,
+ ColorScheme.getColorIdForGeneralPurposeString(kthread.openSlice),
+ ts, args, 0);
+
+ kthread.thread.sliceGroup.pushSlice(slice);
+ },
+
+ i915FreqChangeSlice(ts, eventName, args) {
+ const kthread = this.importer.getOrCreatePseudoThread('i915_gpu_freq');
+ kthread.openSlice = eventName;
+ const slice = new tr.model.ThreadSlice('', kthread.openSlice,
+ ColorScheme.getColorIdForGeneralPurposeString(kthread.openSlice),
+ ts, args, 0);
+
+ kthread.thread.sliceGroup.pushSlice(slice);
+ },
+
+ /**
+ * Parses i915 driver events and sets up state in the importer.
+ */
+ gemObjectCreateEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /obj=(\w+), size=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ const obj = event[1];
+ const size = parseInt(event[2]);
+ this.i915GemObjectSlice(ts, eventName, obj,
+ {
+ obj,
+ size
+ });
+ return true;
+ },
+
+ gemObjectBindEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ // TODO(sleffler) mappable
+ const event = /obj=(\w+), offset=(\w+), size=(\d+)/.exec(
+ eventBase.details);
+ if (!event) return false;
+
+ const obj = event[1];
+ const offset = event[2];
+ const size = parseInt(event[3]);
+ this.i915ObjectGemSlice(ts, eventName + ':' + obj,
+ {
+ obj,
+ offset,
+ size
+ });
+ return true;
+ },
+
+ gemObjectChangeDomainEvent(eventName, cpuNumber, pid, ts,
+ eventBase) {
+ const event = /obj=(\w+), read=(\w+=>\w+), write=(\w+=>\w+)/.exec(
+ eventBase.details);
+ if (!event) return false;
+
+ const obj = event[1];
+ const read = event[2];
+ const write = event[3];
+ this.i915GemObjectSlice(ts, eventName, obj,
+ {
+ obj,
+ read,
+ write
+ });
+ return true;
+ },
+
+ gemObjectPreadWriteEvent(eventName, cpuNumber, pid, ts,
+ eventBase) {
+ const event = /obj=(\w+), offset=(\d+), len=(\d+)/.exec(
+ eventBase.details);
+ if (!event) return false;
+
+ const obj = event[1];
+ const offset = parseInt(event[2]);
+ const len = parseInt(event[3]);
+ this.i915GemObjectSlice(ts, eventName, obj,
+ {
+ obj,
+ offset,
+ len
+ });
+ return true;
+ },
+
+ gemObjectFaultEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ // TODO(sleffler) writable
+ const event = /obj=(\w+), (\w+) index=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ const obj = event[1];
+ const type = event[2];
+ const index = parseInt(event[3]);
+ this.i915GemObjectSlice(ts, eventName, obj,
+ {
+ obj,
+ type,
+ index
+ });
+ return true;
+ },
+
+ gemObjectDestroyEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /obj=(\w+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ const obj = event[1];
+ this.i915GemObjectSlice(ts, eventName, obj,
+ {
+ obj
+ });
+ return true;
+ },
+
+ gemRingDispatchEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /dev=(\d+), ring=(\d+), seqno=(\d+)/.exec(
+ eventBase.details);
+ if (!event) return false;
+
+ const dev = parseInt(event[1]);
+ const ring = parseInt(event[2]);
+ const seqno = parseInt(event[3]);
+ this.i915GemRingSlice(ts, eventName, dev, ring,
+ {
+ dev,
+ ring,
+ seqno
+ });
+ return true;
+ },
+
+ gemRingFlushEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /dev=(\d+), ring=(\w+), invalidate=(\w+), flush=(\w+)/
+ .exec(eventBase.details);
+ if (!event) return false;
+
+ const dev = parseInt(event[1]);
+ const ring = parseInt(event[2]);
+ const invalidate = event[3];
+ const flush = event[4];
+ this.i915GemRingSlice(ts, eventName, dev, ring,
+ {
+ dev,
+ ring,
+ invalidate,
+ flush
+ });
+ return true;
+ },
+
+ gemRequestEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /dev=(\d+), ring=(\d+), seqno=(\d+)/.exec(
+ eventBase.details);
+ if (!event) return false;
+
+ const dev = parseInt(event[1]);
+ const ring = parseInt(event[2]);
+ const seqno = parseInt(event[3]);
+ this.i915GemRingSlice(ts, eventName, dev, ring,
+ {
+ dev,
+ ring,
+ seqno
+ });
+ return true;
+ },
+
+ gemRingWaitEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /dev=(\d+), ring=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ const dev = parseInt(event[1]);
+ const ring = parseInt(event[2]);
+ this.i915GemRingSlice(ts, eventName, dev, ring,
+ {
+ dev,
+ ring
+ });
+ return true;
+ },
+
+ regRWEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /(\w+) reg=(\w+), len=(\d+), val=(\(\w+, \w+\))/
+ .exec(eventBase.details);
+ if (!event) return false;
+
+ const rw = event[1];
+ const reg = event[2];
+ const len = event[3];
+ const data = event[3];
+ this.i915RegSlice(ts, rw, reg,
+ {
+ rw,
+ reg,
+ len,
+ data
+ });
+ return true;
+ },
+
+ flipEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /plane=(\d+), obj=(\w+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ const plane = parseInt(event[1]);
+ const obj = event[2];
+ if (eventName === 'i915_flip_request') {
+ this.i915FlipOpenSlice(ts, obj, plane);
+ } else {
+ this.i915FlipCloseSlice(ts,
+ {
+ obj,
+ plane
+ });
+ }
+ return true;
+ },
+
+ gpuFrequency(eventName, cpuNumver, pid, ts, eventBase) {
+ const event = /new_freq=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+ const freq = parseInt(event[1]);
+
+ this.i915FreqChangeSlice(ts, eventName, {
+ freq
+ });
+ return true;
+ }
+ };
+
+ Parser.register(I915Parser);
+
+ return {
+ I915Parser,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i915_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i915_parser_test.html
new file mode 100644
index 00000000000..e3564493b96
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/i915_parser_test.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('i915Import', function() {
+ const lines = [
+ // NB: spliced from different traces; mismatched timestamps don't matter
+ ' chrome-1223 [000] 2784.773556: i915_gem_object_pwrite: ' +
+ 'obj=ffff88013f13fc00, offset=0, len=2984',
+ ' chrome-1539 [000] 18420.677750: ' +
+ 'i915_gem_object_change_domain: ' +
+ 'obj=ffff8800a88d1400, read=44=>40, write=00=>40',
+ ' chrome-1539 [000] 18420.677759: i915_gem_object_fault: ' +
+ 'obj=ffff8800a88d1400, GTT index=0 , writable',
+ ' X-964 [000] 2784.774864: i915_flip_request: ' +
+ 'plane=0, obj=ffff88013f0b9a00',
+ ' <idle>-0 [000] 2784.788644: i915_flip_complete: ' +
+ 'plane=0, obj=ffff88013f0b9a00',
+ ' chrome-1539 [001] 18420.681687: i915_gem_request_retire: ' +
+ 'dev=0, ring=1, seqno=1178152',
+ ' chrome-1539 [000] 18422.955688: i915_gem_request_add: ' +
+ 'dev=0, ring=1, seqno=1178364',
+ ' cat-21833 [000] 18422.956832: i915_gem_request_complete: ' +
+ 'dev=0, ring=1, seqno=1178364',
+ ' X-1012 [001] 18420.682511: i915_gem_request_wait_begin: ' +
+ 'dev=0, ring=4, seqno=1178156',
+ ' X-1012 [000] 18422.765707: i915_gem_request_wait_end: ' +
+ 'dev=0, ring=4, seqno=1178359',
+ ' chrome-1539 [000] 18422.955655: i915_gem_ring_flush: ' +
+ 'dev=0, ring=1, invalidate=001e, flush=0040',
+ ' chrome-1539 [000] 18422.955660: i915_gem_ring_dispatch: ' +
+ 'dev=0, ring=1, seqno=1178364',
+ ' chrome-1539 [000] 18420.677772: i915_reg_rw: ' +
+ 'write reg=0x100030, len=8, val=(0xfca9001, 0xfce8007)',
+ ' kworker/u16:2-13998 [005] 1577664.436065: ' +
+ 'intel_gpu_freq_change: new_freq=350'
+
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ let i915GemThread = undefined;
+ let i915FlipThread = undefined;
+ let i915GemRingThread = undefined;
+ let i915RegThread = undefined;
+ let i915GpuFreqThread = undefined;
+ m.getAllThreads().forEach(function(t) {
+ switch (t.name) {
+ case 'i915_gem':
+ i915GemThread = t;
+ break;
+ case 'i915_flip':
+ i915FlipThread = t;
+ break;
+ case 'i915_gem_ring':
+ i915GemRingThread = t;
+ break;
+ case 'i915_reg':
+ i915RegThread = t;
+ break;
+ case 'i915_gpu_freq':
+ i915GpuFreqThread = t;
+ break;
+ default:
+ assert.fail(t, undefined, 'Unexpected thread named ' + t.name);
+ }
+ });
+ assert.isDefined(i915GemThread);
+ assert.isDefined(i915FlipThread);
+ assert.isDefined(i915GemRingThread);
+ assert.isDefined(i915RegThread);
+ assert.isDefined(i915GpuFreqThread);
+
+ assert.strictEqual(i915GemThread.sliceGroup.length, 3);
+
+ assert.strictEqual(i915FlipThread.sliceGroup.length, 1);
+
+ assert.closeTo(
+ 2784.774864 * 1000.0,
+ i915FlipThread.sliceGroup.slices[0].start,
+ 1e-5);
+ assert.closeTo(
+ (2784.788644 - 2784.774864) * 1000.0,
+ i915FlipThread.sliceGroup.slices[0].duration,
+ 1e-5);
+
+ assert.strictEqual(i915GemRingThread.sliceGroup.length, 7);
+ assert.strictEqual(i915RegThread.sliceGroup.length, 1);
+ assert.strictEqual(i915GpuFreqThread.sliceGroup.length, 1);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ion_heap_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ion_heap_parser.html
new file mode 100644
index 00000000000..bb08b7b4cc9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ion_heap_parser.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2019 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+<link rel="import" href="/tracing/model/counter_series.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses ion heap events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses ion heap trace events.
+ * @constructor
+ */
+ function IonHeapParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('ion_heap_shrink',
+ IonHeapParser.prototype.traceIonHeapShrink.bind(this));
+
+ importer.registerEventHandler('ion_heap_grow',
+ IonHeapParser.prototype.traceIonHeapGrow.bind(this));
+
+ this.model_ = importer.model_;
+ }
+
+ const TestExports = {};
+
+ // Matches the ion_heap_shrink and ion_heap_grow records
+ const ionHeapRE = new RegExp(
+ 'heap_name=(\\S+), len=(\\d+), total_allocated=(\\d+)');
+
+ TestExports.ionHeapRE = ionHeapRE;
+
+
+ IonHeapParser.prototype = {
+ __proto__: Parser.prototype,
+
+ /**
+ * Parses ion_heap_shrink events and sets up state in the importer.
+ */
+ traceIonHeapShrink(eventName, cpuNumber, pid, ts, eventBase, threadName) {
+ const event = ionHeapRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const name = event[1];
+ const len = parseInt(event[2]);
+ const totalAllocated = parseInt(event[3]);
+ const ionHeap = totalAllocated + len;
+
+ const ctr = this.model_.kernel.getOrCreateCounter(
+ null, name + ' ion heap');
+ if (ctr.numSeries === 0) {
+ ctr.addSeries(new tr.model.CounterSeries('value',
+ ColorScheme.getColorIdForGeneralPurposeString(
+ ctr.name + '.' + 'value')));
+ }
+ ctr.series.forEach(function(series) {
+ series.addCounterSample(ts, ionHeap);
+ });
+
+ return true;
+ },
+
+ /**
+ * Parses ion_heap_grow events and sets up state in the importer.
+ */
+ traceIonHeapGrow(eventName, cpuNumber, pid, ts, eventBase, threadName) {
+ const event = ionHeapRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const name = event[1];
+ const len = parseInt(event[2]);
+ const totalAllocated = parseInt(event[3]);
+ const ionHeap = totalAllocated + len;
+
+ const ctr = this.model_.kernel.getOrCreateCounter(
+ null, name + ' ion heap');
+ if (ctr.numSeries === 0) {
+ ctr.addSeries(new tr.model.CounterSeries('value',
+ ColorScheme.getColorIdForGeneralPurposeString(
+ ctr.name + '.' + 'value')));
+ }
+ ctr.series.forEach(function(series) {
+ series.addCounterSample(ts, ionHeap);
+ });
+
+ return true;
+ }
+ };
+
+ Parser.register(IonHeapParser);
+
+ return {
+ IonHeapParser,
+ _IonHeapParserTestExports: TestExports
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ion_heap_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ion_heap_parser_test.html
new file mode 100644
index 00000000000..3979f56870f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/ion_heap_parser_test.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2019 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ test('ionHeapRE', function() {
+ const re = tr.e.importer.linux_perf._IonHeapParserTestExports.ionHeapRE;
+ let x = re.exec('ion_heap_shrink: heap_name=system, len=20480, ' +
+ 'total_allocated=181116928');
+ assert.isNotNull(x);
+ assert.strictEqual(x[1], 'system');
+ assert.strictEqual(x[2], '20480');
+ assert.strictEqual(x[3], '181116928');
+
+ x = re.exec('ion_heap_grow: heap_name=system, len=17526784, ' +
+ 'total_allocated=181096448');
+ assert.isNotNull(x);
+ assert.strictEqual(x[1], 'system');
+ assert.strictEqual(x[2], '17526784');
+ assert.strictEqual(x[3], '181096448');
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/irq_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/irq_parser.html
new file mode 100644
index 00000000000..0654c9d96be
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/irq_parser.html
@@ -0,0 +1,271 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses drm driver events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux irq trace events.
+ * @constructor
+ */
+ function IrqParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('irq_handler_entry',
+ IrqParser.prototype.irqHandlerEntryEvent.bind(this));
+ importer.registerEventHandler('irq_handler_exit',
+ IrqParser.prototype.irqHandlerExitEvent.bind(this));
+ importer.registerEventHandler('softirq_raise',
+ IrqParser.prototype.softirqRaiseEvent.bind(this));
+ importer.registerEventHandler('softirq_entry',
+ IrqParser.prototype.softirqEntryEvent.bind(this));
+ importer.registerEventHandler('softirq_exit',
+ IrqParser.prototype.softirqExitEvent.bind(this));
+ importer.registerEventHandler('ipi_entry',
+ IrqParser.prototype.ipiEntryEvent.bind(this));
+ importer.registerEventHandler('ipi_exit',
+ IrqParser.prototype.ipiExitEvent.bind(this));
+ importer.registerEventHandler('preempt_disable',
+ IrqParser.prototype.preemptStartEvent.bind(this));
+ importer.registerEventHandler('preempt_enable',
+ IrqParser.prototype.preemptEndEvent.bind(this));
+ importer.registerEventHandler('irq_disable',
+ IrqParser.prototype.irqoffStartEvent.bind(this));
+ importer.registerEventHandler('irq_enable',
+ IrqParser.prototype.irqoffEndEvent.bind(this));
+ }
+
+ // Matches the irq_handler_entry record
+ const irqHandlerEntryRE = /irq=(\d+) name=(.+)/;
+
+ // Matches the irq_handler_exit record
+ const irqHandlerExitRE = /irq=(\d+) ret=(.+)/;
+
+ // Matches the softirq_raise record
+ const softirqRE = /vec=(\d+) \[action=(.+)\]/;
+
+ // Matches the ipi_exit_
+ const ipiHandlerExitRE = /\((.+)\)/;
+
+ // Matches the preempt_disable/enable records
+ const preemptirqRE = /caller=(.+) parent=(.+)/;
+
+ IrqParser.prototype = {
+ __proto__: Parser.prototype,
+
+ /**
+ * Parses irq events and sets up state in the mporter.
+ */
+ irqHandlerEntryEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = irqHandlerEntryRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const irq = parseInt(event[1]);
+ const name = event[2];
+
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'irqs cpu ' + cpuNumber);
+ thread.lastEntryTs = ts;
+ thread.irqName = name;
+
+ return true;
+ },
+
+ irqHandlerExitEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = irqHandlerExitRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const irq = parseInt(event[1]);
+ const ret = event[2];
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'irqs cpu ' + cpuNumber);
+
+ if (thread.lastEntryTs !== undefined) {
+ const duration = ts - thread.lastEntryTs;
+ const slice = new tr.model.ThreadSlice(
+ '',
+ 'IRQ (' + thread.irqName + ')',
+ ColorScheme.getColorIdForGeneralPurposeString(event[1]),
+ thread.lastEntryTs, { ret },
+ duration);
+ thread.thread.sliceGroup.pushSlice(slice);
+ }
+ thread.lastEntryTs = undefined;
+ thread.irqName = undefined;
+ return true;
+ },
+
+ softirqRaiseEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ return true;
+ },
+
+ softirqEntryEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = softirqRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const action = event[2];
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'softirq cpu ' + cpuNumber);
+ thread.lastEntryTs = ts;
+
+ return true;
+ },
+
+ softirqExitEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = softirqRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const vec = parseInt(event[1]);
+ const action = event[2];
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'softirq cpu ' + cpuNumber);
+
+ if (thread.lastEntryTs !== undefined) {
+ const duration = ts - thread.lastEntryTs;
+ const slice = new tr.model.ThreadSlice(
+ '', action,
+ ColorScheme.getColorIdForGeneralPurposeString(event[1]),
+ thread.lastEntryTs, { vec },
+ duration);
+ thread.thread.sliceGroup.pushSlice(slice);
+ }
+ thread.lastEntryTs = undefined;
+ return true;
+ },
+ /**
+ * Parses ipi events and sets up state in the mporter.
+ */
+ ipiEntryEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'irqs cpu ' + cpuNumber);
+ thread.lastEntryTs = ts;
+
+ return true;
+ },
+
+ ipiExitEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = ipiHandlerExitRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const ipiName = event[1];
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'irqs cpu ' + cpuNumber);
+
+ if (thread.lastEntryTs !== undefined) {
+ const duration = ts - thread.lastEntryTs;
+ const slice = new tr.model.ThreadSlice(
+ '',
+ 'IPI (' + ipiName + ')',
+ ColorScheme.getColorIdForGeneralPurposeString(ipiName),
+ thread.lastEntryTs,
+ {},
+ duration);
+ thread.thread.sliceGroup.pushSlice(slice);
+ }
+ thread.lastEntryTs = undefined;
+ return true;
+ },
+
+ /**
+ * Parses preempt disable/enable events and sets up state in the importer.
+ */
+ preemptStartEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = preemptirqRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'preempt cpu ' + cpuNumber);
+ thread.lastEntryTs = ts;
+
+ thread.preemptStartCaller = event[1];
+ thread.preemptStartParent = event[2];
+ return true;
+ },
+
+ preemptEndEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = preemptirqRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'preempt cpu ' + cpuNumber);
+
+ thread.preemptEndCaller = event[1];
+ thread.preemptEndParent = event[2];
+
+ if (thread.lastEntryTs !== undefined) {
+ const duration = ts - thread.lastEntryTs;
+ const slice = new tr.model.ThreadSlice(
+ '',
+ thread.preemptStartParent + ': ' + thread.preemptStartCaller,
+ ColorScheme.getColorIdForGeneralPurposeString(
+ thread.preemptEndCaller),
+ thread.lastEntryTs, {},
+ duration);
+ thread.thread.sliceGroup.pushSlice(slice);
+ }
+ thread.lastEntryTs = undefined;
+ return true;
+ },
+
+ /**
+ * Parses irqoff disable/enable events and sets up state in the importer.
+ */
+ irqoffStartEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = preemptirqRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'irqoff cpu ' + cpuNumber);
+ thread.lastEntryTs = ts;
+
+ thread.irqoffStartCaller = event[1];
+ thread.irqoffStartParent = event[2];
+ return true;
+ },
+
+ irqoffEndEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = preemptirqRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const thread = this.importer.getOrCreatePseudoThread(
+ 'irqoff cpu ' + cpuNumber);
+
+ thread.irqoffEndCaller = event[1];
+ thread.irqoffEndParent = event[2];
+
+ if (thread.lastEntryTs !== undefined) {
+ const duration = ts - thread.lastEntryTs;
+ const slice = new tr.model.ThreadSlice(
+ '',
+ thread.irqoffStartParent + ': ' + thread.irqoffStartCaller,
+ ColorScheme.getColorIdForGeneralPurposeString(
+ thread.irqoffEndCaller),
+ thread.lastEntryTs, {},
+ duration);
+ thread.thread.sliceGroup.pushSlice(slice);
+ }
+ thread.lastEntryTs = undefined;
+ return true;
+ }
+ };
+
+ Parser.register(IrqParser);
+
+ return {
+ IrqParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/irq_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/irq_parser_test.html
new file mode 100644
index 00000000000..84080971650
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/irq_parser_test.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('irqImport', function() {
+ const lines = [
+ ' kworker/u4:1-31907 (31907) [001] d.h3 14063.748288: ' +
+ 'irq_handler_entry: irq=27 name=arch_timer',
+ ' kworker/u4:1-31907 (31907) [001] dNh3 14063.748384: ' +
+ 'irq_handler_exit: irq=27 ret=handled',
+ ' kworker/u4:2-31908 (31908) [000] ..s3 14063.477231: ' +
+ 'softirq_entry: vec=9 [action=RCU]',
+ ' kworker/u4:2-31908 (31908) [000] ..s3 14063.477246: ' +
+ 'softirq_exit: vec=9 [action=RCU]',
+ ' RenderThread-2978 ( 2794) [002] dN.1 62828.421805: ' +
+ 'ipi_entry: (Rescheduling interrupts)',
+ ' RenderThread-2978 ( 2794) [002] dN.1 62828.421809: ' +
+ 'ipi_exit: (Rescheduling interrupts)',
+ ' RenderThread-2978 ( 2794) [002] dN.1 62829.421809: ' +
+ 'preempt_disable: caller=avc_lookup parent=avc_has_perm_noaudit',
+ ' RenderThread-2978 ( 2794) [002] dN.1 62830.421809: ' +
+ 'preempt_enable: caller=avc_lookup parent=avc_has_perm_noaudit',
+ ' kworker/u1:2-31908 (31908) [004] ..s3 14163.477231: ' +
+ 'irq_disable: caller=avc_lookup parent=avc_has_perm_noaudit',
+ ' kworker/u1:2-31908 (31908) [004] ..s3 14163.477246: ' +
+ 'irq_enable: caller=avc_lookup parent=avc_has_perm_noaudit'
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ let threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 5);
+
+ threads = m.findAllThreadsNamed('irqs cpu 1');
+ assert.strictEqual(threads.length, 1);
+ assert.strictEqual(threads[0].sliceGroup.length, 1);
+
+ threads = m.findAllThreadsNamed('softirq cpu 0');
+ assert.strictEqual(threads.length, 1);
+ assert.strictEqual(threads[0].sliceGroup.length, 1);
+
+ threads = m.findAllThreadsNamed('irqs cpu 2');
+ assert.strictEqual(threads.length, 1);
+ assert.strictEqual(threads[0].sliceGroup.length, 1);
+
+ threads = m.findAllThreadsNamed('preempt cpu 2');
+ assert.strictEqual(threads.length, 1);
+ assert.strictEqual(threads[0].sliceGroup.length, 1);
+
+ threads = m.findAllThreadsNamed('irqoff cpu 4');
+ assert.strictEqual(threads.length, 1);
+ assert.strictEqual(threads[0].sliceGroup.length, 1);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/kfunc_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/kfunc_parser.html
new file mode 100644
index 00000000000..b8f6f9aa915
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/kfunc_parser.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses graph_ent and graph_ret events that were inserted by
+ * the Linux kernel's function graph trace.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const LinuxPerfParser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses graph_ent and graph_ret events that were inserted by the Linux
+ * kernel's function graph trace.
+ * @constructor
+ */
+ function KernelFuncParser(importer) {
+ LinuxPerfParser.call(this, importer);
+
+ importer.registerEventHandler('graph_ent',
+ KernelFuncParser.prototype.traceKernelFuncEnterEvent.
+ bind(this));
+ importer.registerEventHandler('graph_ret',
+ KernelFuncParser.prototype.traceKernelFuncReturnEvent.
+ bind(this));
+
+ this.model_ = importer.model_;
+ this.ppids_ = {};
+ }
+
+ const TestExports = {};
+
+ const funcEnterRE = new RegExp('func=(.+)');
+ TestExports.funcEnterRE = funcEnterRE;
+
+ KernelFuncParser.prototype = {
+ __proto__: LinuxPerfParser.prototype,
+
+ traceKernelFuncEnterEvent(eventName, cpuNumber, pid, ts,
+ eventBase) {
+ const eventData = funcEnterRE.exec(eventBase.details);
+ if (!eventData) return false;
+
+ if (eventBase.tgid === undefined) {
+ return false;
+ }
+
+ const tgid = parseInt(eventBase.tgid);
+ const name = eventData[1];
+ const thread = this.model_.getOrCreateProcess(tgid)
+ .getOrCreateThread(pid);
+ thread.name = eventBase.threadName;
+
+ const slices = thread.kernelSliceGroup;
+ if (!slices.isTimestampValidForBeginOrEnd(ts)) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Timestamps are moving backward.'
+ });
+ return false;
+ }
+
+ const slice = slices.beginSlice(null, name, ts, {});
+
+ return true;
+ },
+
+ traceKernelFuncReturnEvent(eventName, cpuNumber, pid, ts,
+ eventBase) {
+ if (eventBase.tgid === undefined) {
+ return false;
+ }
+
+ const tgid = parseInt(eventBase.tgid);
+ const thread = this.model_.getOrCreateProcess(tgid)
+ .getOrCreateThread(pid);
+ thread.name = eventBase.threadName;
+
+ const slices = thread.kernelSliceGroup;
+ if (!slices.isTimestampValidForBeginOrEnd(ts)) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Timestamps are moving backward.'
+ });
+ return false;
+ }
+
+ if (slices.openSliceCount > 0) {
+ slices.endSlice(ts);
+ }
+
+ return true;
+ }
+ };
+
+ LinuxPerfParser.register(KernelFuncParser);
+
+ return {
+ KernelFuncParser,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/kfunc_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/kfunc_parser_test.html
new file mode 100644
index 00000000000..16f2a13c934
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/kfunc_parser_test.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('kernelFunctionParser', function() {
+ const lines = [
+ 'Binder_2-127 ( 127) [001] .... 3431.906759: graph_ent: func=sys_write',
+ 'Binder_2-127 ( 127) [001] .... 3431.906769: graph_ret: func=sys_write',
+ 'Binder_2-127 ( 127) [001] .... 3431.906785: graph_ent: func=sys_write',
+ 'Binder_2-127 ( 127) [001] ...1 3431.906798: tracing_mark_write: B|' +
+ '127|dequeueBuffer',
+ 'Binder_2-127 ( 127) [001] .... 3431.906802: graph_ret: func=sys_write',
+ 'Binder_2-127 ( 127) [001] .... 3431.906842: graph_ent: func=sys_write',
+ 'Binder_2-127 ( 127) [001] ...1 3431.906849: tracing_mark_write: E',
+ 'Binder_2-127 ( 127) [001] .... 3431.906853: graph_ret: func=sys_write',
+ 'Binder_2-127 ( 127) [001] .... 3431.906896: graph_ent: func=sys_write',
+ 'Binder_2-127 ( 127) [001] .... 3431.906906: graph_ret: func=sys_write'
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ const process = m.processes[127];
+ assert.isDefined(process);
+
+ const thread = process.threads[127];
+ assert.isDefined(thread);
+
+ const slices = thread.sliceGroup.slices;
+ assert.strictEqual(thread.sliceGroup.length, 7);
+
+ // Slice 0 is an un-split sys_write
+ assert.strictEqual(slices[0].title, 'sys_write');
+
+ // Slices 1 & 3 are a split sys_write
+ assert.strictEqual(slices[1].title, 'sys_write');
+ assert.strictEqual(slices[2].title, 'dequeueBuffer');
+ assert.strictEqual(slices[3].title, 'sys_write (cont.)');
+
+ // Slices 4 & 5 are a split sys_write with the dequeueBuffer in between
+ assert.strictEqual(slices[4].title, 'sys_write');
+ assert.strictEqual(slices[5].title, 'sys_write (cont.)');
+
+ // Slice 6 is another un-split sys_write
+ assert.strictEqual(slices[6].title, 'sys_write');
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/mali_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/mali_parser.html
new file mode 100644
index 00000000000..38c54d176fc
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/mali_parser.html
@@ -0,0 +1,659 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses Mali DDK/kernel events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses Mali DDK/kernel trace events.
+ * @constructor
+ */
+ function MaliParser(importer) {
+ Parser.call(this, importer);
+
+ // kernel DVFS events
+ importer.registerEventHandler('mali_dvfs_event',
+ MaliParser.prototype.dvfsEventEvent.bind(this));
+ importer.registerEventHandler('mali_dvfs_set_clock',
+ MaliParser.prototype.dvfsSetClockEvent.bind(this));
+ importer.registerEventHandler('mali_dvfs_set_voltage',
+ MaliParser.prototype.dvfsSetVoltageEvent.bind(this));
+
+ // kernel Mali hw counter events
+ this.addJMCounter('mali_hwc_MESSAGES_SENT', 'Messages Sent');
+ this.addJMCounter('mali_hwc_MESSAGES_RECEIVED', 'Messages Received');
+ this.addJMCycles('mali_hwc_GPU_ACTIVE', 'GPU Active');
+ this.addJMCycles('mali_hwc_IRQ_ACTIVE', 'IRQ Active');
+
+ for (let i = 0; i < 7; i++) {
+ const jobStr = 'JS' + i;
+ const jobHWCStr = 'mali_hwc_' + jobStr;
+ this.addJMCounter(jobHWCStr + '_JOBS', jobStr + ' Jobs');
+ this.addJMCounter(jobHWCStr + '_TASKS', jobStr + ' Tasks');
+ this.addJMCycles(jobHWCStr + '_ACTIVE', jobStr + ' Active');
+ this.addJMCycles(jobHWCStr + '_WAIT_READ', jobStr + ' Wait Read');
+ this.addJMCycles(jobHWCStr + '_WAIT_ISSUE', jobStr + ' Wait Issue');
+ this.addJMCycles(jobHWCStr + '_WAIT_DEPEND', jobStr + ' Wait Depend');
+ this.addJMCycles(jobHWCStr + '_WAIT_FINISH', jobStr + ' Wait Finish');
+ }
+
+ this.addTilerCounter('mali_hwc_TRIANGLES', 'Triangles');
+ this.addTilerCounter('mali_hwc_QUADS', 'Quads');
+ this.addTilerCounter('mali_hwc_POLYGONS', 'Polygons');
+ this.addTilerCounter('mali_hwc_POINTS', 'Points');
+ this.addTilerCounter('mali_hwc_LINES', 'Lines');
+ this.addTilerCounter('mali_hwc_VCACHE_HIT', 'VCache Hit');
+ this.addTilerCounter('mali_hwc_VCACHE_MISS', 'VCache Miss');
+ this.addTilerCounter('mali_hwc_FRONT_FACING', 'Front Facing');
+ this.addTilerCounter('mali_hwc_BACK_FACING', 'Back Facing');
+ this.addTilerCounter('mali_hwc_PRIM_VISIBLE', 'Prim Visible');
+ this.addTilerCounter('mali_hwc_PRIM_CULLED', 'Prim Culled');
+ this.addTilerCounter('mali_hwc_PRIM_CLIPPED', 'Prim Clipped');
+
+ this.addTilerCounter('mali_hwc_WRBUF_HIT', 'Wrbuf Hit');
+ this.addTilerCounter('mali_hwc_WRBUF_MISS', 'Wrbuf Miss');
+ this.addTilerCounter('mali_hwc_WRBUF_LINE', 'Wrbuf Line');
+ this.addTilerCounter('mali_hwc_WRBUF_PARTIAL', 'Wrbuf Partial');
+ this.addTilerCounter('mali_hwc_WRBUF_STALL', 'Wrbuf Stall');
+
+ this.addTilerCycles('mali_hwc_ACTIVE', 'Tiler Active');
+ this.addTilerCycles('mali_hwc_INDEX_WAIT', 'Index Wait');
+ this.addTilerCycles('mali_hwc_INDEX_RANGE_WAIT', 'Index Range Wait');
+ this.addTilerCycles('mali_hwc_VERTEX_WAIT', 'Vertex Wait');
+ this.addTilerCycles('mali_hwc_PCACHE_WAIT', 'Pcache Wait');
+ this.addTilerCycles('mali_hwc_WRBUF_WAIT', 'Wrbuf Wait');
+ this.addTilerCycles('mali_hwc_BUS_READ', 'Bus Read');
+ this.addTilerCycles('mali_hwc_BUS_WRITE', 'Bus Write');
+
+ this.addTilerCycles('mali_hwc_TILER_UTLB_STALL', 'Tiler UTLB Stall');
+ this.addTilerCycles('mali_hwc_TILER_UTLB_HIT', 'Tiler UTLB Hit');
+
+ this.addFragCycles('mali_hwc_FRAG_ACTIVE', 'Active');
+ /* NB: don't propagate spelling mistakes to labels */
+ this.addFragCounter('mali_hwc_FRAG_PRIMATIVES', 'Primitives');
+ this.addFragCounter('mali_hwc_FRAG_PRIMATIVES_DROPPED',
+ 'Primitives Dropped');
+ this.addFragCycles('mali_hwc_FRAG_CYCLE_DESC', 'Descriptor Processing');
+ this.addFragCycles('mali_hwc_FRAG_CYCLES_PLR', 'PLR Processing??');
+ this.addFragCycles('mali_hwc_FRAG_CYCLES_VERT', 'Vertex Processing');
+ this.addFragCycles('mali_hwc_FRAG_CYCLES_TRISETUP', 'Triangle Setup');
+ this.addFragCycles('mali_hwc_FRAG_CYCLES_RAST', 'Rasterization???');
+ this.addFragCounter('mali_hwc_FRAG_THREADS', 'Threads');
+ this.addFragCounter('mali_hwc_FRAG_DUMMY_THREADS', 'Dummy Threads');
+ this.addFragCounter('mali_hwc_FRAG_QUADS_RAST', 'Quads Rast');
+ this.addFragCounter('mali_hwc_FRAG_QUADS_EZS_TEST', 'Quads EZS Test');
+ this.addFragCounter('mali_hwc_FRAG_QUADS_EZS_KILLED', 'Quads EZS Killed');
+ this.addFragCounter('mali_hwc_FRAG_QUADS_LZS_TEST', 'Quads LZS Test');
+ this.addFragCounter('mali_hwc_FRAG_QUADS_LZS_KILLED', 'Quads LZS Killed');
+ this.addFragCycles('mali_hwc_FRAG_CYCLE_NO_TILE', 'No Tiles');
+ this.addFragCounter('mali_hwc_FRAG_NUM_TILES', 'Tiles');
+ this.addFragCounter('mali_hwc_FRAG_TRANS_ELIM', 'Transactions Eliminated');
+
+ this.addComputeCycles('mali_hwc_COMPUTE_ACTIVE', 'Active');
+ this.addComputeCounter('mali_hwc_COMPUTE_TASKS', 'Tasks');
+ this.addComputeCounter('mali_hwc_COMPUTE_THREADS', 'Threads Started');
+ this.addComputeCycles('mali_hwc_COMPUTE_CYCLES_DESC',
+ 'Waiting for Descriptors');
+
+ this.addTripipeCycles('mali_hwc_TRIPIPE_ACTIVE', 'Active');
+
+ this.addArithCounter('mali_hwc_ARITH_WORDS', 'Instructions (/Pipes)');
+ this.addArithCycles('mali_hwc_ARITH_CYCLES_REG',
+ 'Reg scheduling stalls (/Pipes)');
+ this.addArithCycles('mali_hwc_ARITH_CYCLES_L0',
+ 'L0 cache miss stalls (/Pipes)');
+ this.addArithCounter('mali_hwc_ARITH_FRAG_DEPEND',
+ 'Frag dep check failures (/Pipes)');
+
+ this.addLSCounter('mali_hwc_LS_WORDS', 'Instruction Words Completed');
+ this.addLSCounter('mali_hwc_LS_ISSUES', 'Full Pipeline Issues');
+ this.addLSCounter('mali_hwc_LS_RESTARTS', 'Restarts (unpairable insts)');
+ this.addLSCounter('mali_hwc_LS_REISSUES_MISS',
+ 'Pipeline reissue (cache miss/uTLB)');
+ this.addLSCounter('mali_hwc_LS_REISSUES_VD',
+ 'Pipeline reissue (varying data)');
+ /* TODO(sleffler) fix kernel event typo */
+ this.addLSCounter('mali_hwc_LS_REISSUE_ATTRIB_MISS',
+ 'Pipeline reissue (attribute cache miss)');
+ this.addLSCounter('mali_hwc_LS_REISSUE_NO_WB', 'Writeback not used');
+
+ this.addTexCounter('mali_hwc_TEX_WORDS', 'Words');
+ this.addTexCounter('mali_hwc_TEX_BUBBLES', 'Bubbles');
+ this.addTexCounter('mali_hwc_TEX_WORDS_L0', 'Words L0');
+ this.addTexCounter('mali_hwc_TEX_WORDS_DESC', 'Words Desc');
+ this.addTexCounter('mali_hwc_TEX_THREADS', 'Threads');
+ this.addTexCounter('mali_hwc_TEX_RECIRC_FMISS', 'Recirc due to Full Miss');
+ this.addTexCounter('mali_hwc_TEX_RECIRC_DESC', 'Recirc due to Desc Miss');
+ this.addTexCounter('mali_hwc_TEX_RECIRC_MULTI', 'Recirc due to Multipass');
+ this.addTexCounter('mali_hwc_TEX_RECIRC_PMISS',
+ 'Recirc due to Partial Cache Miss');
+ this.addTexCounter('mali_hwc_TEX_RECIRC_CONF',
+ 'Recirc due to Cache Conflict');
+
+ this.addLSCCounter('mali_hwc_LSC_READ_HITS', 'Read Hits');
+ this.addLSCCounter('mali_hwc_LSC_READ_MISSES', 'Read Misses');
+ this.addLSCCounter('mali_hwc_LSC_WRITE_HITS', 'Write Hits');
+ this.addLSCCounter('mali_hwc_LSC_WRITE_MISSES', 'Write Misses');
+ this.addLSCCounter('mali_hwc_LSC_ATOMIC_HITS', 'Atomic Hits');
+ this.addLSCCounter('mali_hwc_LSC_ATOMIC_MISSES', 'Atomic Misses');
+ this.addLSCCounter('mali_hwc_LSC_LINE_FETCHES', 'Line Fetches');
+ this.addLSCCounter('mali_hwc_LSC_DIRTY_LINE', 'Dirty Lines');
+ this.addLSCCounter('mali_hwc_LSC_SNOOPS', 'Snoops');
+
+ this.addAXICounter('mali_hwc_AXI_TLB_STALL', 'Address channel stall');
+ this.addAXICounter('mali_hwc_AXI_TLB_MISS', 'Cache Miss');
+ this.addAXICounter('mali_hwc_AXI_TLB_TRANSACTION', 'Transactions');
+ this.addAXICounter('mali_hwc_LS_TLB_MISS', 'LS Cache Miss');
+ this.addAXICounter('mali_hwc_LS_TLB_HIT', 'LS Cache Hit');
+ this.addAXICounter('mali_hwc_AXI_BEATS_READ', 'Read Beats');
+ this.addAXICounter('mali_hwc_AXI_BEATS_WRITE', 'Write Beats');
+
+ this.addMMUCounter('mali_hwc_MMU_TABLE_WALK', 'Page Table Walks');
+ this.addMMUCounter('mali_hwc_MMU_REPLAY_MISS',
+ 'Cache Miss from Replay Buffer');
+ this.addMMUCounter('mali_hwc_MMU_REPLAY_FULL', 'Replay Buffer Full');
+ this.addMMUCounter('mali_hwc_MMU_NEW_MISS', 'Cache Miss on New Request');
+ this.addMMUCounter('mali_hwc_MMU_HIT', 'Cache Hit');
+
+ this.addMMUCycles('mali_hwc_UTLB_STALL', 'UTLB Stalled');
+ this.addMMUCycles('mali_hwc_UTLB_REPLAY_MISS', 'UTLB Replay Miss');
+ this.addMMUCycles('mali_hwc_UTLB_REPLAY_FULL', 'UTLB Replay Full');
+ this.addMMUCycles('mali_hwc_UTLB_NEW_MISS', 'UTLB New Miss');
+ this.addMMUCycles('mali_hwc_UTLB_HIT', 'UTLB Hit');
+
+ this.addL2Counter('mali_hwc_L2_READ_BEATS', 'Read Beats');
+ this.addL2Counter('mali_hwc_L2_WRITE_BEATS', 'Write Beats');
+ this.addL2Counter('mali_hwc_L2_ANY_LOOKUP', 'Any Lookup');
+ this.addL2Counter('mali_hwc_L2_READ_LOOKUP', 'Read Lookup');
+ this.addL2Counter('mali_hwc_L2_SREAD_LOOKUP', 'Shareable Read Lookup');
+ this.addL2Counter('mali_hwc_L2_READ_REPLAY', 'Read Replayed');
+ this.addL2Counter('mali_hwc_L2_READ_SNOOP', 'Read Snoop');
+ this.addL2Counter('mali_hwc_L2_READ_HIT', 'Read Cache Hit');
+ this.addL2Counter('mali_hwc_L2_CLEAN_MISS', 'CleanUnique Miss');
+ this.addL2Counter('mali_hwc_L2_WRITE_LOOKUP', 'Write Lookup');
+ this.addL2Counter('mali_hwc_L2_SWRITE_LOOKUP', 'Shareable Write Lookup');
+ this.addL2Counter('mali_hwc_L2_WRITE_REPLAY', 'Write Replayed');
+ this.addL2Counter('mali_hwc_L2_WRITE_SNOOP', 'Write Snoop');
+ this.addL2Counter('mali_hwc_L2_WRITE_HIT', 'Write Cache Hit');
+ this.addL2Counter('mali_hwc_L2_EXT_READ_FULL', 'ExtRD with BIU Full');
+ this.addL2Counter('mali_hwc_L2_EXT_READ_HALF', 'ExtRD with BIU >1/2 Full');
+ this.addL2Counter('mali_hwc_L2_EXT_WRITE_FULL', 'ExtWR with BIU Full');
+ this.addL2Counter('mali_hwc_L2_EXT_WRITE_HALF', 'ExtWR with BIU >1/2 Full');
+
+ this.addL2Counter('mali_hwc_L2_EXT_READ', 'External Read (ExtRD)');
+ this.addL2Counter('mali_hwc_L2_EXT_READ_LINE', 'ExtRD (linefill)');
+ this.addL2Counter('mali_hwc_L2_EXT_WRITE', 'External Write (ExtWR)');
+ this.addL2Counter('mali_hwc_L2_EXT_WRITE_LINE', 'ExtWR (linefill)');
+ this.addL2Counter('mali_hwc_L2_EXT_WRITE_SMALL', 'ExtWR (burst size <64B)');
+ this.addL2Counter('mali_hwc_L2_EXT_BARRIER', 'External Barrier');
+ this.addL2Counter('mali_hwc_L2_EXT_AR_STALL', 'Address Read stalls');
+ this.addL2Counter('mali_hwc_L2_EXT_R_BUF_FULL',
+ 'Response Buffer full stalls');
+ this.addL2Counter('mali_hwc_L2_EXT_RD_BUF_FULL',
+ 'Read Data Buffer full stalls');
+ this.addL2Counter('mali_hwc_L2_EXT_R_RAW', 'RAW hazard stalls');
+ this.addL2Counter('mali_hwc_L2_EXT_W_STALL', 'Write Data stalls');
+ this.addL2Counter('mali_hwc_L2_EXT_W_BUF_FULL', 'Write Data Buffer full');
+ this.addL2Counter('mali_hwc_L2_EXT_R_W_HAZARD', 'WAW or WAR hazard stalls');
+ this.addL2Counter('mali_hwc_L2_TAG_HAZARD', 'Tag hazard replays');
+ this.addL2Cycles('mali_hwc_L2_SNOOP_FULL', 'Snoop buffer full');
+ this.addL2Cycles('mali_hwc_L2_REPLAY_FULL', 'Replay buffer full');
+
+ // DDK events (from X server)
+ importer.registerEventHandler('tracing_mark_write:mali_driver',
+ MaliParser.prototype.maliDDKEvent.bind(this));
+
+ // Mali job scheduler events from Exynos kernels
+ importer.registerEventHandler('mali_job_systrace_event_start',
+ MaliParser.prototype.maliJobEvent.bind(this));
+ importer.registerEventHandler('mali_job_systrace_event_stop',
+ MaliParser.prototype.maliJobEvent.bind(this));
+
+ this.model_ = importer.model_;
+
+ this.deferredJobs_ = {}; // <job type>: [<deferred jobs>]
+ }
+
+ MaliParser.prototype = {
+ __proto__: Parser.prototype,
+
+ maliDDKOpenSlice(pid, tid, ts, func, blockinfo) {
+ const thread = this.importer.model_.getOrCreateProcess(pid)
+ .getOrCreateThread(tid);
+ const funcArgs = /^([\w\d_]*)(?:\(\))?:?\s*(.*)$/.exec(func);
+ thread.sliceGroup.beginSlice('gpu-driver', funcArgs[1], ts,
+ { 'args': funcArgs[2],
+ blockinfo });
+ },
+
+ maliDDKCloseSlice(pid, tid, ts, args, blockinfo) {
+ const thread = this.importer.model_.getOrCreateProcess(pid)
+ .getOrCreateThread(tid);
+ if (!thread.sliceGroup.openSliceCount) {
+ // Discard unmatched ends.
+ return;
+ }
+ thread.sliceGroup.endSlice(ts);
+ },
+
+ /**
+ * Deduce the format of Mali perf events.
+ *
+ * @return {RegExp} the regular expression for parsing data when the format
+ * is recognized; otherwise null.
+ */
+ autoDetectLineRE(line) {
+ // Matches Mali perf events with thread info
+ const lineREWithThread =
+ /^\s*\(([\w\-]*)\)\s*(\w+):\s*([\w\\\/\.\-]*@\d*):?\s*(.*)$/;
+ if (lineREWithThread.test(line)) {
+ return lineREWithThread;
+ }
+
+ // Matches old-style Mali perf events
+ const lineRENoThread = /^s*()(\w+):\s*([\w\\\/.\-]*):?\s*(.*)$/;
+ if (lineRENoThread.test(line)) {
+ return lineRENoThread;
+ }
+ return null;
+ },
+
+ lineRE: null,
+
+ /**
+ * Parses maliDDK events and sets up state in the importer.
+ * events will come in pairs with a cros_trace_print_enter
+ * like this (line broken here for formatting):
+ *
+ * tracing_mark_write: mali_driver: (mali-012345) cros_trace_print_enter: \
+ * gles/src/texture/mali_gles_texture_slave.c@1505: gles2_texturep_upload
+ *
+ * and a cros_trace_print_exit like this:
+ *
+ * tracing_mark_write: mali_driver: (mali-012345) cros_trace_print_exit: \
+ * gles/src/texture/mali_gles_texture_slave.c@1505:
+ */
+ maliDDKEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ if (this.lineRE === null) {
+ this.lineRE = this.autoDetectLineRE(eventBase.details);
+ if (this.lineRE === null) return false;
+ }
+ const maliEvent = this.lineRE.exec(eventBase.details);
+ // Old-style Mali perf events have no thread id, so make one.
+ const tid = (maliEvent[1] === '' ? 'mali' : maliEvent[1]);
+ switch (maliEvent[2]) {
+ case 'cros_trace_print_enter':
+ this.maliDDKOpenSlice(pid, tid, ts, maliEvent[4],
+ maliEvent[3]);
+ break;
+ case 'cros_trace_print_exit':
+ this.maliDDKCloseSlice(pid, tid, ts, [], maliEvent[3]);
+ }
+ return true;
+ },
+
+ /**
+ * Parses Mali Job events and sets up state in the importer.
+ * Events come in pairs like this:
+ *
+ * tracing_mark_write: S|7229|vertex-job|62|0|0|0|0|f46aed08|722962164982...
+ * tracing_mark_write: F|7229|vertex-job|62|0|0|0|0|f46aed08|722962164982...
+ *
+ * The '|' separated fields are (numbers correspond to jobEventRE groups
+ * below):
+ * 1: 'S'tart / 'F'inish
+ * 2: tgid that submitted the job
+ * 3: job type
+ * 4: job id
+ * 5,6: dependency 0: job id, dependency type
+ * 7,8: dependency 1: job id, dependency type
+ * 9: ctx id
+ * 10: unique job submission id
+ */
+ maliJobEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const jobEventRE =
+ // eslint-disable-next-line max-len
+ /^.*tracing_mark_write: (S|F)\|(\d+)\|(\w+)-job\|(\d+)\|(\d+)\|(\d+)\|(\d+)\|(\d+)\|([a-z0-9]+)\|(\d+)$/;
+ const jobEvent = jobEventRE.exec(eventBase.details);
+ if (!jobEvent) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ args: 'unexpected mali_job_systrace_event_* event syntax'
+ });
+ return;
+ }
+ const jobType = jobEvent[3];
+ const jobId = jobEvent[4];
+ const thread = this.importer.model_.getOrCreateProcess(0)
+ .getOrCreateThread('mali:' + jobType);
+ switch (jobEvent[1]) {
+ case 'S': {
+ /* The HW has a two-deep ("head", "next") job FIFO for each job
+ * type. Start events indicate when the job was pushed into the FIFO,
+ * *not* when the hardware actually starts executing the job. It
+ * either starts ~immediately if the FIFO was empty, or when the
+ * previous job finishes otherwise.
+ *
+ * So we can get events in the order S|0, S|1, F|0, F|1, but we want
+ * to turn that into a '0' slice from S|0 to F|0, and another '1'
+ * slice from F|0 to F|1. In other words, when there is already a
+ * slice open, we want to defer creating a slice for this job until we
+ * finish the previous slice, then immediately open the slice for this
+ * event using the same timestamp.
+ */
+ const args = {
+ ctx: jobEvent[9],
+ pid: parseInt(jobEvent[2], 10),
+ dep0: parseInt(jobEvent[5], 10),
+ dep1: parseInt(jobEvent[7], 10)
+ };
+ if (thread.sliceGroup.openSliceCount) {
+ if (!(jobType in this.deferredJobs_)) {
+ this.deferredJobs_[jobType] = [];
+ }
+ this.deferredJobs_[jobType].push({id: jobId, args});
+ } else {
+ thread.sliceGroup.beginSlice(null, jobId, ts, args);
+ }
+ } break;
+ case 'F': {
+ if (!thread.sliceGroup.openSliceCount) {
+ // Discard unmatched ends.
+ return;
+ }
+ if (thread.sliceGroup.mostRecentlyOpenedPartialSlice.title !==
+ jobId) {
+ this.model_.importWarning({
+ type: 'invalid event nesting',
+ message: 'non-sequential jobs in same mali job slot'
+ });
+ }
+ thread.sliceGroup.endSlice(ts);
+ const deferredJobs = this.deferredJobs_[jobType];
+ if (deferredJobs && deferredJobs.length) {
+ const job = deferredJobs.shift();
+ thread.sliceGroup.beginSlice(null, job.id, ts, job.args);
+ }
+ } break;
+ }
+ return true;
+ },
+
+ /*
+ * Kernel event support.
+ */
+
+ dvfsSample(counterName, seriesName, ts, s) {
+ const value = parseInt(s);
+ const counter = this.model_.kernel.
+ getOrCreateCounter('DVFS', counterName);
+ if (counter.numSeries === 0) {
+ counter.addSeries(new tr.model.CounterSeries(seriesName,
+ ColorScheme.getColorIdForGeneralPurposeString(counter.name)));
+ }
+ counter.series.forEach(function(series) {
+ series.addCounterSample(ts, value);
+ });
+ },
+
+ dvfsEventEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /utilization=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ this.dvfsSample('DVFS Utilization', 'utilization', ts, event[1]);
+ return true;
+ },
+
+ dvfsSetClockEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /frequency=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ this.dvfsSample('DVFS Frequency', 'frequency', ts, event[1]);
+ return true;
+ },
+
+ dvfsSetVoltageEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /voltage=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ this.dvfsSample('DVFS Voltage', 'voltage', ts, event[1]);
+ return true;
+ },
+
+ hwcSample(cat, counterName, seriesName, ts, eventBase) {
+ const event = /val=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+ const value = parseInt(event[1]);
+
+ const counter = this.model_.kernel.
+ getOrCreateCounter(cat, counterName);
+ if (counter.numSeries === 0) {
+ counter.addSeries(new tr.model.CounterSeries(seriesName,
+ ColorScheme.getColorIdForGeneralPurposeString(counter.name)));
+ }
+ counter.series.forEach(function(series) {
+ series.addCounterSample(ts, value);
+ });
+ return true;
+ },
+
+ /*
+ * Job Manager block counters.
+ */
+ jmSample(ctrName, seriesName, ts, eventBase) {
+ return this.hwcSample('mali:jm', 'JM: ' + ctrName, seriesName, ts,
+ eventBase);
+ },
+ addJMCounter(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.jmSample(hwcTitle, 'count', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+ addJMCycles(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.jmSample(hwcTitle, 'cycles', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+
+ /*
+ * Tiler block counters.
+ */
+ tilerSample(ctrName, seriesName, ts, eventBase) {
+ return this.hwcSample('mali:tiler', 'Tiler: ' + ctrName, seriesName,
+ ts, eventBase);
+ },
+ addTilerCounter(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.tilerSample(hwcTitle, 'count', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+ addTilerCycles(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.tilerSample(hwcTitle, 'cycles', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+
+ /*
+ * Fragment counters.
+ */
+ fragSample(ctrName, seriesName, ts, eventBase) {
+ return this.hwcSample('mali:fragment', 'Fragment: ' + ctrName,
+ seriesName, ts, eventBase);
+ },
+ addFragCounter(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.fragSample(hwcTitle, 'count', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+ addFragCycles(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.fragSample(hwcTitle, 'cycles', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+
+ /*
+ * Compute counters.
+ */
+ computeSample(ctrName, seriesName, ts, eventBase) {
+ return this.hwcSample('mali:compute', 'Compute: ' + ctrName,
+ seriesName, ts, eventBase);
+ },
+ addComputeCounter(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.computeSample(hwcTitle, 'count', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+ addComputeCycles(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.computeSample(hwcTitle, 'cycles', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+
+ /*
+ * Tripipe counters.
+ */
+ addTripipeCycles(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.hwcSample('mali:shader', 'Tripipe: ' + hwcTitle, 'cycles',
+ ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+
+ /*
+ * Arith counters.
+ */
+ arithSample(ctrName, seriesName, ts, eventBase) {
+ return this.hwcSample('mali:arith', 'Arith: ' + ctrName, seriesName, ts,
+ eventBase);
+ },
+ addArithCounter(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.arithSample(hwcTitle, 'count', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+ addArithCycles(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.arithSample(hwcTitle, 'cycles', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+
+ /*
+ * Load/Store counters.
+ */
+ addLSCounter(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.hwcSample('mali:ls', 'LS: ' + hwcTitle, 'count', ts,
+ eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+
+ /*
+ * Texture counters.
+ */
+ textureSample(ctrName, seriesName, ts, eventBase) {
+ return this.hwcSample('mali:texture', 'Texture: ' + ctrName,
+ seriesName, ts, eventBase);
+ },
+ addTexCounter(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.textureSample(hwcTitle, 'count', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+
+ /*
+ * LSC counters.
+ */
+ addLSCCounter(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.hwcSample('mali:lsc', 'LSC: ' + hwcTitle, 'count', ts,
+ eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+
+ /*
+ * TLB counters.
+ */
+ addAXICounter(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.hwcSample('mali:axi', 'AXI: ' + hwcTitle, 'count', ts,
+ eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+
+ /*
+ * MMU counters.
+ */
+ mmuSample(ctrName, seriesName, ts, eventBase) {
+ return this.hwcSample('mali:mmu', 'MMU: ' + ctrName, seriesName, ts,
+ eventBase);
+ },
+ addMMUCounter(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.mmuSample(hwcTitle, 'count', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+ addMMUCycles(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.mmuSample(hwcTitle, 'cycles', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+
+ /*
+ * L2 counters.
+ */
+ l2Sample(ctrName, seriesName, ts, eventBase) {
+ return this.hwcSample('mali:l2', 'L2: ' + ctrName, seriesName, ts,
+ eventBase);
+ },
+ addL2Counter(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.l2Sample(hwcTitle, 'count', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ },
+ addL2Cycles(hwcEventName, hwcTitle) {
+ function handler(eventName, cpuNumber, pid, ts, eventBase) {
+ return this.l2Sample(hwcTitle, 'cycles', ts, eventBase);
+ }
+ this.importer.registerEventHandler(hwcEventName, handler.bind(this));
+ }
+ };
+
+ Parser.register(MaliParser);
+
+ return {
+ MaliParser,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/mali_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/mali_parser_test.html
new file mode 100644
index 00000000000..76cc2e73b9c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/mali_parser_test.html
@@ -0,0 +1,559 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ test('maliDDKImport', function() {
+ const linesNoThread = [
+ // Row 1 open
+ ' chrome-1780 [001] ...1 28.562633: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_enter: ' +
+ 'gles/src/dispatch/mali_gles_dispatch_entrypoints.c992: ' +
+ 'glTexSubImage2D',
+ // Row 2 open
+ ' chrome-1780 [001] ...1 28.562655: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_enter: ' +
+ 'gles/src/texture/mali_gles_texture_api.c996: ' +
+ 'gles_texture_tex_sub_image_2d',
+ // Row 3 open
+ ' chrome-1780 [001] ...1 28.562671: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_enter: ' +
+ 'gles/src/texture/mali_gles_texture_slave.c295: ' +
+ 'gles_texturep_slave_map_master',
+ // Row 3 close
+ ' chrome-1780 [001] ...1 28.562684: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_exit: ' +
+ 'gles/src/texture/mali_gles_texture_slave.c295: ',
+ // Row 3 open
+ ' chrome-1780 [001] ...1 28.562700: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_enter: ' +
+ 'gles/src/texture/mali_gles_texture_slave.c1505: ' +
+ 'gles2_texturep_upload_2d',
+ // Row 4 open
+ ' chrome-1780 [001] ...1 28.562726: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_enter: ' +
+ 'gles/src/texture/mali_gles_texture_slave.c1612: ' +
+ 'gles2_texturep_upload_2d: pixel array: wait for dependencies',
+ // Row 5 open
+ ' chrome-1780 [001] ...1 28.562742: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_enter: ' +
+ 'cobj/src/mali_cobj_surface_operations.c1693: ' +
+ 'cobj_convert_pixels_to_surface',
+ // Row 6 open
+ ' chrome-1780 [001] ...1 28.562776: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_enter: ' +
+ 'cobj/src/mali_cobj_surface_operations.c1461: ' +
+ 'cobj_convert_pixels',
+ // Row 7 open
+ ' chrome-1780 [001] ...1 28.562791: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_enter: ' +
+ 'cobj/src/mali_cobj_surface_operations.c1505: ' +
+ 'cobj_convert_pixels: fast-path linear copy',
+ // Row 8 open
+ ' chrome-1780 [001] ...1 28.562808: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_enter: ' +
+ 'cobj/src/mali_cobj_surface_operations.c1511: ' +
+ 'cobj_convert_pixels: reorder-only',
+ // Row 8 close
+ ' chrome-1780 [001] ...1 28.563383: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_exit: ' +
+ 'cobj/src/mali_cobj_surface_operations.c1511',
+ // Row 7 close
+ ' chrome-1780 [001] ...1 28.563397: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_exit: ' +
+ 'cobj/src/mali_cobj_surface_operations.c1505',
+ // Row 6 close
+ ' chrome-1780 [001] ...1 28.563409: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_exit: ' +
+ 'cobj/src/mali_cobj_surface_operations.c1461',
+ // Row 5 close
+ ' chrome-1780 [001] ...1 28.563438: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_exit: ' +
+ 'cobj/src/mali_cobj_surface_operations.c1693',
+ // Row 4 close
+ ' chrome-1780 [001] ...1 28.563451: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_exit: ' +
+ 'gles/src/texture/mali_gles_texture_slave.c1612',
+ // Row 3 close
+ ' chrome-1780 [001] ...1 28.563462: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_exit: ' +
+ 'gles/src/texture/mali_gles_texture_slave.c1505',
+ // Row 2 close
+ ' chrome-1780 [001] ...1 28.563475: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_exit: ' +
+ 'gles/src/texture/mali_gles_texture_api.c996',
+ // Row 1 close
+ ' chrome-1780 [001] ...1 28.563486: tracing_mark_write: ' +
+ 'mali_driver: cros_trace_print_exit: ' +
+ 'gles/src/dispatch/mali_gles_dispatch_entrypoints.c992'
+ ];
+
+ const linesWithThread = [
+ // Row 1 open
+ ' chrome-1780 [001] ...1 28.562633: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_enter: ' +
+ 'gles/src/dispatch/mali_gles_dispatch_entrypoints.c@992: ' +
+ 'glTexSubImage2D',
+ // Row 2 open
+ ' chrome-1780 [001] ...1 28.562655: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_enter: ' +
+ 'gles/src/texture/mali_gles_texture_api.c@996: ' +
+ 'gles_texture_tex_sub_image_2d',
+ // Row 3 open
+ ' chrome-1780 [001] ...1 28.562671: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_enter: ' +
+ 'gles/src/texture/mali_gles_texture_slave.c@295: ' +
+ 'gles_texturep_slave_map_master',
+ // Row 3 close
+ ' chrome-1780 [001] ...1 28.562684: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_exit: ' +
+ 'gles/src/texture/mali_gles_texture_slave.c@295: ',
+ // Row 3 open
+ ' chrome-1780 [001] ...1 28.562700: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_enter: ' +
+ 'gles/src/texture/mali_gles_texture_slave.c@1505: ' +
+ 'gles2_texturep_upload_2d',
+ // Row 4 open
+ ' chrome-1780 [001] ...1 28.562726: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_enter: ' +
+ 'gles/src/texture/mali_gles_texture_slave.c@1612: ' +
+ 'gles2_texturep_upload_2d: pixel array: wait for dependencies',
+ // Row 5 open
+ ' chrome-1780 [001] ...1 28.562742: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_enter: ' +
+ 'cobj/src/mali_cobj_surface_operations.c@1693: ' +
+ 'cobj_convert_pixels_to_surface',
+ // Row 6 open
+ ' chrome-1780 [001] ...1 28.562776: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_enter: ' +
+ 'cobj/src/mali_cobj_surface_operations.c@1461: ' +
+ 'cobj_convert_pixels',
+ // Row 7 open
+ ' chrome-1780 [001] ...1 28.562791: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_enter: ' +
+ 'cobj/src/mali_cobj_surface_operations.c@1505: ' +
+ 'cobj_convert_pixels: fast-path linear copy',
+ // Row 8 open
+ ' chrome-1780 [001] ...1 28.562808: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_enter: ' +
+ 'cobj/src/mali_cobj_surface_operations.c@1511: ' +
+ 'cobj_convert_pixels: reorder-only',
+ // Row 8 close
+ ' chrome-1780 [001] ...1 28.563383: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_exit: ' +
+ 'cobj/src/mali_cobj_surface_operations.c@1511',
+ // Row 7 close
+ ' chrome-1780 [001] ...1 28.563397: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_exit: ' +
+ 'cobj/src/mali_cobj_surface_operations.c@1505',
+ // Row 6 close
+ ' chrome-1780 [001] ...1 28.563409: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_exit: ' +
+ 'cobj/src/mali_cobj_surface_operations.c@1461',
+ // Row 5 close
+ ' chrome-1780 [001] ...1 28.563438: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_exit: ' +
+ 'cobj/src/mali_cobj_surface_operations.c@1693',
+ // Row 4 close
+ ' chrome-1780 [001] ...1 28.563451: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_exit: ' +
+ 'gles/src/texture/mali_gles_texture_slave.c@1612',
+ // Row 3 close
+ ' chrome-1780 [001] ...1 28.563462: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_exit: ' +
+ 'gles/src/texture/mali_gles_texture_slave.c@1505',
+ // Row 2 close
+ ' chrome-1780 [001] ...1 28.563475: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_exit: ' +
+ 'gles/src/texture/mali_gles_texture_api.c@996',
+ // Row 1 close
+ ' chrome-1780 [001] ...1 28.563486: tracing_mark_write: ' +
+ 'mali_driver: (mali-1878934320) cros_trace_print_exit: ' +
+ 'gles/src/dispatch/mali_gles_dispatch_entrypoints.c@992'
+ ];
+ const traceNoThread = newModel(linesNoThread.join('\n'));
+ const traceWithThread = newModel(linesWithThread.join('\n'));
+ assert.isFalse(traceNoThread.hasImportWarnings);
+ assert.isFalse(traceWithThread.hasImportWarnings);
+
+ const threadsNoThread = traceNoThread.getAllThreads();
+ const threadsWithThread = traceWithThread.getAllThreads();
+ assert.strictEqual(threadsNoThread.length, 1);
+ assert.strictEqual(threadsWithThread.length, 1);
+
+ const maliThreadNoThread = threadsNoThread[0];
+ const maliThreadWithThread = threadsWithThread[0];
+ assert.strictEqual(maliThreadNoThread.tid, 'mali');
+ assert.strictEqual(maliThreadWithThread.tid, 'mali-1878934320');
+ assert.strictEqual(maliThreadNoThread.sliceGroup.length, 9);
+ assert.strictEqual(maliThreadWithThread.sliceGroup.length, 9);
+ });
+
+ test('DVFSFrequencyImport', function() {
+ const lines = [
+ ' kworker/u:0-5 [001] .... 1174.839552: mali_dvfs_set_clock: ' +
+ 'frequency=266',
+ ' kworker/u:0-5 [000] .... 1183.840486: mali_dvfs_set_clock: ' +
+ 'frequency=400'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const counters = m.getAllCounters();
+ assert.strictEqual(counters.length, 1);
+
+ const c0 = counters[0];
+ assert.strictEqual(c0.name, 'DVFS Frequency');
+ assert.strictEqual(c0.series[0].samples.length, 2);
+ });
+
+ test('DVFSVoltageImport', function() {
+ const lines = [
+ ' kworker/u:0-5 [001] .... 1174.839562: mali_dvfs_set_voltage: ' +
+ 'voltage=937500',
+ ' kworker/u:0-5 [000] .... 1183.840009: mali_dvfs_set_voltage: ' +
+ 'voltage=1100000'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const counters = m.getAllCounters();
+ assert.strictEqual(counters.length, 1);
+
+ const c0 = counters[0];
+ assert.strictEqual(c0.name, 'DVFS Voltage');
+ assert.strictEqual(c0.series[0].samples.length, 2);
+ });
+
+ test('DVFSUtilizationImport', function() {
+ const lines = [
+ ' kworker/u:0-5 [001] .... 1174.839552: mali_dvfs_event: ' +
+ 'utilization=7',
+ ' kworker/u:0-5 [000] .... 1183.840486: mali_dvfs_event: ' +
+ 'utilization=37'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const counters = m.getAllCounters();
+ assert.strictEqual(counters.length, 1);
+
+ const c0 = counters[0];
+ assert.strictEqual(c0.name, 'DVFS Utilization');
+ assert.strictEqual(c0.series[0].samples.length, 2);
+ });
+
+ test('maliHWCImport', function() {
+ const lines = [
+ ' kworker/u:0-5 [000] .... 78.896588: ' +
+ 'mali_hwc_ACTIVE: val=238',
+ ' kworker/u:0-5 [000] .... 79.046889: ' +
+ 'mali_hwc_ARITH_CYCLES_L0: val=1967',
+ ' kworker/u:0-5 [000] .... 79.046888: ' +
+ 'mali_hwc_ARITH_CYCLES_REG: val=136',
+ ' kworker/u:0-5 [000] .... 79.046890: ' +
+ 'mali_hwc_ARITH_FRAG_DEPEND: val=19676',
+ ' kworker/u:0-5 [000] .... 79.046886: ' +
+ 'mali_hwc_ARITH_WORDS: val=255543',
+ ' kworker/u:0-5 [000] .... 79.046920: ' +
+ 'mali_hwc_AXI_BEATS_READ: val=257053',
+ ' kworker/u:0-5 [000] .... 78.896594: ' +
+ 'mali_hwc_AXI_TLB_STALL: val=1',
+ ' kworker/u:0-5 [000] .... 78.946646: ' +
+ 'mali_hwc_AXI_TLB_TRANSACTION: val=4',
+ ' kworker/u:0-5 [000] .... 79.046853: ' +
+ 'mali_hwc_BACK_FACING: val=104',
+ ' kworker/u:0-5 [000] .... 79.046880: ' +
+ 'mali_hwc_COMPUTE_ACTIVE: val=17462',
+ ' kworker/u:0-5 [000] .... 79.046884: ' +
+ 'mali_hwc_COMPUTE_CYCLES_DESC: val=3933',
+ ' kworker/u:0-5 [000] .... 79.046881: ' +
+ 'mali_hwc_COMPUTE_TASKS: val=15',
+ ' kworker/u:0-5 [000] .... 79.046883: ' +
+ 'mali_hwc_COMPUTE_THREADS: val=60',
+ ' kworker/u:0-5 [000] .... 79.046860: ' +
+ 'mali_hwc_FRAG_ACTIVE: val=690986',
+ ' kworker/u:0-5 [000] .... 79.046864: ' +
+ 'mali_hwc_FRAG_CYCLE_DESC: val=13980',
+ ' kworker/u:0-5 [000] .... 79.046876: ' +
+ 'mali_hwc_FRAG_CYCLE_NO_TILE: val=3539',
+ ' kworker/u:0-5 [000] .... 79.046865: ' +
+ 'mali_hwc_FRAG_CYCLES_PLR: val=1499',
+ ' kworker/u:0-5 [000] .... 79.046869: ' +
+ 'mali_hwc_FRAG_CYCLES_RAST: val=1999',
+ ' kworker/u:0-5 [000] .... 79.046868: ' +
+ 'mali_hwc_FRAG_CYCLES_TRISETUP: val=22353',
+ ' kworker/u:0-5 [000] .... 79.046867: ' +
+ 'mali_hwc_FRAG_CYCLES_VERT: val=20763',
+ ' kworker/u:0-5 [000] .... 79.046872: ' +
+ 'mali_hwc_FRAG_DUMMY_THREADS: val=1968',
+ ' kworker/u:0-5 [000] .... 79.046877: ' +
+ 'mali_hwc_FRAG_NUM_TILES: val=1840',
+ ' kworker/u:0-5 [000] .... 79.046862: ' +
+ 'mali_hwc_FRAG_PRIMATIVES: val=3752',
+ ' kworker/u:0-5 [000] .... 79.046863: ' +
+ 'mali_hwc_FRAG_PRIMATIVES_DROPPED: val=18',
+ ' kworker/u:0-5 [000] .... 79.046874: ' +
+ 'mali_hwc_FRAG_QUADS_EZS_TEST: val=117925',
+ ' kworker/u:0-5 [000] .... 79.046873: ' +
+ 'mali_hwc_FRAG_QUADS_RAST: val=117889',
+ ' kworker/u:0-5 [000] .... 79.046870: ' +
+ 'mali_hwc_FRAG_THREADS: val=471507',
+ ' kworker/u:0-5 [000] .... 79.046879: ' +
+ 'mali_hwc_FRAG_TRANS_ELIM: val=687',
+ ' kworker/u:0-5 [000] .... 80.315162: ' +
+ 'mali_hwc_FRONT_FACING: val=56',
+ ' kworker/u:0-5 [000] .... 78.896582: ' +
+ 'mali_hwc_GPU_ACTIVE: val=1316',
+ ' kworker/u:0-5 [000] .... 78.896584: ' +
+ 'mali_hwc_IRQ_ACTIVE: val=17',
+ ' kworker/u:0-5 [000] .... 79.046834: ' +
+ 'mali_hwc_JS0_ACTIVE: val=709444',
+ ' kworker/u:0-5 [000] .... 79.046831: ' +
+ 'mali_hwc_JS0_JOBS: val=2',
+ ' kworker/u:0-5 [000] .... 79.046832: ' +
+ 'mali_hwc_JS0_TASKS: val=7263',
+ ' kworker/u:0-5 [000] .... 79.046836: ' +
+ 'mali_hwc_JS0_WAIT_DEPEND: val=665876',
+ ' kworker/u:0-5 [000] .... 79.046835: ' +
+ 'mali_hwc_JS0_WAIT_ISSUE: val=910',
+ ' kworker/u:0-5 [000] .... 79.046840: ' +
+ 'mali_hwc_JS1_ACTIVE: val=153980',
+ ' kworker/u:0-5 [000] .... 79.046838: ' +
+ 'mali_hwc_JS1_JOBS: val=133',
+ ' kworker/u:0-5 [000] .... 79.046839: ' +
+ 'mali_hwc_JS1_TASKS: val=128',
+ ' kworker/u:0-5 [000] .... 79.046843: ' +
+ 'mali_hwc_JS1_WAIT_FINISH: val=74404',
+ ' kworker/u:0-5 [000] .... 79.046842: ' +
+ 'mali_hwc_JS1_WAIT_ISSUE: val=10146',
+ ' kworker/u:0-5 [000] .... 78.896603: ' +
+ 'mali_hwc_L2_ANY_LOOKUP: val=22',
+ ' kworker/u:0-5 [000] .... 79.046942: ' +
+ 'mali_hwc_L2_CLEAN_MISS: val=116',
+ ' kworker/u:0-5 [000] .... 79.063515: ' +
+ 'mali_hwc_L2_EXT_AR_STALL: val=9',
+ ' kworker/u:0-5 [000] .... 78.963384: ' +
+ 'mali_hwc_L2_EXT_BARRIER: val=1',
+ ' kworker/u:0-5 [000] .... 79.063516: ' +
+ 'mali_hwc_L2_EXT_R_BUF_FULL: val=43',
+ ' kworker/u:0-5 [000] .... 78.896611: ' +
+ 'mali_hwc_L2_EXT_READ: val=4',
+ ' kworker/u:0-5 [000] .... 78.896612: ' +
+ 'mali_hwc_L2_EXT_READ_LINE: val=4',
+ ' kworker/u:0-5 [000] .... 79.046956: ' +
+ 'mali_hwc_L2_EXT_R_RAW: val=1',
+ ' kworker/u:0-5 [000] .... 79.063518: ' +
+ 'mali_hwc_L2_EXT_R_W_HAZARD: val=15',
+ ' kworker/u:0-5 [000] .... 78.963381: ' +
+ 'mali_hwc_L2_EXT_WRITE: val=25',
+ ' kworker/u:0-5 [000] .... 79.046952: ' +
+ 'mali_hwc_L2_EXT_WRITE_LINE: val=63278',
+ ' kworker/u:0-5 [000] .... 78.963382: ' +
+ 'mali_hwc_L2_EXT_WRITE_SMALL: val=1',
+ ' kworker/u:0-5 [000] .... 79.814532: ' +
+ 'mali_hwc_L2_EXT_W_STALL: val=9',
+ ' kworker/u:0-5 [000] .... 78.896602: ' +
+ 'mali_hwc_L2_READ_BEATS: val=16',
+ ' kworker/u:0-5 [000] .... 78.896607: ' +
+ 'mali_hwc_L2_READ_HIT: val=11',
+ ' kworker/u:0-5 [000] .... 78.896604: ' +
+ 'mali_hwc_L2_READ_LOOKUP: val=19',
+ ' kworker/u:0-5 [000] .... 78.896606: ' +
+ 'mali_hwc_L2_READ_REPLAY: val=2',
+ ' kworker/u:0-5 [000] .... 79.046940: ' +
+ 'mali_hwc_L2_READ_SNOOP: val=24',
+ ' kworker/u:0-5 [000] .... 79.046959: ' +
+ 'mali_hwc_L2_REPLAY_FULL: val=6629',
+ ' kworker/u:0-5 [000] .N.. 80.565684: ' +
+ 'mali_hwc_L2_SNOOP_FULL: val=5',
+ ' kworker/u:0-5 [000] .... 79.046937: ' +
+ 'mali_hwc_L2_SREAD_LOOKUP: val=241',
+ ' kworker/u:0-5 [000] .... 79.046944: ' +
+ 'mali_hwc_L2_SWRITE_LOOKUP: val=133',
+ ' kworker/u:0-5 [000] .... 78.896614: ' +
+ 'mali_hwc_L2_TAG_HAZARD: val=4',
+ ' kworker/u:0-5 [000] .... 78.963368: ' +
+ 'mali_hwc_L2_WRITE_BEATS: val=96',
+ ' kworker/u:0-5 [000] .... 79.046947: ' +
+ 'mali_hwc_L2_WRITE_HIT: val=78265',
+ ' kworker/u:0-5 [000] .... 78.896608: ' +
+ 'mali_hwc_L2_WRITE_LOOKUP: val=3',
+ ' kworker/u:0-5 [000] .... 79.046946: ' +
+ 'mali_hwc_L2_WRITE_REPLAY: val=15879',
+ ' kworker/u:0-5 [000] .... 79.046912: ' +
+ 'mali_hwc_LSC_LINE_FETCHES: val=15',
+ ' kworker/u:0-5 [000] .... 79.046909: ' +
+ 'mali_hwc_LSC_READ_HITS: val=2961',
+ ' kworker/u:0-5 [000] .... 79.046911: ' +
+ 'mali_hwc_LSC_READ_MISSES: val=22',
+ ' kworker/u:0-5 [000] .... 79.046914: ' +
+ 'mali_hwc_LSC_SNOOPS: val=10',
+ ' kworker/u:0-5 [000] .... 79.046893: ' +
+ 'mali_hwc_LS_ISSUES: val=524219',
+ ' kworker/u:0-5 [000] .... 79.046894: ' +
+ 'mali_hwc_LS_REISSUES_MISS: val=439',
+ ' kworker/u:0-5 [000] .... 79.046895: ' +
+ 'mali_hwc_LS_REISSUES_VD: val=52007',
+ ' kworker/u:0-5 [000] .... 79.046919: ' +
+ 'mali_hwc_LS_TLB_HIT: val=3043',
+ ' kworker/u:0-5 [000] .... 79.046918: ' +
+ 'mali_hwc_LS_TLB_MISS: val=5',
+ ' kworker/u:0-5 [000] .... 79.046891: ' +
+ 'mali_hwc_LS_WORDS: val=471514',
+ ' kworker/u:0-5 [000] .... 79.046925: ' +
+ 'mali_hwc_MMU_HIT: val=771',
+ ' kworker/u:0-5 [000] .... 79.046924: ' +
+ 'mali_hwc_MMU_NEW_MISS: val=494',
+ ' kworker/u:0-5 [000] .... 79.046922: ' +
+ 'mali_hwc_MMU_REPLAY_MISS: val=841',
+ ' kworker/u:0-5 [000] .... 79.046921: ' +
+ 'mali_hwc_MMU_TABLE_WALK: val=3119',
+ ' kworker/u:0-5 [000] .... 79.046848: ' +
+ 'mali_hwc_POINTS: val=5',
+ ' kworker/u:0-5 [000] .... 79.046856: ' +
+ 'mali_hwc_PRIM_CLIPPED: val=70',
+ ' kworker/u:0-5 [000] .... 79.046855: ' +
+ 'mali_hwc_PRIM_CULLED: val=26',
+ ' kworker/u:0-5 [000] .... 79.046854: ' +
+ 'mali_hwc_PRIM_VISIBLE: val=109',
+ ' kworker/u:0-5 [000] .... 79.046898: ' +
+ 'mali_hwc_TEX_BUBBLES: val=24874',
+ ' kworker/u:0-5 [000] .... 79.046905: ' +
+ 'mali_hwc_TEX_RECIRC_DESC: val=5937',
+ ' kworker/u:0-5 [000] .... 79.046904: ' +
+ 'mali_hwc_TEX_RECIRC_FMISS: val=209450',
+ ' kworker/u:0-5 [000] .... 78.896592: ' +
+ 'mali_hwc_TEX_RECIRC_MULTI: val=238',
+ ' kworker/u:0-5 [000] .... 79.046908: ' +
+ 'mali_hwc_TEX_RECIRC_PMISS: val=9672',
+ ' kworker/u:0-5 [000] .... 79.046903: ' +
+ 'mali_hwc_TEX_THREADS: val=660900',
+ ' kworker/u:0-5 [000] .... 79.046897: ' +
+ 'mali_hwc_TEX_WORDS: val=471193',
+ ' kworker/u:0-5 [000] .... 79.046901: ' +
+ 'mali_hwc_TEX_WORDS_DESC: val=707',
+ ' kworker/u:0-5 [000] .... 79.046900: ' +
+ 'mali_hwc_TEX_WORDS_L0: val=32',
+ ' kworker/u:0-5 [000] .... 79.046846: ' +
+ 'mali_hwc_TRIANGLES: val=130',
+ ' kworker/u:0-5 [000] .... 79.046885: ' +
+ 'mali_hwc_TRIPIPE_ACTIVE: val=691001',
+ ' kworker/u:0-5 [000] .... 78.896600: ' +
+ 'mali_hwc_UTLB_NEW_MISS: val=6',
+ ' kworker/u:0-5 [000] .... 78.896599: ' +
+ 'mali_hwc_UTLB_REPLAY_FULL: val=248',
+ ' kworker/u:0-5 [000] .... 78.896597: ' +
+ 'mali_hwc_UTLB_REPLAY_MISS: val=1',
+ ' kworker/u:0-5 [000] .... 78.896596: ' +
+ 'mali_hwc_UTLB_STALL: val=1',
+ ' kworker/u:0-5 [000] .... 79.046850: ' +
+ 'mali_hwc_VCACHE_HIT: val=311',
+ ' kworker/u:0-5 [000] .... 79.046851: ' +
+ 'mali_hwc_VCACHE_MISS: val=70'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const counters = m.getAllCounters();
+ assert.strictEqual(counters.length, 103);
+
+ // all counters should have 1 sample
+ for (let tI = 0; tI < counters.length; tI++) {
+ const counter = counters[tI];
+ assert.strictEqual(counter.series[0].samples.length, 1);
+ }
+ // TODO(sleffler) verify counter names? (not sure if it's worth the effort)
+ });
+
+ test('maliJobImport', function() {
+ const jobEvents = [
+ // vertex slice 0 open
+ ' surfaceflinger-6962 ( 6962) [000] d.h2 498269.966588: ' +
+ 'mali_job_systrace_event_start: tracing_mark_write: ' +
+ 'S|6962|vertex-job|85|84|1|0|0|920d408|69628513498269966584032',
+ // vertex slice 0 close
+ ' surfaceflinger-6962 ( 6962) [000] d.h2 498269.966702: ' +
+ 'mali_job_systrace_event_stop: tracing_mark_write: ' +
+ 'F|6962|vertex-job|85|84|1|0|0|920d408|69628513498269966584032',
+
+ // vertex slice 1 open
+ ' surfaceflinger-6962 ( 6962) [000] d..1 498269.966966: ' +
+ 'mali_job_systrace_event_start: tracing_mark_write: ' +
+ 'S|7541|vertex-job|225|0|0|0|0|f5e13b48|754122517498269966963802',
+ // vertex slice 1 close
+ ' surfaceflinger-6962 ( 6962) [000] d.h1 498269.967109: ' +
+ 'mali_job_systrace_event_stop: tracing_mark_write: ' +
+ 'F|7541|vertex-job|225|0|0|0|0|f5e13b48|754122517498269966963802',
+
+ // fragment slice 0 open
+ ' kworker/u17:4-6269 ( 6269) [001] d..1 498269.967667: ' +
+ 'mali_job_systrace_event_start: tracing_mark_write: ' +
+ 'S|6962|fragment-job|86|85|1|0|0|920d408|69628613498269967664417',
+ // fragment slice 1 open (deferred)
+ ' mali-cmar-backe-7634 ( 7541) [003] d..1 498269.968473: ' +
+ 'mali_job_systrace_event_start: tracing_mark_write: ' +
+ 'S|7541|fragment-job|223|225|1|0|0|f5e13b48|754122317498269968470186',
+ // fragment slice 0 close (adjusted open of slice 1)
+ ' HwBinder:6936_2-7001 ( 6936) [000] d.h3 498269.970336: ' +
+ 'mali_job_systrace_event_stop: tracing_mark_write: ' +
+ 'F|6962|fragment-job|86|85|1|0|0|920d408|69628613498269967664417',
+ // fragment slice 1 close
+ ' RenderThread-7619 ( 7541) [000] d.h1 498269.971688: ' +
+ 'mali_job_systrace_event_stop: tracing_mark_write: ' +
+ 'F|7541|fragment-job|223|225|1|0|0|f5e13b48|754122317498269970293032',
+ ];
+
+ const expectedThreads = [
+ {
+ tid: 'mali:vertex',
+ slices: [
+ {title: '85', start: '498269.966588', end: '498269.966702'},
+ {title: '225', start: '498269.966966', end: '498269.967109'}]},
+ {
+ tid: 'mali:fragment',
+ slices: [
+ {title: '86', start: '498269.967667', end: '498269.970336'},
+ {title: '223', start: '498269.970336', end: '498269.971688'}]},
+ ];
+
+ const trace = newModel(jobEvents.join('\n'));
+ assert.isFalse(trace.hasImportWarnings);
+
+ const threads = trace.getAllThreads();
+ assert.strictEqual(threads.length, expectedThreads.length);
+ for (let tidx = 0; tidx < threads.length; tidx++) {
+ const actualThread = threads[tidx];
+ const expectedThread = expectedThreads[tidx];
+ assert.strictEqual(actualThread.tid, expectedThreads[tidx].tid);
+ assert.strictEqual(actualThread.sliceGroup.length,
+ expectedThreads[tidx].slices.length);
+ for (let sidx = 0; sidx < actualThread.sliceGroup.length; sidx++) {
+ const actualSlice = actualThread.sliceGroup.slices[sidx];
+ const expectedSlice = expectedThread.slices[sidx];
+ assert.strictEqual(actualSlice.title, expectedSlice.title);
+ assert.strictEqual(actualSlice.start,
+ parseFloat(expectedSlice.start) * 1000);
+ assert.strictEqual(actualSlice.end,
+ parseFloat(expectedSlice.end) * 1000);
+ }
+ }
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/memreclaim_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/memreclaim_parser.html
new file mode 100644
index 00000000000..027554eacfd
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/memreclaim_parser.html
@@ -0,0 +1,181 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses drm driver events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux vmscan trace events.
+ * @constructor
+ */
+ function MemReclaimParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('mm_vmscan_kswapd_wake',
+ MemReclaimParser.prototype.kswapdWake.bind(this));
+ importer.registerEventHandler('mm_vmscan_kswapd_sleep',
+ MemReclaimParser.prototype.kswapdSleep.bind(this));
+ importer.registerEventHandler('mm_vmscan_direct_reclaim_begin',
+ MemReclaimParser.prototype.reclaimBegin.bind(this));
+ importer.registerEventHandler('mm_vmscan_direct_reclaim_end',
+ MemReclaimParser.prototype.reclaimEnd.bind(this));
+ importer.registerEventHandler('lowmemory_kill',
+ MemReclaimParser.prototype.lowmemoryKill.bind(this));
+ }
+
+ // Matches the mm_vmscan_kswapd_wake record
+ // mm_vmscan_kswapd_wake: nid=%d order=%d
+ const kswapdWakeRE = /nid=(\d+) order=(\d+)/;
+
+ // Matches the mm_vmscan_kswapd_sleep record
+ // mm_vmscan_kswapd_sleep: order=%d
+ const kswapdSleepRE = /nid=(\d+)/;
+
+ // Matches the mm_vmscan_direct_reclaim_begin record
+ // mm_vmscan_direct_reclaim_begin: order=%d may_writepage=%d gfp_flags=%s
+ const reclaimBeginRE = /order=(\d+) may_writepage=\d+ gfp_flags=(.+)/;
+
+ // Matches the mm_vmscan_direct_reclaim_end record
+ // mm_vmscan_direct_reclaim_end: nr_reclaimed=%lu
+ const reclaimEndRE = /nr_reclaimed=(\d+)/;
+
+ // Matches the lowmemory_kill record
+ const lowmemoryRE =
+ /([^ ]+) \((\d+)\), page cache (\d+)kB \(limit (\d+)kB\), free (-?\d+)Kb/;
+
+ MemReclaimParser.prototype = {
+ __proto__: Parser.prototype,
+
+ /**
+ * Parses memreclaim events and sets up state in the importer.
+ */
+ kswapdWake(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = kswapdWakeRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const tgid = parseInt(eventBase.tgid);
+
+ const nid = parseInt(event[1]);
+ const order = parseInt(event[2]);
+
+ const kthread = this.importer.getOrCreateKernelThread(
+ eventBase.threadName, tgid, pid);
+
+ if (kthread.openSliceTS) {
+ if (order > kthread.order) {
+ kthread.order = order;
+ }
+ } else {
+ kthread.openSliceTS = ts;
+ kthread.order = order;
+ }
+ return true;
+ },
+
+ kswapdSleep(eventName, cpuNumber, pid, ts, eventBase) {
+ const tgid = parseInt(eventBase.tgid);
+
+ const kthread = this.importer.getOrCreateKernelThread(
+ eventBase.threadName, tgid, pid);
+
+ if (kthread.openSliceTS) {
+ kthread.thread.sliceGroup.pushCompleteSlice(
+ 'memreclaim', eventBase.threadName, kthread.openSliceTS,
+ ts - kthread.openSliceTS, 0, 0,
+ {
+ order: kthread.order
+ });
+ }
+ kthread.openSliceTS = undefined;
+ kthread.order = undefined;
+ return true;
+ },
+
+ reclaimBegin(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = reclaimBeginRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const order = parseInt(event[1]);
+ const gfp = event[2];
+ const tgid = parseInt(eventBase.tgid);
+
+ const kthread = this.importer.getOrCreateKernelThread(
+ eventBase.threadName, tgid, pid);
+
+ kthread.openSliceTS = ts;
+ kthread.order = order;
+ kthread.gfp = gfp;
+ return true;
+ },
+
+ reclaimEnd(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = reclaimEndRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const nrReclaimed = parseInt(event[1]);
+ const tgid = parseInt(eventBase.tgid);
+
+ const kthread = this.importer.getOrCreateKernelThread(
+ eventBase.threadName, tgid, pid);
+
+ if (kthread.openSliceTS !== undefined) {
+ kthread.thread.sliceGroup.pushCompleteSlice('memreclaim',
+ 'direct reclaim', kthread.openSliceTS, ts - kthread.openSliceTS,
+ 0, 0,
+ {
+ order: kthread.order,
+ gfp: kthread.gfp,
+ nr_reclaimed: nrReclaimed
+ });
+ }
+ kthread.openSliceTS = undefined;
+ kthread.order = undefined;
+ kthread.gfp = undefined;
+ return true;
+ },
+
+ lowmemoryKill(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = lowmemoryRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const tgid = parseInt(eventBase.tgid);
+ const killedName = event[1];
+ const killedPid = parseInt(event[2]);
+ const cache = parseInt(event[3]);
+ const free = parseInt(event[5]);
+
+ const kthread = this.importer.getOrCreateKernelThread(
+ eventBase.threadName, tgid, pid);
+
+ kthread.thread.sliceGroup.pushCompleteSlice('lowmemory',
+ 'low memory kill', ts, 0,
+ 0, 0,
+ {
+ killed_name: killedName,
+ killed_pid: killedPid,
+ cache,
+ free
+ });
+ return true;
+ }
+ };
+
+ Parser.register(MemReclaimParser);
+
+ return {
+ MemReclaimParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/memreclaim_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/memreclaim_parser_test.html
new file mode 100644
index 00000000000..1396d6713eb
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/memreclaim_parser_test.html
@@ -0,0 +1,170 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ test('memreclaimImport', function() {
+ const lines = [
+ ' surfaceflinger-1155 ( 1155) [001] ...1 12839.528756: mm_vmscan_direct_reclaim_begin: order=0 may_writepage=1 gfp_flags=GFP_KERNEL|GFP_NOWARN|GFP_ZERO|0x2', // @suppress longLineCheck
+ ' surfaceflinger-1155 ( 1155) [001] ...1 12839.531950: mm_vmscan_direct_reclaim_end: nr_reclaimed=66', // @suppress longLineCheck
+ ' kswapd0-33 ( 33) [001] ...1 12838.491407: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ ' kswapd0-33 ( 33) [001] ...1 12838.529770: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ ' kswapd0-33 ( 33) [001] ...1 12840.545737: mm_vmscan_kswapd_sleep: nid=0'// @suppress longLineCheck
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ assert.strictEqual(
+ m.processes['1155'].threads['1155'].sliceGroup.length, 1);
+ assert.strictEqual(m.processes['33'].threads['33'].sliceGroup.length, 1);
+ });
+
+ test('memreclaimDirectReclaim', function() {
+ const lines = [
+ 'RenderThread-9844 ( 9786) [001] ...1 1734.106078: mm_vmscan_direct_reclaim_begin: order=5 may_writepage=1 gfp_flags=GFP_KERNEL|GFP_NOWARN|0x2', // @suppress longLineCheck
+ 'RenderThread-9844 ( 9786) [001] ...1 1734.107619: mm_vmscan_direct_reclaim_end: nr_reclaimed=72', // @suppress longLineCheck
+ 'RenderThread-9844 ( 9786) [001] ...1 1734.107738: mm_vmscan_direct_reclaim_begin: order=4 may_writepage=1 gfp_flags=GFP_KERNEL|GFP_NOWARN|0x2', // @suppress longLineCheck
+ 'RenderThread-9844 ( 9786) [001] ...1 1734.107844: mm_vmscan_direct_reclaim_end: nr_reclaimed=35', // @suppress longLineCheck
+ 'RenderThread-9844 ( 9786) [001] ...1 1734.107891: mm_vmscan_direct_reclaim_begin: order=4 may_writepage=1 gfp_flags=GFP_KERNEL|GFP_NOWARN|0x2', // @suppress longLineCheck
+ 'RenderThread-9844 ( 9786) [001] ...1 1734.107945: mm_vmscan_direct_reclaim_end: nr_reclaimed=35', // @suppress longLineCheck
+ 'RenderThread-9844 ( 9786) [001] ...1 1734.107990: mm_vmscan_direct_reclaim_begin: order=4 may_writepage=1 gfp_flags=GFP_KERNEL|GFP_NOWARN|0x2', // @suppress longLineCheck
+ 'RenderThread-9844 ( 9786) [001] ...1 1734.108062: mm_vmscan_direct_reclaim_end: nr_reclaimed=34', // @suppress longLineCheck
+ 'Binder_8-1735 ( 1022) [001] ...1 1735.472240: mm_vmscan_direct_reclaim_begin: order=3 may_writepage=1 gfp_flags=GFP_KERNEL|GFP_NOWARN|0x2', // @suppress longLineCheck
+ 'Binder_8-1735 ( 1022) [001] ...1 1735.472849: mm_vmscan_direct_reclaim_end: nr_reclaimed=47', // @suppress longLineCheck
+ 'Binder_8-1735 ( 1022) [001] ...1 1735.473002: mm_vmscan_direct_reclaim_begin: order=3 may_writepage=1 gfp_flags=GFP_KERNEL|GFP_NOWARN|0x2', // @suppress longLineCheck
+ 'Binder_8-1735 ( 1022) [001] ...1 1735.474859: mm_vmscan_direct_reclaim_end: nr_reclaimed=48', // @suppress longLineCheck
+ 'touchFusion-88 ( 88) [000] ...1 1736.510656: mm_vmscan_direct_reclaim_begin: order=2 may_writepage=1 gfp_flags=GFP_KERNEL|GFP_NOWARN|GFP_COMP|GFP_NOMEMALLOC|GFP_KMEMCG', // @suppress longLineCheck
+ 'touchFusion-88 ( 88) [000] ...1 1736.517616: mm_vmscan_direct_reclaim_end: nr_reclaimed=34', // @suppress longLineCheck
+ 'touchFusion-88 ( 88) [000] ...1 1736.527061: mm_vmscan_direct_reclaim_begin: order=2 may_writepage=1 gfp_flags=GFP_KERNEL|GFP_NOWARN|GFP_COMP|GFP_NOMEMALLOC|GFP_KMEMCG', // @suppress longLineCheck
+ 'touchFusion-88 ( 88) [000] ...1 1736.530857: mm_vmscan_direct_reclaim_end: nr_reclaimed=39'// @suppress longLineCheck
+ ];
+
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+
+ assert.strictEqual(threads.length, 3);
+
+ const Binder8 = threads[0];
+ const touchFusion = threads[1];
+ const RenderThread = threads[2];
+
+ /* make sure there are the expected amount of slices per thread */
+ assert.strictEqual(Binder8.sliceGroup.length, 2);
+ assert.strictEqual(touchFusion.sliceGroup.length, 2);
+ assert.strictEqual(RenderThread.sliceGroup.length, 4);
+
+ /* make sure the slices have information to display to the
+ * user when selected
+ */
+
+ const iterateMe = [Binder8, touchFusion, RenderThread];
+ iterateMe.forEach(function(thread) {
+ for (let i = 0; i < thread.sliceGroup.length; i++) {
+ assert.isDefined(thread.sliceGroup.slices[i].args);
+ }
+ });
+ });
+
+ test('memreclaimKswapd', function() {
+ const lines = [
+ 'kswapd0-48 ( 48) [001] ...1 1734.210437: mm_vmscan_kswapd_wake: nid=0 order=5', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1734.227291: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1734.237585: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1734.258698: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1734.269642: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1734.319484: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1734.344839: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1734.428425: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1734.429593: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1734.599419: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1734.696606: mm_vmscan_kswapd_sleep: nid=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1735.465745: mm_vmscan_kswapd_wake: nid=0 order=3', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1735.563917: mm_vmscan_kswapd_wake: nid=0 order=5', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1735.570555: mm_vmscan_kswapd_wake: nid=0 order=4', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1735.666658: mm_vmscan_kswapd_sleep: nid=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1736.508069: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1736.529293: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1736.696725: mm_vmscan_kswapd_sleep: nid=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1737.945426: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1737.988642: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1738.057237: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1738.144630: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1738.207546: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1738.221963: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1738.316889: mm_vmscan_kswapd_sleep: nid=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1738.712804: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1738.751103: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1738.773175: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1738.785068: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1738.789545: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1738.873675: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1738.899117: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1738.939214: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1738.990366: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1739.028269: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1739.036765: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1739.077631: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.094731: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.096757: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.160536: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.256638: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.264972: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1739.360137: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.368759: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.387082: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.455657: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.489058: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.507561: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.570247: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [001] ...1 1739.582975: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.678148: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.762025: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.799245: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.821950: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.894130: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1739.919775: lowmemory_kill: d.process.media (10517), page cache 311740kB (limit 322560kB), free -1456Kb', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1740.026933: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1740.126608: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1740.150819: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1740.156101: mm_vmscan_kswapd_wake: nid=0 order=1', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1740.246626: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1740.357055: mm_vmscan_kswapd_sleep: nid=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1740.762705: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1740.772367: mm_vmscan_kswapd_wake: nid=0 order=2', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1740.783509: mm_vmscan_kswapd_wake: nid=0 order=0', // @suppress longLineCheck
+ 'kswapd0-48 ( 48) [000] ...1 1740.876601: mm_vmscan_kswapd_sleep: nid=0'// @suppress longLineCheck
+ ];
+
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 1);
+
+ const thread = threads[0];
+
+ assert.strictEqual(thread.sliceGroup.length, 7);
+
+ thread.sliceGroup.slices.forEach(function(slice) {
+ assert.strictEqual(slice.args !== undefined, true);
+ });
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/parser.html
new file mode 100644
index 00000000000..42151aea319
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/parser.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/base/base.html">
+<link rel="import" href="/tracing/base/extension_registry.html">
+<script>
+'use strict';
+
+/**
+ * @fileoverview Base class for linux perf event parsers.
+ *
+ * The linux perf trace event importer depends on subclasses of
+ * Parser to parse event data. Each subclass corresponds
+ * to a group of trace events; e.g. SchedParser implements
+ * parsing of sched:* kernel trace events. Parser subclasses must
+ * call Parser.register to arrange to be instantiated
+ * and their constructor must register their event handlers with the
+ * importer. For example,
+ *
+ * var Parser = tr.e.importer.linux_perf.Parser;
+ *
+ * function WorkqueueParser(importer) {
+ * Parser.call(this, importer);
+ *
+ * importer.registerEventHandler('workqueue_execute_start',
+ * WorkqueueParser.prototype.executeStartEvent.bind(this));
+ * importer.registerEventHandler('workqueue_execute_end',
+ * WorkqueueParser.prototype.executeEndEvent.bind(this));
+ * }
+ *
+ * Parser.register(WorkqueueParser);
+ *
+ * When a registered event name is found in the data stream the associated
+ * event handler is invoked:
+ *
+ * executeStartEvent: function(eventName, cpuNumber, ts, eventBase)
+ *
+ * If the routine returns false the caller will generate an import error
+ * saying there was a problem parsing it. Handlers can also emit import
+ * messages using this.importer.model.importWarning. If this is done in lieu of
+ * the generic import error it may be desirable for the handler to return
+ * true.
+ *
+ * Trace events generated by writing to the trace_marker file are expected
+ * to have a leading text marker followed by a ':'; e.g. the trace clock
+ * synchronization event is:
+ *
+ * tracing_mark_write: trace_event_clock_sync: parent_ts=0
+ *
+ * To register an event handler for these events, prepend the marker with
+ * 'tracing_mark_write:'; e.g.
+ *
+ * this.registerEventHandler('tracing_mark_write:trace_event_clock_sync',
+ *
+ * All subclasses should depend on importer.linux_perf.parser, e.g.
+ *
+ * tr.defineModule('importer.linux_perf.workqueue_parser')
+ * .dependsOn('importer.linux_perf.parser')
+ * .exportsTo('tracing', function()
+ *
+ * and be listed in the dependsOn of FTraceImporter. Beware that after adding a
+ * new subclass you must run build/generate_about_tracing_contents.py to
+ * regenerate tr.ui.e.about_tracing.*.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ /**
+ * Parses linux perf events.
+ * @constructor
+ */
+ function Parser(importer) {
+ this.importer = importer;
+ this.model = importer.model;
+ }
+
+ Parser.prototype = {
+ __proto__: Object.prototype
+ };
+
+ const options = new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);
+ options.mandatoryBaseClass = Parser;
+ tr.b.decorateExtensionRegistry(Parser, options);
+
+ return {
+ Parser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/power_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/power_parser.html
new file mode 100644
index 00000000000..90a668eb3d2
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/power_parser.html
@@ -0,0 +1,179 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+<link rel="import" href="/tracing/model/counter_series.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses power events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux power trace events.
+ * @constructor
+ */
+ function PowerParser(importer) {
+ Parser.call(this, importer);
+
+ // NB: old-style power events, deprecated
+ importer.registerEventHandler('power_start',
+ PowerParser.prototype.powerStartEvent.bind(this));
+ importer.registerEventHandler('power_frequency',
+ PowerParser.prototype.powerFrequencyEvent.bind(this));
+
+ importer.registerEventHandler('cpu_frequency',
+ PowerParser.prototype.cpuFrequencyEvent.bind(this));
+ importer.registerEventHandler('cpu_frequency_limits',
+ PowerParser.prototype.cpuFrequencyLimitsEvent.bind(this));
+ importer.registerEventHandler('cpu_idle',
+ PowerParser.prototype.cpuIdleEvent.bind(this));
+ }
+
+ PowerParser.prototype = {
+ __proto__: Parser.prototype,
+
+ cpuStateSlice(ts, targetCpuNumber, eventType, cpuState) {
+ const targetCpu = this.importer.getOrCreateCpu(targetCpuNumber);
+ if (eventType !== '1') {
+ this.importer.model.importWarning({
+ type: 'parse_error',
+ message: 'Don\'t understand power_start events of ' +
+ 'type ' + eventType
+ });
+ return;
+ }
+ const powerCounter = targetCpu.getOrCreateCounter('', 'C-State');
+ if (powerCounter.numSeries === 0) {
+ powerCounter.addSeries(new tr.model.CounterSeries('state',
+ ColorScheme.getColorIdForGeneralPurposeString(
+ powerCounter.name + '.' + 'state')));
+ }
+ powerCounter.series.forEach(function(series) {
+ series.addCounterSample(ts, cpuState);
+ });
+ },
+
+ cpuIdleSlice(ts, targetCpuNumber, cpuState) {
+ const targetCpu = this.importer.getOrCreateCpu(targetCpuNumber);
+ const powerCounter = targetCpu.getOrCreateCounter('', 'C-State');
+ if (powerCounter.numSeries === 0) {
+ powerCounter.addSeries(new tr.model.CounterSeries('state',
+ ColorScheme.getColorIdForGeneralPurposeString(powerCounter.name)));
+ }
+ // NB: 4294967295/-1 means an exit from the current state
+ const val = (cpuState !== 4294967295 ? cpuState + 1 : 0);
+ powerCounter.series.forEach(function(series) {
+ series.addCounterSample(ts, val);
+ });
+ },
+
+ cpuFrequencySlice(ts, targetCpuNumber, powerState) {
+ const targetCpu = this.importer.getOrCreateCpu(targetCpuNumber);
+ const powerCounter =
+ targetCpu.getOrCreateCounter('', 'Clock Frequency');
+ if (powerCounter.numSeries === 0) {
+ powerCounter.addSeries(new tr.model.CounterSeries('state',
+ ColorScheme.getColorIdForGeneralPurposeString(
+ powerCounter.name + '.' + 'state')));
+ }
+ powerCounter.series.forEach(function(series) {
+ series.addCounterSample(ts, powerState);
+ });
+ },
+
+ cpuFrequencyLimitsSlice(ts, targetCpuNumber, minFreq, maxFreq) {
+ const targetCpu = this.importer.getOrCreateCpu(targetCpuNumber);
+ const powerCounter =
+ targetCpu.getOrCreateCounter('', 'Clock Frequency Limits');
+ if (powerCounter.numSeries === 0) {
+ powerCounter.addSeries(new tr.model.CounterSeries('Min Frequency',
+ ColorScheme.getColorIdForGeneralPurposeString(
+ powerCounter.name + '.' + 'Min Frequency')));
+ powerCounter.addSeries(new tr.model.CounterSeries('Max Frequency',
+ ColorScheme.getColorIdForGeneralPurposeString(
+ powerCounter.name + '.' + 'Max Frequency')));
+ }
+ powerCounter.series.forEach(function(series) {
+ if (series.name === 'Min Frequency') {
+ series.addCounterSample(ts, minFreq);
+ }
+ if (series.name === 'Max Frequency') {
+ series.addCounterSample(ts, maxFreq);
+ }
+ });
+ },
+
+ /**
+ * Parses power events and sets up state in the importer.
+ */
+ powerStartEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /type=(\d+) state=(\d) cpu_id=(\d+)/.exec(
+ eventBase.details);
+ if (!event) return false;
+
+ const targetCpuNumber = parseInt(event[3]);
+ const cpuState = parseInt(event[2]);
+ this.cpuStateSlice(ts, targetCpuNumber, event[1], cpuState);
+ return true;
+ },
+
+ powerFrequencyEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /type=(\d+) state=(\d+) cpu_id=(\d+)/
+ .exec(eventBase.details);
+ if (!event) return false;
+
+ const targetCpuNumber = parseInt(event[3]);
+ const powerState = parseInt(event[2]);
+ this.cpuFrequencySlice(ts, targetCpuNumber, powerState);
+ return true;
+ },
+
+ cpuFrequencyEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /state=(\d+) cpu_id=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ const targetCpuNumber = parseInt(event[2]);
+ const powerState = parseInt(event[1]);
+ this.cpuFrequencySlice(ts, targetCpuNumber, powerState);
+ return true;
+ },
+
+ cpuFrequencyLimitsEvent(eventName, cpu, pid, ts, eventBase) {
+ const event = /min=(\d+) max=(\d+) cpu_id=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ const targetCpuNumber = parseInt(event[3]);
+ const minFreq = parseInt(event[1]);
+ const maxFreq = parseInt(event[2]);
+ this.cpuFrequencyLimitsSlice(ts, targetCpuNumber, minFreq, maxFreq);
+ return true;
+ },
+
+ cpuIdleEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /state=(\d+) cpu_id=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ const targetCpuNumber = parseInt(event[2]);
+ const cpuState = parseInt(event[1]);
+ this.cpuIdleSlice(ts, targetCpuNumber, cpuState);
+ return true;
+ }
+ };
+
+ Parser.register(PowerParser);
+
+ return {
+ PowerParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/power_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/power_parser_test.html
new file mode 100644
index 00000000000..99335c464c8
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/power_parser_test.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ test('powerFrequencyImport', function() {
+ const lines = [
+ ' kworker/0:3-6880 [000] 2784.783015: power_frequency: ' +
+ 'type=2 state=1000000 cpu_id=0',
+ ' kworker/1:2-7269 [001] 2784.788993: power_frequency: ' +
+ 'type=2 state=800000 cpu_id=1',
+ ' kworker/1:2-7269 [001] 2784.993120: power_frequency: ' +
+ 'type=2 state=1300000 cpu_id=1'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const c0 = m.kernel.cpus[0];
+ assert.strictEqual(c0.slices.length, 0);
+ assert.strictEqual(
+ c0.counters['.Clock Frequency'].series[0].samples.length, 1);
+
+ const c1 = m.kernel.cpus[1];
+ assert.strictEqual(c1.slices.length, 0);
+ assert.strictEqual(
+ c1.counters['.Clock Frequency'].series[0].samples.length, 2);
+ });
+
+ test('cpuFrequencyImport', function() {
+ const lines = [
+ ' kworker/1:0-9665 [001] 15051.007301: cpu_frequency: ' +
+ 'state=800000 cpu_id=1',
+ ' kworker/1:0-9665 [001] 15051.010278: cpu_frequency: ' +
+ 'state=1300000 cpu_id=1',
+ ' kworker/0:2-7972 [000] 15051.010278: cpu_frequency: ' +
+ 'state=1000000 cpu_id=0',
+ ' kworker/0:2-7972 [000] 15051.020304: cpu_frequency: ' +
+ 'state=800000 cpu_id=0'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const c0 = m.kernel.cpus[0];
+ assert.strictEqual(c0.slices.length, 0);
+ assert.strictEqual(
+ c0.counters['.Clock Frequency'].series[0].samples.length, 2);
+
+ const c1 = m.kernel.cpus[1];
+ assert.strictEqual(c1.slices.length, 0);
+ assert.strictEqual(
+ c1.counters['.Clock Frequency'].series[0].samples.length, 2);
+ });
+
+ test('cpuIdleImport', function() {
+ const lines = [
+ ' <idle>-0 [000] 15050.992883: cpu_idle: ' +
+ 'state=1 cpu_id=0',
+ ' <idle>-0 [000] 15050.993027: cpu_idle: ' +
+ 'state=4294967295 cpu_id=0',
+ ' <idle>-0 [001] 15050.993132: cpu_idle: ' +
+ 'state=1 cpu_id=1',
+ ' <idle>-0 [001] 15050.993276: cpu_idle: ' +
+ 'state=4294967295 cpu_id=1',
+ ' <idle>-0 [001] 15050.993279: cpu_idle: ' +
+ 'state=3 cpu_id=1',
+ ' <idle>-0 [001] 15050.993457: cpu_idle: ' +
+ 'state=4294967295 cpu_id=1'
+ ];
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const c0 = m.kernel.cpus[0];
+ assert.strictEqual(c0.slices.length, 0);
+ assert.strictEqual(c0.counters['.C-State'].series[0].samples.length, 2);
+
+ const c1 = m.kernel.cpus[1];
+ assert.strictEqual(c1.slices.length, 0);
+ assert.strictEqual(c1.counters['.C-State'].series[0].samples.length, 4);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/regulator_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/regulator_parser.html
new file mode 100644
index 00000000000..c92767eaba2
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/regulator_parser.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses regulator events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux regulator trace events.
+ * @constructor
+ */
+ function RegulatorParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('regulator_enable',
+ RegulatorParser.prototype.regulatorEnableEvent.bind(this));
+ importer.registerEventHandler('regulator_enable_delay',
+ RegulatorParser.prototype.regulatorEnableDelayEvent.bind(this));
+ importer.registerEventHandler('regulator_enable_complete',
+ RegulatorParser.prototype.regulatorEnableCompleteEvent.bind(this));
+ importer.registerEventHandler('regulator_disable',
+ RegulatorParser.prototype.regulatorDisableEvent.bind(this));
+ importer.registerEventHandler('regulator_disable_complete',
+ RegulatorParser.prototype.regulatorDisableCompleteEvent.bind(this));
+ importer.registerEventHandler('regulator_set_voltage',
+ RegulatorParser.prototype.regulatorSetVoltageEvent.bind(this));
+ importer.registerEventHandler('regulator_set_voltage_complete',
+ RegulatorParser.prototype.regulatorSetVoltageCompleteEvent.bind(this));
+
+ this.model_ = importer.model_;
+ }
+
+ // Matches the regulator_enable record
+ const regulatorEnableRE = /name=(.+)/;
+
+ // Matches the regulator_disable record
+ const regulatorDisableRE = /name=(.+)/;
+
+ // Matches the regulator_set_voltage_complete record
+ const regulatorSetVoltageCompleteRE = /name=(\S+), val=(\d+)/;
+
+ RegulatorParser.prototype = {
+ __proto__: Parser.prototype,
+
+ /**
+ * Get or create a counter with one series.
+ */
+ getCtr_(ctrName, valueName) {
+ const ctr = this.model_.kernel
+ .getOrCreateCounter(null, 'vreg ' + ctrName + ' ' + valueName);
+ // Initialize the counter's series fields if needed.
+ if (ctr.series[0] === undefined) {
+ ctr.addSeries(new tr.model.CounterSeries(valueName,
+ ColorScheme.getColorIdForGeneralPurposeString(
+ ctrName + '.' + valueName)));
+ }
+ return ctr;
+ },
+
+ /**
+ * Parses regulator events and sets up state in the importer.
+ */
+ regulatorEnableEvent(eventName, cpuNum, pid, ts, eventBase) {
+ const event = regulatorEnableRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const name = event[1];
+
+ const ctr = this.getCtr_(name, 'enabled');
+ ctr.series[0].addCounterSample(ts, 1);
+
+ return true;
+ },
+
+ regulatorEnableDelayEvent(eventName, cpuNum, pid, ts, eventBase) {
+ return true;
+ },
+
+ regulatorEnableCompleteEvent(eventName, cpuNum, pid, ts,
+ eventBase) {
+ return true;
+ },
+
+ regulatorDisableEvent(eventName, cpuNum, pid, ts, eventBase) {
+ const event = regulatorDisableRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const name = event[1];
+
+ const ctr = this.getCtr_(name, 'enabled');
+ ctr.series[0].addCounterSample(ts, 0);
+
+ return true;
+ },
+
+ regulatorDisableCompleteEvent(eventName, cpuNum, pid, ts,
+ eventBase) {
+ return true;
+ },
+
+ regulatorSetVoltageEvent(eventName, cpuNum, pid, ts, eventBase) {
+ return true;
+ },
+
+ regulatorSetVoltageCompleteEvent(eventName, cpuNum, pid, ts,
+ eventBase) {
+ const event = regulatorSetVoltageCompleteRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const name = event[1];
+ const voltage = parseInt(event[2]);
+
+ const ctr = this.getCtr_(name, 'voltage');
+ ctr.series[0].addCounterSample(ts, voltage);
+
+ return true;
+ }
+
+ };
+
+ Parser.register(RegulatorParser);
+
+ return {
+ RegulatorParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/regulator_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/regulator_parser_test.html
new file mode 100644
index 00000000000..55ffc85c4aa
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/regulator_parser_test.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('regulatorImport', function() {
+ const lines = [
+ ' kworker/0:2H-14312 [000] ...1 143713.787749: ' +
+ 'regulator_set_voltage: name=krait0 (810000-1100000)',
+ ' kworker/0:2H-14312 [000] ...1 143713.787778: ' +
+ 'regulator_set_voltage_complete: name=krait0, val=810000',
+ ' kworker/0:2H-14312 [000] ...1 143714.037871: ' +
+ 'regulator_set_voltage: name=krait0 (800000-1100000)',
+ ' kworker/0:2H-14312 [000] ...1 143714.037895: ' +
+ 'regulator_set_voltage_complete: name=krait0, val=800000',
+ 'kworker/0:1-30321 [000] ...1 144568.624596: ' +
+ 'regulator_enable: name=8941_smbb_boost',
+ 'kworker/0:1-30321 [000] ...1 144568.624715: ' +
+ 'regulator_enable_delay: name=8941_smbb_boost',
+ 'kworker/0:1-30321 [000] ...1 144568.624723: ' +
+ 'regulator_enable_complete: name=8941_smbb_boost',
+ 'kworker/0:1-30321 [000] ...1 144568.653546: ' +
+ 'regulator_disable: name=8941_smbb_boost',
+ 'kworker/0:1-30321 [000] ...1 144568.654785: ' +
+ 'regulator_disable_complete: name=8941_smbb_boost'
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ assert.property(m.kernel.counters, 'null.vreg krait0 voltage');
+ assert.property(m.kernel.counters, 'null.vreg 8941_smbb_boost enabled');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/rss_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/rss_parser.html
new file mode 100644
index 00000000000..70192df1b5b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/rss_parser.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2018 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+<link rel="import" href="/tracing/model/counter_series.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses rss stat events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses Rss stat trace events.
+ * @constructor
+ */
+ function RssParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('rss_stat',
+ RssParser.prototype.rssStat.bind(this));
+ }
+
+ const TestExports = {};
+
+ // Matches the rss_stat record
+ const rssStatRE = new RegExp('member=(\\d+) size=(\\d+)');
+
+ TestExports.rssStatRE = rssStatRE;
+
+ const unknownThreadName = '<...>';
+
+ RssParser.prototype = {
+ __proto__: Parser.prototype,
+
+ /**
+ * Parses rss stat events and sets up state in the importer.
+ */
+ rssStat(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = rssStatRE.exec(eventBase.details);
+ if (!event) return false;
+ const member = parseInt(event[1]);
+ const size = parseInt(event[2]);
+
+ if (eventBase.tgid === undefined) {
+ return false;
+ }
+
+ const tgid = parseInt(eventBase.tgid);
+ const process = this.importer.model_.getOrCreateProcess(tgid);
+ let subTitle = '';
+
+ if (member === 0) {
+ subTitle = ' (file pages)';
+ } else if (member === 1) {
+ subTitle = ' (anon)';
+ }
+
+ const rssCounter =
+ process.getOrCreateCounter('RSS', 'RSS ' + member + subTitle);
+ if (rssCounter.numSeries === 0) {
+ rssCounter.addSeries(new tr.model.CounterSeries('RSS',
+ tr.b.ColorScheme.getColorIdForGeneralPurposeString(
+ rssCounter.name)));
+ }
+ rssCounter.series.forEach(function(series) {
+ series.addCounterSample(ts, size);
+ });
+ return true;
+ },
+ };
+
+ Parser.register(RssParser);
+
+ return {
+ RssParser,
+ _RssParserTestExports: TestExports
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/rss_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/rss_parser_test.html
new file mode 100644
index 00000000000..7ac477eed50
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/rss_parser_test.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2018 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ test('rssStatRE', function() {
+ const re = tr.e.importer.linux_perf._RssParserTestExports.rssStatRE;
+ let x = re.exec('rss_stat: member=0 size=536576');
+ assert.isNotNull(x);
+ assert.strictEqual(x[1], '0');
+ assert.strictEqual(x[2], '536576');
+
+ x = re.exec('rss_stat: member=1 size=716800');
+ assert.isNotNull(x);
+ assert.strictEqual(x[1], '1');
+ assert.strictEqual(x[2], '716800');
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sched_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sched_parser.html
new file mode 100644
index 00000000000..3004f577f1f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sched_parser.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+<link rel="import" href="/tracing/model/counter.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses scheduler events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux sched trace events.
+ * @constructor
+ */
+ function SchedParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('sched_switch',
+ SchedParser.prototype.schedSwitchEvent.bind(this));
+ importer.registerEventHandler('sched_wakeup',
+ SchedParser.prototype.schedWakeupEvent.bind(this));
+ importer.registerEventHandler('sched_blocked_reason',
+ SchedParser.prototype.schedBlockedEvent.bind(this));
+ importer.registerEventHandler('sched_cpu_hotplug',
+ SchedParser.prototype.schedCpuHotplugEvent.bind(this));
+ }
+
+ const TestExports = {};
+
+ // Matches the sched_switch record
+ const schedSwitchRE = new RegExp(
+ 'prev_comm=(.+) prev_pid=(\\d+) prev_prio=(\\d+) ' +
+ 'prev_state=(\\S\\+?|\\S\\|\\S) ==> ' +
+ 'next_comm=(.+) next_pid=(\\d+) next_prio=(\\d+)');
+
+ // Matches sched_blocked_reason record
+ const schedBlockedRE = new RegExp('pid=(\\d+) iowait=(\\d) caller=(.+)');
+ TestExports.schedSwitchRE = schedSwitchRE;
+
+ // Matches the sched_wakeup record
+ // success=? is optional not all kernels report it, so don't include
+ // it in the capture groups
+ const schedWakeupRE =
+ /comm=(.+) pid=(\d+) prio=(\d+)(?: success=\d+)? target_cpu=(\d+)/;
+ TestExports.schedWakeupRE = schedWakeupRE;
+
+ const unknownThreadName = '<...>';
+
+ SchedParser.prototype = {
+ __proto__: Parser.prototype,
+
+ /**
+ * Parses scheduler events and sets up state in the CPUs of the importer.
+ */
+ schedSwitchEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = schedSwitchRE.exec(eventBase.details);
+ if (!event) return false;
+ const prevState = event[4];
+ const nextComm = event[5];
+ const nextPid = parseInt(event[6]);
+ const nextPrio = parseInt(event[7]);
+
+ if (eventBase.tgid !== undefined) {
+ const tgid = parseInt(eventBase.tgid);
+ const process = this.importer.model_.getOrCreateProcess(tgid);
+ const storedThread = process.getThread(pid);
+ if (!storedThread) {
+ const thread = process.getOrCreateThread(pid);
+ thread.name = eventBase.threadName;
+ } else if (storedThread.name === unknownThreadName) {
+ storedThread.name = eventBase.threadName;
+ }
+ }
+
+ const nextThread = this.importer.threadsByLinuxPid[nextPid];
+ let nextName;
+ if (nextThread) {
+ nextName = nextThread.userFriendlyName;
+ } else {
+ nextName = nextComm;
+ }
+
+ const cpu = this.importer.getOrCreateCpu(cpuNumber);
+ cpu.switchActiveThread(
+ ts,
+ {stateWhenDescheduled: prevState},
+ nextPid,
+ nextName,
+ {
+ comm: nextComm,
+ tid: nextPid,
+ prio: nextPrio
+ });
+
+ return true;
+ },
+
+ schedWakeupEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = schedWakeupRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const fromPid = pid;
+ const comm = event[1];
+ pid = parseInt(event[2]);
+ const prio = parseInt(event[3]);
+ this.importer.markPidRunnable(ts, pid, comm, prio, fromPid);
+ return true;
+ },
+
+ schedCpuHotplugEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = /cpu (\d+) (.+) error=(\d+)/.exec(eventBase.details);
+ if (!event) return false;
+
+ cpuNumber = event[1];
+ const state = event[2];
+ const targetCpu = this.importer.getOrCreateCpu(cpuNumber);
+
+ const powerCounter = targetCpu.getOrCreateCounter('', 'Cpu Hotplug');
+ if (powerCounter.numSeries === 0) {
+ powerCounter.addSeries(new tr.model.CounterSeries('State',
+ tr.b.ColorScheme.getColorIdForGeneralPurposeString(
+ powerCounter.name + '.' + 'State')));
+ }
+ powerCounter.series.forEach(function(series) {
+ if (series.name === 'State') {
+ series.addCounterSample(ts, state.localeCompare('offline') ? 0 : 1);
+ }
+ });
+ return true;
+ },
+
+ schedBlockedEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = schedBlockedRE.exec(eventBase.details);
+ if (!event) return false;
+
+ pid = parseInt(event[1]);
+ const iowait = parseInt(event[2]);
+ const caller = event[3];
+
+ this.importer.addPidBlockedReason(ts, pid, iowait, caller);
+ return true;
+ }
+ };
+
+ Parser.register(SchedParser);
+
+ return {
+ SchedParser,
+ _SchedParserTestExports: TestExports
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sched_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sched_parser_test.html
new file mode 100644
index 00000000000..998d7f4ab21
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sched_parser_test.html
@@ -0,0 +1,262 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ test('schedSwitchRE', function() {
+ const re = tr.e.importer.linux_perf._SchedParserTestExports.schedSwitchRE;
+ let x = re.exec('prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R ' +
+ '==> next_comm=SurfaceFlinger next_pid=178 next_prio=112');
+ assert.isNotNull(x);
+ assert.strictEqual(x[1], 'swapper');
+ assert.strictEqual(x[2], '0');
+ assert.strictEqual(x[3], '120');
+ assert.strictEqual(x[4], 'R');
+ assert.strictEqual(x[5], 'SurfaceFlinger');
+ assert.strictEqual(x[6], '178');
+ assert.strictEqual(x[7], '112');
+
+ x = re.exec('prev_comm=.android.chrome prev_pid=1562 prev_prio=120 prev_state=R ==> next_comm=Binder Thread # next_pid=195 next_prio=120'); // @suppress longLineCheck
+ assert.isNotNull(x);
+ assert.strictEqual(x[1], '.android.chrome');
+ assert.strictEqual(x[5], 'Binder Thread #');
+
+ x = re.exec('prev_comm=Binder Thread # prev_pid=1562 prev_prio=120 prev_state=R ==> next_comm=.android.chrome next_pid=195 next_prio=120'); // @suppress longLineCheck
+ assert.isNotNull(x);
+ assert.strictEqual(x[1], 'Binder Thread #');
+ assert.strictEqual(x[5], '.android.chrome');
+
+ // explicit test for prev_state of D|W
+ x = re.exec('prev_comm=.android.chrome prev_pid=1562 prev_prio=120 ' +
+ 'prev_state=D|W ==> next_comm=Binder Thread # next_pid=195 ' +
+ 'next_prio=120');
+ assert.isNotNull(x);
+ assert.strictEqual(x[4], 'D|W');
+ });
+
+ test('schedWakeupRE', function() {
+ const re = tr.e.importer.linux_perf._SchedParserTestExports.schedWakeupRE;
+ const x = re.exec(
+ 'comm=SensorService pid=207 prio=112 success=1 target_cpu=000');
+ assert.isNotNull(x);
+ assert.strictEqual(x[1], 'SensorService');
+ assert.strictEqual(x[2], '207');
+ assert.strictEqual(x[3], '112');
+ assert.strictEqual(x[4], '000');
+ const y = re.exec(
+ 'comm=SensorService pid=207 prio=112 target_cpu=000');
+ assert.isNotNull(y);
+ assert.strictEqual(y[1], 'SensorService');
+ assert.strictEqual(y[2], '207');
+ assert.strictEqual(y[3], '112');
+ assert.strictEqual(y[4], '000');
+ });
+
+ test('importOneSequenceWithSchedWakeUp', function() {
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const lines = [
+ 'ndroid.launcher-584 [001] d..3 12622.506890: sched_switch: prev_comm=ndroid.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck
+ ' Binder_1-217 [001] d..3 12622.506918: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=D ==> next_comm=ndroid.launcher next_pid=584 next_prio=120', // @suppress longLineCheck
+ 'ndroid.launcher-584 [001] d..4 12622.506936: sched_wakeup: comm=Binder_1 pid=217 prio=120 success=1 target_cpu=001', // @suppress longLineCheck
+ 'ndroid.launcher-584 [001] d..3 12622.506950: sched_switch: prev_comm=ndroid.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck
+ ' Binder_1-217 [001] ...1 12622.507057: tracing_mark_write: B|128|queueBuffer', // @suppress longLineCheck
+ ' Binder_1-217 [001] ...1 12622.507175: tracing_mark_write: E',
+ ' Binder_1-217 [001] d..3 12622.507253: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=S ==> next_comm=ndroid.launcher next_pid=584 next_prio=120' // @suppress longLineCheck
+ ];
+
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const thread = m.findAllThreadsNamed('Binder_1')[0];
+ const timeSlices = thread.timeSlices;
+ assert.strictEqual(timeSlices.length, 4);
+
+ const runningSlice = timeSlices[0];
+ assert.strictEqual(runningSlice.schedulingState, SCHEDULING_STATE.RUNNING);
+ assert.closeTo(12622506.890, runningSlice.start, 1e-5);
+ assert.closeTo(.918 - .890, runningSlice.duration, 1e-5);
+
+ const sleepSlice = timeSlices[1];
+ assert.strictEqual(
+ sleepSlice.schedulingState, SCHEDULING_STATE.UNINTR_SLEEP);
+ assert.closeTo(12622506.918, sleepSlice.start, 1e-5);
+ assert.closeTo(.936 - .918, sleepSlice.duration, 1e-5);
+
+ const wakeupSlice = timeSlices[2];
+ assert.strictEqual(
+ wakeupSlice.schedulingState, SCHEDULING_STATE.RUNNABLE);
+ assert.closeTo(12622506.936, wakeupSlice.start, 1e-5);
+ assert.closeTo(.950 - .936, wakeupSlice.duration, 1e-5);
+ assert.strictEqual(wakeupSlice.args['wakeup from tid'], 584);
+
+ const runningSlice2 = timeSlices[3];
+ assert.strictEqual(
+ runningSlice2.schedulingState, SCHEDULING_STATE.RUNNING);
+ assert.closeTo(12622506.950, runningSlice2.start, 1e-5);
+ assert.closeTo(7.253 - 6.950, runningSlice2.duration, 1e-5);
+ });
+
+ test('importWithUnknownSleepState', function() {
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const lines = [
+ 'ndroid.launcher-584 [001] d..3 12622.506890: sched_switch: prev_comm=ndroid.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck
+ ' Binder_1-217 [001] d..3 12622.506918: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=F|O ==> next_comm=ndroid.launcher next_pid=584 next_prio=120', // @suppress longLineCheck
+ 'ndroid.launcher-584 [001] d..4 12622.506936: sched_wakeup: comm=Binder_1 pid=217 prio=120 success=1 target_cpu=001', // @suppress longLineCheck
+ 'ndroid.launcher-584 [001] d..3 12622.506950: sched_switch: prev_comm=ndroid.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck
+ ' Binder_1-217 [001] ...1 12622.507057: tracing_mark_write: B|128|queueBuffer', // @suppress longLineCheck
+ ' Binder_1-217 [001] ...1 12622.507175: tracing_mark_write: E',
+ ' Binder_1-217 [001] d..3 12622.507253: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=F|O ==> next_comm=ndroid.launcher next_pid=584 next_prio=120' // @suppress longLineCheck
+ ];
+
+ let m;
+ assert.doesNotThrow(function() {
+ m = newModel(lines.join('\n'));
+ });
+ assert.isTrue(m.hasImportWarnings);
+ assert.strictEqual(
+ m.importWarnings[0].message, 'Unrecognized sleep state: F|O');
+
+ const thread = m.findAllThreadsNamed('Binder_1')[0];
+ const timeSlices = thread.timeSlices;
+
+ assert.strictEqual(timeSlices[1].schedulingState, SCHEDULING_STATE.UNKNOWN);
+ });
+
+ test('importWithUninterruptibleSleep', function() {
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const lines = [
+ 'ndroid.launcher-584 [001] d..3 12622.506890: sched_switch: ' +
+ 'prev_comm=ndroid.launcher prev_pid=584 ' +
+ 'prev_prio=120 prev_state=R+ ' +
+ '==> next_comm=Binder_1 next_pid=217 next_prio=120',
+
+ ' Binder_1-217 [001] d..3 12622.506918: sched_switch: ' +
+ 'prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=D|K ' +
+ '==> next_comm=ndroid.launcher next_pid=584 next_prio=120',
+
+ 'ndroid.launcher-584 [001] d..4 12622.506936: sched_wakeup: ' +
+ 'comm=Binder_1 pid=217 prio=120 success=1 target_cpu=001',
+
+ 'ndroid.launcher-584 [001] d..3 12622.506950: sched_switch: ' +
+ 'prev_comm=ndroid.launcher prev_pid=584 ' +
+ 'prev_prio=120 prev_state=R+ ' +
+ '==> next_comm=Binder_1 next_pid=217 next_prio=120',
+
+ ' Binder_1-217 [001] ...1 12622.507057: tracing_mark_write: ' +
+ 'B|128|queueBuffer',
+
+ ' Binder_1-217 [001] ...1 12622.507175: tracing_mark_write: E',
+
+ ' Binder_1-217 [001] d..3 12622.507253: sched_switch: ' +
+ 'prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=S ' +
+ '==> next_comm=ndroid.launcher next_pid=584 next_prio=120'
+ ];
+
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const thread = m.findAllThreadsNamed('Binder_1')[0];
+ const timeSlices = thread.timeSlices;
+ assert.strictEqual(timeSlices.length, 4);
+
+ const wakeKillSlice = timeSlices[1];
+ assert.strictEqual(wakeKillSlice.schedulingState,
+ SCHEDULING_STATE.UNINTR_SLEEP_WAKE_KILL);
+ assert.closeTo(12622506.918, wakeKillSlice.start, 1e-5);
+ assert.closeTo(.936 - .918, wakeKillSlice.duration, 1e-5);
+ });
+
+ test('importWithUninterruptibleSleepAndBlockedReason', function() {
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const lines = [
+ 'ndroid.launcher-584 [001] d..3 12622.506890: sched_switch: ' +
+ 'prev_comm=ndroid.launcher prev_pid=584 ' +
+ 'prev_prio=120 prev_state=R+ ' +
+ '==> next_comm=Binder_1 next_pid=217 next_prio=120',
+
+ ' Binder_1-217 [001] d..3 12622.506918: sched_switch: ' +
+ 'prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=D|K ' +
+ '==> next_comm=ndroid.launcher next_pid=584 next_prio=120',
+
+ ' Binder_1-217 [001] d..3 12622.506930: sched_blocked_reason: ' +
+ 'pid=217 iowait=1 caller=sleep_on_page_killable+0x10/0x4c',
+
+ 'ndroid.launcher-584 [001] d..4 12622.506936: sched_wakeup: ' +
+ 'comm=Binder_1 pid=217 prio=120 success=1 target_cpu=001',
+
+ 'ndroid.launcher-584 [001] d..3 12622.506950: sched_switch: ' +
+ 'prev_comm=ndroid.launcher prev_pid=584 ' +
+ 'prev_prio=120 prev_state=R+ ' +
+ '==> next_comm=Binder_1 next_pid=217 next_prio=120',
+
+ ' Binder_1-217 [001] ...1 12622.507057: tracing_mark_write: ' +
+ 'B|128|queueBuffer',
+
+ ' Binder_1-217 [001] ...1 12622.507175: tracing_mark_write: E',
+
+ ' Binder_1-217 [001] d..3 12622.507253: sched_switch: ' +
+ 'prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=S ' +
+ '==> next_comm=ndroid.launcher next_pid=584 next_prio=120'
+ ];
+
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const thread = m.findAllThreadsNamed('Binder_1')[0];
+ const timeSlices = thread.timeSlices;
+ assert.strictEqual(timeSlices.length, 4);
+
+ const wakeKillSlice = timeSlices[1];
+ assert.strictEqual(wakeKillSlice.schedulingState,
+ SCHEDULING_STATE.UNINTR_SLEEP_WAKE_KILL_IO);
+ assert.closeTo(12622506.918, wakeKillSlice.start, 1e-5);
+ assert.closeTo(.936 - .918, wakeKillSlice.duration, 1e-5);
+ });
+
+ test('importWithTgids', function() {
+ const SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
+ const lines = [
+ 'kworker/u12:4-9393 ( 9393) [002] d..3 10386.798979: sched_switch: ' +
+ 'prev_comm=kworker/u12:4 prev_pid=9393 prev_prio=120 ' +
+ 'prev_state=S ==> next_comm=swapper/2 next_pid=0 next_prio=120',
+ ];
+
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const p = m.processes[9393];
+ assert.strictEqual(p.pid, 9393);
+ });
+
+ test('importWithMissingNames', function() {
+ const lines = [
+ ' Binder_1-217 ( 438) [001] d..3 12622.506918: sched_switch: prev_comm=Binder_1 prev_pid=217 prev_prio=120 prev_state=F|O ==> next_comm=ndroid.launcher next_pid=584 next_prio=120', // @suppress longLineCheck
+ '<...>-584 ( 954) [001] d..4 12622.506936: sched_wakeup: comm=Binder_1 pid=217 prio=120 success=1 target_cpu=001', // @suppress longLineCheck
+ 'ndroid.launcher-584 ( 954) [001] d..3 12622.506950: sched_switch: prev_comm=ndroid.launcher prev_pid=584 prev_prio=120 prev_state=R+ ==> next_comm=Binder_1 next_pid=217 next_prio=120', // @suppress longLineCheck
+ ];
+
+ const m = newModel(lines.join('\n'));
+ assert.isFalse(m.hasImportWarnings);
+
+ const p = m.processes[954];
+ const t = p.getThread(584);
+ assert.strictEqual(t.name, 'ndroid.launcher');
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sync_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sync_parser.html
new file mode 100644
index 00000000000..2e96b8714b6
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sync_parser.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses sync events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux sync trace events.
+ * @constructor
+ */
+ function SyncParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler(
+ 'sync_timeline',
+ SyncParser.prototype.timelineEvent.bind(this));
+ importer.registerEventHandler(
+ 'sync_wait',
+ SyncParser.prototype.syncWaitEvent.bind(this));
+ importer.registerEventHandler(
+ 'sync_pt',
+ SyncParser.prototype.syncPtEvent.bind(this));
+ this.model_ = importer.model_;
+ }
+
+ const syncTimelineRE = /name=(\S+) value=(\S*)/;
+ const syncWaitRE = /(\S+) name=(\S+) state=(\d+)/;
+ const syncPtRE = /name=(\S+) value=(\S*)/;
+
+ SyncParser.prototype = {
+ __proto__: Parser.prototype,
+
+ /**
+ * Parses sync events and sets up state in the importer.
+ */
+ timelineEvent(eventName, cpuNumber, pid,
+ ts, eventBase) {
+ const event = syncTimelineRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const thread = this.importer.getOrCreatePseudoThread(event[1]);
+
+ if (thread.lastActiveTs !== undefined) {
+ const duration = ts - thread.lastActiveTs;
+ let value = thread.lastActiveValue;
+ if (value === undefined) value = ' ';
+ const slice = new tr.model.ThreadSlice(
+ '', value,
+ ColorScheme.getColorIdForGeneralPurposeString(value),
+ thread.lastActiveTs, {},
+ duration);
+ thread.thread.sliceGroup.pushSlice(slice);
+ }
+ thread.lastActiveTs = ts;
+ thread.lastActiveValue = event[2];
+ return true;
+ },
+
+ syncWaitEvent(eventName, cpuNumber, pid, ts,
+ eventBase) {
+ const event = syncWaitRE.exec(eventBase.details);
+ if (!event) return false;
+
+ if (eventBase.tgid === undefined) {
+ return false;
+ }
+
+ const tgid = parseInt(eventBase.tgid);
+ const thread = this.model_.getOrCreateProcess(tgid)
+ .getOrCreateThread(pid);
+ thread.name = eventBase.threadName;
+ const slices = thread.kernelSliceGroup;
+ if (!slices.isTimestampValidForBeginOrEnd(ts)) {
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Timestamps are moving backward.'
+ });
+ return false;
+ }
+
+ const name = 'fence_wait("' + event[2] + '")';
+ if (event[1] === 'begin') {
+ const slice = slices.beginSlice(null, name, ts, {
+ 'Start state': event[3]
+ });
+ } else if (event[1] === 'end') {
+ if (slices.openSliceCount > 0) {
+ slices.endSlice(ts);
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+ },
+
+ syncPtEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ return !!syncPtRE.exec(eventBase.details);
+ }
+ };
+
+ Parser.register(SyncParser);
+
+ return {
+ SyncParser,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sync_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sync_parser_test.html
new file mode 100644
index 00000000000..eaf824acad6
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/sync_parser_test.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('syncEventImport', function() {
+ const lines = [
+ 's3c-fb-92 ( 0) [000] ...1 7206.550061: sync_timeline: name=s3c-fb value=7094', // @suppress longLineCheck
+ 'TimedEventQueue-2700 ( 0) [001] ...1 7206.569027: sync_wait: begin name=SurfaceView:6 state=1', // @suppress longLineCheck
+ 'TimedEventQueue-2700 ( 0) [001] ...1 7206.569038: sync_pt: name=malitl_124_0x40b6406c value=7289', // @suppress longLineCheck
+ 'TimedEventQueue-2700 ( 0) [001] ...1 7206.569056: sync_pt: name=exynos-gsc.0-src value=25', // @suppress longLineCheck
+ 'TimedEventQueue-2700 ( 0) [001] ...1 7206.569068: sync_wait: end name=SurfaceView:6 state=1', // @suppress longLineCheck
+ 'irq/128-s5p-mfc-62 ( 0) [000] d..3 7206.572402: sync_timeline: name=vb2 value=37', // @suppress longLineCheck
+ 'irq/128-s5p-mfc-62 ( 0) [000] d..3 7206.572475: sync_timeline: name=vb2 value=33', // @suppress longLineCheck
+ 'SurfaceFlinger-225 ( 0) [001] ...1 7206.584769: sync_timeline: name=malitl_124_0x40b6406c value=7290', // @suppress longLineCheck
+ 'kworker/u:5-2269 ( 0) [000] ...1 7206.586745: sync_wait: begin name=display state=1', // @suppress longLineCheck
+ 'kworker/u:5-2269 ( 0) [000] ...1 7206.586750: sync_pt: name=s3c-fb value=7093', // @suppress longLineCheck
+ 'kworker/u:5-2269 ( 0) [000] ...1 7206.586760: sync_wait: end name=display state=1', // @suppress longLineCheck
+ 's3c-fb-92 ( 0) [000] ...1 7206.587193: sync_wait: begin name=vb2 state=0', // @suppress longLineCheck
+ 's3c-fb-92 ( 0) [000] ...1 7206.587198: sync_pt: name=exynos-gsc.0-dst value=27', // @suppress longLineCheck
+ '<idle>-0 ( 0) [000] d.h4 7206.591133: sync_timeline: name=exynos-gsc.0-src value=27', // @suppress longLineCheck
+ '<idle>-0 ( 0) [000] d.h4 7206.591152: sync_timeline: name=exynos-gsc.0-dst value=27', // @suppress longLineCheck
+ 's3c-fb-92 ( 0) [000] ...1 7206.591244: sync_wait: end name=vb2 state=1' // @suppress longLineCheck
+ ];
+
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ let threads = m.getAllThreads();
+ assert.strictEqual(threads.length, 4);
+
+ threads = m.findAllThreadsNamed('s3c-fb');
+ assert.strictEqual(threads.length, 1);
+ assert.strictEqual(threads[0].sliceGroup.length, 1);
+
+ threads = m.findAllThreadsNamed('kworker/u:5');
+ assert.strictEqual(threads.length, 1);
+ assert.strictEqual(threads[0].sliceGroup.length, 1);
+ assert.strictEqual('fence_wait("display")',
+ threads[0].sliceGroup.slices[0].title);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/workqueue_parser.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/workqueue_parser.html
new file mode 100644
index 00000000000..5e9ab9db1b4
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/workqueue_parser.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/linux_perf/parser.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Parses workqueue events in the Linux event trace format.
+ */
+tr.exportTo('tr.e.importer.linux_perf', function() {
+ const ColorScheme = tr.b.ColorScheme;
+ const Parser = tr.e.importer.linux_perf.Parser;
+
+ /**
+ * Parses linux workqueue trace events.
+ * @constructor
+ */
+ function WorkqueueParser(importer) {
+ Parser.call(this, importer);
+
+ importer.registerEventHandler('workqueue_execute_start',
+ WorkqueueParser.prototype.executeStartEvent.bind(this));
+ importer.registerEventHandler('workqueue_execute_end',
+ WorkqueueParser.prototype.executeEndEvent.bind(this));
+ importer.registerEventHandler('workqueue_queue_work',
+ WorkqueueParser.prototype.executeQueueWork.bind(this));
+ importer.registerEventHandler('workqueue_activate_work',
+ WorkqueueParser.prototype.executeActivateWork.bind(this));
+ }
+
+ // Matches the workqueue_execute_start record
+ // workqueue_execute_start: work struct c7a8a89c: function MISRWrapper
+ const workqueueExecuteStartRE = /work struct (.+): function (\S+)/;
+
+ // Matches the workqueue_execute_start record
+ // workqueue_execute_end: work struct c7a8a89c
+ const workqueueExecuteEndRE = /work struct (.+)/;
+
+ WorkqueueParser.prototype = {
+ __proto__: Parser.prototype,
+
+ /**
+ * Parses workqueue events and sets up state in the importer.
+ */
+ executeStartEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = workqueueExecuteStartRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const kthread = this.importer.getOrCreateKernelThread(
+ eventBase.threadName, pid, pid);
+ kthread.openSliceTS = ts;
+ kthread.openSlice = event[2];
+ return true;
+ },
+
+ executeEndEvent(eventName, cpuNumber, pid, ts, eventBase) {
+ const event = workqueueExecuteEndRE.exec(eventBase.details);
+ if (!event) return false;
+
+ const kthread = this.importer.getOrCreateKernelThread(
+ eventBase.threadName, pid, pid);
+ if (kthread.openSlice) {
+ const slice = new tr.model.ThreadSlice('', kthread.openSlice,
+ ColorScheme.getColorIdForGeneralPurposeString(kthread.openSlice),
+ kthread.openSliceTS,
+ {},
+ ts - kthread.openSliceTS);
+
+ kthread.thread.sliceGroup.pushSlice(slice);
+ }
+ kthread.openSlice = undefined;
+ return true;
+ },
+
+ executeQueueWork(eventName, cpuNumber, pid, ts, eventBase) {
+ // TODO: Do something with this event?
+ return true;
+ },
+
+ executeActivateWork(eventName, cpuNumber, pid, ts, eventBase) {
+ // TODO: Do something with this event?
+ return true;
+ }
+
+ };
+
+ Parser.register(WorkqueueParser);
+
+ return {
+ WorkqueueParser,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/workqueue_parser_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/workqueue_parser_test.html
new file mode 100644
index 00000000000..dfd6646d27f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/linux_perf/workqueue_parser_test.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('workQueueImport', function() {
+ const lines = [
+ ' kworker/0:3-6880 [000] 2784.771958: workqueue_execute_start: ' +
+ 'work struct ffff8800a5083a20: function intel_unpin_work_fn',
+ ' kworker/0:3-6880 [000] 2784.771966: workqueue_execute_end: ' +
+ 'work struct ffff8800a5083a20',
+ ' kworker/1:2-7269 [001] 2784.805966: workqueue_execute_start: ' +
+ 'work struct ffff88014fb0f158: function do_dbs_timer',
+ ' kworker/1:2-7269 [001] 2784.805975: workqueue_execute_end: ' +
+ 'work struct ffff88014fb0f158'
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([lines.join('\n')], {
+ shiftWorldToZero: false
+ });
+ assert.isFalse(m.hasImportWarnings);
+
+ assert.strictEqual(
+ m.processes['6880'].threads['6880'].sliceGroup.length, 1);
+ assert.strictEqual(
+ m.processes['7269'].threads['7269'].sliceGroup.length, 1);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/oboe.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/oboe.html
new file mode 100644
index 00000000000..e9ae74e5e08
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/oboe.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+
+<script>
+'use strict';
+if (tr.isHeadless) {
+ // oboe requires window; there is no window object in headless mode.
+ // Temporarily make one.
+ global.window = {};
+}
+</script>
+
+<script id="oboe-script" src="/oboe/dist/oboe-browser.js"></script>
+
+<script>
+'use strict';
+if (tr.isVinn) {
+ global.oboe = global.window.oboe;
+ global.window = undefined;
+} else if (tr.isNode) {
+ // Use the node.js version instead.
+ global.window = undefined;
+ const path = HTMLImportsLoader.hrefToAbsolutePath('/oboe/dist/oboe-node.js');
+ global.oboe = require(path);
+}
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/pako.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/pako.html
new file mode 100644
index 00000000000..691a55c0217
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/pako.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/base/base.html">
+
+<script src="/pako.min.js"></script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/profiling_dictionary_reader.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/profiling_dictionary_reader.html
new file mode 100644
index 00000000000..ae8c51334e8
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/profiling_dictionary_reader.html
@@ -0,0 +1,315 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.importer', function() {
+ const STRING_ID_SUFFIX = '_sid';
+ const PLURAL_STRING_ID_SUFFIX = '_sids';
+
+ function isStringReference(s) {
+ return s.endsWith(STRING_ID_SUFFIX) || s.endsWith(PLURAL_STRING_ID_SUFFIX);
+ }
+
+ function getStringReferenceName(name) {
+ if (name.endsWith(PLURAL_STRING_ID_SUFFIX)) {
+ return name.slice(0, -PLURAL_STRING_ID_SUFFIX.length);
+ }
+ return name.slice(0, -STRING_ID_SUFFIX.length);
+ }
+
+ function deferenceStrings(idToString, o) {
+ const clone = Object.assign({}, o);
+ for (const [key, value] of Object.entries(clone)) {
+ if (isStringReference(key)) {
+ const name = getStringReferenceName(key);
+ clone[name] = idToString(value);
+ }
+ }
+ return clone;
+ }
+
+ function singularize(word) {
+ if (word.endsWith('s')) {
+ return word.slice(0, -1);
+ }
+ return word;
+ }
+
+ function getMetadataPairs(dataJson) {
+ const isMetadata = v => typeof v !== 'object' || Array.isArray(v);
+ const pairs = Object.entries(dataJson);
+ const metadataPairs = pairs.filter(([_, v]) => isMetadata(v));
+ return metadataPairs;
+ }
+
+ function getGroupPairs(dataJson) {
+ const pairs = Object.entries(dataJson);
+ const nonMapPairs = pairs.filter(([k, _]) => k !== 'maps');
+ const groupPairs = nonMapPairs.filter(([_, v]) => typeof v === 'object');
+ return groupPairs;
+ }
+
+ function createMap(mapJson) {
+ const map = new Map();
+ for (const entry of mapJson) {
+ if (entry.id === undefined) {
+ throw new Error('Missing required key "id" in streaming event.');
+ }
+ map.set(entry.id, entry);
+ }
+ return map;
+ }
+
+ function createMaps(mapsJson) {
+ const maps = new Map();
+ for (const [name, mapJson] of Object.entries(mapsJson)) {
+ maps.set(name, createMap(mapJson));
+ }
+ return maps;
+ }
+
+ function createGroup(groupJson, opt_startTime) {
+ const entries = [];
+ const n = Object.values(groupJson)[0].length;
+
+ for (let i = 0; i < n; i++) {
+ const entry = {};
+ for (const name in groupJson) {
+ entry[name] = groupJson[name][i];
+ }
+ entries.push(entry);
+ }
+
+ const timeDelta = groupJson.timeDelta;
+ if (opt_startTime === undefined && timeDelta !== undefined) {
+ throw new Error('Missing required key "startTime" in streaming event.');
+ }
+
+ if (opt_startTime) {
+ let delta = 0;
+ for (const entry of entries) {
+ delta += entry.timeDelta ? entry.timeDelta : 0;
+ entry.time = opt_startTime + delta;
+ }
+ }
+
+ return entries;
+ }
+
+ function createGroups(groupsJson, opt_startTime) {
+ const groups = new Map();
+ for (const [name, groupJson] of Object.entries(groupsJson)) {
+ groups.set(name, createGroup(groupJson, opt_startTime));
+ }
+
+ return groups;
+ }
+
+ function createMetadata(metadataPairs) {
+ const metadata = new Map();
+ for (const [name, value] of metadataPairs) {
+ metadata.set(name, value);
+ }
+ if (metadata.get('version') === undefined) {
+ throw new Error('Missing required key "version" in streaming event.');
+ }
+ return metadata;
+ }
+
+ /**
+ * Extracts data from a profiling dictionary. See goo.gl/R0Ae4f.
+ *
+ * A profiling dictionary is a compressed format that is good for recording
+ * sampling data. ProfilingDictionaryReader unpacks that data. To use the
+ * ProfilingDictionaryReader first create an 'empty' reader using .empty()
+ * then call #expandData(data) on your dictionary or the helper:
+ * #expandEvent(event) on a tracing event containing the profiling dictionary.
+ * ProfilingDictionaryReader is an immutable data structure so these methods
+ * don't modify the ProfilingDictionaryReader instead they return new
+ * ProfilingDictionaryReaders which wrap the data you passed. To access the
+ * unpacked data use the #inflated property and the #getMapValue() method.
+ *
+ * Usage example, given input like:
+ * $ let input = {
+ * version: 1,
+ * allocators: {
+ * books: {
+ * authors: [1, 1, 2],
+ * title_sid: [10, 11, 12],
+ * },
+ * },
+ * maps: {
+ * authors: [
+ * { id: 1, name_sid: 1 },
+ * { id: 2, name_sid: 2 },
+ * ],
+ * strings: [
+ * { id: 1, string: 'DFW' },
+ * { id: 2, string: 'C. Stross' },
+ * { id: 10, string: 'Book A' },
+ * { id: 11, string: 'Book B' },
+ * { id: 12, string: 'Book C' },
+ * ],
+ * }
+ * };
+ * We can create an empty reader:
+ * $ let reader = ProfilingDictionaryReader.empty();
+ * Then read in the input:
+ * $ reader = reader.expandData(input);
+ * Then view the expanded data:
+ * $ console.log(reader.inflated);
+ * {
+ * books: [
+ * { author: { id: 1, name: 'DFW' }, title: "Book A", },
+ * { author: { id: 2, name: 'C. Stross' }, title: "Book B", },
+ * { author: { id: 2, name: 'C. Stross' }, title: "Book C", },
+ * ],
+ * }
+ *
+ */
+ class ProfilingDictionaryReader {
+ constructor(opt_metadata, opt_maps, opt_groups, opt_parent) {
+ this.metadata = opt_metadata || new Map();
+ this.maps = opt_maps || new Map();
+ this.groups = opt_groups || new Map();
+ this.parent_ = opt_parent || undefined;
+ this.inflated_ = undefined;
+ this.raw_ = undefined;
+ this.boundGetString_ = this.getString.bind(this);
+ this.deferenceStrings_ = o => deferenceStrings(this.boundGetString_, o);
+ }
+
+ /**
+ * Creates an empty ProfilingDictionaryReader.
+ */
+ static empty() {
+ return new ProfilingDictionaryReader();
+ }
+
+ /**
+ * Returns the parent or null if this is the root ProfilingDictionaryReader.
+ */
+ get parent() {
+ return this.parent_;
+ }
+
+ get raw() {
+ if (this.raw_) return this.raw_;
+ this.raw_ = {};
+ for (const [name, group] of this.groups.entries()) {
+ this.raw_[name] = group;
+ }
+ return this.raw_;
+ }
+
+ get inflated() {
+ if (this.inflated_) return this.inflated_;
+ this.inflated_ = {};
+ for (const [name, group] of this.groups.entries()) {
+ this.inflated_[name] = this.inflateGroup(group);
+ }
+ return this.inflated_;
+ }
+
+ /**
+ * Get a map from the newest event by name.
+ * If no map with that name was present returns an empty Map.
+ */
+ getNewMap(name) {
+ return this.maps.get(name) || new Map();
+ }
+
+ /**
+ * Get a record with the id |id| from the map with name |mapName|.
+ * This method searches through the expanded events in reverse order of
+ * expansion until it finds a matching value. If no value matches returns
+ * undefined.
+ */
+ getMapValue(mapName, id) {
+ let value = this.getNewMap(mapName).get(id);
+ if (value === undefined && this.parent) {
+ value = this.parent.getMapValue(mapName, id);
+ }
+ return value;
+ }
+
+ /**
+ * Get the string with the id |id|.
+ * This method searches through the expanded events in reverse order of
+ * expansion until it finds a string with the matching id. If there is no
+ * matching string with returns undefined.
+ */
+ getString(id) {
+ const value = this.getMapValue('strings', id);
+ if (value === undefined) return undefined;
+ return value.string;
+ }
+
+ /**
+ * True iff this or any parent has a map with name |name|.
+ */
+ hasMap(name) {
+ if (this.maps.has(name)) return true;
+ if (this.parent === undefined) return false;
+ return this.parent.hasMap(name);
+ }
+
+ inflateGroup(group) {
+ return group.map(this.inflateEntry.bind(this));
+ }
+
+ inflateEntry(entry) {
+ const inflatedEntry = {};
+ for (const [name, value] of Object.entries(entry)) {
+ let inflatedValue;
+ if (this.hasMap(name)) {
+ const id = value;
+ inflatedValue = this.deferenceStrings_(this.getMapValue(name, id));
+ } else {
+ inflatedValue = value;
+ }
+ inflatedEntry[singularize(name)] = inflatedValue;
+ }
+ return this.deferenceStrings_(inflatedEntry);
+ }
+
+ /**
+ * Returns a new ProfilingDictionaryReader with this
+ * ProfilingDictionaryReader as its parent and the fields 'maps', 'groups'
+ * and 'metadata' filled in based on |data|.
+ */
+ expandData(data) {
+ const mapsJson = data.maps || {};
+ const groupsJson = data.allocators || {};
+ const metadataPairs = getMetadataPairs(data);
+ const metadata = createMetadata(metadataPairs);
+ const opt_startTime = metadata.get('startTime');
+ const maps = createMaps(mapsJson);
+ const groups = createGroups(groupsJson, opt_startTime);
+ return new ProfilingDictionaryReader(metadata, maps, groups, this);
+ }
+
+ /**
+ * Convenience method for this.expandData(event.args.data).
+ */
+ expandEvent(event) {
+ return this.expandData(event.args.data);
+ }
+ }
+
+ return {
+ ProfilingDictionaryReader,
+ singularize,
+ deferenceStringsForTest: deferenceStrings,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/profiling_dictionary_reader_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/profiling_dictionary_reader_test.html
new file mode 100644
index 00000000000..fb622e1fac9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/profiling_dictionary_reader_test.html
@@ -0,0 +1,275 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/profiling_dictionary_reader.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function makeEvent(json) {
+ return {
+ args: {
+ data: json
+ },
+ };
+ }
+
+ test('deferenceStrings', function() {
+ const stringToId = id => '' + id;
+ const result = tr.e.importer.deferenceStringsForTest(stringToId, {
+ foo: 'foo',
+ one_bar_sid: 1,
+ many_baz_sids: 2,
+ });
+ assert.deepEqual(result, {
+ foo: 'foo',
+ one_bar_sid: 1,
+ many_baz_sids: 2,
+ one_bar: '1',
+ many_baz: '2',
+ });
+ });
+
+ test('singularize', function() {
+ assert.strictEqual('name', tr.e.importer.singularize('names'));
+ assert.strictEqual('aircraft', tr.e.importer.singularize('aircraft'));
+ });
+
+ test('throws_an_error_if_version_key_missing', function() {
+ const data = {};
+ const expander = new tr.e.importer.ProfilingDictionaryReader();
+ assert.throws(() => expander.expandData(data));
+ });
+
+ test('throws_an_error_if_map_entry_is_missing_an_id', function() {
+ const data = {
+ maps: {
+ version: 1,
+ price: [
+ { 'i_dont_have_an_id_key': 100 },
+ ],
+ },
+ };
+ const expander = new tr.e.importer.ProfilingDictionaryReader();
+ assert.throws(() => expander.expandData(data));
+ });
+
+ test('has_an_empty_constructor_to_create_an_empty_reader', function() {
+ const expander = tr.e.importer.ProfilingDictionaryReader.empty();
+ assert.strictEqual(expander.metadata.size, 0);
+ assert.deepEqual(expander.raw, {});
+ assert.deepEqual(expander.inflated, {});
+ });
+
+ test('reads_extra_metadata', function() {
+ const data = {
+ version: 1,
+ myStringMetadata: '1',
+ myNumberMetadata: 2,
+ myArrayMetadata: [3],
+ };
+ const expander1 = new tr.e.importer.ProfilingDictionaryReader();
+ const expander2 = expander1.expandData(data);
+ assert.deepEqual(expander2.metadata.get('myStringMetadata'), '1');
+ assert.deepEqual(expander2.metadata.get('myNumberMetadata'), 2);
+ assert.deepEqual(expander2.metadata.get('myArrayMetadata'), [3]);
+ });
+
+ test('reads_a_single_event_with_single_group_and_single_row', function() {
+ const data = {
+ version: 1,
+ allocators: {
+ books: {
+ page_counts: [10, 20, 30],
+ },
+ },
+ };
+ const expander1 = new tr.e.importer.ProfilingDictionaryReader();
+ const expander2 = expander1.expandData(data);
+ assert.deepEqual(expander2.raw.books, [
+ { page_counts: 10 },
+ { page_counts: 20 },
+ { page_counts: 30 },
+ ]);
+ // Note that the 'inflated' property singularizes key names.
+ assert.deepEqual(expander2.inflated.books, [
+ { page_count: 10 },
+ { page_count: 20 },
+ { page_count: 30 },
+ ]);
+ });
+
+ test('supports_persistant_maps', function() {
+ const expander1 = new tr.e.importer.ProfilingDictionaryReader();
+ const expander2 = expander1.expandData({
+ version: 1,
+ maps: {
+ authors: [{ id: 0, name: 'DE Knuth' }],
+ },
+ });
+ const expander3 = expander2.expandData({
+ version: 1,
+ maps: {
+ authors: [{ id: 1, name: 'JH Morris' }],
+ papers: [{ id: 0, title: 'Literate programming', citations: 1378 }],
+ },
+ });
+ assert.isTrue(expander3.hasMap('authors'));
+ assert.isTrue(expander3.hasMap('papers'));
+ assert.isFalse(expander3.hasMap('books'));
+ assert.strictEqual(expander3.getMapValue('authors', 0).name, 'DE Knuth');
+ assert.strictEqual(expander3.getMapValue('authors', 1).name, 'JH Morris');
+ assert.strictEqual(expander3.getMapValue('papers', 0).citations, 1378);
+ assert.strictEqual(expander3.getMapValue('papers', 1), undefined);
+
+ assert.strictEqual(expander2.getMapValue('authors', 0).name, 'DE Knuth');
+ assert.strictEqual(expander2.getMapValue('authors', 1), undefined);
+ });
+
+ test('has_special_support_for_start_time', function() {
+ const expander1 = new tr.e.importer.ProfilingDictionaryReader();
+ const expander2 = expander1.expandData({
+ version: 1,
+ startTime: 100,
+ allocators: {
+ sales: {
+ price: [1, 2, 3, 4],
+ },
+ },
+ });
+ assert.deepEqual(expander2.inflated.sales, [
+ { time: 100, price: 1 },
+ { time: 100, price: 2 },
+ { time: 100, price: 3 },
+ { time: 100, price: 4 },
+ ]);
+ });
+
+ test('has_special_support_for_time_deltas', function() {
+ const expander1 = new tr.e.importer.ProfilingDictionaryReader();
+ const expander2 = expander1.expandData({
+ version: 1,
+ startTime: 100,
+ allocators: {
+ sales: {
+ timeDelta: [1, 1, 10, 10],
+ price: [1, 2, 3, 4],
+ },
+ },
+ });
+ assert.deepEqual(expander2.inflated.sales, [
+ { time: 101, timeDelta: 1, price: 1 },
+ { time: 102, timeDelta: 1, price: 2 },
+ { time: 112, timeDelta: 10, price: 3 },
+ { time: 122, timeDelta: 10, price: 4 },
+ ]);
+ });
+
+ test('has_special_support_for_strings', function() {
+ const expander1 = new tr.e.importer.ProfilingDictionaryReader();
+ const expander2 = expander1.expandData({
+ version: 1,
+ allocators: {
+ books: {
+ authors: [1, 1, 2, 3],
+ isbn: [100, 101, 102, 103],
+ title_sid: [10, 11, 12, 13],
+ },
+ },
+ maps: {
+ authors: [
+ { id: 1, name_sid: 1 },
+ { id: 2, name_sid: 1 },
+ { id: 3, name_sid: 2 },
+ ],
+ strings: [
+ { id: 1, string: 'DFW' },
+ { id: 2, string: 'C. Stross' },
+ { id: 2, string: 'C. Stross' },
+ { id: 10, string: 'Infinite Jest' },
+ { id: 11, string: 'The Laundry Files' },
+ { id: 12, string: 'Accelerando' },
+ { id: 13, string: 'Halting State' },
+ ],
+ },
+ });
+
+ assert.strictEqual(expander2.inflated.books[0].author.name, 'DFW');
+ assert.strictEqual(expander2.inflated.books[0].isbn, 100);
+ assert.strictEqual(expander2.inflated.books[0].title, 'Infinite Jest');
+ });
+
+ test('can_parse_events_directly', function() {
+ const data = {
+ version: 1,
+ allocators: {
+ books: {
+ pages: [10, 20, 30],
+ },
+ },
+ };
+ const expander1 = new tr.e.importer.ProfilingDictionaryReader();
+ const expander2 = expander1.expandEvent(makeEvent(data));
+ assert.deepEqual(expander2.raw.books, [
+ { pages: 10 },
+ { pages: 20 },
+ { pages: 30 },
+ ]);
+ });
+
+ test('full_example', function() {
+ let reader = new tr.e.importer.ProfilingDictionaryReader();
+ reader = reader.expandData({
+ version: 1,
+ maps: {
+ authors: [
+ { id: 1, name_sid: 1 },
+ ],
+ strings: [
+ { id: 1, string: 'DFW' },
+ { id: 10, string: 'Book A' },
+ { id: 11, string: 'Book B' },
+ ],
+ }
+ });
+ reader = reader.expandData({
+ version: 1,
+ startTime: 1992,
+ allocators: {
+ books: {
+ timeDelta: [1, 0, 2, 3],
+ authors: [1, 1, 2, 2],
+ isbns: [100, 101, 102, 103],
+ title_sid: [10, 11, 12, 13],
+ },
+ papers: {
+ authors: [1, 1, 2, 2],
+ },
+ },
+ maps: {
+ authors: [
+ { id: 2, name_sid: 2 },
+ ],
+ strings: [
+ { id: 2, string: 'C. Stross' },
+ { id: 12, string: 'Book C' },
+ { id: 13, string: 'Book D' },
+ ],
+ }
+ });
+
+ assert.strictEqual(reader.getMapValue('strings', 1).string, 'DFW');
+ assert.strictEqual(reader.getString(2), 'C. Stross');
+ assert.strictEqual(reader.inflated.books[0].author.name, 'DFW');
+ assert.strictEqual(reader.inflated.books[0].isbn, 100);
+ assert.strictEqual(reader.inflated.books[0].title, 'Book A');
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/trace2html_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace2html_importer.html
new file mode 100644
index 00000000000..270db638f66
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace2html_importer.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base64.html">
+<link rel="import" href="/tracing/base/trace_stream.html">
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/importer/simple_line_reader.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.importer', function() {
+ function Trace2HTMLImporter(model, events) {
+ this.importPriority = 0;
+ }
+
+ Trace2HTMLImporter.subtraces_ = [];
+
+ function _extractEventsFromHTML(text) {
+ // Clear the array before pushing data to it.
+ Trace2HTMLImporter.subtraces_ = [];
+
+ const r = new tr.importer.SimpleLineReader(text);
+
+ // Try to find viewer-data...
+ while (true) {
+ if (!r.advanceToLineMatching(
+ new RegExp('^<\s*script id="viewer-data" ' +
+ 'type="(application\/json|text\/plain)">\r?$'))) {
+ break;
+ }
+
+ r.beginSavingLines();
+ if (!r.advanceToLineMatching(/^<\/\s*script>\r?$/)) return;
+
+ let rawEvents = r.endSavingLinesAndGetResult();
+
+ // Drop off first and last event as it contains the end script tag.
+ rawEvents = rawEvents.slice(1, rawEvents.length - 1);
+ const data64 = rawEvents.join('\n');
+ const buffer = new ArrayBuffer(
+ tr.b.Base64.getDecodedBufferLength(data64));
+ const len = tr.b.Base64.DecodeToTypedArray(data64, new DataView(buffer));
+ Trace2HTMLImporter.subtraces_.push(buffer.slice(0, len));
+ }
+ }
+
+ function _canImportFromHTML(text) {
+ if (!/^<!DOCTYPE html>/.test(text)) return false;
+
+ // Try to find viewer-data...
+ _extractEventsFromHTML(text);
+ if (Trace2HTMLImporter.subtraces_.length === 0) return false;
+ return true;
+ }
+
+ Trace2HTMLImporter.canImport = function(events) {
+ if (events instanceof tr.b.TraceStream) return false;
+ return _canImportFromHTML(events);
+ };
+
+ Trace2HTMLImporter.prototype = {
+ __proto__: tr.importer.Importer.prototype,
+
+ get importerName() {
+ return 'Trace2HTMLImporter';
+ },
+
+ isTraceDataContainer() {
+ return true;
+ },
+
+ extractSubtraces() {
+ return Trace2HTMLImporter.subtraces_;
+ },
+
+ importEvents() {
+ }
+ };
+
+
+ tr.importer.Importer.register(Trace2HTMLImporter);
+
+
+ return {
+ Trace2HTMLImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/trace2html_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace2html_importer_test.html
new file mode 100644
index 00000000000..9e268b6e7b1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace2html_importer_test.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="improt" href="/tracing/base/base64.html">
+<link rel="import" href="/tracing/extras/importer/trace2html_importer.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Base64 = tr.b.Base64;
+
+ test('simple', function() {
+ const htmlLines = [
+ '<!DOCTYPE html>',
+ '<script id="viewer-data" type="application/json">',
+ Base64.btoa('hello'),
+ '<\/script>',
+ '<script id="viewer-data" type="text/plain">',
+ Base64.btoa('world'),
+ '<\/script>',
+ '</html>'
+ ];
+ const htmlText = htmlLines.join('\n');
+ assert.isTrue(tr.e.importer.Trace2HTMLImporter.canImport(htmlText));
+
+ const m = new tr.Model();
+ const imp = new tr.e.importer.Trace2HTMLImporter(m, htmlText);
+ const subTracesAsBuffers = imp.extractSubtraces();
+ const subTracesAsStrings = subTracesAsBuffers.map(function(buffer) {
+ let str = '';
+ const ary = new Uint8Array(buffer);
+ for (let i = 0; i < ary.length; i++) {
+ str += String.fromCharCode(ary[i]);
+ }
+ return str;
+ });
+ assert.deepEqual(subTracesAsStrings, ['hello', 'world']);
+ });
+
+ test('windowsLineEndings', function() {
+ const htmlLines = [
+ '<!DOCTYPE html>',
+ '<script id="viewer-data" type="application/json">',
+ Base64.btoa('hello'),
+ '<\/script>',
+ '<script id="viewer-data" type="text/plain">',
+ Base64.btoa('world'),
+ '<\/script>',
+ '</html>'
+ ];
+ const htmlText = htmlLines.join('\r\n');
+ assert.isTrue(tr.e.importer.Trace2HTMLImporter.canImport(htmlText));
+
+ const m = new tr.Model();
+ const imp = new tr.e.importer.Trace2HTMLImporter(m, htmlText);
+ const subTracesAsBuffers = imp.extractSubtraces();
+ const subTracesAsStrings = subTracesAsBuffers.map(function(buffer) {
+ let str = '';
+ const ary = new Uint8Array(buffer);
+ for (let i = 0; i < ary.length; i++) {
+ str += String.fromCharCode(ary[i]);
+ }
+ return str;
+ });
+ assert.deepEqual(subTracesAsStrings, ['hello', 'world']);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_code_entry.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_code_entry.html
new file mode 100644
index 00000000000..56201a0d2c0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_code_entry.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/model/source_info/js_source_info.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview TraceCodeEntry is a wrapper around the V8 CodeEntry that
+ * extracts extra context information for each item. This includes things like
+ * the source file, line and if the function is a native method or not.
+ */
+tr.exportTo('tr.e.importer', function() {
+ function TraceCodeEntry(address, size, name, scriptId) {
+ this.id_ = tr.b.GUID.allocateSimple();
+ this.address_ = address;
+ this.size_ = size;
+
+ // Stolen from DevTools TimelineJSProfileProcessor._buildCallFrame
+ // Code states:
+ // (empty) -> compiled
+ // ~ -> optimizable
+ // * -> optimized
+ const rePrefix = /^(\w*:)?([*~]?)(.*)$/m;
+ const tokens = rePrefix.exec(name);
+ const prefix = tokens[1];
+ let state = tokens[2];
+ const body = tokens[3];
+
+ if (state === '*') {
+ state = tr.model.source_info.JSSourceState.OPTIMIZED;
+ } else if (state === '~') {
+ state = tr.model.source_info.JSSourceState.OPTIMIZABLE;
+ } else if (state === '') {
+ state = tr.model.source_info.JSSourceState.COMPILED;
+ } else {
+ state = tr.model.source_info.JSSourceState.UNKNOWN;
+ }
+
+ let rawName;
+ let rawUrl;
+ if (prefix === 'Script:') {
+ rawName = '';
+ rawUrl = body;
+ } else {
+ const spacePos = body.lastIndexOf(' ');
+ rawName = spacePos !== -1 ? body.substr(0, spacePos) : body;
+ rawUrl = spacePos !== -1 ? body.substr(spacePos + 1) : '';
+ }
+
+ function splitLineAndColumn(url) {
+ const lineColumnRegEx = /(?::(\d+))?(?::(\d+))?$/;
+ const lineColumnMatch = lineColumnRegEx.exec(url);
+ let lineNumber;
+ let columnNumber;
+
+ if (typeof(lineColumnMatch[1]) === 'string') {
+ lineNumber = parseInt(lineColumnMatch[1], 10);
+ // Immediately convert line and column to 0-based numbers.
+ lineNumber = isNaN(lineNumber) ? undefined : lineNumber - 1;
+ }
+ if (typeof(lineColumnMatch[2]) === 'string') {
+ columnNumber = parseInt(lineColumnMatch[2], 10);
+ columnNumber = isNaN(columnNumber) ? undefined : columnNumber - 1;
+ }
+
+ return {
+ url: url.substring(0, url.length - lineColumnMatch[0].length),
+ lineNumber,
+ columnNumber
+ };
+ }
+
+ const nativeSuffix = ' native';
+ const isNative = rawName.endsWith(nativeSuffix);
+ this.name_ =
+ isNative ? rawName.slice(0, -nativeSuffix.length) : rawName;
+
+ const urlData = splitLineAndColumn(rawUrl);
+ const url = urlData.url || '';
+ const line = urlData.lineNumber || 0;
+ const column = urlData.columnNumber || 0;
+
+ this.sourceInfo_ = new tr.model.source_info.JSSourceInfo(
+ url, line, column, isNative, scriptId, state);
+ }
+
+ TraceCodeEntry.prototype = {
+ get id() {
+ return this.id_;
+ },
+
+ get sourceInfo() {
+ return this.sourceInfo_;
+ },
+
+ get name() {
+ return this.name_;
+ },
+
+ set address(address) {
+ this.address_ = address;
+ },
+
+ get address() {
+ return this.address_;
+ },
+
+ set size(size) {
+ this.size_ = size;
+ },
+
+ get size() {
+ return this.size_;
+ }
+ };
+
+ return {
+ TraceCodeEntry,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_code_entry_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_code_entry_test.html
new file mode 100644
index 00000000000..422350f04ba
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_code_entry_test.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/trace_code_entry.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('lazy_compile_method', function() {
+ const tce = new tr.e.importer.TraceCodeEntry(
+ '0x123', 10, 'Handler:timeStamp', 12);
+ assert.strictEqual(tce.size, 10);
+ assert.strictEqual(tce.name, 'timeStamp');
+ assert.strictEqual(tce.sourceInfo.isNative, false);
+ assert.strictEqual(tce.sourceInfo.file, '');
+ assert.strictEqual(tce.sourceInfo.line, -1);
+ assert.strictEqual(tce.sourceInfo.scriptId, 12);
+ });
+
+ test('non_lazy_compile_method', function() {
+ const tce = new tr.e.importer.TraceCodeEntry(
+ '0x123', 10, 'Handler:timeStamp', 12);
+ assert.strictEqual(tce.size, 10);
+ assert.strictEqual(tce.name, 'timeStamp');
+ assert.strictEqual(tce.sourceInfo.state, 'compiled');
+ assert.strictEqual(tce.sourceInfo.isNative, false);
+ assert.strictEqual(tce.sourceInfo.file, '');
+ assert.strictEqual(tce.sourceInfo.line, -1);
+ assert.strictEqual(tce.sourceInfo.scriptId, 12);
+ });
+
+ test('native_matching', function() {
+ const tce = new tr.e.importer.TraceCodeEntry('0x123', 10,
+ 'LazyCompile:~IsAccessorDescriptor native v8natives.js:183', 12);
+ assert.strictEqual(tce.size, 10);
+ assert.strictEqual(tce.name, 'IsAccessorDescriptor');
+ assert.strictEqual(tce.sourceInfo.isNative, true);
+ assert.strictEqual(tce.sourceInfo.state, 'optimizable');
+ assert.strictEqual(tce.sourceInfo.file, 'v8natives.js');
+ assert.strictEqual(tce.sourceInfo.line, 182);
+ assert.strictEqual(tce.sourceInfo.scriptId, 12);
+ });
+
+ test('strips_*_from_name', function() {
+ const tce = new tr.e.importer.TraceCodeEntry('0x123', 10,
+ 'LazyCompile:*IsAccessorDescriptor native v8natives.js:183', 12);
+ assert.strictEqual(tce.size, 10);
+ assert.strictEqual(tce.name, 'IsAccessorDescriptor');
+ assert.strictEqual(tce.sourceInfo.isNative, true);
+ assert.strictEqual(tce.sourceInfo.state, 'optimized');
+ assert.strictEqual(tce.sourceInfo.file, 'v8natives.js');
+ assert.strictEqual(tce.sourceInfo.line, 182);
+ assert.strictEqual(tce.sourceInfo.scriptId, 12);
+ });
+
+ test('non_native_matching', function() {
+ const tce = new tr.e.importer.TraceCodeEntry('0x123', 10,
+ 'LazyCompile:~IsAccessorDescriptor v8natives.js:183', 12);
+ assert.strictEqual(tce.size, 10);
+ assert.strictEqual(tce.name, 'IsAccessorDescriptor');
+ assert.strictEqual(tce.sourceInfo.isNative, false);
+ assert.strictEqual(tce.sourceInfo.file, 'v8natives.js');
+ assert.strictEqual(tce.sourceInfo.line, 182);
+ assert.strictEqual(tce.sourceInfo.scriptId, 12);
+ });
+
+ test('lazy_compile_without_script', function() {
+ const tce = new tr.e.importer.TraceCodeEntry(
+ '0x123', 10, 'LazyCompile:~Object', 12);
+ assert.strictEqual(tce.size, 10);
+ assert.strictEqual(tce.name, 'Object');
+ assert.strictEqual(tce.sourceInfo.isNative, false);
+ assert.strictEqual(tce.sourceInfo.file, '');
+ assert.strictEqual(tce.sourceInfo.line, -1);
+ assert.strictEqual(tce.sourceInfo.scriptId, 12);
+ });
+
+ test('line_matching_without_script', function() {
+ const tce = new tr.e.importer.TraceCodeEntry('0x123', 10,
+ 'LazyCompile:~Object :220', 12);
+ assert.strictEqual(tce.size, 10);
+ assert.strictEqual(tce.name, 'Object');
+ assert.strictEqual(tce.sourceInfo.isNative, false);
+ assert.strictEqual(tce.sourceInfo.file, '');
+ assert.strictEqual(tce.sourceInfo.line, 219);
+ assert.strictEqual(tce.sourceInfo.scriptId, 12);
+ });
+
+ test('unknown_method_name', function() {
+ let tce = new tr.e.importer.TraceCodeEntry('0x123', 10, 'LazyCompile:', 12);
+ assert.strictEqual(tce.size, 10);
+ assert.strictEqual(tce.name, '');
+ assert.strictEqual(tce.sourceInfo.isNative, false);
+ assert.strictEqual(tce.sourceInfo.file, '');
+ assert.strictEqual(tce.sourceInfo.line, -1);
+ assert.strictEqual(tce.sourceInfo.scriptId, 12);
+
+ tce = new tr.e.importer.TraceCodeEntry(
+ '0x123', 10, 'LazyCompile:~ :37', 12);
+ assert.strictEqual(tce.size, 10);
+ assert.strictEqual(tce.name, '');
+ assert.strictEqual(tce.sourceInfo.isNative, false);
+ assert.strictEqual(tce.sourceInfo.file, '');
+ assert.strictEqual(tce.sourceInfo.line, 36);
+ assert.strictEqual(tce.sourceInfo.scriptId, 12);
+
+ tce = new tr.e.importer.TraceCodeEntry('0x123', 10,
+ 'LazyCompile:~ native liveedit.js:37:10', 12);
+ assert.strictEqual(tce.size, 10);
+ assert.strictEqual(tce.name, '');
+ assert.strictEqual(tce.sourceInfo.isNative, true);
+ assert.strictEqual(tce.sourceInfo.file, 'liveedit.js');
+ assert.strictEqual(tce.sourceInfo.line, 36);
+ assert.strictEqual(tce.sourceInfo.column, 9);
+ assert.strictEqual(tce.sourceInfo.scriptId, 12);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_code_map.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_code_map.html
new file mode 100644
index 00000000000..b66df9155de
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_code_map.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel='import' href='/tracing/extras/importer/trace_code_entry.html'>
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.importer', function() {
+ // This code is a tracification of:
+ // devtools/front_end/timeline/TimelineJSProfile.js
+ function TraceCodeMap() {
+ this.banks_ = new Map();
+ }
+
+ TraceCodeMap.prototype = {
+ addEntry(addressHex, size, name, scriptId) {
+ const entry = new tr.e.importer.TraceCodeEntry(
+ this.getAddress_(addressHex), size, name, scriptId);
+
+ this.addEntry_(addressHex, entry);
+ },
+
+ moveEntry(oldAddressHex, newAddressHex, size) {
+ const entry = this.getBank_(oldAddressHex)
+ .removeEntry(this.getAddress_(oldAddressHex));
+ if (!entry) return;
+
+ entry.address = this.getAddress_(newAddressHex);
+ entry.size = size;
+ this.addEntry_(newAddressHex, entry);
+ },
+
+ lookupEntry(addressHex) {
+ return this.getBank_(addressHex)
+ .lookupEntry(this.getAddress_(addressHex));
+ },
+
+ addEntry_(addressHex, entry) {
+ // FIXME: Handle bank spanning addresses ...
+ this.getBank_(addressHex).addEntry(entry);
+ },
+
+ getAddress_(addressHex) {
+ // 13 hex digits === 52 bits, double mantissa fits 53 bits.
+ const bankSizeHexDigits = 13;
+ addressHex = addressHex.slice(2); // cut 0x prefix.
+ return parseInt(addressHex.slice(-bankSizeHexDigits), 16);
+ },
+
+ getBank_(addressHex) {
+ addressHex = addressHex.slice(2); // cut 0x prefix.
+
+ // 13 hex digits === 52 bits, double mantissa fits 53 bits.
+ const bankSizeHexDigits = 13;
+ const maxHexDigits = 16;
+ const bankName = addressHex.slice(-maxHexDigits, -bankSizeHexDigits);
+ let bank = this.banks_.get(bankName);
+ if (!bank) {
+ bank = new TraceCodeBank();
+ this.banks_.set(bankName, bank);
+ }
+ return bank;
+ }
+ };
+
+ function TraceCodeBank() {
+ this.entries_ = [];
+ }
+
+ TraceCodeBank.prototype = {
+ removeEntry(address) {
+ // findLowIndexInSortedArray returns 1 for empty. Just handle the
+ // empty list and bail early.
+ if (this.entries_.length === 0) return undefined;
+
+ const index = tr.b.findLowIndexInSortedArray(
+ this.entries_, function(entry) { return entry.address; }, address);
+ const entry = this.entries_[index];
+ if (!entry || entry.address !== address) return undefined;
+
+ this.entries_.splice(index, 1);
+ return entry;
+ },
+
+ lookupEntry(address) {
+ const index = tr.b.findFirstTrueIndexInSortedArray(
+ this.entries_, e => (address < e.address)) - 1;
+ const entry = this.entries_[index];
+ return entry &&
+ address < entry.address + entry.size ? entry : undefined;
+ },
+
+ addEntry(newEntry) {
+ // findLowIndexInSortedArray returns 1 for empty list. Just push the
+ // new address as it's the only item.
+ if (this.entries_.length === 0) {
+ this.entries_.push(newEntry);
+ }
+
+ const endAddress = newEntry.address + newEntry.size;
+ const lastIndex = tr.b.findLowIndexInSortedArray(
+ this.entries_, function(entry) { return entry.address; }, endAddress);
+ let index;
+ for (index = lastIndex - 1; index >= 0; --index) {
+ const entry = this.entries_[index];
+ const entryEndAddress = entry.address + entry.size;
+ if (entryEndAddress <= newEntry.address) break;
+ }
+ ++index;
+ this.entries_.splice(index, lastIndex - index, newEntry);
+ }
+ };
+
+ return {
+ TraceCodeMap,
+ };
+});
+
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer.html
new file mode 100644
index 00000000000..2c9a84edfe4
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer.html
@@ -0,0 +1,3394 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base64.html">
+<link rel="import" href="/tracing/base/color_scheme.html">
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/base/scalar.html">
+<link rel="import" href="/tracing/base/trace_stream.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/extras/importer/heap_dump_trace_event_importer.html">
+<link rel="import" href="/tracing/extras/importer/legacy_heap_dump_trace_event_importer.html">
+<link rel="import" href="/tracing/extras/importer/oboe.html">
+<link rel="import" href="/tracing/extras/importer/profiling_dictionary_reader.html">
+<link rel="import" href="/tracing/extras/importer/trace_code_entry.html">
+<link rel="import" href="/tracing/extras/importer/trace_code_map.html">
+<link rel="import" href="/tracing/extras/importer/v8/codemap.html">
+<link rel="import" href="/tracing/extras/measure/measure_async_slice.html">
+<link rel="import" href="/tracing/importer/context_processor.html">
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/model/comment_box_annotation.html">
+<link rel="import" href="/tracing/model/constants.html">
+<link rel="import" href="/tracing/model/container_memory_dump.html">
+<link rel="import" href="/tracing/model/counter_series.html">
+<link rel="import" href="/tracing/model/flow_event.html">
+<link rel="import" href="/tracing/model/global_memory_dump.html">
+<link rel="import" href="/tracing/model/instant_event.html">
+<link rel="import" href="/tracing/model/memory_allocator_dump.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/process_memory_dump.html">
+<link rel="import" href="/tracing/model/profile_node.html">
+<link rel="import" href="/tracing/model/profile_tree.html">
+<link rel="import" href="/tracing/model/rect_annotation.html">
+<link rel="import" href="/tracing/model/scoped_id.html">
+<link rel="import" href="/tracing/model/slice_group.html">
+<link rel="import" href="/tracing/model/vm_region.html">
+<link rel="import" href="/tracing/model/x_marker_annotation.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview TraceEventImporter imports TraceEvent-formatted data
+ * into the provided model.
+ */
+tr.exportTo('tr.e.importer', function() {
+ const Base64 = tr.b.Base64;
+ const deepCopy = tr.b.deepCopy;
+ const ColorScheme = tr.b.ColorScheme;
+ const HeapDumpTraceEventImporter = tr.e.importer.HeapDumpTraceEventImporter;
+ const LegacyHeapDumpTraceEventImporter =
+ tr.e.importer.LegacyHeapDumpTraceEventImporter;
+ const StreamingEventExpander = tr.e.importer.StreamingEventExpander;
+ const ProfilingDictionaryReader = tr.e.importer.ProfilingDictionaryReader;
+ const MEASURE_NAME_REGEX = tr.e.measure.MEASURE_NAME_REGEX;
+
+ function getEventColor(event, opt_customName) {
+ if (event.cname) {
+ return ColorScheme.getColorIdForReservedName(event.cname);
+ } else if (opt_customName || event.name) {
+ return ColorScheme.getColorIdForGeneralPurposeString(
+ opt_customName || event.name);
+ }
+ }
+
+ function isLegacyChromeClockSyncEvent(event) {
+ return event.name !== undefined &&
+ event.name.startsWith(LEGACY_CHROME_CLOCK_SYNC_EVENT_NAME_PREFIX) &&
+ ((event.ph === 'S') || (event.ph === 'F'));
+ }
+
+ const PRODUCER = 'producer';
+ const CONSUMER = 'consumer';
+ const STEP = 'step';
+
+ const BACKGROUND = tr.model.ContainerMemoryDump.LevelOfDetail.BACKGROUND;
+ const LIGHT = tr.model.ContainerMemoryDump.LevelOfDetail.LIGHT;
+ const DETAILED = tr.model.ContainerMemoryDump.LevelOfDetail.DETAILED;
+ const MEMORY_DUMP_LEVEL_OF_DETAIL_ORDER = [undefined, BACKGROUND, LIGHT,
+ DETAILED];
+
+ const GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX = 'global/';
+
+ const LEGACY_CHROME_CLOCK_SYNC_EVENT_NAME_PREFIX = 'ClockSyncEvent.';
+
+ // Map from raw memory dump byte stat names to model byte stat names. See
+ // //base/trace_event/process_memory_maps.cc in Chromium.
+ const BYTE_STAT_NAME_MAP = {
+ 'pc': 'privateCleanResident',
+ 'pd': 'privateDirtyResident',
+ 'sc': 'sharedCleanResident',
+ 'sd': 'sharedDirtyResident',
+ 'pss': 'proportionalResident',
+ 'sw': 'swapped'
+ };
+
+ // See tr.model.MemoryAllocatorDump 'weak' field and
+ // base::trace_event::MemoryAllocatorDump::Flags::WEAK in the Chromium
+ // codebase.
+ const WEAK_MEMORY_ALLOCATOR_DUMP_FLAG = 1 << 0;
+
+ // Object type name patterns for various compilers.
+ const OBJECT_TYPE_NAME_PATTERNS = [
+ {
+ // Clang.
+ prefix: 'const char *WTF::getStringWithTypeName() [T = ',
+ suffix: ']'
+ },
+ {
+ // GCC.
+ prefix: 'const char* WTF::getStringWithTypeName() [with T = ',
+ suffix: ']'
+ },
+ {
+ // Microsoft Visual C++
+ prefix: 'const char *__cdecl WTF::getStringWithTypeName<',
+ suffix: '>(void)'
+ }
+ ];
+
+ // The list of fields on the trace that are known to contain subtraces.
+ const SUBTRACE_FIELDS = new Set([
+ 'powerTraceAsString',
+ 'systemTraceEvents',
+ 'androidProcessDump',
+ ]);
+
+ // The complete list of fields on the trace that should not be treated as
+ // trace metadata.
+ const NON_METADATA_FIELDS = new Set([
+ 'displayTimeUnit',
+ 'samples',
+ 'stackFrames',
+ 'traceAnnotations',
+ 'traceEvents',
+ ...SUBTRACE_FIELDS
+ ]);
+
+ function TraceEventImporter(model, eventData) {
+ this.hasEvents_ = undefined; // Set properly when importEvents is called.
+ this.importPriority = 1;
+ this.model_ = model;
+ this.events_ = undefined;
+ this.sampleEvents_ = undefined;
+ this.stackFrameEvents_ = undefined;
+ this.stackFrameTree_ = new tr.model.ProfileTree();
+ this.subtraces_ = [];
+ this.eventsWereFromString_ = false;
+ this.softwareMeasuredCpuCount_ = undefined;
+
+
+ this.allAsyncEvents_ = [];
+ this.allFlowEvents_ = [];
+ this.allObjectEvents_ = [];
+
+ this.contextProcessorPerThread = {};
+
+ this.traceEventSampleStackFramesByName_ = {};
+
+ this.v8ProcessCodeMaps_ = {};
+ this.v8ProcessRootStackFrame_ = {};
+ this.v8SamplingData_ = [];
+
+ // Profile Tree Map.
+ // Type of Profile Tree -> Map(ID -> Profile Tree)
+ this.profileTrees_ = new Map();
+ // ID -> Info Object
+ this.profileInfo_ = new Map();
+
+ // For tracking async events that is used to create back-compat clock sync
+ // event.
+ this.legacyChromeClockSyncStartEvent_ = undefined;
+ this.legacyChromeClockSyncFinishEvent_ = undefined;
+
+ // Dump ID -> PID -> [process memory dump events].
+ this.allMemoryDumpEvents_ = {};
+
+ // Unpacks size, count, stackId and heapId from 'P' events. Also remembers
+ // stack frame and type information.
+ this.heapProfileExpander = new ProfilingDictionaryReader();
+
+ // PID -> Object type ID -> Object type name.
+ this.objectTypeNameMap_ = {};
+
+ // For old Chrome traces with no clock domain metadata, just use a
+ // placeholder clock domain.
+ this.clockDomainId_ = tr.model.ClockDomainId.UNKNOWN_CHROME_LEGACY;
+ // A function able to transform timestamps in |clockDomainId| to timestamps
+ // in the model clock domain.
+ this.toModelTime_ = undefined;
+
+ if (typeof(eventData) === 'string' || eventData instanceof String) {
+ eventData = eventData.trim();
+ // If the event data begins with a [, then we know it should end with a ].
+ // The reason we check for this is because some tracing implementations
+ // cannot guarantee that a ']' gets written to the trace file. So, we are
+ // forgiving and if this is obviously the case, we fix it up before
+ // throwing the string at JSON.parse.
+ if (eventData[0] === '[') {
+ eventData = eventData.replace(/\s*,\s*$/, '');
+ if (eventData[eventData.length - 1] !== ']') {
+ eventData = eventData + ']';
+ }
+ }
+
+ this.events_ = JSON.parse(eventData);
+ this.eventsWereFromString_ = true;
+ } else {
+ this.events_ = eventData;
+ }
+
+ // Some trace_event implementations put the actual trace events
+ // inside a container. E.g { ... , traceEvents: [ ] }
+ // If we see that, just pull out the trace events.
+ if (this.events_.traceEvents) {
+ const container = this.events_;
+ this.events_ = this.events_.traceEvents;
+
+ for (const subtraceField of SUBTRACE_FIELDS) {
+ if (container[subtraceField]) {
+ this.storeSubtrace_(container[subtraceField]);
+ }
+ }
+ this.storeSamples_(container.samples);
+ this.storeStackFrames_(container.stackFrames);
+ this.storeDisplayTimeUnit_(container.displayTimeUnit);
+ this.storeTraceAnnotations_(container.traceAnnotations);
+ this.storeMetadata_(container);
+ } else if (this.events_ instanceof tr.b.TraceStream) {
+ const parser = oboe()
+ .node('{cat ph}', function(e) { return oboe.drop; })
+ .node('!.powerTraceAsString', this.storeSubtrace_.bind(this))
+ .node('!.systemTraceEvents', this.storeSubtrace_.bind(this))
+ .node('!.samples', this.storeSamples_.bind(this))
+ .node('!.stackFrames', this.storeStackFrames_.bind(this))
+ .node('!.displayTimeUnit', this.storeDisplayTimeUnit_.bind(this))
+ .node('!.traceAnnotations', this.storeTraceAnnotations_.bind(this))
+ .done(this.storeMetadata_.bind(this));
+ this.events_.rewind();
+ while (this.events_.hasData) {
+ parser.write(this.events_.readNumBytes());
+ }
+ parser.finish();
+ }
+ }
+
+ /**
+ * @return {boolean} Whether obj is a TraceEvent array.
+ */
+ TraceEventImporter.canImport = function(eventData) {
+ // May be encoded JSON. But we dont want to parse it fully yet.
+ // Use a simple heuristic:
+ // - eventData that starts with [ are probably trace_event
+ // - eventData that starts with { are probably trace_event
+ // May be encoded JSON. Treat files that start with { as importable by us.
+ if (eventData instanceof tr.b.TraceStream) {
+ if (eventData.isBinary) return false;
+ eventData = eventData.header;
+ }
+
+ if (typeof(eventData) === 'string' || eventData instanceof String) {
+ eventData = eventData.trim();
+ return eventData[0] === '{' || eventData[0] === '[';
+ }
+
+ // Might just be an array of events
+ if (eventData instanceof Array && eventData.length && eventData[0].ph) {
+ return true;
+ }
+
+ // Might be an object with a traceEvents field in it.
+ if (eventData.traceEvents) {
+ if (eventData.traceEvents instanceof Array) {
+ if (eventData.traceEvents.length && eventData.traceEvents[0].ph) {
+ return true;
+ }
+ if (eventData.samples && eventData.samples.length &&
+ eventData.stackFrames !== undefined) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ };
+
+ /**
+ * Extracts a scoped ID from an event.
+ *
+ * In legacy trace files, the ID of an event is always stored in the 'id'
+ * field and the event phase determines if the ID is process-local or
+ * global. For example, async event IDs are considered global and object
+ * event IDs are considered process-local.
+ *
+ * New trace files can explicitly specify whether an event ID is
+ * process-local or global. These new IDs are stored in the 'id2' field so
+ * that old trace importers that do not implement this logic break when they
+ * try to import a new trace file. The value of id2 can be either of the
+ * form '{global: 0x1000}' or of the form '{local: 0x1000}'.
+ *
+ * @param {!Object} event A trace event.
+ * @return {!tr.model.ScopedId}
+ */
+ TraceEventImporter.scopedIdForEvent_ = function(event) {
+ const scope = event.scope || tr.model.OBJECT_DEFAULT_SCOPE;
+ let pid = undefined;
+ if (event.id !== undefined) {
+ if (event.id2 !== undefined) {
+ throw new Error('Event has both id and id2');
+ }
+ pid = tr.model.LOCAL_ID_PHASES.has(event.ph) ? event.pid : undefined;
+ return new tr.model.ScopedId(scope, event.id, pid);
+ } else if (event.id2 !== undefined) {
+ if (event.id2.global !== undefined) {
+ return new tr.model.ScopedId(scope, event.id2.global);
+ } else if (event.id2.local !== undefined) {
+ return new tr.model.ScopedId(scope, event.id2.local, event.pid);
+ }
+ throw new Error(
+ 'Event that uses id2 must have either a global or local ID');
+ }
+ return undefined;
+ };
+
+ TraceEventImporter.prototype = {
+ __proto__: tr.importer.Importer.prototype,
+
+ get importerName() {
+ return 'TraceEventImporter';
+ },
+
+ extractSubtraces() {
+ // Because subtraces can be quite large, we need to make sure that we
+ // don't hold a reference to the memory.
+ const subtraces = this.subtraces_;
+ this.subtraces_ = [];
+ return subtraces;
+ },
+
+ /**
+ * Deep copying is only needed if the trace was given to us as events.
+ */
+ deepCopyIfNeeded_(obj) {
+ if (obj === undefined) obj = {};
+ if (this.eventsWereFromString_) return obj;
+ return deepCopy(obj);
+ },
+
+ /**
+ * Always perform deep copying.
+ */
+ deepCopyAlways_(obj) {
+ if (obj === undefined) obj = {};
+ return deepCopy(obj);
+ },
+
+ /**
+ * Helper to process an async event.
+ */
+ processAsyncEvent(event) {
+ const thread = this.model_.getOrCreateProcess(event.pid).
+ getOrCreateThread(event.tid);
+ this.allAsyncEvents_.push({
+ sequenceNumber: this.allAsyncEvents_.length,
+ event,
+ thread
+ });
+ },
+
+ /**
+ * Helper to process a flow event.
+ */
+ processFlowEvent(event, opt_slice) {
+ const thread = this.model_.getOrCreateProcess(event.pid).
+ getOrCreateThread(event.tid);
+ this.allFlowEvents_.push({
+ refGuid: tr.b.GUID.getLastSimpleGuid(),
+ sequenceNumber: this.allFlowEvents_.length,
+ event,
+ slice: opt_slice, // slice for events that have flow info
+ thread
+ });
+ },
+
+ /**
+ * Helper that creates and adds samples to a Counter object based on
+ * 'C' phase events.
+ */
+ processCounterEvent(event) {
+ let ctrName;
+ if (event.id !== undefined) {
+ ctrName = event.name + '[' + event.id + ']';
+ } else {
+ ctrName = event.name;
+ }
+
+ const ctr = this.model_.getOrCreateProcess(event.pid)
+ .getOrCreateCounter(event.cat, ctrName);
+ const reservedColorId = event.cname ? getEventColor(event) : undefined;
+
+ // Initialize the counter's series fields if needed.
+ if (ctr.numSeries === 0) {
+ for (const seriesName in event.args) {
+ const colorId = reservedColorId ||
+ getEventColor(event, ctr.name + '.' + seriesName);
+ ctr.addSeries(new tr.model.CounterSeries(seriesName, colorId));
+ }
+
+ if (ctr.numSeries === 0) {
+ this.model_.importWarning({
+ type: 'counter_parse_error',
+ message: 'Expected counter ' + event.name +
+ ' to have at least one argument to use as a value.'
+ });
+
+ // Drop the counter.
+ delete ctr.parent.counters[ctr.name];
+ return;
+ }
+ }
+
+ const ts = this.toModelTimeFromUs_(event.ts);
+ ctr.series.forEach(function(series) {
+ const val = event.args[series.name] ? event.args[series.name] : 0;
+ series.addCounterSample(ts, val);
+ });
+ },
+
+ processObjectEvent(event) {
+ const thread = this.model_.getOrCreateProcess(event.pid).
+ getOrCreateThread(event.tid);
+ this.allObjectEvents_.push({
+ sequenceNumber: this.allObjectEvents_.length,
+ event,
+ thread});
+ if (thread.guid in this.contextProcessorPerThread) {
+ const processor = this.contextProcessorPerThread[thread.guid];
+ const scopedId = TraceEventImporter.scopedIdForEvent_(event);
+ if (event.ph === 'D') {
+ processor.destroyContext(scopedId);
+ }
+ // The context processor maintains a cache of unique context objects and
+ // active context sets to reduce memory usage. If an object is modified,
+ // we should invalidate this cache, because otherwise context sets from
+ // before and after the modification may erroneously point to the same
+ // context snapshot (as both are the same set/object instances).
+ processor.invalidateContextCacheForSnapshot(scopedId);
+ }
+ },
+
+ processContextEvent(event) {
+ const thread = this.model_.getOrCreateProcess(event.pid).
+ getOrCreateThread(event.tid);
+ if (!(thread.guid in this.contextProcessorPerThread)) {
+ this.contextProcessorPerThread[thread.guid] =
+ new tr.importer.ContextProcessor(this.model_);
+ }
+ const scopedId = TraceEventImporter.scopedIdForEvent_(event);
+ const contextType = event.name;
+ const processor = this.contextProcessorPerThread[thread.guid];
+ if (event.ph === '(') {
+ processor.enterContext(contextType, scopedId);
+ } else if (event.ph === ')') {
+ processor.leaveContext(contextType, scopedId);
+ } else {
+ this.model_.importWarning({
+ type: 'unknown_context_phase',
+ message: 'Unknown context event phase: ' + event.ph + '.'
+ });
+ }
+ },
+
+ setContextsFromThread_(thread, slice) {
+ if (thread.guid in this.contextProcessorPerThread) {
+ slice.contexts =
+ this.contextProcessorPerThread[thread.guid].activeContexts;
+ }
+ },
+
+ processDurationEvent(event) {
+ const thread = this.model_.getOrCreateProcess(event.pid)
+ .getOrCreateThread(event.tid);
+ const ts = this.toModelTimeFromUs_(event.ts);
+ if (event.dur === 0 &&
+ !thread.sliceGroup.isTimestampValidForBeginOrEnd(ts)) {
+ this.model_.importWarning({
+ type: 'duration_parse_error',
+ message: 'Timestamps are moving backward.'
+ });
+ return;
+ }
+
+ if (event.ph === 'B') {
+ const slice = thread.sliceGroup.beginSlice(
+ event.cat, event.name, this.toModelTimeFromUs_(event.ts),
+ this.deepCopyIfNeeded_(event.args),
+ this.toModelTimeFromUs_(event.tts), event.argsStripped,
+ getEventColor(event), event.bind_id);
+ slice.startStackFrame = this.getStackFrameForEvent_(event);
+ this.setContextsFromThread_(thread, slice);
+ } else if (event.ph === 'I' || event.ph === 'i' || event.ph === 'R') {
+ if (event.s !== undefined && event.s !== 't') {
+ throw new Error('This should never happen');
+ }
+
+ thread.sliceGroup.beginSlice(event.cat, event.name,
+ this.toModelTimeFromUs_(event.ts),
+ this.deepCopyIfNeeded_(event.args),
+ this.toModelTimeFromUs_(event.tts),
+ event.argsStripped,
+ getEventColor(event), event.bind_id);
+ const slice = thread.sliceGroup.endSlice(
+ this.toModelTimeFromUs_(event.ts),
+ this.toModelTimeFromUs_(event.tts));
+ slice.startStackFrame = this.getStackFrameForEvent_(event);
+ slice.endStackFrame = undefined;
+ } else {
+ if (!thread.sliceGroup.openSliceCount) {
+ this.model_.importWarning({
+ type: 'duration_parse_error',
+ message: 'E phase event without a matching B phase event.'
+ });
+ return;
+ }
+
+ const slice = thread.sliceGroup.endSlice(
+ this.toModelTimeFromUs_(event.ts),
+ this.toModelTimeFromUs_(event.tts),
+ getEventColor(event));
+ if (event.name && slice.title !== event.name) {
+ this.model_.importWarning({
+ type: 'title_match_error',
+ message: 'Titles do not match. Title is ' +
+ slice.title + ' in openSlice, and is ' +
+ event.name + ' in endSlice'
+ });
+ }
+ slice.endStackFrame = this.getStackFrameForEvent_(event);
+
+ this.mergeArgsInto_(slice.args, event.args, slice.title);
+ }
+ },
+
+ mergeArgsInto_(dstArgs, srcArgs, eventName) {
+ for (const arg in srcArgs) {
+ if (dstArgs[arg] !== undefined) {
+ this.model_.importWarning({
+ type: 'arg_merge_error',
+ message: 'Different phases of ' + eventName +
+ ' provided values for argument ' + arg + '.' +
+ ' The last provided value will be used.'
+ });
+ }
+ dstArgs[arg] = this.deepCopyIfNeeded_(srcArgs[arg]);
+ }
+ },
+
+ processCompleteEvent(event) {
+ // Preventing the overhead slices from making it into the model. This
+ // only applies to legacy traces, as the overhead traces have been
+ // removed from the chromium code.
+ if (event.cat !== undefined &&
+ event.cat.indexOf('trace_event_overhead') > -1) {
+ return undefined;
+ }
+
+ const thread = this.model_.getOrCreateProcess(event.pid)
+ .getOrCreateThread(event.tid);
+
+ if (event.flow_out) {
+ if (event.flow_in) {
+ event.flowPhase = STEP;
+ } else {
+ event.flowPhase = PRODUCER;
+ }
+ } else if (event.flow_in) {
+ event.flowPhase = CONSUMER;
+ }
+
+ const slice = thread.sliceGroup.pushCompleteSlice(event.cat, event.name,
+
+ this.toModelTimeFromUs_(event.ts),
+ this.maybeToModelTimeFromUs_(event.dur),
+ this.maybeToModelTimeFromUs_(event.tts),
+ this.maybeToModelTimeFromUs_(event.tdur),
+ this.deepCopyIfNeeded_(event.args),
+ event.argsStripped,
+ getEventColor(event),
+ event.bind_id);
+ slice.startStackFrame = this.getStackFrameForEvent_(event);
+ slice.endStackFrame = this.getStackFrameForEvent_(event, true);
+ this.setContextsFromThread_(thread, slice);
+
+ return slice;
+ },
+
+ processJitCodeEvent(event) {
+ if (this.v8ProcessCodeMaps_[event.pid] === undefined) {
+ this.v8ProcessCodeMaps_[event.pid] = new tr.e.importer.TraceCodeMap();
+ }
+ const map = this.v8ProcessCodeMaps_[event.pid];
+
+ const data = event.args.data;
+ // TODO(dsinclair): There are _a lot_ of JitCode events so I'm skipping
+ // the display for now. Can revisit later if we want to show them.
+ // Handle JitCodeMoved and JitCodeAdded event.
+ if (event.name === 'JitCodeMoved') {
+ map.moveEntry(data.code_start, data.new_code_start, data.code_len);
+ } else { // event.name === 'JitCodeAdded'
+ map.addEntry(data.code_start, data.code_len, data.name, data.script_id);
+ }
+ },
+
+ processMetadataEvent(event) {
+ // V8 JIT events are currently logged as phase 'M' so we need to
+ // separate them out and handle specially.
+ if (event.name === 'JitCodeAdded' || event.name === 'JitCodeMoved') {
+ this.v8SamplingData_.push(event);
+ return;
+ }
+
+ // The metadata events aren't useful without args.
+ if (event.argsStripped) return;
+
+ if (event.name === 'process_name') {
+ const process = this.model_.getOrCreateProcess(event.pid);
+ process.name = event.args.name;
+ } else if (event.name === 'process_labels') {
+ const process = this.model_.getOrCreateProcess(event.pid);
+ const labels = event.args.labels.split(',');
+ for (let i = 0; i < labels.length; i++) {
+ process.addLabelIfNeeded(labels[i]);
+ }
+ } else if (event.name === 'process_uptime_seconds') {
+ const process = this.model_.getOrCreateProcess(event.pid);
+ process.uptime_seconds = event.args.uptime;
+ } else if (event.name === 'process_sort_index') {
+ const process = this.model_.getOrCreateProcess(event.pid);
+ process.sortIndex = event.args.sort_index;
+ } else if (event.name === 'thread_name') {
+ const thread = this.model_.getOrCreateProcess(event.pid).
+ getOrCreateThread(event.tid);
+ thread.name = event.args.name;
+ } else if (event.name === 'thread_sort_index') {
+ const thread = this.model_.getOrCreateProcess(event.pid).
+ getOrCreateThread(event.tid);
+ thread.sortIndex = event.args.sort_index;
+ } else if (event.name === 'num_cpus') {
+ let n = event.args.number;
+ // Not all render processes agree on the cpu count in trace_event. Some
+ // processes will report 1, while others will report the actual cpu
+ // count. To deal with this, take the max of what is reported.
+ if (this.softwareMeasuredCpuCount_ !== undefined) {
+ n = Math.max(n, this.softwareMeasuredCpuCount_);
+ }
+ this.softwareMeasuredCpuCount_ = n;
+ } else if (event.name === 'stackFrames') {
+ const stackFrames = event.args.stackFrames;
+ if (stackFrames === undefined) {
+ this.model_.importWarning({
+ type: 'metadata_parse_error',
+ message: 'No stack frames found in a \'' + event.name +
+ '\' metadata event'
+ });
+ } else {
+ this.importStackFrames_(stackFrames, 'p' + event.pid + ':');
+ }
+ } else if (event.name === 'typeNames') {
+ const objectTypeNameMap = event.args.typeNames;
+ if (objectTypeNameMap === undefined) {
+ this.model_.importWarning({
+ type: 'metadata_parse_error',
+ message: 'No mapping from object type IDs to names found in a \'' +
+ event.name + '\' metadata event'
+ });
+ } else {
+ this.importObjectTypeNameMap_(objectTypeNameMap, event.pid);
+ }
+ } else if (event.name === 'TraceConfig') {
+ this.model_.metadata.push(
+ {name: 'TraceConfig', value: event.args.value});
+ } else {
+ this.model_.importWarning({
+ type: 'metadata_parse_error',
+ message: 'Unrecognized metadata name: ' + event.name
+ });
+ }
+ },
+
+ processInstantEvent(event) {
+ // V8 JIT events were logged as phase 'I' in the old format,
+ // so we need to separate them out and handle specially.
+ if (event.name === 'JitCodeAdded' || event.name === 'JitCodeMoved') {
+ this.v8SamplingData_.push(event);
+ return;
+ }
+
+ // Thread-level instant events are treated as zero-duration slices.
+ if (event.s === 't' || event.s === undefined) {
+ this.processDurationEvent(event);
+ return;
+ }
+
+ let constructor;
+ switch (event.s) {
+ case 'g':
+ constructor = tr.model.GlobalInstantEvent;
+ break;
+ case 'p':
+ constructor = tr.model.ProcessInstantEvent;
+ break;
+ default:
+ this.model_.importWarning({
+ type: 'instant_parse_error',
+ message: 'I phase event with unknown "s" field value.'
+ });
+ return;
+ }
+
+ const instantEvent = new constructor(event.cat, event.name,
+ getEventColor(event), this.toModelTimeFromUs_(event.ts),
+ this.deepCopyIfNeeded_(event.args));
+
+ switch (instantEvent.type) {
+ case tr.model.InstantEventType.GLOBAL:
+ this.model_.instantEvents.push(instantEvent);
+ break;
+
+ case tr.model.InstantEventType.PROCESS: {
+ const process = this.model_.getOrCreateProcess(event.pid);
+ process.instantEvents.push(instantEvent);
+ break;
+ }
+
+ default:
+ throw new Error('Unknown instant event type: ' + event.s);
+ }
+ },
+
+ getOrCreateProfileTree_(sampleType, id) {
+ if (!this.profileTrees_.has(sampleType)) {
+ this.profileTrees_.set(sampleType, new Map());
+ }
+ const profileTreeMap = this.profileTrees_.get(sampleType);
+ if (profileTreeMap.has(id)) {
+ return profileTreeMap.get(id);
+ }
+ const profileTree = new tr.model.ProfileTree();
+ profileTreeMap.set(id, profileTree);
+ const info = this.profileInfo_.get(id);
+ if (info !== undefined) {
+ profileTree.startTime = info.startTime;
+ profileTree.pid = info.pid;
+ profileTree.tid = info.tid;
+ }
+ return profileTree;
+ },
+
+ processSample(event) {
+ if (event.args === undefined || event.args.data === undefined) {
+ return;
+ }
+ if (event.id === undefined) {
+ throw new Error('No event ID in sample');
+ }
+
+ const data = event.args.data;
+ // Sampling usually happens in a separate thread, but start time is issued
+ // in the main thread, in order to get the correct thread object,
+ // we should use pid and tid from main thread.
+ if (data.startTime !== undefined) {
+ this.profileInfo_.set(event.id, {
+ startTime: data.startTime,
+ pid: event.pid,
+ tid: event.tid
+ });
+ }
+ const timeDeltas = data.timeDeltas;
+ for (const sampleType in data) {
+ if (sampleType === 'timeDeltas' || sampleType === 'startTime') {
+ continue;
+ }
+ // The length of samples array and the length of timeDeltas array
+ // should be the same.
+ if (data[sampleType].samples && timeDeltas &&
+ data[sampleType].samples.length !== timeDeltas.length) {
+ // eslint-disable-next-line
+ throw new Error('samples and timeDeltas array should have same length');
+ }
+
+ const profileTree = this.getOrCreateProfileTree_(sampleType, event.id);
+ const nodes = data[sampleType].nodes;
+ const samples = data[sampleType].samples;
+ if (nodes !== undefined) {
+ for (const node of nodes) {
+ // Get the sepcific ProfileNode type based on sampleType.
+ const ProfileNodeType =
+ tr.model.ProfileNode.subTypes.getConstructor(undefined,
+ sampleType);
+ const profileNode = ProfileNodeType.constructFromObject(profileTree,
+ node);
+ if (profileNode === undefined) {
+ continue;
+ }
+ profileTree.add(profileNode);
+ }
+ }
+ // The samples array contains id pointing to the profile node.
+ if (samples !== undefined) {
+ const thread = this.model_.getOrCreateProcess(profileTree.pid)
+ .getOrCreateThread(profileTree.tid);
+ for (let i = 0, len = samples.length; i < len; ++i) {
+ const node = profileTree.getNode(samples[i]);
+ profileTree.endTime += timeDeltas[i];
+ if (node === undefined) continue;
+ const start = this.toModelTimeFromUs_(profileTree.endTime);
+ this.model_.samples.push(
+ new tr.model.Sample(start, node.sampleTitle, node, thread));
+ }
+ }
+ }
+ },
+
+ processLegacyV8Sample(event) {
+ const data = event.args.data;
+ const sampleType = 'legacySample';
+ const ProfileNodeType =
+ tr.model.ProfileNode.subTypes.getConstructor(undefined, sampleType);
+
+ // As-per DevTools, the backend sometimes creates bogus samples. Skip it.
+ if (data.vm_state === 'js' && !data.stack.length) return;
+
+ const profileTree = this.getOrCreateProfileTree_(sampleType, event.pid);
+ if (profileTree.getNode(-1) === undefined) {
+ profileTree.add(
+ new ProfileNodeType(-1, {
+ url: '',
+ scriptId: -1,
+ functionName: 'unknown'
+ }, undefined));
+ }
+ // There are several types of v8 sample events, gc, native, compiler, etc.
+ // Some of these types have stacks and some don't, we handle those two
+ // cases differently. For types that don't have any stack frames attached
+ // we synthesize one based on the type of thing that's happening so when
+ // we view all the samples we'll see something like 'external' or 'gc'
+ // as a fraction of the time spent.
+ let node = undefined;
+ if (data.stack.length > 0 && this.v8ProcessCodeMaps_[event.pid]) {
+ const map = this.v8ProcessCodeMaps_[event.pid];
+ // Stacks have the leaf node first, flip them around so the root
+ // comes first.
+ data.stack.reverse();
+ let parentNode = undefined;
+ for (let i = 0; i < data.stack.length; i++) {
+ const entry = map.lookupEntry(data.stack[i]);
+ if (entry === undefined) {
+ node = profileTree.getNode(-1);
+ } else {
+ node = profileTree.getNode(entry.id);
+ if (node === undefined) {
+ const sourceInfo = entry.sourceInfo;
+ node = new ProfileNodeType(entry.id, {
+ functionName: entry.name,
+ url: entry.sourceInfo.file,
+ lineNumber: sourceInfo.line !== -1 ? sourceInfo.line :
+ undefined,
+ columnNumber: sourceInfo.column !== -1 ? sourceInfo.column :
+ undefined,
+ scriptid: entry.sourceInfo.scriptId
+ }, parentNode);
+ profileTree.add(node);
+ }
+ }
+ parentNode = node;
+ }
+ } else {
+ node = profileTree.getNode(data.vm_state);
+ if (node === undefined) {
+ node = new ProfileNodeType(data.vm_state, {
+ url: '',
+ functionName: data.vm_state
+ }, undefined);
+ profileTree.add(node);
+ }
+ }
+
+ const thread = this.model_.getOrCreateProcess(event.pid)
+ .getOrCreateThread(event.tid);
+
+ this.model_.samples.push(
+ new tr.model.Sample(this.toModelTimeFromUs_(event.ts),
+ node.sampleTitle, node, thread));
+ },
+
+ processTraceSampleEvent(event) {
+ if (event.name === 'V8Sample' || event.name.startsWith('Profile')) {
+ this.v8SamplingData_.push(event);
+ return;
+ }
+
+ let node = this.stackFrameTree_.getNode(event.name);
+ if (node === undefined && event.sf !== undefined) {
+ node = this.stackFrameTree_.getNode('g' + event.sf);
+ }
+
+ if (node === undefined) {
+ let id = event.name;
+ if (event.sf) {
+ id = 'g' + event.sf;
+ }
+ const ProfileNodeType =
+ tr.model.ProfileNode.subTypes.getConstructor(undefined,
+ 'legacySample');
+ node = this.stackFrameTree_.add(new ProfileNodeType(
+ id, {
+ functionName: event.name
+ }, undefined
+ ));
+ }
+ const thread = this.model_.getOrCreateProcess(event.pid)
+ .getOrCreateThread(event.tid);
+
+ const sample = new tr.model.Sample(
+ this.toModelTimeFromUs_(event.ts), 'Trace Event Sample',
+ node, thread, undefined, 1,
+ this.deepCopyIfNeeded_(event.args));
+ this.setContextsFromThread_(thread, sample);
+ this.model_.samples.push(sample);
+ },
+
+ processMemoryDumpEvent(event) {
+ // TODO(chiniforooshan): Make memory dumps either use local or global IDs
+ // instead of the generic IDs with different phases.
+ // https://github.com/catapult-project/catapult/issues/2957
+ if (event.ph !== 'v') {
+ throw new Error('Invalid memory dump event phase "' + event.ph + '".');
+ }
+
+ const dumpId = event.id;
+ if (dumpId === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Memory dump event (phase \'' + event.ph +
+ '\') without a dump ID.'
+ });
+ return;
+ }
+
+ const pid = event.pid;
+ if (pid === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Memory dump event (phase\'' + event.ph + '\', dump ID \'' +
+ dumpId + '\') without a PID.'
+ });
+ return;
+ }
+
+ // Dump ID -> PID -> [process memory dump events].
+ const allEvents = this.allMemoryDumpEvents_;
+
+ // PID -> [process memory dump events].
+ let dumpIdEvents = allEvents[dumpId];
+ if (dumpIdEvents === undefined) {
+ allEvents[dumpId] = dumpIdEvents = {};
+ }
+
+ // [process memory dump events].
+ let processEvents = dumpIdEvents[pid];
+ if (processEvents === undefined) {
+ dumpIdEvents[pid] = processEvents = [];
+ }
+
+ processEvents.push(event);
+ },
+
+ processClockSyncEvent(event) {
+ if (event.ph !== 'c') {
+ throw new Error('Invalid clock sync event phase "' + event.ph + '".');
+ }
+
+ const syncId = event.args.sync_id;
+ if (syncId === undefined) {
+ this.model_.importWarning({
+ type: 'clock_sync_parse_error',
+ message: 'Clock sync at time ' + event.ts + ' without an ID.'
+ });
+ return;
+ }
+
+ if (event.args && event.args.issue_ts !== undefined) {
+ // When Chrome is the tracing controller and is the requester of the
+ // clock sync, the clock sync event looks like:
+ //
+ // {
+ // "args": {
+ // "sync_id": "abc123",
+ // "issue_ts": 12340
+ // }
+ // "ph": "c"
+ // "ts": 12345
+ // ...
+ // }
+ this.model_.clockSyncManager.addClockSyncMarker(
+ this.clockDomainId_, syncId,
+ tr.b.Unit.timestampFromUs(event.args.issue_ts),
+ tr.b.Unit.timestampFromUs(event.ts));
+ } else {
+ // When Chrome is a tracing agent and is the recipient of the clock
+ // sync request, the clock sync event looks like:
+ //
+ // {
+ // "args": { "sync_id": "abc123" }
+ // "ph": "c"
+ // "ts": 12345
+ // ...
+ // }
+ this.model_.clockSyncManager.addClockSyncMarker(
+ this.clockDomainId_, syncId, tr.b.Unit.timestampFromUs(event.ts));
+ }
+ },
+
+ processLegacyChromeClockSyncEvent(event) {
+ // Older versions of Chrome don't support the devtools clock sync API, but
+ // Telemetry can work around this by creating clock sync events via
+ // console.time & console.timeEnd, which issue trace events as long as the
+ // 'blink.console' category is enabled. When we encounter async events
+ // with names starting with the prefix 'ClockSyncEvent.' , treat these as
+ // clock sync markers.
+ if (event.ph === 'S') {
+ this.legacyChromeClockSyncStartEvent_ = event;
+ } else if (event.ph === 'F') {
+ this.legacyChromeClockSyncFinishEvent_ = event;
+ }
+
+ if (this.legacyChromeClockSyncStartEvent_ === undefined ||
+ this.legacyChromeClockSyncFinishEvent_ === undefined) {
+ return;
+ }
+
+ const startSyncId = this.legacyChromeClockSyncStartEvent_.name.substring(
+ LEGACY_CHROME_CLOCK_SYNC_EVENT_NAME_PREFIX.length);
+ const finishSyncId =
+ this.legacyChromeClockSyncFinishEvent_.name.substring(
+ LEGACY_CHROME_CLOCK_SYNC_EVENT_NAME_PREFIX.length);
+
+ if (startSyncId !== finishSyncId) {
+ throw new Error(
+ 'Inconsistent clock sync ID of legacy Chrome clock sync events');
+ }
+
+ this.model_.clockSyncManager.addClockSyncMarker(
+ this.clockDomainId_, startSyncId,
+ tr.b.Unit.timestampFromUs(this.legacyChromeClockSyncStartEvent_.ts),
+ tr.b.Unit.timestampFromUs(this.legacyChromeClockSyncFinishEvent_.ts));
+ },
+
+ // Because the order of Jit code events and V8 samples are not guaranteed,
+ // We store them in an array, sort by timestamp, and then process them.
+ processV8Events() {
+ this.v8SamplingData_.sort(function(a, b) {
+ if (a.ts !== b.ts) return a.ts - b.ts;
+ if (a.ph === 'M' || a.ph === 'I') {
+ return -1;
+ } else if (b.ph === 'M' || b.ph === 'I') {
+ return 1;
+ }
+ return 0;
+ });
+ const length = this.v8SamplingData_.length;
+ for (let i = 0; i < length; ++i) {
+ const event = this.v8SamplingData_[i];
+ if (event.ph === 'M' || event.ph === 'I') {
+ this.processJitCodeEvent(event);
+ } else if (event.ph === 'P') {
+ // The current sampling format and legacy sampling format
+ // have the same ph, but the current sampling format will
+ // start with 'Profile' in its name.
+ if (event.name.startsWith('Profile')) {
+ this.processSample(event);
+ } else {
+ this.processLegacyV8Sample(event);
+ }
+ }
+ }
+ },
+
+ importClockSyncMarkers() {
+ if (this.events_ instanceof tr.b.TraceStream) {
+ const parser = oboe().node(
+ '{cat ph}', this.importClockSyncMarker_.bind(this));
+ this.events_.rewind();
+ while (this.events_.hasData) {
+ parser.write(this.events_.readNumBytes());
+ }
+ parser.finish();
+ } else {
+ for (let i = 0; i < this.events_.length; i++) {
+ this.importClockSyncMarker_(this.events_[i]);
+ }
+ }
+ },
+
+ importClockSyncMarker_(event) {
+ const isLegacyChromeClockSync = isLegacyChromeClockSyncEvent(event);
+ if (event.ph !== 'c' && !isLegacyChromeClockSync) return;
+
+ const eventSizeInBytes =
+ this.model_.importOptions.trackDetailedModelStats ?
+ JSON.stringify(event).length : undefined;
+
+ this.model_.stats.willProcessBasicTraceEvent(
+ 'clock_sync', event.cat, event.name, event.ts, eventSizeInBytes);
+
+ if (isLegacyChromeClockSync) {
+ this.processLegacyChromeClockSyncEvent(event);
+ } else {
+ this.processClockSyncEvent(event);
+ }
+ },
+
+ /**
+ * Walks through the events_ list and outputs the structures discovered to
+ * model_.
+ */
+ importEvents() {
+ this.hasEvents_ = false;
+ if (this.stackFrameEvents_) {
+ this.importStackFrames_(this.stackFrameEvents_, 'g');
+ }
+
+ if (this.traceAnnotations_) this.importAnnotations_();
+
+ if (this.events_ instanceof tr.b.TraceStream) {
+ const parser = oboe().node('{cat ph}', this.processEvent_.bind(this));
+ this.events_.rewind();
+ while (this.events_.hasData) {
+ parser.write(this.events_.readNumBytes());
+ }
+ parser.finish();
+ } else {
+ for (let eI = 0; eI < this.events_.length; eI++) {
+ this.processEvent_(this.events_[eI]);
+ }
+ }
+
+ // Import calls importEvents then autoCloseOpenSlices then finalizeImport.
+ // AsyncSlices must be created in importEvents so they can be auto-closed.
+ this.createAsyncSlices_();
+
+ this.processV8Events();
+
+ // Remove all the root stack frame children as they should
+ // already be added.
+ for (const frame of Object.values(this.v8ProcessRootStackFrame_)) {
+ frame.removeAllChildren();
+ }
+ },
+
+ // Some trace authors store subtraces as specific properties of the trace.
+ storeSubtrace_(subtrace) {
+ this.subtraces_.push(subtrace);
+ return oboe.drop;
+ },
+
+ storeSamples_(samples) {
+ this.sampleEvents_ = samples;
+ return oboe.drop;
+ },
+
+ storeStackFrames_(stackFrames) {
+ this.stackFrameEvents_ = stackFrames;
+ return oboe.drop;
+ },
+
+ // Some implementations specify displayTimeUnit
+ storeDisplayTimeUnit_(unitName) {
+ if (!unitName) return;
+ const unit = tr.b.TimeDisplayModes[unitName];
+ if (unit === undefined) {
+ throw new Error('Unit ' + unitName + ' is not supported.');
+ }
+ this.model_.intrinsicTimeUnit = unit;
+ return oboe.drop;
+ },
+
+ storeTraceAnnotations_(traceAnnotations) {
+ this.traceAnnotations_ = traceAnnotations;
+ return oboe.drop;
+ },
+
+ // Any fields in the container that is not in NON_METADATA_FIELDS should be
+ // treated as metadata.
+ storeMetadata_(container) {
+ for (const fieldName of Object.keys(container)) {
+ if (NON_METADATA_FIELDS.has(fieldName)) continue;
+ this.model_.metadata.push(
+ { name: fieldName, value: container[fieldName] });
+ if (fieldName !== 'metadata') continue;
+ const metadata = container[fieldName];
+ if (metadata['highres-ticks']) {
+ this.model_.isTimeHighResolution = metadata['highres-ticks'];
+ }
+ if (metadata['clock-domain']) {
+ this.clockDomainId_ = metadata['clock-domain'];
+ }
+ }
+ return oboe.drop;
+ },
+
+ processEvent_(event) {
+ this.hasEvents_ = true;
+ const importOptions = this.model_.importOptions;
+ const trackDetailedModelStats = importOptions.trackDetailedModelStats;
+ const modelStats = this.model_.stats;
+
+ if (event.args === '__stripped__') {
+ event.argsStripped = true;
+ event.args = undefined;
+ }
+
+ let eventSizeInBytes = undefined;
+ if (trackDetailedModelStats) {
+ eventSizeInBytes = JSON.stringify(event).length;
+ }
+
+ switch (event.ph) {
+ case 'B':
+ case 'E':
+ modelStats.willProcessBasicTraceEvent(
+ 'begin_end (non-compact)', event.cat, event.name, event.ts,
+ eventSizeInBytes);
+ this.processDurationEvent(event);
+ break;
+
+ case 'X': {
+ modelStats.willProcessBasicTraceEvent(
+ 'begin_end (compact)', event.cat, event.name, event.ts,
+ eventSizeInBytes);
+ const slice = this.processCompleteEvent(event);
+ // TODO(yuhaoz): If Chrome supports creating other events with flow,
+ // we will need to call processFlowEvent for them also.
+ // https://github.com/catapult-project/catapult/issues/1259
+ if (slice !== undefined && event.bind_id !== undefined) {
+ this.processFlowEvent(event, slice);
+ }
+ break;
+ }
+
+ case 'b':
+ case 'e':
+ case 'n':
+ case 'S':
+ case 'F':
+ case 'T':
+ case 'p':
+ modelStats.willProcessBasicTraceEvent(
+ 'async', event.cat, event.name, event.ts, eventSizeInBytes);
+ this.processAsyncEvent(event);
+ break;
+
+ // Note, I is historic. The instant event marker got changed, but we
+ // want to support loading old trace files so we have both I and i.
+ case 'I':
+ case 'i':
+ case 'R':
+ modelStats.willProcessBasicTraceEvent(
+ 'instant', event.cat, event.name, event.ts, eventSizeInBytes);
+ this.processInstantEvent(event);
+ break;
+
+ case 'P':
+ modelStats.willProcessBasicTraceEvent(
+ 'samples', event.cat, event.name, event.ts, eventSizeInBytes);
+ this.processTraceSampleEvent(event);
+ break;
+
+ case 'C':
+ modelStats.willProcessBasicTraceEvent(
+ 'counters', event.cat, event.name, event.ts, eventSizeInBytes);
+ this.processCounterEvent(event);
+ break;
+
+ case 'M':
+ modelStats.willProcessBasicTraceEvent(
+ 'metadata', event.cat, event.name, event.ts, eventSizeInBytes);
+ this.processMetadataEvent(event);
+ break;
+
+ case 'N':
+ case 'D':
+ case 'O':
+ modelStats.willProcessBasicTraceEvent(
+ 'objects', event.cat, event.name, event.ts, eventSizeInBytes);
+ this.processObjectEvent(event);
+ break;
+
+ case 's':
+ case 't':
+ case 'f':
+ modelStats.willProcessBasicTraceEvent(
+ 'flows', event.cat, event.name, event.ts, eventSizeInBytes);
+ this.processFlowEvent(event);
+ break;
+
+ case 'v':
+ modelStats.willProcessBasicTraceEvent(
+ 'memory_dumps', event.cat, event.name, event.ts,
+ eventSizeInBytes);
+ this.processMemoryDumpEvent(event);
+ break;
+
+ case '(':
+ case ')':
+ this.processContextEvent(event);
+ break;
+
+ // No-op. Clock sync events have already been processed in
+ // importClockSyncMarkers().
+ case 'c':
+ break;
+
+ default:
+ modelStats.willProcessBasicTraceEvent(
+ 'unknown', event.cat, event.name, event.ts, eventSizeInBytes);
+ this.model_.importWarning({
+ type: 'parse_error',
+ message: 'Unrecognized event phase: ' +
+ event.ph + ' (' + event.name + ')'
+ });
+ }
+ return oboe.drop;
+ },
+
+ importStackFrames_(rawStackFrames, idPrefix) {
+ const model = this.model_;
+
+ for (const id in rawStackFrames) {
+ const rawStackFrame = rawStackFrames[id];
+ const fullId = idPrefix + id;
+ const textForColor = rawStackFrame.category ?
+ rawStackFrame.category : rawStackFrame.name;
+ const stackFrame = new tr.model.StackFrame(
+ undefined /* parentFrame */, fullId, rawStackFrame.name,
+ ColorScheme.getColorIdForGeneralPurposeString(textForColor));
+ model.addStackFrame(stackFrame);
+ }
+
+ for (const id in rawStackFrames) {
+ const fullId = idPrefix + id;
+ const stackFrame = model.stackFrames[fullId];
+ if (stackFrame === undefined) {
+ throw new Error('Internal error');
+ }
+
+ const rawStackFrame = rawStackFrames[id];
+ const parentId = rawStackFrame.parent;
+ let parentStackFrame;
+ if (parentId === undefined) {
+ parentStackFrame = undefined;
+ } else {
+ const parentFullId = idPrefix + parentId;
+ parentStackFrame = model.stackFrames[parentFullId];
+ if (parentStackFrame === undefined) {
+ this.model_.importWarning({
+ type: 'metadata_parse_error',
+ message: 'Missing parent frame with ID ' + parentFullId +
+ ' for stack frame \'' + stackFrame.name + '\' (ID ' + fullId +
+ ').'
+ });
+ }
+ }
+ stackFrame.parentFrame = parentStackFrame;
+ }
+
+ // This section is a reimplementation of the above behaviour but using
+ // the new ProfileNode type instead of StackFrame.
+ // We keep the StackFrame version for compatibility for now.
+ const ProfileNodeType =
+ tr.model.ProfileNode.subTypes.getConstructor(undefined,
+ 'legacySample');
+ if (idPrefix === 'g') {
+ for (const id in rawStackFrames) {
+ const rawStackFrame = rawStackFrames[id];
+ const textForColor = rawStackFrame.category ?
+ rawStackFrame.category : rawStackFrame.name;
+ const node = this.stackFrameTree_.add(new ProfileNodeType(
+ 'g' + id, {
+ functionName: rawStackFrame.name
+ }, undefined));
+ node.colorId =
+ ColorScheme.getColorIdForGeneralPurposeString(textForColor);
+ node.parentId = rawStackFrame.parent;
+ }
+
+ for (const id in rawStackFrames) {
+ const node = this.stackFrameTree_.getNode('g' + id);
+ const parentId = node.parentId;
+ let parentNode = undefined;
+ if (parentId !== undefined) {
+ parentNode = this.stackFrameTree_.getNode('g' + parentId);
+ if (parentNode === undefined) {
+ this.model_.importWarning({
+ type: 'metadata_parse_error',
+ message: 'Missing parent frame with ID ' + parentId +
+ ' for stack frame \'' + node.name + '\' (ID ' + node.id +
+ ').'
+ });
+ }
+ node.parentNode = parentNode;
+ }
+ }
+ }
+ },
+
+ importObjectTypeNameMap_(rawObjectTypeNameMap, pid) {
+ if (pid in this.objectTypeNameMap_) {
+ this.model_.importWarning({
+ type: 'metadata_parse_error',
+ message: 'Mapping from object type IDs to names provided for pid=' +
+ pid + ' multiple times.'
+ });
+ return;
+ }
+
+ let objectTypeNamePrefix = undefined;
+ let objectTypeNameSuffix = undefined;
+ const objectTypeNameMap = {};
+ for (const objectTypeId in rawObjectTypeNameMap) {
+ const rawObjectTypeName = rawObjectTypeNameMap[objectTypeId];
+
+ // If we haven't figured out yet which compiler the object type names
+ // come from, we try to do it now.
+ if (objectTypeNamePrefix === undefined) {
+ for (let i = 0; i < OBJECT_TYPE_NAME_PATTERNS.length; i++) {
+ const pattern = OBJECT_TYPE_NAME_PATTERNS[i];
+ if (rawObjectTypeName.startsWith(pattern.prefix) &&
+ rawObjectTypeName.endsWith(pattern.suffix)) {
+ objectTypeNamePrefix = pattern.prefix;
+ objectTypeNameSuffix = pattern.suffix;
+ break;
+ }
+ }
+ }
+
+ if (objectTypeNamePrefix !== undefined &&
+ rawObjectTypeName.startsWith(objectTypeNamePrefix) &&
+ rawObjectTypeName.endsWith(objectTypeNameSuffix)) {
+ // With compiler-specific prefix and suffix (automatically annotated
+ // object types).
+ objectTypeNameMap[objectTypeId] = rawObjectTypeName.substring(
+ objectTypeNamePrefix.length,
+ rawObjectTypeName.length - objectTypeNameSuffix.length);
+ } else {
+ // Without compiler-specific prefix and suffix (manually annotated
+ // object types and '[unknown]').
+ objectTypeNameMap[objectTypeId] = rawObjectTypeName;
+ }
+ }
+
+ this.objectTypeNameMap_[pid] = objectTypeNameMap;
+ },
+
+ importAnnotations_() {
+ for (const id in this.traceAnnotations_) {
+ const annotation = tr.model.Annotation.fromDictIfPossible(
+ this.traceAnnotations_[id]);
+ if (!annotation) {
+ this.model_.importWarning({
+ type: 'annotation_warning',
+ message: 'Unrecognized traceAnnotation typeName \"' +
+ this.traceAnnotations_[id].typeName + '\"'
+ });
+ continue;
+ }
+ this.model_.addAnnotation(annotation);
+ }
+ },
+
+ /**
+ * Called by the Model after all other importers have imported their
+ * events.
+ */
+ finalizeImport() {
+ if (this.softwareMeasuredCpuCount_ !== undefined) {
+ this.model_.kernel.softwareMeasuredCpuCount =
+ this.softwareMeasuredCpuCount_;
+ }
+ this.createFlowSlices_();
+ this.createExplicitObjects_();
+ this.createImplicitObjects_();
+ this.createMemoryDumps_();
+ },
+
+ /* Events can have one or more stack frames associated with them, but
+ * that frame might be encoded either as a stack trace of program counters,
+ * or as a direct stack frame reference. This handles either case and
+ * if found, returns the stackframe.
+ */
+ getStackFrameForEvent_(event, opt_lookForEndEvent) {
+ let sf;
+ let stack;
+ if (opt_lookForEndEvent) {
+ sf = event.esf;
+ stack = event.estack;
+ } else {
+ sf = event.sf;
+ stack = event.stack;
+ }
+ if (stack !== undefined && sf !== undefined) {
+ this.model_.importWarning({
+ type: 'stack_frame_and_stack_error',
+ message: 'Event at ' + event.ts +
+ ' cannot have both a stack and a stackframe.'
+ });
+ return undefined;
+ }
+
+ if (stack !== undefined) {
+ return this.model_.resolveStackToStackFrame_(event.pid, stack);
+ }
+ if (sf === undefined) return undefined;
+
+ const stackFrame = this.model_.stackFrames['g' + sf];
+ if (stackFrame === undefined) {
+ this.model_.importWarning({
+ type: 'sample_import_error',
+ message: 'No frame for ' + sf
+ });
+ return;
+ }
+ return stackFrame;
+ },
+
+ resolveStackToStackFrame_(pid, stack) {
+ // TODO(alph,fmeawad): Add codemap resolution code here.
+ return undefined;
+ },
+
+ importSampleData() {
+ if (!this.sampleEvents_) return;
+ const m = this.model_;
+
+ // If this is the only importer, then fake-create the threads.
+ const events = this.sampleEvents_;
+ if (this.hasEvents_ === undefined) {
+ throw new Error('importEvents is not run before importSampleData');
+ } else if (!this.hasEvents_) {
+ for (let i = 0; i < events.length; i++) {
+ const event = events[i];
+ m.getOrCreateProcess(event.tid).getOrCreateThread(event.tid);
+ }
+ }
+
+ const threadsByTid = {};
+ m.getAllThreads().forEach(function(t) {
+ threadsByTid[t.tid] = t;
+ });
+
+ for (let i = 0; i < events.length; i++) {
+ const event = events[i];
+ const thread = threadsByTid[event.tid];
+ if (thread === undefined) {
+ m.importWarning({
+ type: 'sample_import_error',
+ message: 'Thread ' + events.tid + 'not found'
+ });
+ continue;
+ }
+
+ let cpu;
+ if (event.cpu !== undefined) {
+ cpu = m.kernel.getOrCreateCpu(event.cpu);
+ }
+
+ const leafNode = this.stackFrameTree_.getNode('g' + event.sf);
+
+ const sample = new tr.model.Sample(
+ this.toModelTimeFromUs_(event.ts),
+ event.name,
+ leafNode,
+ thread,
+ cpu,
+ event.weight
+ );
+ m.samples.push(sample);
+ }
+ },
+
+ createAsyncSlices_() {
+ if (this.allAsyncEvents_.length === 0) return;
+
+ this.allAsyncEvents_.sort(function(x, y) {
+ const d = x.event.ts - y.event.ts;
+ if (d !== 0) return d;
+ return x.sequenceNumber - y.sequenceNumber;
+ });
+
+ const legacyEvents = [];
+ // Group nestable async events by ID. Events with the same ID should
+ // belong to the same parent async event.
+ const nestableAsyncEventsByKey = {};
+ const nestableMeasureAsyncEventsByKey = {};
+ for (let i = 0; i < this.allAsyncEvents_.length; i++) {
+ const asyncEventState = this.allAsyncEvents_[i];
+ const event = asyncEventState.event;
+ if (event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
+ event.ph === 'p') {
+ legacyEvents.push(asyncEventState);
+ continue;
+ }
+ if (event.cat === undefined) {
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: 'Nestable async events (ph: b, e, or n) require a ' +
+ 'cat parameter.'
+ });
+ continue;
+ }
+
+ if (event.name === undefined) {
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: 'Nestable async events (ph: b, e, or n) require a ' +
+ 'name parameter.'
+ });
+ continue;
+ }
+
+ const id = TraceEventImporter.scopedIdForEvent_(event);
+ if (id === undefined) {
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: 'Nestable async events (ph: b, e, or n) require an ' +
+ 'id parameter.'
+ });
+ continue;
+ }
+
+ if (event.cat === 'blink.user_timing') {
+ const matched = MEASURE_NAME_REGEX.exec(event.name);
+ if (matched !== null) {
+ const key = matched[1] + ':' + event.cat;
+ try {
+ event.args = JSON.parse(Base64.atob(matched[3]) || '{}');
+ } catch (e) {
+ // ignored because we assume that we accidentally
+ // matched a performance.measure() where the user was
+ // not aware of this convention.
+ }
+ if (nestableMeasureAsyncEventsByKey[key] === undefined) {
+ nestableMeasureAsyncEventsByKey[key] = [];
+ }
+ nestableMeasureAsyncEventsByKey[key].push(asyncEventState);
+ continue;
+ }
+ }
+
+ const key = event.cat + ':' + id.toStringWithDelimiter(':');
+ if (nestableAsyncEventsByKey[key] === undefined) {
+ nestableAsyncEventsByKey[key] = [];
+ }
+ nestableAsyncEventsByKey[key].push(asyncEventState);
+ }
+ // Handle legacy async events.
+ this.createLegacyAsyncSlices_(legacyEvents);
+
+ // Parse nestable measure async events into AsyncSlices.
+ this.createNestableAsyncSlices_(nestableMeasureAsyncEventsByKey);
+
+ // Parse nestable async events into AsyncSlices.
+ this.createNestableAsyncSlices_(nestableAsyncEventsByKey);
+ },
+
+ createLegacyAsyncSlice_(events) {
+ const asyncEventState = events[events.length - 1];
+ const event = asyncEventState.event;
+ const name = event.name;
+ const id = TraceEventImporter.scopedIdForEvent_(event);
+ const key = id.toStringWithDelimiter(':');
+ const asyncSliceConstructor = tr.model.AsyncSlice.subTypes.getConstructor(
+ events[0].event.cat, name);
+ let duration;
+ if (event.ts !== undefined) {
+ duration = this.toModelTimeFromUs_(event.ts - events[0].event.ts);
+ }
+ const slice = new asyncSliceConstructor(
+ events[0].event.cat,
+ name,
+ getEventColor(events[0].event),
+ this.toModelTimeFromUs_(events[0].event.ts),
+ Object.assign({}, events[0].event.args, event.args),
+ duration || 0,
+ true,
+ undefined,
+ undefined,
+ events[0].event.argsStripped);
+ if (duration === undefined) {
+ slice.didNotFinish = true;
+ slice.error = 'Slice has no matching END. End time has been adjusted.';
+ // End time will be adjusted by AsyncSliceGroup.autoCloseOpenSlices().
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: 'Legacy async BEGIN event at ' +
+ events[0].event.ts + ' with name="' +
+ name + '" and id=' + key + ' was unmatched.'
+ });
+ }
+ slice.startThread = events[0].thread;
+ slice.endThread = asyncEventState.thread;
+ slice.id = key;
+
+ const stepType = events[1].event.ph;
+ let isValid = true;
+
+ // Create subSlices for each step. Skip the start and finish events,
+ // which are always first and last respectively.
+ for (let j = 1; j < events.length - 1; ++j) {
+ if (events[j].event.ph === 'T' || events[j].event.ph === 'p') {
+ isValid = this.assertStepTypeMatches_(stepType, events[j]);
+ if (!isValid) break;
+ }
+
+ if (events[j].event.ph === 'S') {
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: 'At ' + events[j].event.ts + ', a slice named "' +
+ name + '" with id=' + id +
+ ' had a step before the start event.'
+ });
+ continue;
+ }
+
+ if (events[j].event.ph === 'F') {
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: 'At ' + events[j].event.ts + ', a slice named ' +
+ name + ' with id=' + id +
+ ' had a step after the finish event.'
+ });
+ continue;
+ }
+
+ const startIndex = j + (stepType === 'T' ? 0 : -1);
+ const endIndex = startIndex + 1;
+
+ let subName = name;
+ if (!events[j].event.argsStripped &&
+ (events[j].event.ph === 'T' || events[j].event.ph === 'p')) {
+ subName = events[j].event.args.step;
+ }
+
+ const asyncSliceConstructor =
+ tr.model.AsyncSlice.subTypes.getConstructor(
+ events[0].event.cat,
+ subName);
+ let duration;
+ if (events[endIndex].event.ts !== undefined) {
+ duration = this.toModelTimeFromUs_(
+ events[endIndex].event.ts - events[startIndex].event.ts);
+ }
+ const subSlice = new asyncSliceConstructor(
+ events[0].event.cat,
+ subName,
+ getEventColor(events[0].event, subName + j),
+ this.toModelTimeFromUs_(events[startIndex].event.ts),
+ this.deepCopyIfNeeded_(events[j].event.args),
+ duration || 0,
+ undefined,
+ undefined,
+ events[startIndex].event.argsStripped);
+ if (duration === undefined) {
+ subSlice.didNotFinish = true;
+ subSlice.error =
+ 'Slice has no matching END. End time has been adjusted.';
+ // End time will be adjusted by AsyncSliceGroup.autoCloseOpenSlices().
+ }
+ subSlice.startThread = events[startIndex].thread;
+ subSlice.endThread = events[endIndex].thread;
+ subSlice.id = key;
+
+ slice.subSlices.push(subSlice);
+ }
+
+ if (isValid) {
+ // Add |slice| to the start-thread's asyncSlices.
+ slice.startThread.asyncSliceGroup.push(slice);
+ }
+ },
+
+ createLegacyAsyncSlices_(legacyEvents) {
+ if (legacyEvents.length === 0) return;
+
+ legacyEvents.sort(function(x, y) {
+ const d = x.event.ts - y.event.ts;
+ if (d !== 0) return d;
+ return x.sequenceNumber - y.sequenceNumber;
+ });
+
+ const asyncEventStatesByNameThenID = {};
+
+ for (let i = 0; i < legacyEvents.length; i++) {
+ const asyncEventState = legacyEvents[i];
+
+ const event = asyncEventState.event;
+ const name = event.name;
+ if (name === undefined) {
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: 'Async events (ph: S, T, p, or F) require a name ' +
+ ' parameter.'
+ });
+ continue;
+ }
+
+ const id = TraceEventImporter.scopedIdForEvent_(event);
+ if (id === undefined) {
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: 'Async events (ph: S, T, p, or F) require an id parameter.'
+ });
+ continue;
+ }
+ const key = id.toStringWithDelimiter(':');
+ // TODO(simonjam): Add a synchronous tick on the appropriate thread.
+
+ if (event.ph === 'S') {
+ if (asyncEventStatesByNameThenID[name] === undefined) {
+ asyncEventStatesByNameThenID[name] = {};
+ }
+ if (asyncEventStatesByNameThenID[name][key]) {
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: 'At ' + event.ts + ', a slice of the same id ' + id +
+ ' was alrady open.'
+ });
+ continue;
+ }
+ asyncEventStatesByNameThenID[name][key] = [];
+ asyncEventStatesByNameThenID[name][key].push(asyncEventState);
+ } else {
+ if (asyncEventStatesByNameThenID[name] === undefined) {
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: `At ${event.ts}, no slice named "${name}" was open.`,
+ });
+ continue;
+ }
+ if (asyncEventStatesByNameThenID[name][key] === undefined) {
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message:
+ `At ${event.ts}, no slice named "${name}" with id=${id} was ` +
+ 'open.',
+ });
+ continue;
+ }
+ const events = asyncEventStatesByNameThenID[name][key];
+ events.push(asyncEventState);
+
+ if (event.ph === 'F') {
+ this.createLegacyAsyncSlice_(events);
+ delete asyncEventStatesByNameThenID[name][key];
+ }
+ }
+ }
+
+ // Auto-close unmatched S events by creating a fake F event.
+ for (const [name, statesByID] of
+ Object.entries(asyncEventStatesByNameThenID)) {
+ for (const [id, states] of Object.entries(statesByID)) {
+ const startEvent = states[0].event;
+ // Don't set 'ts' in the fake 'F' event. createLegacyAsyncSlice_ will
+ // set duration=undefined and didNotFinish=true.
+ // AsyncSliceGroup.autoCloseOpenSlices() will set duration so that the
+ // AsyncSlice ends when the trace ends.
+ states.push({
+ sequenceNumber: 1 + states[states.length - 1].sequenceNumber,
+ event: {
+ ph: 'F',
+ name,
+ id: startEvent.id,
+ id2: startEvent.id2,
+ scope: startEvent.scope,
+ pid: startEvent.pid,
+ tid: startEvent.tid,
+ cat: startEvent.cat,
+ args: {},
+ },
+ thread: this.model_.getOrCreateProcess(startEvent.pid).
+ getOrCreateThread(startEvent.tid),
+ });
+ this.createLegacyAsyncSlice_(states);
+ }
+ }
+ },
+
+ createNestableAsyncSlices_(nestableEventsByKey) {
+ for (const key in nestableEventsByKey) {
+ const eventStateEntries = nestableEventsByKey[key];
+ // Stack of enclosing BEGIN events.
+ const parentStack = [];
+ for (let i = 0; i < eventStateEntries.length; ++i) {
+ const eventStateEntry = eventStateEntries[i];
+ // If this is the end of an event, match it to the start.
+ if (eventStateEntry.event.ph === 'e') {
+ // Walk up the parent stack to find the corresponding BEGIN for
+ // this END.
+ let parentIndex = -1;
+ for (let k = parentStack.length - 1; k >= 0; --k) {
+ if (parentStack[k].event.name === eventStateEntry.event.name) {
+ parentIndex = k;
+ break;
+ }
+ }
+ if (parentIndex === -1) {
+ // Unmatched end.
+ eventStateEntry.finished = false;
+ } else {
+ parentStack[parentIndex].end = eventStateEntry;
+ // Pop off all enclosing unmatched BEGINs util parentIndex.
+ while (parentIndex < parentStack.length) {
+ parentStack.pop();
+ }
+ }
+ }
+ // Inherit the current parent.
+ if (parentStack.length > 0) {
+ eventStateEntry.parentEntry = parentStack[parentStack.length - 1];
+ }
+ if (eventStateEntry.event.ph === 'b') {
+ parentStack.push(eventStateEntry);
+ }
+ }
+ const topLevelSlices = [];
+ for (let i = 0; i < eventStateEntries.length; ++i) {
+ const eventStateEntry = eventStateEntries[i];
+ // Skip matched END, as its slice will be created when we
+ // encounter its corresponding BEGIN.
+ if (eventStateEntry.event.ph === 'e' &&
+ eventStateEntry.finished === undefined) {
+ continue;
+ }
+ let startState = undefined;
+ let endState = undefined;
+ let sliceArgs = eventStateEntry.event.args || {};
+ let sliceError = undefined;
+ const id = TraceEventImporter.scopedIdForEvent_(
+ eventStateEntry.event);
+ if (eventStateEntry.event.ph === 'n') {
+ startState = eventStateEntry;
+ endState = eventStateEntry;
+ } else if (eventStateEntry.event.ph === 'b') {
+ if (eventStateEntry.end === undefined) {
+ // Unmatched BEGIN. End it when last event with this ID ends.
+ eventStateEntry.end =
+ eventStateEntries[eventStateEntries.length - 1];
+ sliceError =
+ 'Slice has no matching END. End time has been adjusted.';
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: 'Nestable async BEGIN event at ' +
+ eventStateEntry.event.ts + ' with name="' +
+ eventStateEntry.event.name + '" and id=' + id +
+ ' was unmatched.'
+ });
+ } else {
+ // Include args for both END and BEGIN for a matched pair.
+ function concatenateArguments(args1, args2) {
+ if (args1.params === undefined || args2.params === undefined) {
+ return Object.assign({}, args1, args2);
+ }
+ // Make an argument object to hold the combined params.
+ const args3 = {};
+ args3.params = Object.assign({}, args1.params, args2.params);
+ return Object.assign({}, args1, args2, args3);
+ }
+ const endArgs = eventStateEntry.end.event.args || {};
+ sliceArgs = concatenateArguments(sliceArgs, endArgs);
+ }
+ startState = eventStateEntry;
+ endState = eventStateEntry.end;
+ } else {
+ // Unmatched END. Start it at the first event with this ID starts.
+ sliceError =
+ 'Slice has no matching BEGIN. Start time has been adjusted.';
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: 'Nestable async END event at ' +
+ eventStateEntry.event.ts + ' with name=' +
+ eventStateEntry.event.name +
+ ' and id=' + id + ' was unmatched.'
+ });
+ startState = eventStateEntries[0];
+ endState = eventStateEntry;
+ }
+
+ const isTopLevel = (eventStateEntry.parentEntry === undefined);
+ const asyncSliceConstructor =
+ tr.model.AsyncSlice.subTypes.getConstructor(
+ eventStateEntry.event.cat,
+ eventStateEntry.event.name);
+
+ let threadStart = undefined;
+ let threadDuration = undefined;
+ if (startState.event.tts && startState.event.use_async_tts) {
+ threadStart = this.toModelTimeFromUs_(startState.event.tts);
+ if (endState.event.tts) {
+ const threadEnd = this.toModelTimeFromUs_(endState.event.tts);
+ threadDuration = threadEnd - threadStart;
+ }
+ }
+
+ const slice = new asyncSliceConstructor(
+ eventStateEntry.event.cat,
+ eventStateEntry.event.name,
+ getEventColor(endState.event),
+ this.toModelTimeFromUs_(startState.event.ts),
+ sliceArgs,
+ this.toModelTimeFromUs_(endState.event.ts - startState.event.ts),
+ isTopLevel,
+ threadStart,
+ threadDuration,
+ startState.event.argsStripped);
+
+ slice.startThread = startState.thread;
+ slice.endThread = endState.thread;
+
+ slice.startStackFrame = this.getStackFrameForEvent_(startState.event);
+ slice.endStackFrame = this.getStackFrameForEvent_(endState.event);
+
+ slice.id = key;
+ if (sliceError !== undefined) {
+ slice.error = sliceError;
+ }
+ eventStateEntry.slice = slice;
+ // Add the slice to the topLevelSlices array if there is no parent.
+ // Otherwise, add the slice to the subSlices of its parent.
+ if (isTopLevel) {
+ topLevelSlices.push(slice);
+ } else if (eventStateEntry.parentEntry.slice !== undefined) {
+ eventStateEntry.parentEntry.slice.subSlices.push(slice);
+ }
+ }
+ for (let si = 0; si < topLevelSlices.length; si++) {
+ topLevelSlices[si].startThread.asyncSliceGroup.push(
+ topLevelSlices[si]);
+ }
+ }
+ },
+
+ assertStepTypeMatches_(stepType, event) {
+ if (stepType !== event.event.ph) {
+ this.model_.importWarning({
+ type: 'async_slice_parse_error',
+ message: 'At ' + event.event.ts + ', a slice named ' +
+ event.event.name + ' with id=' +
+ TraceEventImporter.scopedIdForEvent_(event.event) +
+ ' had both begin and end steps, which is not allowed.'
+ });
+ return false;
+ }
+ return true;
+ },
+
+ validateFlowEvent_(event) {
+ if (event.name === undefined) {
+ this.model_.importWarning({
+ type: 'flow_slice_parse_error',
+ message: 'Flow events (ph: s, t or f) require a name parameter.'
+ });
+ return false;
+ }
+
+ // Support Flow API v1.
+ if (event.ph === 's' || event.ph === 'f' || event.ph === 't') {
+ if (event.id === undefined) {
+ this.model_.importWarning({
+ type: 'flow_slice_parse_error',
+ message: 'Flow events (ph: s, t or f) require an id parameter.'
+ });
+ return false;
+ }
+ return true;
+ }
+
+ // Support Flow API v2.
+ if (event.bind_id) {
+ if (event.flow_in === undefined && event.flow_out === undefined) {
+ this.model_.importWarning({
+ type: 'flow_slice_parse_error',
+ message: 'Flow producer or consumer require flow_in or flow_out.'
+ });
+ return false;
+ }
+ return true;
+ }
+
+ return false;
+ },
+
+ createFlowSlices_() {
+ if (this.allFlowEvents_.length === 0) return;
+
+ const createFlowEvent = function(thread, event, opt_slice) {
+ let startSlice;
+ let flowId;
+ let flowStartTs;
+
+ if (event.bind_id) {
+ // Support Flow API v2.
+ startSlice = opt_slice;
+ flowId = event.bind_id;
+ flowStartTs = this.toModelTimeFromUs_(event.ts + event.dur);
+ } else {
+ // Support Flow API v1.
+ const ts = this.toModelTimeFromUs_(event.ts);
+ startSlice = thread.sliceGroup.findSliceAtTs(ts);
+ if (startSlice === undefined) return undefined;
+ flowId = event.id;
+ flowStartTs = ts;
+ }
+
+ const flowEvent = new tr.model.FlowEvent(
+ event.cat,
+ flowId,
+ event.name,
+ getEventColor(event),
+ flowStartTs,
+ this.deepCopyAlways_(event.args));
+ flowEvent.startSlice = startSlice;
+ flowEvent.startStackFrame = this.getStackFrameForEvent_(event);
+ flowEvent.endStackFrame = undefined;
+ startSlice.outFlowEvents.push(flowEvent);
+ return flowEvent;
+ }.bind(this);
+
+ const finishFlowEventWith = function(
+ flowEvent, thread, event, refGuid, bindToParent, opt_slice) {
+ let endSlice;
+
+ if (event.bind_id) {
+ // Support Flow API v2.
+ endSlice = opt_slice;
+ } else {
+ // Support Flow API v1.
+ const ts = this.toModelTimeFromUs_(event.ts);
+ if (bindToParent) {
+ endSlice = thread.sliceGroup.findSliceAtTs(ts);
+ } else {
+ endSlice = thread.sliceGroup.findNextSliceAfter(ts, refGuid);
+ }
+ if (endSlice === undefined) return false;
+ }
+
+ endSlice.inFlowEvents.push(flowEvent);
+ flowEvent.endSlice = endSlice;
+ flowEvent.duration =
+ this.toModelTimeFromUs_(event.ts) - flowEvent.start;
+ flowEvent.endStackFrame = this.getStackFrameForEvent_(event);
+ this.mergeArgsInto_(flowEvent.args, event.args, flowEvent.title);
+ return true;
+ }.bind(this);
+
+ const processFlowConsumer = function(
+ flowIdToEvent, sliceGuidToEvent, event, slice) {
+ let flowEvent = flowIdToEvent[event.bind_id];
+ if (flowEvent === undefined) {
+ this.model_.importWarning({
+ type: 'flow_slice_ordering_error',
+ message: 'Flow consumer ' + event.bind_id + ' does not have ' +
+ 'a flow producer'});
+ return false;
+ } else if (flowEvent.endSlice) {
+ // One flow producer can have more than one flow consumers.
+ // In this case, create a new flow event using the flow producer.
+ const flowProducer = flowEvent.startSlice;
+ flowEvent = createFlowEvent(undefined,
+ sliceGuidToEvent[flowProducer.guid], flowProducer);
+ }
+
+ const refGuid = undefined;
+ const ok = finishFlowEventWith(flowEvent, undefined, event,
+ refGuid, undefined, slice);
+ if (ok) {
+ this.model_.flowEvents.push(flowEvent);
+ } else {
+ this.model_.importWarning({
+ type: 'flow_slice_end_error',
+ message: 'Flow consumer ' + event.bind_id + ' does not end ' +
+ 'at an actual slice, so cannot be created.'});
+ return false;
+ }
+
+ return true;
+ }.bind(this);
+
+ const processFlowProducer = function(
+ flowIdToEvent, flowStatus, event, slice) {
+ if (flowIdToEvent[event.bind_id] &&
+ flowStatus[event.bind_id]) {
+ // Can't open the same flow again while it's still open.
+ // This is essentially the multi-producer case which we don't support
+ this.model_.importWarning({
+ type: 'flow_slice_start_error',
+ message: 'Flow producer ' + event.bind_id + ' already seen'});
+ return false;
+ }
+
+ const flowEvent = createFlowEvent(undefined, event, slice);
+ if (!flowEvent) {
+ this.model_.importWarning({
+ type: 'flow_slice_start_error',
+ message: 'Flow producer ' + event.bind_id + ' does not start' +
+ 'a flow'});
+ return false;
+ }
+ flowIdToEvent[event.bind_id] = flowEvent;
+ }.bind(this);
+
+ // Actual import.
+ this.allFlowEvents_.sort(function(x, y) {
+ const d = x.event.ts - y.event.ts;
+ if (d !== 0) return d;
+ return x.sequenceNumber - y.sequenceNumber;
+ });
+
+ const flowIdToEvent = {};
+ const sliceGuidToEvent = {};
+ const flowStatus = {}; // true: open; false: closed.
+ for (let i = 0; i < this.allFlowEvents_.length; ++i) {
+ const data = this.allFlowEvents_[i];
+ const refGuid = data.refGuid;
+ const event = data.event;
+ const thread = data.thread;
+
+ if (!this.validateFlowEvent_(event)) continue;
+
+ // Support for Flow API v2.
+ if (event.bind_id) {
+ const slice = data.slice;
+ sliceGuidToEvent[slice.guid] = event;
+
+ if (event.flowPhase === PRODUCER) {
+ if (!processFlowProducer(flowIdToEvent, flowStatus, event, slice)) {
+ continue;
+ }
+ flowStatus[event.bind_id] = true; // open the flow.
+ } else {
+ if (!processFlowConsumer(flowIdToEvent, sliceGuidToEvent,
+ event, slice)) {
+ continue;
+ }
+ flowStatus[event.bind_id] = false; // close the flow.
+
+ if (event.flowPhase === STEP) {
+ if (!processFlowProducer(flowIdToEvent, flowStatus,
+ event, slice)) {
+ continue;
+ }
+ flowStatus[event.bind_id] = true; // open the flow again.
+ }
+ }
+ continue;
+ }
+
+ // Support for Flow API v1.
+ let flowEvent;
+ if (event.ph === 's') {
+ if (flowIdToEvent[event.id]) {
+ this.model_.importWarning({
+ type: 'flow_slice_start_error',
+ message: 'event id ' + event.id + ' already seen when ' +
+ 'encountering start of flow event.'});
+ continue;
+ }
+ flowEvent = createFlowEvent(thread, event);
+ if (!flowEvent) {
+ this.model_.importWarning({
+ type: 'flow_slice_start_error',
+ message: 'event id ' + event.id + ' does not start ' +
+ 'at an actual slice, so cannot be created.'});
+ continue;
+ }
+ flowIdToEvent[event.id] = flowEvent;
+ } else if (event.ph === 't' || event.ph === 'f') {
+ flowEvent = flowIdToEvent[event.id];
+ if (flowEvent === undefined) {
+ this.model_.importWarning({
+ type: 'flow_slice_ordering_error',
+ message: 'Found flow phase ' + event.ph + ' for id: ' + event.id +
+ ' but no flow start found.'
+ });
+ continue;
+ }
+
+ let bindToParent = event.ph === 't';
+
+ if (event.ph === 'f') {
+ if (event.bp === undefined) {
+ // TODO(yuhaoz): In flow V2, there is no notion of binding point.
+ // Removal of binding point is tracked in
+ // https://github.com/google/trace-viewer/issues/991.
+ if (event.cat.indexOf('input') > -1) {
+ bindToParent = true;
+ } else if (event.cat.indexOf('ipc.flow') > -1) {
+ bindToParent = true;
+ }
+ } else {
+ if (event.bp !== 'e') {
+ this.model_.importWarning({
+ type: 'flow_slice_bind_point_error',
+ message: 'Flow event with invalid binding point (event.bp).'
+ });
+ continue;
+ }
+ bindToParent = true;
+ }
+ }
+
+ const ok = finishFlowEventWith(flowEvent, thread, event,
+ refGuid, bindToParent);
+ if (ok) {
+ this.model_.flowEvents.push(flowEvent);
+ } else {
+ this.model_.importWarning({
+ type: 'flow_slice_end_error',
+ message: 'event id ' + event.id + ' does not end ' +
+ 'at an actual slice, so cannot be created.'});
+ }
+ flowIdToEvent[event.id] = undefined;
+
+ // If this is a step, then create another flow event.
+ if (ok && event.ph === 't') {
+ flowEvent = createFlowEvent(thread, event);
+ flowIdToEvent[event.id] = flowEvent;
+ }
+ }
+ }
+ },
+
+ /**
+ * This function creates objects described via the N, D, and O phase
+ * events.
+ */
+ createExplicitObjects_() {
+ if (this.allObjectEvents_.length === 0) return;
+
+ const processEvent = function(objectEventState) {
+ const event = objectEventState.event;
+ const scopedId = TraceEventImporter.scopedIdForEvent_(event);
+ const thread = objectEventState.thread;
+ if (event.name === undefined) {
+ this.model_.importWarning({
+ type: 'object_parse_error',
+ message: 'While processing ' + JSON.stringify(event) + ': ' +
+ 'Object events require an name parameter.'
+ });
+ }
+
+ if (scopedId === undefined || scopedId.id === undefined) {
+ this.model_.importWarning({
+ type: 'object_parse_error',
+ message: 'While processing ' + JSON.stringify(event) + ': ' +
+ 'Object events require an id parameter.'
+ });
+ }
+ const process = thread.parent;
+ const ts = this.toModelTimeFromUs_(event.ts);
+ let instance;
+ if (event.ph === 'N') {
+ try {
+ instance = process.objects.idWasCreated(
+ scopedId, event.cat, event.name, ts);
+ } catch (e) {
+ this.model_.importWarning({
+ type: 'object_parse_error',
+ message: 'While processing create of ' +
+ scopedId + ' at ts=' + ts + ': ' + e
+ });
+ return;
+ }
+ } else if (event.ph === 'O') {
+ if (event.args.snapshot === undefined) {
+ this.model_.importWarning({
+ type: 'object_parse_error',
+ message: 'While processing ' + scopedId + ' at ts=' + ts + ': ' +
+ 'Snapshots must have args: {snapshot: ...}'
+ });
+ return;
+ }
+ let snapshot;
+ try {
+ const args = this.deepCopyIfNeeded_(event.args.snapshot);
+ let cat;
+ if (args.cat) {
+ cat = args.cat;
+ delete args.cat;
+ } else {
+ cat = event.cat;
+ }
+
+ let baseTypename;
+ if (args.base_type) {
+ baseTypename = args.base_type;
+ delete args.base_type;
+ } else {
+ baseTypename = undefined;
+ }
+ snapshot = process.objects.addSnapshot(
+ scopedId, cat, event.name, ts, args, baseTypename);
+ snapshot.snapshottedOnThread = thread;
+ } catch (e) {
+ this.model_.importWarning({
+ type: 'object_parse_error',
+ message: 'While processing snapshot of ' +
+ scopedId + ' at ts=' + ts + ': ' + e
+ });
+ return;
+ }
+ instance = snapshot.objectInstance;
+ } else if (event.ph === 'D') {
+ try {
+ process.objects.idWasDeleted(scopedId, event.cat, event.name, ts);
+ const instanceMap = process.objects.getOrCreateInstanceMap_(
+ scopedId);
+ instance = instanceMap.lastInstance;
+ } catch (e) {
+ this.model_.importWarning({
+ type: 'object_parse_error',
+ message: 'While processing delete of ' +
+ scopedId + ' at ts=' + ts + ': ' + e
+ });
+ return;
+ }
+ }
+
+ if (instance) {
+ instance.colorId = getEventColor(event, instance.typeName);
+ }
+ }.bind(this);
+
+ this.allObjectEvents_.sort(function(x, y) {
+ const d = x.event.ts - y.event.ts;
+ if (d !== 0) return d;
+ return x.sequenceNumber - y.sequenceNumber;
+ });
+
+ const allObjectEvents = this.allObjectEvents_;
+ for (let i = 0; i < allObjectEvents.length; i++) {
+ const objectEventState = allObjectEvents[i];
+ try {
+ processEvent.call(this, objectEventState);
+ } catch (e) {
+ this.model_.importWarning({
+ type: 'object_parse_error',
+ message: e.message
+ });
+ }
+ }
+ },
+
+ createImplicitObjects_() {
+ for (const proc of Object.values(this.model_.processes)) {
+ this.createImplicitObjectsForProcess_(proc);
+ }
+ },
+
+ // Here, we collect all the snapshots that internally contain a
+ // Javascript-level object inside their args list that has an "id" field,
+ // and turn that into a snapshot of the instance referred to by id.
+ createImplicitObjectsForProcess_(process) {
+ function processField(referencingObject,
+ referencingObjectFieldName,
+ referencingObjectFieldValue,
+ containingSnapshot) {
+ if (!referencingObjectFieldValue) return;
+
+ if (referencingObjectFieldValue instanceof
+ tr.model.ObjectSnapshot) {
+ return null;
+ }
+ if (referencingObjectFieldValue.id === undefined) return;
+
+ const implicitSnapshot = referencingObjectFieldValue;
+
+ const rawId = implicitSnapshot.id;
+ const m = /(.+)\/(.+)/.exec(rawId);
+ if (!m) {
+ throw new Error('Implicit snapshots must have names.');
+ }
+ delete implicitSnapshot.id;
+ const name = m[1];
+ const id = m[2];
+ let res;
+
+ let cat;
+ if (implicitSnapshot.cat !== undefined) {
+ cat = implicitSnapshot.cat;
+ } else {
+ cat = containingSnapshot.objectInstance.category;
+ }
+
+ let baseTypename;
+ if (implicitSnapshot.base_type) {
+ baseTypename = implicitSnapshot.base_type;
+ } else {
+ baseTypename = undefined;
+ }
+
+ const scope = containingSnapshot.objectInstance.scopedId.scope;
+
+ try {
+ res = process.objects.addSnapshot(
+ new tr.model.ScopedId(scope, id), cat,
+ name, containingSnapshot.ts,
+ implicitSnapshot, baseTypename);
+ } catch (e) {
+ this.model_.importWarning({
+ type: 'object_snapshot_parse_error',
+ message: 'While processing implicit snapshot of ' +
+ rawId + ' at ts=' + containingSnapshot.ts + ': ' + e
+ });
+ return;
+ }
+ res.objectInstance.hasImplicitSnapshots = true;
+ res.containingSnapshot = containingSnapshot;
+ res.snapshottedOnThread = containingSnapshot.snapshottedOnThread;
+ referencingObject[referencingObjectFieldName] = res;
+ if (!(res instanceof tr.model.ObjectSnapshot)) {
+ throw new Error('Created object must be instanceof snapshot');
+ }
+ return res.args;
+ }
+
+ /**
+ * Iterates over the fields in the object, calling func for every
+ * field/value found.
+ *
+ * @return {object} If the function does not want the field's value to be
+ * iterated, return null. If iteration of the field value is desired, then
+ * return either undefined (if the field value did not change) or the new
+ * field value if it was changed.
+ */
+ function iterObject(object, func, containingSnapshot, thisArg) {
+ if (!(object instanceof Object)) return;
+
+ if (object instanceof Array) {
+ for (let i = 0; i < object.length; i++) {
+ const res = func.call(thisArg, object, i, object[i],
+ containingSnapshot);
+ if (res === null) continue;
+ if (res) {
+ iterObject(res, func, containingSnapshot, thisArg);
+ } else {
+ iterObject(object[i], func, containingSnapshot, thisArg);
+ }
+ }
+ return;
+ }
+
+ for (const key in object) {
+ const res = func.call(thisArg, object, key, object[key],
+ containingSnapshot);
+ if (res === null) continue;
+ if (res) {
+ iterObject(res, func, containingSnapshot, thisArg);
+ } else {
+ iterObject(object[key], func, containingSnapshot, thisArg);
+ }
+ }
+ }
+
+ // TODO(nduca): We may need to iterate the instances in sorted order by
+ // creationTs.
+ process.objects.iterObjectInstances(function(instance) {
+ instance.snapshots.forEach(function(snapshot) {
+ if (snapshot.args.id !== undefined) {
+ throw new Error('args cannot have an id field inside it');
+ }
+ iterObject(snapshot.args, processField, snapshot, this);
+ }, this);
+ }, this);
+ },
+
+ // Takes an object of type: PID -> [process memory dump events]
+ // Returns smallest timestamp among events or Infinity if there are none.
+ minimalTimestampInPidToEvents_(pidToEvents) {
+ let smallestTs = Infinity;
+ for (const events of Object.values(pidToEvents)) {
+ for (const event of events) {
+ if (event.ts < smallestTs) {
+ smallestTs = event.ts;
+ }
+ }
+ }
+ return smallestTs;
+ },
+
+ createMemoryDumps_() {
+ // We must create the global dumps in chronological order since later
+ // heap dumps can depend on earlier ones. Not all events within a single
+ // global dump have the same timestamp but events for two different
+ // global dumps can not be interspersed. However, for consistency,
+ // we sort the global dumps by the earliest timestamp present in an event
+ // in that dump.
+ const pairs = Object.entries(this.allMemoryDumpEvents_);
+ const key = x => this.minimalTimestampInPidToEvents_(x);
+ pairs.sort((x, y) => key(x[1]) - key(y[1]));
+ for (const [dumpId, pidToEvents] of pairs) {
+ this.createGlobalMemoryDump_(pidToEvents, dumpId);
+ }
+ },
+
+ createGlobalMemoryDump_(dumpIdEvents, dumpId) {
+ // 1. Create a GlobalMemoryDump for the provided process memory dump
+ // the events, all of which have the same dump ID.
+
+ // Calculate the range of the global memory dump.
+ const globalRange = new tr.b.math.Range();
+ for (const pid in dumpIdEvents) {
+ const processEvents = dumpIdEvents[pid];
+ for (let i = 0; i < processEvents.length; i++) {
+ globalRange.addValue(this.toModelTimeFromUs_(processEvents[i].ts));
+ }
+ }
+ if (globalRange.isEmpty) {
+ throw new Error('Internal error: Global memory dump without events');
+ }
+
+ // Create the global memory dump.
+ const globalMemoryDump = new tr.model.GlobalMemoryDump(
+ this.model_, globalRange.min);
+ globalMemoryDump.duration = globalRange.range;
+ this.model_.globalMemoryDumps.push(globalMemoryDump);
+
+ const globalMemoryAllocatorDumpsByFullName = {};
+ const levelsOfDetail = {};
+ const allMemoryAllocatorDumpsByGuid = {};
+
+ // 2. Create a ProcessMemoryDump for each PID in the provided process
+ // memory dump events. Everything except for edges between memory
+ // allocator dumps is parsed from the process memory dump trace events at
+ // this step.
+ for (const pid in dumpIdEvents) {
+ this.createProcessMemoryDump_(globalMemoryDump,
+ globalMemoryAllocatorDumpsByFullName, levelsOfDetail,
+ allMemoryAllocatorDumpsByGuid, dumpIdEvents[pid], pid, dumpId);
+ }
+
+ // 3. Set the level of detail and memory allocator dumps of the
+ // GlobalMemoryDump, which come from the process memory dump trace
+ // events parsed in the prebvious step.
+ globalMemoryDump.levelOfDetail = levelsOfDetail.global;
+
+ // Find the root allocator dumps and establish the parent links of
+ // the global memory dump.
+ globalMemoryDump.memoryAllocatorDumps =
+ this.inferMemoryAllocatorDumpTree_(
+ globalMemoryAllocatorDumpsByFullName);
+
+ // 4. Finally, parse the edges between all memory allocator dumps within
+ // the GlobalMemoryDump. This can only be done once all memory allocator
+ // dumps have been parsed (i.e. it is necessary to iterate over the
+ // process memory dump trace events once more).
+ this.parseMemoryDumpAllocatorEdges_(allMemoryAllocatorDumpsByGuid,
+ dumpIdEvents, dumpId);
+ },
+
+ createProcessMemoryDump_(globalMemoryDump,
+ globalMemoryAllocatorDumpsByFullName, levelsOfDetail,
+ allMemoryAllocatorDumpsByGuid, processEvents, pid, dumpId) {
+ // Calculate the range of the process memory dump.
+ const processRange = new tr.b.math.Range();
+ for (let i = 0; i < processEvents.length; i++) {
+ processRange.addValue(this.toModelTimeFromUs_(processEvents[i].ts));
+ }
+ if (processRange.isEmpty) {
+ throw new Error('Internal error: Process memory dump without events');
+ }
+
+ // Create the process memory dump.
+ const process = this.model_.getOrCreateProcess(pid);
+ const processMemoryDump = new tr.model.ProcessMemoryDump(
+ globalMemoryDump, process, processRange.min);
+ processMemoryDump.duration = processRange.range;
+ process.memoryDumps.push(processMemoryDump);
+ globalMemoryDump.processMemoryDumps[pid] = processMemoryDump;
+
+ const processMemoryAllocatorDumpsByFullName = {};
+
+ // Parse all process memory dump trace events for the newly created
+ // ProcessMemoryDump.
+ for (let i = 0; i < processEvents.length; i++) {
+ const processEvent = processEvents[i];
+
+ const dumps = processEvent.args.dumps;
+ if (dumps === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: '\'dumps\' field not found in a process memory dump' +
+ ' event for PID=' + pid + ' and dump ID=' + dumpId + '.'
+ });
+ continue;
+ }
+
+ // Totals, VM regions, and heap dumps for the newly created
+ // ProcessMemoryDump should be present in at most one event, so they
+ // can be added to the ProcessMemoryDump immediately.
+ this.parseMemoryDumpTotals_(processMemoryDump, dumps, pid, dumpId);
+ this.parseMemoryDumpVmRegions_(processMemoryDump, dumps, pid, dumpId);
+ this.parseMemoryDumpHeapDumps_(processMemoryDump, dumps, pid, dumpId);
+
+ // All process memory dump trace events for the newly created
+ // ProcessMemoryDump must be processed before level of detail and
+ // allocator dumps can be added to it.
+ this.parseMemoryDumpLevelOfDetail_(levelsOfDetail, dumps, pid,
+ dumpId);
+ this.parseMemoryDumpAllocatorDumps_(processMemoryDump, globalMemoryDump,
+ processMemoryAllocatorDumpsByFullName,
+ globalMemoryAllocatorDumpsByFullName,
+ allMemoryAllocatorDumpsByGuid, dumps, pid, dumpId);
+ }
+
+ if (levelsOfDetail.process === undefined) {
+ // Infer level of detail from the presence of VM regions in legacy
+ // traces (where raw process memory dump events don't contain the
+ // level_of_detail field). These traces will not have BACKGROUND mode.
+ levelsOfDetail.process = processMemoryDump.vmRegions ? DETAILED : LIGHT;
+ }
+ if (!this.updateMemoryDumpLevelOfDetail_(
+ levelsOfDetail, 'global', levelsOfDetail.process)) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'diffent levels of detail provided for global memory' +
+ ' dump (dump ID=' + dumpId + ').'
+ });
+ }
+ processMemoryDump.levelOfDetail = levelsOfDetail.process;
+ delete levelsOfDetail.process; // Reused for all process dumps.
+
+ // Find the root allocator dumps and establish the parent links of
+ // the process memory dump.
+ processMemoryDump.memoryAllocatorDumps =
+ this.inferMemoryAllocatorDumpTree_(
+ processMemoryAllocatorDumpsByFullName);
+ },
+
+ parseMemoryDumpTotals_(processMemoryDump, dumps, pid, dumpId) {
+ const rawTotals = dumps.process_totals;
+ if (rawTotals === undefined) return;
+
+ if (processMemoryDump.totals !== undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Process totals provided multiple times for' +
+ ' process memory dump for PID=' + pid +
+ ' and dump ID=' + dumpId + '.'
+ });
+ return;
+ }
+
+ const totals = {};
+ let platformSpecificTotals = undefined;
+
+ for (const rawTotalName in rawTotals) {
+ const rawTotalValue = rawTotals[rawTotalName];
+ if (rawTotalValue === undefined) continue;
+
+ // Total resident bytes.
+ if (rawTotalName === 'resident_set_bytes') {
+ totals.residentBytes = parseInt(rawTotalValue, 16);
+ continue;
+ }
+
+ // Peak resident bytes.
+ if (rawTotalName === 'peak_resident_set_bytes') {
+ totals.peakResidentBytes = parseInt(rawTotalValue, 16);
+ continue;
+ }
+ if (rawTotalName === 'is_peak_rss_resetable') {
+ totals.arePeakResidentBytesResettable = !!rawTotalValue;
+ continue;
+ }
+
+ // Private footprint
+ if (rawTotalName === 'private_footprint_bytes') {
+ totals.privateFootprintBytes = parseInt(rawTotalValue, 16);
+ continue;
+ }
+
+ // OS-specific totals (e.g. private resident on Mac).
+ if (platformSpecificTotals === undefined) {
+ platformSpecificTotals = {};
+ totals.platformSpecific = platformSpecificTotals;
+ }
+ platformSpecificTotals[rawTotalName] = parseInt(rawTotalValue, 16);
+ }
+
+ // Either both peak_resident_set_bytes and is_peak_rss_resetable should
+ // be present in the trace, or neither.
+ if (totals.peakResidentBytes === undefined &&
+ totals.arePeakResidentBytesResettable !== undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Optional field peak_resident_set_bytes found' +
+ ' but is_peak_rss_resetable not found in' +
+ ' process memory dump for PID=' + pid +
+ ' and dump ID=' + dumpId + '.'
+ });
+ }
+ if (totals.arePeakResidentBytesResettable !== undefined &&
+ totals.peakResidentBytes === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Optional field is_peak_rss_resetable found' +
+ ' but peak_resident_set_bytes not found in' +
+ ' process memory dump for PID=' + pid +
+ ' and dump ID=' + dumpId + '.'
+ });
+ }
+
+ processMemoryDump.totals = totals;
+ },
+
+ parseMemoryDumpVmRegions_(processMemoryDump, dumps, pid, dumpId) {
+ const rawProcessMmaps = dumps.process_mmaps;
+ if (rawProcessMmaps === undefined) return;
+
+ const rawVmRegions = rawProcessMmaps.vm_regions;
+ if (rawVmRegions === undefined) return;
+
+ if (processMemoryDump.vmRegions !== undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'VM regions provided multiple times for' +
+ ' process memory dump for PID=' + pid +
+ ' and dump ID=' + dumpId + '.'
+ });
+ return;
+ }
+
+ // In Chromium under //services/resource_coordinator/public see
+ // mojom/memory_instrumentation/memory_instrumentation.mojom and
+ // cpp/memory_instrumentation/tracing_observer.cc
+ const vmRegions = new Array(rawVmRegions.length);
+ for (let i = 0; i < rawVmRegions.length; i++) {
+ const rawVmRegion = rawVmRegions[i];
+
+ const byteStats = {};
+ const rawByteStats = rawVmRegion.bs;
+ for (const rawByteStatName in rawByteStats) {
+ const rawByteStatValue = rawByteStats[rawByteStatName];
+ if (rawByteStatValue === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Byte stat \'' + rawByteStatName + '\' of VM region ' +
+ i + ' (' + rawVmRegion.mf + ') in process memory dump for ' +
+ 'PID=' + pid + ' and dump ID=' + dumpId +
+ ' does not have a value.'
+ });
+ continue;
+ }
+ const byteStatName = BYTE_STAT_NAME_MAP[rawByteStatName];
+ if (byteStatName === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Unknown byte stat name \'' + rawByteStatName + '\' (' +
+ rawByteStatValue + ') of VM region ' + i + ' (' +
+ rawVmRegion.mf + ') in process memory dump for PID=' + pid +
+ ' and dump ID=' + dumpId + '.'
+ });
+ continue;
+ }
+ byteStats[byteStatName] = parseInt(rawByteStatValue, 16);
+ // OSX does not report PSS and instead emits all zeros.
+ // Zero is a valid value for some byteStats but not PSS so this could
+ // could to confusion when we later compute with and display the PSS.
+ // To avoid this we ignore zero PSS values (since PSS should never
+ // be zero). See: github.com/catapult-project/catapult/issues/3501
+ if (byteStatName === 'proportionalResident' &&
+ byteStats[byteStatName] === 0) {
+ byteStats[byteStatName] = undefined;
+ }
+ }
+
+ vmRegions[i] = new tr.model.VMRegion(
+ parseInt(rawVmRegion.sa, 16), // startAddress
+ parseInt(rawVmRegion.sz, 16), // sizeInBytes
+ rawVmRegion.pf, // protectionFlags
+ rawVmRegion.mf, // mappedFile
+ byteStats);
+ }
+
+ processMemoryDump.vmRegions =
+ tr.model.VMRegionClassificationNode.fromRegions(vmRegions);
+ },
+
+ parseMemoryDumpHeapDumps_(processMemoryDump, dumps, pid, dumpId) {
+ const idPrefix = 'p' + pid + ':';
+ let importer;
+ if (dumps.heaps) {
+ const processTypeMap = this.objectTypeNameMap_[pid];
+ if (processTypeMap === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Missing mapping from object type IDs to names.'
+ });
+ }
+ importer = new LegacyHeapDumpTraceEventImporter(this.model_,
+ processMemoryDump, processTypeMap, idPrefix, dumpId, dumps.heaps);
+ } else if (dumps.heaps_v2) {
+ const data = dumps.heaps_v2;
+ this.heapProfileExpander = this.heapProfileExpander.expandData(data);
+ // TODO(hjd): Unify how we are reading & inflating stack frame data.
+ this.addNewStackFramesFromExpander_(this.heapProfileExpander, idPrefix);
+ importer = new HeapDumpTraceEventImporter(this.heapProfileExpander,
+ this.model_.stackFrames, processMemoryDump, idPrefix, this.model_);
+ }
+
+ if (!importer) return;
+
+ const heapDumps = importer.parse();
+ if (!heapDumps) return;
+
+ if (processMemoryDump.heapDumps !== undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Heap dumps provided multiple times for' +
+ ' process memory dump for PID=' + pid +
+ ' and dump ID=' + dumpId + '.'
+ });
+ return;
+ }
+
+ if (Object.keys(heapDumps).length > 0) {
+ processMemoryDump.heapDumps = heapDumps;
+ }
+ },
+
+ addNewStackFramesFromExpander_(expander, idPrefix) {
+ const nodeMap = expander.getNewMap('nodes');
+ const newStackFrames = {};
+ for (const [id, stackFrame] of nodeMap.entries()) {
+ if (!this.model_.stackFrames[idPrefix + id]) {
+ newStackFrames[id] = {
+ id,
+ name: expander.getString(stackFrame.name_sid),
+ };
+ if (stackFrame.parent) newStackFrames[id].parent = stackFrame.parent;
+ }
+ }
+ this.importStackFrames_(newStackFrames, idPrefix);
+ },
+
+ parseMemoryDumpLevelOfDetail_(levelsOfDetail, dumps, pid,
+ dumpId) {
+ const rawLevelOfDetail = dumps.level_of_detail;
+ let level;
+ switch (rawLevelOfDetail) {
+ case 'background':
+ level = BACKGROUND;
+ break;
+ case 'light':
+ level = LIGHT;
+ break;
+ case 'detailed':
+ level = DETAILED;
+ break;
+ case undefined:
+ level = undefined;
+ break;
+ default:
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'unknown raw level of detail \'' + rawLevelOfDetail +
+ '\' of process memory dump for PID=' + pid +
+ ' and dump ID=' + dumpId + '.'
+ });
+ return;
+ }
+
+ if (!this.updateMemoryDumpLevelOfDetail_(
+ levelsOfDetail, 'process', level)) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'diffent levels of detail provided for process memory' +
+ ' dump for PID=' + pid + ' (dump ID=' + dumpId + ').'
+ });
+ }
+ },
+
+ updateMemoryDumpLevelOfDetail_(levelsOfDetail, scope, level) {
+ // If all process memory dump events have the same level of detail (for
+ // the particular 'process' or 'global' scope), return true.
+ if (!(scope in levelsOfDetail) || level === levelsOfDetail[scope]) {
+ levelsOfDetail[scope] = level;
+ return true;
+ }
+
+ // If the process memory dump events have different levels of detail (for
+ // the particular 'process' or 'global' scope), use the highest level and
+ // return false.
+ if (MEMORY_DUMP_LEVEL_OF_DETAIL_ORDER.indexOf(level) >
+ MEMORY_DUMP_LEVEL_OF_DETAIL_ORDER.indexOf(levelsOfDetail[scope])) {
+ levelsOfDetail[scope] = level;
+ }
+ return false;
+ },
+
+ parseMemoryDumpAllocatorDumps_(processMemoryDump,
+ globalMemoryDump, processMemoryAllocatorDumpsByFullName,
+ globalMemoryAllocatorDumpsByFullName, allMemoryAllocatorDumpsByGuid,
+ dumps, pid, dumpId) {
+ const rawAllocatorDumps = dumps.allocators;
+ if (rawAllocatorDumps === undefined) return;
+
+ // Construct the MemoryAllocatorDump objects without parent links
+ // and add them to the processMemoryAllocatorDumpsByName and
+ // globalMemoryAllocatorDumpsByName indices appropriately.
+ for (let fullName in rawAllocatorDumps) {
+ const rawAllocatorDump = rawAllocatorDumps[fullName];
+
+ // Every memory allocator dump should have a GUID. If not, then
+ // it cannot be associated with any edges.
+ const guid = rawAllocatorDump.guid;
+ if (guid === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Memory allocator dump ' + fullName + ' for PID=' + pid +
+ ' and dump ID=' + dumpId + ' does not have a GUID.'
+ });
+ }
+
+ // A memory allocator dump can have optional flags.
+ const flags = rawAllocatorDump.flags || 0;
+ const isWeakDump = !!(flags & WEAK_MEMORY_ALLOCATOR_DUMP_FLAG);
+
+ // Determine if this is a global memory allocator dump (check if
+ // it's prefixed with 'global/').
+ let containerMemoryDump;
+ let dstIndex;
+ if (fullName.startsWith(GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX)) {
+ // Global memory allocator dump.
+ fullName = fullName.substring(
+ GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX.length);
+ containerMemoryDump = globalMemoryDump;
+ dstIndex = globalMemoryAllocatorDumpsByFullName;
+ } else {
+ // Process memory allocator dump.
+ containerMemoryDump = processMemoryDump;
+ dstIndex = processMemoryAllocatorDumpsByFullName;
+ }
+
+ // Construct or retrieve a memory allocator dump with the provided
+ // GUID.
+ let allocatorDump = allMemoryAllocatorDumpsByGuid[guid];
+ if (allocatorDump === undefined) {
+ if (fullName in dstIndex) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Multiple GUIDs provided for' +
+ ' memory allocator dump ' + fullName + ': ' +
+ dstIndex[fullName].guid + ', ' + guid + ' (ignored) for' +
+ ' PID=' + pid + ' and dump ID=' + dumpId + '.'
+ });
+ continue;
+ }
+ allocatorDump = new tr.model.MemoryAllocatorDump(
+ containerMemoryDump, fullName, guid);
+ allocatorDump.weak = isWeakDump;
+ dstIndex[fullName] = allocatorDump;
+ if (guid !== undefined) {
+ allMemoryAllocatorDumpsByGuid[guid] = allocatorDump;
+ }
+ } else {
+ // A memory allocator dump with this GUID has already been
+ // dumped (so we will only add new attributes). Check that it
+ // belonged to the same process or was also global.
+ if (allocatorDump.containerMemoryDump !== containerMemoryDump) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Memory allocator dump ' + fullName +
+ ' (GUID=' + guid + ') for PID=' + pid + ' and dump ID=' +
+ dumpId + ' dumped in different contexts.'
+ });
+ continue;
+ }
+ // Check that the names of the memory allocator dumps match.
+ if (allocatorDump.fullName !== fullName) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Memory allocator dump with GUID=' + guid + ' for PID=' +
+ pid + ' and dump ID=' + dumpId + ' has multiple names: ' +
+ allocatorDump.fullName + ', ' + fullName + ' (ignored).'
+ });
+ continue;
+ }
+ if (!isWeakDump) {
+ // A MemoryAllocatorDump is non-weak if at least one process dumped
+ // it without WEAK_MEMORY_ALLOCATOR_DUMP_FLAG.
+ allocatorDump.weak = false;
+ }
+ }
+
+ // Add all new attributes to the memory allocator dump.
+ let attributes = rawAllocatorDump.attrs;
+ if (attributes === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Memory allocator dump ' + fullName + ' (GUID=' + guid +
+ ') for PID=' + pid + ' and dump ID=' + dumpId +
+ ' does not have attributes.'
+ });
+ attributes = {};
+ }
+
+ for (const attrName in attributes) {
+ const attrArgs = attributes[attrName];
+ const attrType = attrArgs.type;
+ const attrValue = attrArgs.value;
+
+ switch (attrType) {
+ case 'scalar': {
+ if (attrName in allocatorDump.numerics) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Multiple values provided for scalar attribute ' +
+ attrName + ' of memory allocator dump ' + fullName +
+ ' (GUID=' + guid + ') for PID=' + pid + ' and dump ID=' +
+ dumpId + '.'
+ });
+ break;
+ }
+ const unit = attrArgs.units === 'bytes' ?
+ tr.b.Unit.byName.sizeInBytes_smallerIsBetter :
+ tr.b.Unit.byName.unitlessNumber_smallerIsBetter;
+ const value = parseInt(attrValue, 16);
+ allocatorDump.addNumeric(attrName,
+ new tr.b.Scalar(unit, value));
+ break;
+ }
+
+ case 'string':
+ if (attrName in allocatorDump.diagnostics) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Multiple values provided for string attribute ' +
+ attrName + ' of memory allocator dump ' + fullName +
+ ' (GUID=' + guid + ') for PID=' + pid + ' and dump ID=' +
+ dumpId + '.'
+ });
+ break;
+ }
+ allocatorDump.addDiagnostic(attrName, attrValue);
+ break;
+
+ default:
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Unknown type provided for attribute ' + attrName +
+ ' of memory allocator dump ' + fullName + ' (GUID=' + guid +
+ ') for PID=' + pid + ' and dump ID=' + dumpId + ': ' +
+ attrType
+ });
+ break;
+ }
+ }
+ }
+ },
+
+ inferMemoryAllocatorDumpTree_(memoryAllocatorDumpsByFullName) {
+ const rootAllocatorDumps = [];
+
+ const fullNames = Object.keys(memoryAllocatorDumpsByFullName);
+ fullNames.sort();
+ for (let i = 0; i < fullNames.length; i++) {
+ let fullName = fullNames[i];
+ let allocatorDump = memoryAllocatorDumpsByFullName[fullName];
+
+ // This is a loop because we might need to build implicit
+ // ancestors in case they were not present in the trace.
+ while (true) {
+ const lastSlashIndex = fullName.lastIndexOf('/');
+ if (lastSlashIndex === -1) {
+ // If the dump is a root, add it to the top-level
+ // rootAllocatorDumps list.
+ rootAllocatorDumps.push(allocatorDump);
+ break;
+ }
+
+ // If the dump is not a root, find its parent.
+ const parentFullName = fullName.substring(0, lastSlashIndex);
+ let parentAllocatorDump =
+ memoryAllocatorDumpsByFullName[parentFullName];
+
+ // If the parent dump does not exist yet, we build an implicit
+ // one and continue up the ancestor chain.
+ let parentAlreadyExisted = true;
+ if (parentAllocatorDump === undefined) {
+ parentAlreadyExisted = false;
+ parentAllocatorDump = new tr.model.MemoryAllocatorDump(
+ allocatorDump.containerMemoryDump, parentFullName);
+ if (allocatorDump.weak !== false) {
+ // If we are inferring a parent dump (e.g. 'root/parent') of a
+ // current dump (e.g. 'root/parent/current') which is weak (or
+ // was also inferred and we don't know yet whether it's weak or
+ // not), then we clear the weak flag on the parent dump because
+ // we don't know yet whether it should be weak or non-weak:
+ //
+ // * We can't mark the parent as non-weak straightaway because
+ // the parent might have no non-weak descendants (in which
+ // case we want the inferred parent to be weak, so that it
+ // would be later removed like the current dump).
+ // * We can't mark the parent as weak immediately either. If we
+ // did and later encounter a non-weak child of the parent
+ // (e.g. 'root/parent/another_child'), then we couldn't
+ // retroactively mark the inferred parent dump as non-weak
+ // because we couldn't tell whether the parent dump was
+ // dumped in the trace as weak (in which case it should stay
+ // weak and be subsequently removed) or whether it was
+ // inferred as weak (in which case it should be changed to
+ // non-weak).
+ //
+ // Therefore, we defer marking the inferred parent as
+ // weak/non-weak. If an inferred parent dump does not have any
+ // non-weak child, it will be marked as weak at the end of this
+ // method.
+ //
+ // Note that this should not be confused with the recursive
+ // propagation of the weak flag from parent dumps to their
+ // children and from owned dumps to their owners, which is
+ // performed in GlobalMemoryDump.prototype.removeWeakDumps().
+ parentAllocatorDump.weak = undefined;
+ }
+ memoryAllocatorDumpsByFullName[parentFullName] =
+ parentAllocatorDump;
+ }
+
+ // Setup the parent <-> children relationships
+ allocatorDump.parent = parentAllocatorDump;
+ parentAllocatorDump.children.push(allocatorDump);
+
+ // If the parent already existed, then its ancestors were/will be
+ // constructed in another iteration of the forEach loop.
+ if (parentAlreadyExisted) {
+ if (!allocatorDump.weak) {
+ // If the current dump is non-weak, then we must ensure that all
+ // its inferred ancestors are also non-weak.
+ while (parentAllocatorDump !== undefined &&
+ parentAllocatorDump.weak === undefined) {
+ parentAllocatorDump.weak = false;
+ parentAllocatorDump = parentAllocatorDump.parent;
+ }
+ }
+ break;
+ }
+
+ fullName = parentFullName;
+ allocatorDump = parentAllocatorDump;
+ }
+ }
+
+ // All inferred ancestor dumps that have a non-weak child have already
+ // been marked as non-weak. We now mark the rest as weak.
+ for (const fullName in memoryAllocatorDumpsByFullName) {
+ const allocatorDump = memoryAllocatorDumpsByFullName[fullName];
+ if (allocatorDump.weak === undefined) {
+ allocatorDump.weak = true;
+ }
+ }
+
+ return rootAllocatorDumps;
+ },
+
+ parseMemoryDumpAllocatorEdges_(allMemoryAllocatorDumpsByGuid,
+ dumpIdEvents, dumpId) {
+ for (const pid in dumpIdEvents) {
+ const processEvents = dumpIdEvents[pid];
+
+ for (let i = 0; i < processEvents.length; i++) {
+ const processEvent = processEvents[i];
+
+ const dumps = processEvent.args.dumps;
+ if (dumps === undefined) continue;
+
+ const rawEdges = dumps.allocators_graph;
+ if (rawEdges === undefined) continue;
+
+ for (let j = 0; j < rawEdges.length; j++) {
+ const rawEdge = rawEdges[j];
+
+ const sourceGuid = rawEdge.source;
+ const sourceDump = allMemoryAllocatorDumpsByGuid[sourceGuid];
+ if (sourceDump === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Edge for PID=' + pid + ' and dump ID=' + dumpId +
+ ' is missing source memory allocator dump (GUID=' +
+ sourceGuid + ').'
+ });
+ continue;
+ }
+
+ const targetGuid = rawEdge.target;
+ const targetDump = allMemoryAllocatorDumpsByGuid[targetGuid];
+ if (targetDump === undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Edge for PID=' + pid + ' and dump ID=' + dumpId +
+ ' is missing target memory allocator dump (GUID=' +
+ targetGuid + ').'
+ });
+ continue;
+ }
+
+ const importance = rawEdge.importance;
+ const edge = new tr.model.MemoryAllocatorDumpLink(
+ sourceDump, targetDump, importance);
+
+ switch (rawEdge.type) {
+ case 'ownership':
+ if (sourceDump.owns !== undefined) {
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Memory allocator dump ' + sourceDump.fullName +
+ ' (GUID=' + sourceGuid + ') already owns a memory' +
+ ' allocator dump (' +
+ sourceDump.owns.target.fullName + ').'
+ });
+ } else {
+ sourceDump.owns = edge;
+ targetDump.ownedBy.push(edge);
+ }
+ break;
+
+ case 'retention':
+ sourceDump.retains.push(edge);
+ targetDump.retainedBy.push(edge);
+ break;
+
+ default:
+ this.model_.importWarning({
+ type: 'memory_dump_parse_error',
+ message: 'Invalid edge type: ' + rawEdge.type +
+ ' (PID=' + pid + ', dump ID=' + dumpId +
+ ', source=' + sourceGuid + ', target=' + targetGuid +
+ ', importance=' + importance + ').'
+ });
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Converts |ts| (in microseconds) to a timestamp in the model clock domain
+ * (in milliseconds).
+ */
+ toModelTimeFromUs_(ts) {
+ if (!this.toModelTime_) {
+ this.toModelTime_ =
+ this.model_.clockSyncManager.getModelTimeTransformer(
+ this.clockDomainId_);
+ }
+
+ return this.toModelTime_(tr.b.Unit.timestampFromUs(ts));
+ },
+
+ /**
+ * Converts |ts| (in microseconds) to a timestamp in the model clock domain
+ * (in milliseconds). If |ts| is undefined, undefined is returned.
+ */
+ maybeToModelTimeFromUs_(ts) {
+ if (ts === undefined) {
+ return undefined;
+ }
+
+ return this.toModelTimeFromUs_(ts);
+ }
+ };
+
+ tr.importer.Importer.register(TraceEventImporter);
+
+ return {
+ TraceEventImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer_perf_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer_perf_test.html
new file mode 100644
index 00000000000..1c25c83fb57
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer_perf_test.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/xhr.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const eventStrings = {};
+
+ // @const
+ const N_ITERATIONS = 200;
+
+ // @const
+ const TEST_NAMES = [
+ 'simple_trace',
+ 'flow',
+ ];
+ // @const
+ const TEST_FILES_PATHS = [
+ '/test_data/simple_trace.json',
+ '/test_data/flow.json',
+ ];
+
+ function stringToStream(str) {
+ const buffer = new ArrayBuffer(str.length);
+ const bufferView = new Uint8Array(buffer);
+ for (let i = 0; i < bufferView.length; i++) {
+ bufferView[i] = str.charCodeAt(i);
+ }
+ const stream = new tr.b.InMemoryTraceStream(bufferView, false);
+ return stream;
+ }
+
+ function getEvents(url, asStream) {
+ if (url in eventStrings && asStream in eventStrings[url]) {
+ return eventStrings[url][asStream];
+ }
+ if (!(url in eventStrings)) {
+ eventStrings[url] = {};
+ }
+ // TODO(ehmaldonado): See catapult:#3845.
+ // tr.b.getSync does not return a trace stream in browser mode, so we
+ // convert to stream manually.
+ eventStrings[url][asStream] = tr.b.getSync(url);
+ if (asStream) {
+ eventStrings[url][asStream] = stringToStream(eventStrings[url][asStream]);
+ }
+ return eventStrings[url][asStream];
+ }
+
+ function timedPerfTestWithEvents(name, testFn, initialOptions, asStream) {
+ if (initialOptions.setUp) {
+ throw new Error(
+ 'Per-test setUp not supported. Trivial to fix if needed.');
+ }
+
+ const options = {};
+ for (const k in initialOptions) {
+ options[k] = initialOptions[k];
+ }
+ options.setUp = function() {
+ TEST_FILES_PATHS.forEach(
+ function warmup(url) {
+ getEvents(url, asStream);
+ });
+ };
+ timedPerfTest(name, testFn, options);
+ }
+
+ for (let i = 0; i < TEST_NAMES.length; ++i) {
+ const testName = TEST_NAMES[i];
+ const testFilePath = TEST_FILES_PATHS[i];
+
+ // Import trace as a string.
+ timedPerfTestWithEvents(testName + '_string', function() {
+ const events = getEvents(testFilePath, false);
+ const m = tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false,
+ pruneContainers: false
+ });
+ }, {iterations: N_ITERATIONS}, false);
+
+ // Import trace as a stream.
+ timedPerfTestWithEvents(testName + '_stream', function() {
+ const events = getEvents(testFilePath, true);
+ const m = tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false,
+ pruneContainers: false
+ });
+ }, {iterations: N_ITERATIONS}, true);
+ }
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer_test.html
new file mode 100644
index 00000000000..c0a0b6edec3
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/trace_event_importer_test.html
@@ -0,0 +1,6593 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/in_memory_trace_stream.html">
+<link rel="import" href="/tracing/base/scalar.html">
+<link rel="import" href="/tracing/base/time_display_modes.html">
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/battor_importer.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/extras/measure/measure.html">
+<link rel="import" href="/tracing/extras/v8_config.html">
+<link rel="import" href="/tracing/importer/import.html">
+<link rel="import" href="/tracing/model/container_memory_dump.html">
+<link rel="import" href="/tracing/model/memory_dump_test_utils.html">
+<link rel="import" href="/tracing/model/scoped_id.html">
+<link rel="import" href="/tracing/model/vm_region.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const findSliceNamed = tr.c.TestUtils.findSliceNamed;
+ const ClockDomainId = tr.model.ClockDomainId;
+ const ColorScheme = tr.b.ColorScheme;
+ const MeasureAsyncSlice = tr.e.measure.MeasureAsyncSlice;
+ const ScopedId = tr.model.ScopedId;
+ const VMRegion = tr.model.VMRegion;
+ const Scalar = tr.b.Scalar;
+ const unitlessNumber_smallerIsBetter =
+ tr.b.Unit.byName.unitlessNumber_smallerIsBetter;
+ const checkDumpNumericsAndDiagnostics =
+ tr.model.MemoryDumpTestUtils.checkDumpNumericsAndDiagnostics;
+ const checkVMRegions = tr.model.MemoryDumpTestUtils.checkVMRegions;
+ const LIGHT = tr.model.ContainerMemoryDump.LevelOfDetail.LIGHT;
+ const DETAILED = tr.model.ContainerMemoryDump.LevelOfDetail.DETAILED;
+
+ function makeModel(events, opt_shift, opt_prune) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: opt_shift,
+ pruneEmptyContainers: opt_prune
+ });
+ }
+
+ function checkHeapEntry(entry, expectedDump, expectedSize, expectedTitles,
+ expectedObjectTypeName, expectedCount) {
+ assert.strictEqual(entry.heapDump, expectedDump);
+ assert.strictEqual(entry.size, expectedSize);
+ assert.strictEqual(entry.objectTypeName, expectedObjectTypeName);
+ assert.strictEqual(entry.count, expectedCount);
+ if (expectedTitles === undefined) {
+ assert.isUndefined(entry.leafStackFrame);
+ } else {
+ assert.deepEqual(
+ entry.leafStackFrame.getUserFriendlyStackTrace(), expectedTitles);
+ }
+ }
+
+ function getFrame(heapEntry, distance) {
+ let frame = heapEntry.leafStackFrame;
+ for (; distance > 0; distance--) {
+ frame = frame.parentFrame;
+ }
+ return frame;
+ }
+
+ function stringToUint8Array(str) {
+ const buffer = new ArrayBuffer(str.length);
+ const bufferView = new Uint8Array(buffer);
+ for (let i = 0; i < bufferView.length; i++) {
+ bufferView[i] = str.charCodeAt(i);
+ }
+ return bufferView;
+ }
+
+ function checkParsedAndStreamInput(events, checkFn, opt_shift, opt_prune) {
+ const stream = new tr.b.InMemoryTraceStream(
+ stringToUint8Array(JSON.stringify(events)), false);
+
+ checkFn(makeModel(events, opt_shift, opt_prune));
+ checkFn(makeModel(stream, opt_shift, opt_prune));
+ }
+
+ test('canImportEmpty', function() {
+ assert.isFalse(tr.e.importer.TraceEventImporter.canImport([]));
+ assert.isFalse(tr.e.importer.TraceEventImporter.canImport(''));
+ });
+
+ test('basicSingleThreadNonnestedParsing', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.numProcesses, 1);
+ const p = m.processes[52];
+ assert.isDefined(p);
+
+ assert.strictEqual(p.numThreads, 1);
+ const t = p.threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.length, 2);
+ assert.strictEqual(t.tid, 53);
+ let slice = t.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ assert.strictEqual(slice.start, 0);
+ assert.closeTo((560 - 520) / 1000, slice.duration, 1e-5);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ slice = t.sliceGroup.slices[1];
+ assert.strictEqual(slice.title, 'b');
+ assert.strictEqual(slice.category, 'bar');
+ assert.closeTo((629 - 520) / 1000, slice.start, 1e-5);
+ assert.closeTo((631 - 629) / 1000, slice.duration, 1e-5);
+ assert.strictEqual(slice.subSlices.length, 0);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'},
+ {name: 'b', args: {}, pid: 52, ts: 629, cat: 'bar', tid: 53, ph: 'B'},
+ {name: 'b', args: {}, pid: 52, ts: 631, cat: 'bar', tid: 53, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nonMonotonicInstantEvents', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.numProcesses, 1);
+ const p = m.processes[52];
+ assert.isDefined(p);
+
+ assert.strictEqual(p.numThreads, 3);
+ const t1 = p.threads[1];
+ assert.isDefined(t1);
+ assert.strictEqual(t1.sliceGroup.length, 3);
+
+ let slice = t1.sliceGroup.slices[1];
+ assert.strictEqual(slice.title, 'b');
+ assert.strictEqual(slice.category, 'foo');
+ assert.strictEqual(slice.start, 2);
+ assert.strictEqual(slice.duration, 0);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ slice = t1.sliceGroup.slices[2];
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ assert.strictEqual(slice.start, 3);
+ assert.strictEqual(slice.duration, 5);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ const t2 = p.threads[2];
+ assert.isDefined(t2);
+ assert.strictEqual(t2.sliceGroup.length, 3);
+
+ slice = t2.sliceGroup.slices[1];
+ assert.strictEqual(slice.title, 'b');
+ assert.strictEqual(slice.start, 2);
+ assert.strictEqual(slice.duration, 0);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ slice = t2.sliceGroup.slices[2];
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ assert.strictEqual(slice.start, 3);
+ assert.strictEqual(slice.duration, 5);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ const t3 = p.threads[3];
+ assert.isDefined(t3);
+ assert.strictEqual(t3.sliceGroup.length, 3);
+
+ slice = t3.sliceGroup.slices[1];
+ assert.strictEqual(slice.title, 'b');
+ assert.strictEqual(slice.start, 2);
+ assert.strictEqual(slice.duration, 0);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ slice = t3.sliceGroup.slices[2];
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ assert.strictEqual(slice.start, 3);
+ assert.strictEqual(slice.duration, 5);
+ assert.strictEqual(slice.subSlices.length, 0);
+ }
+
+ const events = [
+ {name: 'x', args: {}, pid: 52, ts: 0, cat: 'foo', tid: 1, ph: 'R'},
+ {name: 'a', args: {}, pid: 52, ts: 3000, cat: 'foo', tid: 1, ph: 'X',
+ dur: 5000},
+ {name: 'b', args: {}, pid: 52, ts: 2000, cat: 'foo', tid: 1, ph: 'R'},
+
+ {name: 'x', args: {}, pid: 52, ts: 0, cat: 'foo', tid: 2, ph: 'R'},
+ {name: 'b', args: {}, pid: 52, ts: 2000, cat: 'foo', tid: 2, ph: 'R'},
+ {name: 'a', args: {}, pid: 52, ts: 3000, cat: 'foo', tid: 2, ph: 'X',
+ dur: 5000},
+
+ {name: 'x', args: {}, pid: 52, ts: 0, cat: 'foo', tid: 3, ph: 'R'},
+ {name: 'a', args: {}, pid: 52, ts: 3000, cat: 'foo', tid: 3, ph: 'B'},
+ {name: 'b', args: {}, pid: 52, ts: 2000, cat: 'foo', tid: 3, ph: 'R'},
+ {name: 'a', args: {}, pid: 52, ts: 8000, cat: 'foo', tid: 3, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('basicSingleThreadNonnestedParsingWithCpuDuration', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.numProcesses, 1);
+ const p = m.processes[52];
+ assert.isDefined(p);
+
+ assert.strictEqual(p.numThreads, 1);
+ const t = p.threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.length, 2);
+ assert.strictEqual(t.tid, 53);
+ let slice = t.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ assert.strictEqual(slice.start, 0);
+ assert.closeTo((560 - 520) / 1000, slice.duration, 1e-5);
+ assert.closeTo((259 - 221) / 1000, slice.cpuDuration, 1e-5);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ slice = t.sliceGroup.slices[1];
+ assert.strictEqual(slice.title, 'b');
+ assert.strictEqual(slice.category, 'bar');
+ assert.closeTo((629 - 520) / 1000, slice.start, 1e-5);
+ assert.closeTo((631 - 629) / 1000, slice.duration, 1e-5);
+ assert.closeTo((331 - 329) / 1000, slice.cpuDuration, 1e-5);
+ assert.strictEqual(slice.subSlices.length, 0);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B', tts: 221}, // @suppress longLineCheck
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E', tts: 259}, // @suppress longLineCheck
+ {name: 'b', args: {}, pid: 52, ts: 629, cat: 'bar', tid: 53, ph: 'B', tts: 329}, // @suppress longLineCheck
+ {name: 'b', args: {}, pid: 52, ts: 631, cat: 'bar', tid: 53, ph: 'E', tts: 331} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('argumentDupeCreatesNonFailingImportError', function() {
+ function checkModel(m) {
+ const t = m.processes[1].threads[1];
+ const sA = findSliceNamed(t.sliceGroup, 'a');
+
+ assert.strictEqual(sA.args.x, 2);
+ assert.isTrue(m.hasImportWarnings);
+ assert.strictEqual(1, m.importWarnings.length);
+ }
+
+ const events = [
+ {name: 'a',
+ args: {'x': 1},
+ pid: 1,
+ ts: 520,
+ cat: 'foo',
+ tid: 1,
+ ph: 'B'},
+ {name: 'a',
+ args: {'x': 2},
+ pid: 1,
+ ts: 560,
+ cat: 'foo',
+ tid: 1,
+ ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importMissingArgs', function() {
+ const events = [
+ {name: 'a', pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'},
+ {name: 'b', pid: 52, ts: 629, cat: 'bar', tid: 53, ph: 'I'}
+ ];
+
+ // This should not throw an exception.
+ checkParsedAndStreamInput(events, m => {});
+ });
+
+ test('importDoesNotChokeOnNulls', function() {
+ const events = [
+ {name: 'a', args: { foo: null }, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'}, // @suppress longLineCheck
+ {name: 'a', pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+
+ // This should not throw an exception.
+ checkParsedAndStreamInput(events, m => {});
+ });
+
+ test('categoryBeginEndMismatchPrefersBegin', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.numProcesses, 1);
+ const p = m.processes[52];
+ assert.isDefined(p);
+
+ assert.strictEqual(p.numThreads, 1);
+ const t = p.threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.length, 1);
+ assert.strictEqual(t.tid, 53);
+ const slice = t.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'bar', tid: 53, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('beginEndNameMismatch', function() {
+ function checkModel(m) {
+ assert.isTrue(m.hasImportWarnings);
+ assert.strictEqual(m.importWarnings.length, 1);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'b', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nestedParsing', function() {
+ function checkModel(m) {
+ const t = m.processes[1].threads[1];
+
+ const sA = findSliceNamed(t.sliceGroup, 'a');
+ const sB = findSliceNamed(t.sliceGroup, 'b');
+
+ assert.strictEqual(sA.title, 'a');
+ assert.strictEqual(sA.category, 'foo');
+ assert.strictEqual(sA.start, 0.001);
+ assert.strictEqual(sA.duration, 0.003);
+ assert.strictEqual(sA.selfTime, 0.002);
+ assert.strictEqual(sA.cpuSelfTime, 0.001);
+
+ assert.strictEqual(sB.title, 'b');
+ assert.strictEqual(sB.category, 'bar');
+ assert.strictEqual(sB.start, 0.002);
+ assert.strictEqual(sB.duration, 0.001);
+
+ assert.strictEqual(1, sA.subSlices.length);
+ assert.strictEqual(sB, sA.subSlices[0]);
+ assert.strictEqual(sA, sB.parentSlice);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, tts: 1, cat: 'foo', tid: 1, ph: 'B'},
+ {name: 'b', args: {}, pid: 1, ts: 2, tts: 2, cat: 'bar', tid: 1, ph: 'B'},
+ {name: 'b', args: {}, pid: 1, ts: 3, tts: 3, cat: 'bar', tid: 1, ph: 'E'},
+ {name: 'a', args: {}, pid: 1, ts: 4, tts: 3, cat: 'foo', tid: 1, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('nestedParsingWithTwoSubSlices', function() {
+ function checkModel(m) {
+ const t = m.processes[1].threads[1];
+
+ const sA = findSliceNamed(t.sliceGroup, 'a');
+ const sB = findSliceNamed(t.sliceGroup, 'b');
+ const sC = findSliceNamed(t.sliceGroup, 'c');
+
+ assert.strictEqual(sA.title, 'a');
+ assert.strictEqual(sA.category, 'foo');
+ assert.strictEqual(sA.start, 0.001);
+ assert.strictEqual(sA.duration, 0.007);
+ assert.strictEqual(sA.selfTime, 0.004);
+ assert.strictEqual(sA.cpuSelfTime, 0.005);
+
+ assert.strictEqual(sB.title, 'b');
+ assert.strictEqual(sB.category, 'bar');
+ assert.strictEqual(sB.start, 0.002);
+ assert.strictEqual(sB.duration, 0.001);
+
+ assert.strictEqual(sC.title, 'c');
+ assert.strictEqual(sC.category, 'baz');
+ assert.strictEqual(sC.start, 0.005);
+ assert.strictEqual(sC.duration, 0.002);
+
+ assert.strictEqual(sA.subSlices.length, 2);
+ assert.strictEqual(sA.subSlices[0], sB);
+ assert.strictEqual(sA.subSlices[1], sC);
+ assert.strictEqual(sB.parentSlice, sA);
+ assert.strictEqual(sC.parentSlice, sA);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, tts: 1, cat: 'foo', tid: 1, ph: 'B'},
+ {name: 'b', args: {}, pid: 1, ts: 2, tts: 2, cat: 'bar', tid: 1, ph: 'B'},
+ {name: 'b', args: {}, pid: 1, ts: 3, tts: 3, cat: 'bar', tid: 1, ph: 'E'},
+ {name: 'c', args: {}, pid: 1, ts: 5, tts: 5, cat: 'baz', tid: 1, ph: 'B'},
+ {name: 'c', args: {}, pid: 1, ts: 7, tts: 6, cat: 'baz', tid: 1, ph: 'E'},
+ {name: 'a', args: {}, pid: 1, ts: 8, tts: 8, cat: 'foo', tid: 1, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('nestedParsingWithDoubleNesting', function() {
+ function checkModel(m) {
+ const t = m.processes[1].threads[1];
+
+ const sA = findSliceNamed(t.sliceGroup, 'a');
+ const sB = findSliceNamed(t.sliceGroup, 'b');
+ const sC = findSliceNamed(t.sliceGroup, 'c');
+
+ assert.strictEqual(sA.title, 'a');
+ assert.strictEqual(sA.category, 'foo');
+ assert.strictEqual(sA.start, 0.001);
+ assert.strictEqual(sA.duration, 0.007);
+ assert.strictEqual(sA.selfTime, 0.002);
+
+ assert.strictEqual(sB.title, 'b');
+ assert.strictEqual(sB.category, 'bar');
+ assert.strictEqual(sB.start, 0.002);
+ assert.strictEqual(sB.duration, 0.005);
+ assert.strictEqual(sA.selfTime, 0.002);
+
+ assert.strictEqual(sC.title, 'c');
+ assert.strictEqual(sC.category, 'baz');
+ assert.strictEqual(sC.start, 0.003);
+ assert.strictEqual(sC.duration, 0.002);
+
+ assert.strictEqual(sA.subSlices.length, 1);
+ assert.strictEqual(sA.subSlices[0], sB);
+ assert.strictEqual(sB.parentSlice, sA);
+
+ assert.strictEqual(sB.subSlices.length, 1);
+ assert.strictEqual(sB.subSlices[0], sC);
+ assert.strictEqual(sC.parentSlice, sB);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
+ {name: 'b', args: {}, pid: 1, ts: 2, cat: 'bar', tid: 1, ph: 'B'},
+ {name: 'c', args: {}, pid: 1, ts: 3, cat: 'baz', tid: 1, ph: 'B'},
+ {name: 'c', args: {}, pid: 1, ts: 5, cat: 'baz', tid: 1, ph: 'E'},
+ {name: 'b', args: {}, pid: 1, ts: 7, cat: 'bar', tid: 1, ph: 'E'},
+ {name: 'a', args: {}, pid: 1, ts: 8, cat: 'foo', tid: 1, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+
+ test('autoclosing', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t1 = p.threads[1];
+ const t2 = p.threads[2];
+ assert.isTrue(t1.sliceGroup.slices[0].didNotFinish);
+ assert.strictEqual(t1.sliceGroup.slices[0].end, 0.001);
+ }
+
+ const events = [
+ // Slice that doesn't finish.
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
+
+ // Slice that does finish to give an 'end time' to make autoclosing work.
+ {name: 'b', args: {}, pid: 1, ts: 1, cat: 'bar', tid: 2, ph: 'B'},
+ {name: 'b', args: {}, pid: 1, ts: 2, cat: 'bar', tid: 2, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('autoclosingLoneBegin', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[1];
+ const slice = t.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ assert.isTrue(slice.didNotFinish);
+ assert.strictEqual(slice.start, 0);
+ assert.strictEqual(slice.duration, 0);
+ }
+
+ const events = [
+ // Slice that doesn't finish.
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('autoclosingWithSubTasks', function() {
+ function checkModel(m) {
+ const t = m.processes[1].threads[1];
+
+ const sA = findSliceNamed(t.sliceGroup, 'a');
+ const sB1 = findSliceNamed(t.sliceGroup, 'b1');
+ const sB2 = findSliceNamed(t.sliceGroup, 'b2');
+
+ assert.strictEqual(sA.end, 0.003);
+ assert.strictEqual(sB1.end, 0.003);
+ assert.strictEqual(sB2.end, 0.003);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
+ {name: 'b1', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'B'},
+ {name: 'b1', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'E'},
+ {name: 'b2', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'B'}
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('autoclosingWithEventsOutsideBounds', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[1];
+ assert.strictEqual(t.sliceGroup.length, 2);
+
+ const slice = findSliceNamed(t.sliceGroup, 'a');
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ assert.strictEqual(slice.start, 0);
+ assert.strictEqual(slice.duration, 0.003);
+
+ const t2 = p.threads[2];
+ const slice2 = findSliceNamed(t2.sliceGroup, 'c');
+ assert.strictEqual(slice2.title, 'c');
+ assert.strictEqual(slice2.category, 'bar');
+ assert.strictEqual(slice2.start, 0.001);
+ assert.strictEqual(slice2.duration, 0.001);
+
+ assert.strictEqual(m.bounds.min, 0.000);
+ assert.strictEqual(m.bounds.max, 0.003);
+ }
+
+ const events = [
+ // Slice that begins before min and ends after max of the other threads.
+ {name: 'a', args: {}, pid: 1, ts: 0, cat: 'foo', tid: 1, ph: 'B'},
+ {name: 'b', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'B'},
+
+ // Slice that does finish to give an 'end time' to establish a basis
+ {name: 'c', args: {}, pid: 1, ts: 1, cat: 'bar', tid: 2, ph: 'B'},
+ {name: 'c', args: {}, pid: 1, ts: 2, cat: 'bar', tid: 2, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nestedAutoclosing', function() {
+ function checkModel(m) {
+ const t1 = m.processes[1].threads[1];
+ const t2 = m.processes[1].threads[2];
+
+ const sA1 = findSliceNamed(t1.sliceGroup, 'a1');
+ const sA2 = findSliceNamed(t1.sliceGroup, 'a2');
+ const sB = findSliceNamed(t2.sliceGroup, 'b');
+
+ assert.strictEqual(sA1.end, 0.0015);
+ assert.strictEqual(sA2.end, 0.0015);
+ }
+
+ const events = [
+ // Tasks that don't finish.
+ {name: 'a1', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
+ {name: 'a2', args: {}, pid: 1, ts: 1.5, cat: 'foo', tid: 1, ph: 'B'},
+
+ // Slice that does finish to give an 'end time' to make autoclosing work.
+ {name: 'b', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 2, ph: 'B'},
+ {name: 'b', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 2, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('taskColoring', function() {
+ // The test below depends on hashing of 'a' !== 'b'. Fail early if that
+ // assumption is incorrect.
+ assert.notEqual(ColorScheme.getStringHash('a'),
+ ColorScheme.getStringHash('b'));
+
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[1];
+ const a1 = t.sliceGroup.slices[0];
+ assert.strictEqual(a1.title, 'a');
+ assert.strictEqual(a1.category, 'foo');
+ const b = t.sliceGroup.slices[1];
+ assert.strictEqual(b.title, 'b');
+ assert.strictEqual(b.category, 'bar');
+ assert.notEqual(b.colorId, a1.colorId);
+ const a2 = t.sliceGroup.slices[2];
+ assert.strictEqual(a2.title, 'a');
+ assert.strictEqual(a2.category, 'baz');
+ assert.strictEqual(a1.colorId, a2.colorId);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
+ {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'E'},
+ {name: 'b', args: {}, pid: 1, ts: 3, cat: 'bar', tid: 1, ph: 'B'},
+ {name: 'b', args: {}, pid: 1, ts: 4, cat: 'bar', tid: 1, ph: 'E'},
+ {name: 'a', args: {}, pid: 1, ts: 5, cat: 'baz', tid: 1, ph: 'B'},
+ {name: 'a', args: {}, pid: 1, ts: 6, cat: 'baz', tid: 1, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('durationColorArgument', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[1];
+ assert.strictEqual(t.sliceGroup.slices[0].colorId,
+ ColorScheme.getColorIdForReservedName('thread_state_unknown'));
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B', cname: 'thread_state_unknown'}, // @suppress longLineCheck
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'E', cname: 'thread_state_unknown'} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('durationColorEnd', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[1];
+ assert.strictEqual(t.sliceGroup.slices[0].colorId,
+ ColorScheme.getColorIdForReservedName('thread_state_unknown'));
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B', cname: 'thread_state_sleeping'}, // @suppress longLineCheck
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'E', cname: 'thread_state_unknown'} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('completeColorArgument', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[1];
+ assert.strictEqual(t.sliceGroup.slices[0].colorId,
+ ColorScheme.getColorIdForReservedName('generic_work'));
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, dur: 1, cat: 'foo', tid: 1, ph: 'X', cname: 'generic_work'} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('asyncColorArgument', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[1];
+ assert.strictEqual(t.asyncSliceGroup.slices[0].colorId,
+ ColorScheme.getColorIdForReservedName('generic_work'));
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'b', id: 1, cname: 'generic_work'}, // @suppress longLineCheck
+ {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'e', id: 1, cname: 'generic_work'} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('asyncColorEnd', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[1];
+ assert.strictEqual(t.asyncSliceGroup.slices[0].colorId,
+ ColorScheme.getColorIdForReservedName('generic_work'));
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'b', id: 1, cname: 'thread_state_unknown'}, // @suppress longLineCheck
+ {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'e', id: 1, cname: 'generic_work'} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('instantThreadColorArgument', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[1];
+ assert.strictEqual(t.sliceGroup.slices[0].colorId,
+ ColorScheme.getColorIdForReservedName('generic_work'));
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'I', id: 1, cname: 'generic_work'} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('instantProcessColorArgument', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ assert.strictEqual(p.instantEvents[0].colorId,
+ ColorScheme.getColorIdForReservedName('generic_work'));
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'I', id: 1, s: 'p', cname: 'generic_work'} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('counterColorArgument', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ assert.strictEqual(p.counters['foo.a[1]'].series[0].color,
+ ColorScheme.getColorIdForReservedName('generic_work'));
+ assert.strictEqual(p.counters['foo.a[1]'].series.length, 1);
+ }
+
+ const events = [
+ {name: 'a', args: {'cats': 10}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'C', id: 1, cname: 'generic_work'} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('objectColorArgument', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const i = p.objects.instanceMapsByScopedId_.ptr[1].instances[0];
+ assert.strictEqual(i.colorId,
+ ColorScheme.getColorIdForReservedName('generic_work'));
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'N', id: 1, cname: 'generic_work'}, // @suppress longLineCheck
+ {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'O', id: 1, cname: 'generic_work'}, // @suppress longLineCheck
+ {name: 'a', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'D', id: 1, cname: 'generic_work'} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('objectColorEnd', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const i = p.objects.instanceMapsByScopedId_.ptr[1].instances[0];
+ assert.strictEqual(i.colorId,
+ ColorScheme.getColorIdForReservedName('generic_work'));
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'N', id: 1, cname: 'thread_state_sleeping'}, // @suppress longLineCheck
+ {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'O', id: 1, cname: 'thread_state_unknown'}, // @suppress longLineCheck
+ {name: 'a', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'D', id: 1, cname: 'generic_work'} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('multipleThreadParsing', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.numProcesses, 1);
+ const p = m.processes[1];
+ assert.isDefined(p);
+
+ assert.strictEqual(p.numThreads, 2);
+
+ // Check thread 1.
+ let t = p.threads[1];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.length, 1);
+ assert.strictEqual(t.tid, 1);
+
+ let slice = t.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ assert.strictEqual(slice.start, 0);
+ assert.strictEqual(slice.duration, (2 - 1) / 1000);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ // Check thread 2.
+ t = p.threads[2];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.length, 1);
+ assert.strictEqual(t.tid, 2);
+
+ slice = t.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'b');
+ assert.strictEqual(slice.category, 'bar');
+ assert.strictEqual(slice.start, (3 - 1) / 1000);
+ assert.strictEqual(slice.duration, (4 - 3) / 1000);
+ assert.strictEqual(slice.subSlices.length, 0);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
+ {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'E'},
+ {name: 'b', args: {}, pid: 1, ts: 3, cat: 'bar', tid: 2, ph: 'B'},
+ {name: 'b', args: {}, pid: 1, ts: 4, cat: 'bar', tid: 2, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('multiplePidParsing', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.numProcesses, 2);
+ let p = m.processes[1];
+ assert.isDefined(p);
+
+ assert.strictEqual(p.numThreads, 1);
+
+ // Check process 1 thread 1.
+ let t = p.threads[1];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.length, 1);
+ assert.strictEqual(t.tid, 1);
+
+ let slice = t.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ assert.strictEqual(slice.start, 0);
+ assert.strictEqual(slice.duration, (2 - 1) / 1000);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ // Check process 2 thread 2.
+ p = m.processes[2];
+ assert.isDefined(p);
+ assert.strictEqual(p.numThreads, 1);
+ t = p.threads[2];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.length, 1);
+ assert.strictEqual(t.tid, 2);
+
+ slice = t.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'b');
+ assert.strictEqual(slice.category, 'bar');
+ assert.strictEqual(slice.start, (3 - 1) / 1000);
+ assert.strictEqual(slice.duration, (4 - 3) / 1000);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ // Check getAllThreads.
+ assert.deepEqual(m.getAllThreads(),
+ [m.processes[1].threads[1], m.processes[2].threads[2]]);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
+ {name: 'a', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'E'},
+ {name: 'b', args: {}, pid: 2, ts: 3, cat: 'bar', tid: 2, ph: 'B'},
+ {name: 'b', args: {}, pid: 2, ts: 4, cat: 'bar', tid: 2, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ // Process names.
+ test('processNames', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.processes[1].name, 'SomeProcessName');
+ }
+
+ const events = [
+ {name: 'process_name', args: {name: 'SomeProcessName'},
+ pid: 1, ts: 0, cat: 'foo', tid: 1, ph: 'M'},
+ {name: 'process_name', args: {name: 'SomeProcessName'},
+ pid: 2, ts: 0, cat: 'foo', tid: 1, ph: 'M'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ // Process labels.
+ test('processLabels', function() {
+ function checkModel(m) {
+ assert.deepEqual(m.processes[1].labels, ['foo', 'bar', 'baz']);
+ assert.deepEqual(m.processes[2].labels, ['baz']);
+ }
+
+ const events = [
+ {name: 'process_labels', args: {labels: 'foo,bar,bar,foo,baz'},
+ pid: 1, ts: 0, cat: 'foo', tid: 1, ph: 'M'},
+ {name: 'process_labels', args: {labels: 'baz'},
+ pid: 2, ts: 0, cat: 'foo', tid: 1, ph: 'M'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ // Process uptime.
+ test('processUptime', function() {
+ const events = [
+ {name: 'process_uptime_seconds', args: {uptime: 10},
+ pid: 1, ts: 0, tid: 1, ph: 'M'},
+ {name: 'process_uptime_seconds', args: {uptime: 20},
+ pid: 2, ts: 0, tid: 1, ph: 'M'}
+ ];
+ const m = makeModel(events);
+ assert.strictEqual(m.processes[1].uptime_seconds, 10);
+ assert.strictEqual(m.processes[2].uptime_seconds, 20);
+ });
+
+ // Process sort index.
+ test('processSortIndex', function() {
+ function checkModel(m) {
+ // By name, p1 is before p2. But, its sort index overrides that.
+ assert.isAbove(m.processes[1].compareTo(m.processes[2]), 0);
+ }
+
+ const events = [
+ {name: 'process_name', args: {name: 'First'},
+ pid: 2, ts: 0, cat: 'foo', tid: 1, ph: 'M'},
+ {name: 'process_name', args: {name: 'Second'},
+ pid: 2, ts: 0, cat: 'foo', tid: 1, ph: 'M'},
+ {name: 'process_sort_index', args: {sort_index: 1},
+ pid: 1, ts: 0, cat: 'foo', tid: 1, ph: 'M'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ // Thread names.
+ test('threadNames', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.processes[1].threads[1].name, 'Thread 1');
+ assert.strictEqual(m.processes[2].threads[2].name, 'Thread 2');
+ }
+
+ const events = [
+ {name: 'thread_name', args: {name: 'Thread 1'},
+ pid: 1, ts: 0, cat: 'foo', tid: 1, ph: 'M'},
+ {name: 'thread_name', args: {name: 'Thread 2'},
+ pid: 2, ts: 0, cat: 'foo', tid: 2, ph: 'M'}
+ ];
+ checkParsedAndStreamInput(events, checkModel, false, false);
+ });
+
+ // Thread sort index.
+ test('threadSortIndex', function() {
+ function checkModel(m) {
+ // By name, t1 is before t2. But, its sort index overrides that.
+ const t1 = m.processes[1].threads[1];
+ const t2 = m.processes[1].threads[2];
+ assert.isAbove(t1.compareTo(t2), 0);
+ }
+
+ const events = [
+ {name: 'thread_name', args: {name: 'Thread 1'},
+ pid: 1, ts: 0, cat: 'foo', tid: 1, ph: 'M'},
+ {name: 'thread_name', args: {name: 'Thread 2'},
+ pid: 1, ts: 0, cat: 'foo', tid: 2, ph: 'M'},
+ {name: 'thread_sort_index', args: {sort_index: 1},
+ pid: 1, ts: 0, cat: 'foo', tid: 1, ph: 'M'}
+ ];
+ checkParsedAndStreamInput(events, checkModel, false, false);
+ });
+
+ // CPU counts.
+ test('cpuCounts', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.kernel.softwareMeasuredCpuCount, 4);
+ assert.strictEqual(m.kernel.bestGuessAtCpuCount, 4);
+ }
+
+ const events = [
+ {name: 'num_cpus', args: {number: 4},
+ pid: 7, ts: 0, cat: 'foo', tid: 0, ph: 'M'},
+ {name: 'num_cpus', args: {number: 4},
+ pid: 14, ts: 0, cat: 'foo', tid: 0, ph: 'M'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('cpuCountsWithSandboxBeingConfused', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.kernel.softwareMeasuredCpuCount, 4);
+ assert.strictEqual(m.kernel.bestGuessAtCpuCount, 4);
+ }
+
+ const events = [
+ {name: 'num_cpus', args: {number: 4},
+ pid: 7, ts: 0, cat: 'foo', tid: 0, ph: 'M'},
+ {name: 'num_cpus', args: {number: 1},
+ pid: 14, ts: 0, cat: 'foo', tid: 0, ph: 'M'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('parsingWhenEndComesFirst', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[1];
+ assert.strictEqual(t.sliceGroup.length, 1);
+ assert.strictEqual(t.sliceGroup.slices[0].title, 'a');
+ assert.strictEqual(t.sliceGroup.slices[0].category, 'foo');
+ assert.strictEqual(t.sliceGroup.slices[0].start, 0.004);
+ assert.strictEqual(t.sliceGroup.slices[0].duration, 0.001);
+ assert.isTrue(m.hasImportWarnings);
+ assert.strictEqual(m.importWarnings.length, 1);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'E'},
+ {name: 'a', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'B'},
+ {name: 'a', args: {}, pid: 1, ts: 5, cat: 'foo', tid: 1, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('immediateParsing', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[1];
+
+ assert.strictEqual(t.sliceGroup.length, 3);
+ assert.strictEqual(t.sliceGroup.slices[0].start, 0.001);
+ assert.strictEqual(t.sliceGroup.slices[0].duration, 0.003);
+ assert.strictEqual(t.sliceGroup.slices[1].start, 0.002);
+ assert.strictEqual(t.sliceGroup.slices[1].duration, 0);
+ assert.strictEqual(t.sliceGroup.slices[2].start, 0.004);
+
+ const slice = findSliceNamed(t.sliceGroup, 'a');
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ assert.strictEqual(slice.duration, 0.003);
+
+ const immed = findSliceNamed(t.sliceGroup, 'immediate');
+ assert.strictEqual(immed.title, 'immediate');
+ assert.strictEqual(immed.category, 'bar');
+ assert.strictEqual(immed.start, 0.002);
+ assert.strictEqual(immed.duration, 0);
+
+ const slower = findSliceNamed(t.sliceGroup, 'slower');
+ assert.strictEqual(slower.title, 'slower');
+ assert.strictEqual(slower.category, 'baz');
+ assert.strictEqual(slower.start, 0.004);
+ assert.strictEqual(slower.duration, 0);
+ }
+
+ const events = [
+ // Need to include immediates inside a task so the timeline
+ // recentering/zeroing doesn't clobber their timestamp.
+ {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
+ {name: 'immediate', args: {}, pid: 1, ts: 2, cat: 'bar', tid: 1, ph: 'I'},
+ {name: 'slower', args: {}, pid: 1, ts: 4, cat: 'baz', tid: 1, ph: 'i'},
+ {name: 'a', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('simpleCounter', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const ctr = m.processes[1].counters['foo.ctr'];
+
+ assert.strictEqual(ctr.name, 'ctr');
+ assert.strictEqual(ctr.category, 'foo');
+ assert.strictEqual(ctr.numSamples, 3);
+ assert.strictEqual(ctr.numSeries, 1);
+
+ assert.strictEqual(ctr.series[0].name, 'value');
+ assert.strictEqual(ctr.series[0].color,
+ ColorScheme.getColorIdForGeneralPurposeString('ctr.value'));
+
+ assert.deepEqual(ctr.timestamps, [0, 0.01, 0.02]);
+
+ const samples = [];
+ ctr.series[0].samples.forEach(function(sample) {
+ samples.push(sample.value);
+ });
+ assert.deepEqual(samples, [0, 10, 0]);
+
+ assert.deepEqual(ctr.totals, [0, 10, 0]);
+ assert.strictEqual(ctr.maxTotal, 10);
+ }
+
+ const events = [
+ {name: 'ctr', args: {'value': 0}, pid: 1, ts: 0, cat: 'foo', tid: 1,
+ ph: 'C'},
+ {name: 'ctr', args: {'value': 10}, pid: 1, ts: 10, cat: 'foo', tid: 1,
+ ph: 'C'},
+ {name: 'ctr', args: {'value': 0}, pid: 1, ts: 20, cat: 'foo', tid: 1,
+ ph: 'C'}
+
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('instanceCounter', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ let ctr = m.processes[1].counters['foo.ctr[0]'];
+ assert.strictEqual(ctr.name, 'ctr[0]');
+ assert.strictEqual(ctr.category, 'foo');
+ assert.strictEqual(ctr.numSamples, 2);
+ assert.strictEqual(ctr.numSeries, 1);
+
+ assert.deepEqual(ctr.timestamps, [0, 0.01]);
+ let samples = [];
+ ctr.series[0].samples.forEach(function(sample) {
+ samples.push(sample.value);
+ });
+ assert.deepEqual(samples, [0, 10]);
+
+ ctr = m.processes[1].counters['foo.ctr[1]'];
+ assert.strictEqual(ctr.name, 'ctr[1]');
+ assert.strictEqual(ctr.category, 'foo');
+ assert.strictEqual(ctr.numSamples, 3);
+ assert.strictEqual(ctr.numSeries, 1);
+ assert.deepEqual(ctr.timestamps, [0.01, 0.015, 0.02]);
+
+ samples = [];
+ ctr.series[0].samples.forEach(function(sample) {
+ samples.push(sample.value);
+ });
+ assert.deepEqual(samples, [10, 20, 30]);
+
+ ctr = m.processes[1].counters['bar.ctr[2]'];
+ assert.strictEqual(ctr.name, 'ctr[2]');
+ assert.strictEqual(ctr.category, 'bar');
+ assert.strictEqual(ctr.numSamples, 1);
+ assert.strictEqual(ctr.numSeries, 1);
+ assert.deepEqual(ctr.timestamps, [0.025]);
+ samples = [];
+ ctr.series[0].samples.forEach(function(sample) {
+ samples.push(sample.value);
+ });
+ assert.deepEqual(samples, [40]);
+ }
+
+ const events = [
+ {name: 'ctr', args: {'value': 0}, pid: 1, ts: 0, cat: 'foo', tid: 1,
+ ph: 'C', id: 0},
+ {name: 'ctr', args: {'value': 10}, pid: 1, ts: 10, cat: 'foo', tid: 1,
+ ph: 'C', id: 0},
+ {name: 'ctr', args: {'value': 10}, pid: 1, ts: 10, cat: 'foo', tid: 1,
+ ph: 'C', id: 1},
+ {name: 'ctr', args: {'value': 20}, pid: 1, ts: 15, cat: 'foo', tid: 1,
+ ph: 'C', id: 1},
+ {name: 'ctr', args: {'value': 30}, pid: 1, ts: 20, cat: 'foo', tid: 1,
+ ph: 'C', id: 1},
+ {name: 'ctr', args: {'value': 40}, pid: 1, ts: 25, cat: 'bar', tid: 1,
+ ph: 'C', id: 2}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('multiCounterUpdateBounds', function() {
+ const ctr = new tr.model.Counter(undefined, 'testBasicCounter',
+ '', 'testBasicCounter');
+ const value1Series = new tr.model.CounterSeries(
+ 'value1', 'testBasicCounter.value1');
+ const value2Series = new tr.model.CounterSeries(
+ 'value2', 'testBasicCounter.value2');
+ ctr.addSeries(value1Series);
+ ctr.addSeries(value2Series);
+
+ value1Series.addCounterSample(0, 0);
+ value1Series.addCounterSample(1, 1);
+ value1Series.addCounterSample(2, 1);
+ value1Series.addCounterSample(3, 2);
+ value1Series.addCounterSample(4, 3);
+ value1Series.addCounterSample(5, 1);
+ value1Series.addCounterSample(6, 3);
+ value1Series.addCounterSample(7, 3.1);
+
+ value2Series.addCounterSample(0, 0);
+ value2Series.addCounterSample(1, 0);
+ value2Series.addCounterSample(2, 1);
+ value2Series.addCounterSample(3, 1.1);
+ value2Series.addCounterSample(4, 0);
+ value2Series.addCounterSample(5, 7);
+ value2Series.addCounterSample(6, 0);
+ value2Series.addCounterSample(7, 0.5);
+
+ ctr.updateBounds();
+
+ assert.strictEqual(ctr.bounds.min, 0);
+ assert.strictEqual(ctr.bounds.max, 7);
+ assert.strictEqual(ctr.maxTotal, 8);
+ assert.deepEqual([0, 0,
+ 1, 1,
+ 1, 2,
+ 2, 3.1,
+ 3, 3,
+ 1, 8,
+ 3, 3,
+ 3.1, 3.6], ctr.totals);
+ });
+
+ test('multiCounter', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const ctr = m.processes[1].counters['foo.ctr'];
+ assert.strictEqual(ctr.name, 'ctr');
+
+ assert.strictEqual(ctr.name, 'ctr');
+ assert.strictEqual(ctr.category, 'foo');
+ assert.strictEqual(ctr.numSamples, 3);
+ assert.strictEqual(ctr.numSeries, 2);
+
+ assert.strictEqual(ctr.series[0].name, 'value1');
+ assert.strictEqual(ctr.series[1].name, 'value2');
+ assert.strictEqual(ctr.series[0].color,
+ ColorScheme.getColorIdForGeneralPurposeString('ctr.value1'));
+ assert.strictEqual(ctr.series[1].color,
+ ColorScheme.getColorIdForGeneralPurposeString('ctr.value2'));
+
+ assert.deepEqual(ctr.timestamps, [0, 0.01, 0.02]);
+ const samples = [];
+ ctr.series[0].samples.forEach(function(sample) {
+ samples.push(sample.value);
+ });
+ assert.deepEqual(samples, [0, 10, 0]);
+
+ const samples1 = [];
+ ctr.series[1].samples.forEach(function(sample) {
+ samples1.push(sample.value);
+ });
+ assert.deepEqual(samples1, [7, 4, 1]);
+ assert.deepEqual([0, 7,
+ 10, 14,
+ 0, 1], ctr.totals);
+ assert.strictEqual(ctr.maxTotal, 14);
+ }
+
+ const events = [
+ {name: 'ctr', args: {'value1': 0, 'value2': 7}, pid: 1, ts: 0, cat: 'foo', tid: 1, ph: 'C'}, // @suppress longLineCheck
+ {name: 'ctr', args: {'value1': 10, 'value2': 4}, pid: 1, ts: 10, cat: 'foo', tid: 1, ph: 'C'}, // @suppress longLineCheck
+ {name: 'ctr', args: {'value1': 0, 'value2': 1 }, pid: 1, ts: 20, cat: 'foo', tid: 1, ph: 'C'} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importObjectInsteadOfArray', function() {
+ const events = { traceEvents: [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
+ ] };
+
+ const m = makeModel(events);
+ assert.strictEqual(m.numProcesses, 1);
+ });
+
+ test('importString', function() {
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+
+ const m = makeModel(JSON.stringify(events));
+ assert.strictEqual(m.numProcesses, 1);
+ });
+
+ test('importStringWithLeadingSpaces', function() {
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+
+ const m = makeModel(' ' + JSON.stringify(events));
+ assert.strictEqual(m.numProcesses, 1);
+ });
+
+ test('importStringWithTrailingNewLine', function() {
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+
+ const m = makeModel(JSON.stringify(events) + '\n');
+ assert.strictEqual(m.numProcesses, 1);
+ });
+
+ test('importStringWithMissingCloseSquareBracket', function() {
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+
+ const tmp = JSON.stringify(events);
+ assert.strictEqual(tmp[tmp.length - 1], ']');
+
+ // Drop off the trailing ]
+ const dropped = tmp.substring(0, tmp.length - 1);
+ const m = makeModel(dropped);
+ assert.strictEqual(m.numProcesses, 1);
+ });
+
+ test('importStringWithEndingCommaButMissingCloseSquareBracket', function() {
+ const lines = [
+ '[',
+ '{"name": "a", "args": {}, "pid": 52, "ts": 524, "cat": "foo", "tid": 53, "ph": "B"},', // @suppress longLineCheck
+ '{"name": "a", "args": {}, "pid": 52, "ts": 560, "cat": "foo", "tid": 53, "ph": "E"},' // @suppress longLineCheck
+ ];
+ const text = lines.join('\n');
+
+ const m = makeModel(text);
+ assert.strictEqual(m.numProcesses, 1);
+ assert.strictEqual(m.processes[52].threads[53].sliceGroup.length, 1);
+ });
+
+ test('importStringWithMissingCloseSquareBracketAndNewline', function() {
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+
+ const tmp = JSON.stringify(events);
+ assert.strictEqual(tmp[tmp.length - 1], ']');
+
+ // Drop off the trailing ] and add a newline
+ const dropped = tmp.substring(0, tmp.length - 1);
+ const m = makeModel(dropped + '\n');
+ assert.strictEqual(m.numProcesses, 1);
+ });
+
+ test('ImportStringEndingCommaButMissingCloseSquareBracketCRLF', function() {
+ const lines = [
+ '[',
+ '{"name": "a", "args": {}, "pid": 52, "ts": 524, "cat": "foo", "tid": 53, "ph": "B"},', // @suppress longLineCheck
+ '{"name": "a", "args": {}, "pid": 52, "ts": 560, "cat": "foo", "tid": 53, "ph": "E"},' // @suppress longLineCheck
+ ];
+ const text = lines.join('\r\n');
+
+ const m = makeModel(text);
+ assert.strictEqual(m.numProcesses, 1);
+ assert.strictEqual(m.processes[52].threads[53].sliceGroup.length, 1);
+ });
+
+ test('importOldFormat', function() {
+ const lines = [
+ '[',
+ '{"cat":"a","pid":9,"tid":8,"ts":194,"ph":"E","name":"I","args":{}},',
+ '{"cat":"b","pid":9,"tid":8,"ts":194,"ph":"B","name":"I","args":{}}',
+ ']'
+ ];
+ const text = lines.join('\n');
+ const m = makeModel(text);
+ assert.strictEqual(m.numProcesses, 1);
+ assert.strictEqual(m.processes[9].threads[8].sliceGroup.length, 1);
+ });
+
+ test('startFinishOneSliceOneThread', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ assert.strictEqual(t.asyncSliceGroup.slices[0].title, 'a');
+ assert.strictEqual(t.asyncSliceGroup.slices[0].category, 'cat');
+ assert.isTrue(t.asyncSliceGroup.slices[0].isTopLevel);
+ assert.strictEqual(t.asyncSliceGroup.slices[0].id, ':ptr:72');
+ assert.strictEqual(t.asyncSliceGroup.slices[0].args.foo, 'bar');
+ assert.strictEqual(t.asyncSliceGroup.slices[0].start, 0);
+ assert.closeTo(
+ (60 - 24) / 1000, t.asyncSliceGroup.slices[0].duration, 1e-5);
+ assert.strictEqual(t.asyncSliceGroup.slices[0].startThread, t);
+ assert.strictEqual(t.asyncSliceGroup.slices[0].endThread, t);
+ }
+
+ const events = [
+ // Time is intentionally out of order.
+ {name: 'a', args: {}, pid: 52, ts: 560, cat: 'cat', tid: 53,
+ ph: 'F', id: 72},
+ {name: 'a', pid: 52, ts: 524, cat: 'cat', tid: 53,
+ ph: 'S', id: 72, args: {'foo': 'bar'}}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('endArgsAddedToSlice', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.numProcesses, 1);
+ const p = m.processes[52];
+ assert.isDefined(p);
+
+ assert.strictEqual(p.numThreads, 1);
+ const t = p.threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.length, 1);
+ assert.strictEqual(t.tid, 53);
+ const slice = t.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'foo');
+ assert.strictEqual(slice.start, 0);
+ assert.strictEqual(slice.subSlices.length, 0);
+ assert.strictEqual(slice.args.x, 1);
+ assert.strictEqual(slice.args.y, 2);
+ }
+
+ const events = [
+ {name: 'a', args: {x: 1}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {y: 2}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('endArgOverrwritesOriginalArgValueIfDuplicated', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.numProcesses, 1);
+ const p = m.processes[52];
+ assert.isDefined(p);
+
+ assert.strictEqual(p.numThreads, 1);
+ const t = p.threads[53];
+ assert.isDefined(t);
+ const slice = t.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'b');
+ assert.strictEqual(slice.category, 'foo');
+ assert.strictEqual(slice.start, 0);
+ assert.strictEqual(slice.subSlices.length, 0);
+ assert.strictEqual(slice.args.z, 4);
+ }
+
+ const events = [
+ {name: 'b', args: {z: 3}, pid: 52, ts: 629, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'b', args: {z: 4}, pid: 52, ts: 631, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('asyncEndArgsAddedToSlice', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'c');
+ assert.strictEqual(parentSlice.category, 'foo');
+ assert.isTrue(parentSlice.isTopLevel);
+ assert.strictEqual(parentSlice.args.x, 1);
+ assert.strictEqual(parentSlice.args.y, 2);
+
+ assert.isDefined(parentSlice.subSlices);
+ assert.strictEqual(parentSlice.subSlices.length, 0);
+ }
+
+ const events = [
+ // Time is intentionally out of order.
+ {name: 'c', args: {y: 2}, pid: 52, ts: 560, cat: 'foo', tid: 53,
+ ph: 'F', id: 72},
+ {name: 'c', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'S', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('asyncEndArgOverwritesOriginalArgValueIfDuplicated', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'd');
+ assert.strictEqual(parentSlice.category, 'foo');
+ assert.isTrue(parentSlice.isTopLevel);
+ assert.strictEqual(parentSlice.args.z, 4);
+
+ assert.isDefined(parentSlice.subSlices);
+ assert.strictEqual(parentSlice.subSlices.length, 0);
+ }
+
+ const events = [
+ // Time is intentionally out of order.
+ {name: 'd', args: {z: 4}, pid: 52, ts: 560, cat: 'foo', tid: 53,
+ ph: 'F', id: 72},
+ {name: 'd', args: {z: 3}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'S', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('asyncStepsInOneThread', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'a');
+ assert.strictEqual(parentSlice.category, 'foo');
+ assert.isTrue(parentSlice.isTopLevel);
+ assert.strictEqual(parentSlice.start, 0);
+ assert.strictEqual(parentSlice.args.x, 1);
+ assert.isUndefined(parentSlice.args.y);
+ assert.strictEqual(parentSlice.args.z, 3);
+
+ assert.isDefined(parentSlice.subSlices);
+ assert.strictEqual(parentSlice.subSlices.length, 1);
+
+ const subSlice = parentSlice.subSlices[0];
+ assert.strictEqual(subSlice.title, 's1');
+ assert.strictEqual(subSlice.category, 'foo');
+ assert.isFalse(subSlice.isTopLevel);
+ assert.closeTo((548 - 524) / 1000, subSlice.start, 1e-5);
+ assert.closeTo((560 - 548) / 1000, subSlice.duration, 1e-5);
+ assert.isUndefined(subSlice.args.x);
+ assert.strictEqual(subSlice.args.y, 2);
+ assert.isUndefined(subSlice.args.z);
+ }
+
+ const events = [
+ // Time is intentionally out of order.
+ {name: 'a', args: {z: 3}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'F', id: 72}, // @suppress longLineCheck
+ {name: 'a', args: {step: 's1', y: 2}, pid: 52, ts: 548, cat: 'foo', tid: 53, ph: 'T', id: 72}, // @suppress longLineCheck
+ {name: 'a', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'S', id: 72} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('asyncStepsMissingStart', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isUndefined(t);
+ }
+
+ const events = [
+ // Time is intentionally out of order.
+ {name: 'a', args: {z: 3}, pid: 52, ts: 560, cat: 'foo', tid: 53,
+ ph: 'F', id: 72},
+ {name: 'a', args: {step: 's1', y: 2}, pid: 52, ts: 548, cat: 'foo',
+ tid: 53, ph: 'T', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('autoClosingAsyncStepsMissingFinish', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'a');
+ assert.strictEqual(parentSlice.category, 'foo');
+ assert.isTrue(parentSlice.isTopLevel);
+ assert.strictEqual(parentSlice.start, 0);
+ assert.strictEqual(parentSlice.duration, 2);
+ assert.isTrue(parentSlice.didNotFinish);
+ assert.isDefined(parentSlice.error);
+ assert.isUndefined(parentSlice.args.y);
+ assert.strictEqual(parentSlice.args.z, 3);
+
+ assert.isDefined(parentSlice.subSlices);
+ assert.strictEqual(parentSlice.subSlices.length, 1);
+ const subSlice = parentSlice.subSlices[0];
+ assert.strictEqual(subSlice.title, 's1');
+ assert.strictEqual(subSlice.category, 'foo');
+ assert.isFalse(subSlice.isTopLevel);
+ assert.strictEqual(subSlice.start, 0);
+ assert.strictEqual(subSlice.duration, 2);
+ assert.isUndefined(subSlice.args.x);
+ assert.strictEqual(subSlice.args.y, 2);
+ assert.isUndefined(subSlice.args.z);
+ assert.isTrue(subSlice.didNotFinish);
+ assert.isDefined(subSlice.error);
+ }
+
+ const events = [
+ {name: 'a', args: {z: 3}, pid: 52, ts: 0, cat: 'foo', tid: 53,
+ ph: 'S', id: 72},
+ {name: 'a', args: {step: 's1', y: 2}, pid: 52, ts: 0, cat: 'foo',
+ tid: 53, ph: 'T', id: 72},
+
+ // Slice that does finish to give an 'end time' to make autoclosing work.
+ {name: 'b', args: {}, pid: 1, ts: 1000, cat: 'bar', tid: 2, ph: 'B'},
+ {name: 'b', args: {}, pid: 1, ts: 2000, cat: 'bar', tid: 2, ph: 'E'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('asyncStepEndEvent', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'a');
+ assert.strictEqual(parentSlice.category, 'foo');
+ assert.isTrue(parentSlice.isTopLevel);
+ assert.strictEqual(parentSlice.start, 0);
+ assert.strictEqual(parentSlice.args.x, 1);
+ assert.isUndefined(parentSlice.args.y);
+ assert.strictEqual(parentSlice.args.z, 3);
+
+ assert.isDefined(parentSlice.subSlices);
+ assert.strictEqual(parentSlice.subSlices.length, 1);
+ const subSlice = parentSlice.subSlices[0];
+ assert.strictEqual(subSlice.title, 's1');
+ assert.strictEqual(subSlice.category, 'foo');
+ assert.isFalse(subSlice.isTopLevel);
+ assert.strictEqual(subSlice.start, 0);
+ assert.closeTo((548 - 524) / 1000, subSlice.duration, 1e-5);
+ assert.isUndefined(subSlice.args.x);
+ assert.strictEqual(subSlice.args.y, 2);
+ assert.isUndefined(subSlice.args.z);
+ }
+
+ const events = [
+ // Time is intentionally out of order.
+ {name: 'a', args: {z: 3}, pid: 52, ts: 560, cat: 'foo', tid: 53,
+ ph: 'F', id: 72},
+ {name: 'a', args: {step: 's1', y: 2}, pid: 52, ts: 548, cat: 'foo',
+ tid: 53, ph: 'p', id: 72},
+ {name: 'a', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'S', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('asyncStepMismatch', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isUndefined(t);
+ assert.isTrue(m.hasImportWarnings);
+ }
+
+ const events = [
+ // Time is intentionally out of order.
+ {name: 'a', args: {z: 3}, pid: 52, ts: 560, cat: 'foo', tid: 53,
+ ph: 'F', id: 72},
+ {name: 'a', args: {step: 's2'}, pid: 52, ts: 548, cat: 'foo', tid: 53,
+ ph: 'T', id: 72},
+ {name: 'a', args: {step: 's1'}, pid: 52, ts: 548, cat: 'foo', tid: 53,
+ ph: 'p', id: 72},
+ {name: 'a', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'S', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('asyncSliceWithoutCPUDuration', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 3);
+
+ const noTTSNoField = t.asyncSliceGroup.slices[0];
+ assert.isUndefined(noTTSNoField.cpuStart);
+ assert.isUndefined(noTTSNoField.cpuDuration);
+
+ const TTSNoField = t.asyncSliceGroup.slices[1];
+ assert.isUndefined(TTSNoField.cpuStart);
+ assert.isUndefined(TTSNoField.cpuDuration);
+
+ const TTSZeroField = t.asyncSliceGroup.slices[2];
+ assert.isUndefined(TTSZeroField.cpuStart);
+ assert.isUndefined(TTSZeroField.cpuDuration);
+ }
+
+ const events = [
+ // Async slice without tts field.
+ {name: 'a', args: {params: ''}, pid: 52, ts: 10, cat: 'foo', tid: 53,
+ id: 72, ph: 'b'},
+ {name: 'a', args: {params: ''}, pid: 52, ts: 20, cat: 'foo', tid: 53,
+ id: 72, ph: 'e'},
+ // Async slice with tts field but without use_async_tts marker field.
+ {name: 'b', args: {params: ''}, pid: 52, ts: 30, cat: 'foo', tid: 53,
+ id: 72, ph: 'b', tts: 30000},
+ {name: 'b', args: {params: ''}, pid: 52, ts: 40, cat: 'foo', tid: 53,
+ id: 72, ph: 'e', tts: 40000},
+ // Async slice with tts field but with use_async_tts marker set to 0.
+ {name: 'c', args: {params: ''}, pid: 52, ts: 50000, cat: 'foo', tid: 53,
+ id: 72, ph: 'b', tts: 50000, use_async_tts: 0},
+ {name: 'c', args: {params: ''}, pid: 52, ts: 60000, cat: 'foo', tid: 53,
+ id: 72, ph: 'e', tts: 60000, use_async_tts: 0}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('asyncSliceWithCPUDuration', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+
+ const asyncSlice = t.asyncSliceGroup.slices[0];
+ assert.isDefined(asyncSlice);
+ assert.strictEqual(asyncSlice.duration, 10);
+ assert.strictEqual(asyncSlice.cpuStart, 100);
+ assert.strictEqual(asyncSlice.cpuDuration, 5);
+ }
+
+ const events = [
+ {name: 'a', args: {params: ''}, pid: 52, ts: 50000, cat: 'foo', tid: 53,
+ id: 72, ph: 'b', tts: 100000, use_async_tts: 1},
+ {name: 'a', args: {params: ''}, pid: 52, ts: 60000, cat: 'foo', tid: 53,
+ id: 72, ph: 'e', tts: 105000, use_async_tts: 1}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nestableAsyncBasic', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'a');
+ assert.strictEqual(parentSlice.category, 'foo');
+ assert.isTrue(parentSlice.isTopLevel);
+
+ assert.isDefined(parentSlice.subSlices);
+ assert.strictEqual(parentSlice.subSlices.length, 1);
+ const subSlice = parentSlice.subSlices[0];
+ assert.isFalse(subSlice.isTopLevel);
+ // Arguments should include both BEGIN and END event.
+ assert.strictEqual(subSlice.args.x, 1);
+ assert.strictEqual(subSlice.args.y, 2);
+ assert.sameMembers(subSlice.subSlices, []);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'b', args: {x: 1}, pid: 52, ts: 525, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'b', args: {y: 2}, pid: 52, ts: 560, cat: 'foo', tid: 53,
+ ph: 'e', id: 72},
+ {name: 'a', args: {}, pid: 52, ts: 565, cat: 'foo', tid: 53,
+ ph: 'e', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+
+ test('nestableAsyncNoArgs', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const slice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'name');
+ assert.strictEqual(slice.category, 'foo');
+ assert.isTrue(slice.isTopLevel);
+
+ assert.isDefined(slice.subSlices);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ assert.deepEqual(slice.args, {});
+ }
+
+ const events = [
+ {name: 'name', pid: 52, ts: 525, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'name', pid: 52, ts: 560, cat: 'foo', tid: 53,
+ ph: 'e', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nestableAsyncCombinedParams', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 3);
+
+ const sliceA = t.asyncSliceGroup.slices[0];
+ // Arguments should include both BEGIN and END event.
+ assert.strictEqual(sliceA.args.x, 1);
+ assert.strictEqual(sliceA.args.y, 2);
+ const paramsA = sliceA.args.params;
+ assert.isDefined(paramsA);
+ assert.strictEqual(paramsA.p1, 'hello');
+ assert.strictEqual(paramsA.p2, 123);
+ assert.strictEqual(paramsA.p3, 'hi');
+ assert.isTrue(sliceA.isTopLevel);
+
+ const sliceB = t.asyncSliceGroup.slices[1];
+ // Arguments should include both BEGIN and END event.
+ const paramsB = sliceB.args.params;
+ assert.isDefined(paramsB);
+ assert.strictEqual(paramsB.p4, 'foo');
+ assert.isTrue(sliceB.isTopLevel);
+
+ const sliceC = t.asyncSliceGroup.slices[2];
+ // Arguments should include both BEGIN and END event.
+ const paramsC = sliceC.args.params;
+ assert.isDefined(paramsC);
+ assert.strictEqual(paramsC.p5, 'bar');
+ assert.isTrue(sliceC.isTopLevel);
+ }
+
+ const events = [
+ {name: 'a', args: {x: 1, params: {p1: 'hello', p2: 123}},
+ pid: 52, ts: 525, cat: 'foo', tid: 53, ph: 'b', id: 72},
+ {name: 'a', args: {y: 2, params: {p3: 'hi'}}, pid: 52, ts: 560,
+ cat: 'foo', tid: 53, ph: 'e', id: 72},
+ {name: 'b', args: {params: {p4: 'foo'}},
+ pid: 52, ts: 525, cat: 'foo', tid: 53, ph: 'b', id: 73},
+ {name: 'b', args: {params: ''}, pid: 52, ts: 560,
+ cat: 'foo', tid: 53, ph: 'e', id: 73},
+ {name: 'c', args: {params: {p5: 'bar'}},
+ pid: 52, ts: 525, cat: 'foo', tid: 53, ph: 'b', id: 74},
+ {name: 'c', args: {}, pid: 52, ts: 560,
+ cat: 'foo', tid: 53, ph: 'e', id: 74}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nestableAsyncManyLevels', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ // Perfectly matched events should not produce a warning.
+ assert.isFalse(m.hasImportWarnings);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+
+ const l1Slice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(l1Slice.title, 'l1');
+ assert.closeTo(0, l1Slice.start, 1e-5);
+ assert.closeTo(9 / 1000, l1Slice.duration, 1e-5);
+ assert.isTrue(l1Slice.isTopLevel);
+
+ assert.isDefined(l1Slice.subSlices);
+ assert.strictEqual(l1Slice.subSlices.length, 1);
+ const l2Slice = l1Slice.subSlices[0];
+ assert.strictEqual(l2Slice.title, 'l2');
+ assert.closeTo(1 / 1000, l2Slice.start, 1e-5);
+ assert.closeTo(7 / 1000, l2Slice.duration, 1e-5);
+ assert.isFalse(l2Slice.isTopLevel);
+
+ assert.isDefined(l2Slice.subSlices);
+ assert.strictEqual(l2Slice.subSlices.length, 1);
+ const l3Slice = l2Slice.subSlices[0];
+ assert.strictEqual(l3Slice.title, 'l3');
+ assert.closeTo(2 / 1000, l3Slice.start, 1e-5);
+ assert.closeTo(5 / 1000, l3Slice.duration, 1e-5);
+ assert.isFalse(l3Slice.isTopLevel);
+
+ assert.isDefined(l3Slice.subSlices);
+ assert.strictEqual(l3Slice.subSlices.length, 1);
+ const l4Slice = l3Slice.subSlices[0];
+ assert.strictEqual(l4Slice.title, 'l4');
+ assert.closeTo(3 / 1000, l4Slice.start, 1e-5);
+ assert.closeTo(3 / 1000, l4Slice.duration, 1e-5);
+ assert.isFalse(l4Slice.isTopLevel);
+
+ assert.isDefined(l4Slice.subSlices);
+ assert.strictEqual(l4Slice.subSlices.length, 1);
+ const l5Slice = l4Slice.subSlices[0];
+ assert.strictEqual(l5Slice.title, 'l5');
+ assert.closeTo(4 / 1000, l5Slice.start, 1e-5);
+ assert.closeTo(1 / 1000, l5Slice.duration, 1e-5);
+ assert.isFalse(l5Slice.isTopLevel);
+ }
+
+ // There are 5 nested levels.
+ const events = [
+ {name: 'l1', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'l2', args: {}, pid: 52, ts: 525, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'l3', args: {}, pid: 52, ts: 526, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'l4', args: {}, pid: 52, ts: 527, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'l5', args: {}, pid: 52, ts: 528, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'l5', args: {}, pid: 52, ts: 529, cat: 'foo', tid: 53,
+ ph: 'e', id: 72},
+ {name: 'l4', args: {}, pid: 52, ts: 530, cat: 'foo', tid: 53,
+ ph: 'e', id: 72},
+ {name: 'l3', args: {}, pid: 52, ts: 531, cat: 'foo', tid: 53,
+ ph: 'e', id: 72},
+ {name: 'l2', args: {}, pid: 52, ts: 532, cat: 'foo', tid: 53,
+ ph: 'e', id: 72},
+ {name: 'l1', args: {}, pid: 52, ts: 533, cat: 'foo', tid: 53,
+ ph: 'e', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nestableAsyncInstantEvent', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 2);
+ const instantSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(instantSlice.title, 'c');
+ assert.closeTo(0, instantSlice.start, 1e-5);
+ assert.closeTo(0, instantSlice.duration, 1e-5);
+ assert.sameMembers(instantSlice.subSlices, []);
+ assert.isTrue(instantSlice.isTopLevel);
+
+ const nestedSlice = t.asyncSliceGroup.slices[1];
+ assert.strictEqual(nestedSlice.title, 'a');
+ assert.closeTo(0, nestedSlice.start, 1e-5);
+ assert.closeTo((565 - 524) / 1000, nestedSlice.duration, 1e-5);
+ assert.isTrue(nestedSlice.isTopLevel);
+ assert.isDefined(nestedSlice.subSlices);
+ assert.strictEqual(nestedSlice.subSlices.length, 1);
+ const nestedInstantSlice = nestedSlice.subSlices[0];
+ assert.sameMembers(nestedInstantSlice.subSlices, []);
+ assert.strictEqual(nestedInstantSlice.title, 'd');
+ assert.isFalse(nestedInstantSlice.isTopLevel);
+ }
+
+ const events = [
+ {name: 'c', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'n', id: 71},
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'd', args: {}, pid: 52, ts: 525, cat: 'foo', tid: 53,
+ ph: 'n', id: 72},
+ {name: 'a', args: {}, pid: 52, ts: 565, cat: 'foo', tid: 53,
+ ph: 'e', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nestableAsyncUnmatchedOuterBeginEvent', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ // Unmatched BEGIN should produce a warning.
+ assert.isTrue(m.hasImportWarnings);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'a');
+ assert.strictEqual(parentSlice.category, 'foo');
+ assert.isTrue(parentSlice.isTopLevel);
+ assert.closeTo(0, parentSlice.start, 0.0001);
+ // Unmatched BEGIN event ends at the last event of that ID.
+ assert.closeTo(36 / 1000, parentSlice.duration, 0.0001);
+ // Arguments should include only include its arguments.
+ assert.isUndefined(parentSlice.args.y);
+ assert.strictEqual(parentSlice.args.x, 1);
+ assert.isDefined(parentSlice.error);
+
+ assert.isDefined(parentSlice.subSlices);
+ assert.strictEqual(parentSlice.subSlices.length, 1);
+ const subSlice = parentSlice.subSlices[0];
+ assert.isFalse(subSlice.isTopLevel);
+ assert.closeTo(1 / 1000, subSlice.start, 1e-5);
+ assert.closeTo(35 / 1000, subSlice.duration, 1e-5);
+ assert.sameMembers(subSlice.subSlices, []);
+ // Arguments should include those of the END event.
+ assert.strictEqual(subSlice.args.y, 2);
+ assert.sameMembers(subSlice.subSlices, []);
+ }
+
+ const events = [
+ {name: 'a', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'b', args: {}, pid: 52, ts: 525, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'b', args: {y: 2}, pid: 52, ts: 560, cat: 'foo', tid: 53,
+ ph: 'e', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nestableAsyncUnmatchedInnerBeginEvent', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ // Unmatched BEGIN should produce a warning.
+ assert.isTrue(m.hasImportWarnings);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'a');
+ assert.strictEqual(parentSlice.category, 'foo');
+ assert.isTrue(parentSlice.isTopLevel);
+ assert.closeTo(0, parentSlice.start, 1e-5);
+ assert.closeTo(41 / 1000, parentSlice.duration, 1e-5);
+ // Arguments should include both BEGIN and END event.
+ assert.strictEqual(parentSlice.args.y, 2);
+ assert.strictEqual(parentSlice.args.z, 3);
+ assert.isUndefined(parentSlice.args.x);
+
+ assert.isDefined(parentSlice.subSlices);
+ assert.strictEqual(parentSlice.subSlices.length, 2);
+ const subSliceInstant = parentSlice.subSlices[0];
+ const subSliceUnmatched = parentSlice.subSlices[1];
+ assert.strictEqual(subSliceInstant.title, 'c');
+ assert.isFalse(subSliceInstant.isTopLevel);
+ assert.strictEqual(subSliceUnmatched.title, 'b');
+ assert.isFalse(subSliceUnmatched.isTopLevel);
+ // Unmatched BEGIN ends at the last event of that ID.
+ assert.closeTo(1 / 1000, subSliceUnmatched.start, 1e-5);
+ assert.closeTo(40 / 1000, subSliceUnmatched.duration, 1e-5);
+ assert.sameMembers(subSliceUnmatched.subSlices, []);
+ assert.strictEqual(subSliceUnmatched.args.x, 1);
+ assert.isUndefined(subSliceUnmatched.y);
+ assert.isDefined(subSliceUnmatched.error);
+ assert.closeTo(1 / 1000, subSliceInstant.start, 1e-5);
+ assert.closeTo(0, subSliceInstant.duration, 1e-5);
+ assert.sameMembers(subSliceInstant.subSlices, []);
+ }
+
+ const events = [
+ {name: 'a', args: {z: 3}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'c', args: {}, pid: 52, ts: 525, cat: 'foo', tid: 53,
+ ph: 'n', id: 72},
+ {name: 'b', args: {x: 1}, pid: 52, ts: 525, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'a', args: {y: 2}, pid: 52, ts: 565, cat: 'foo', tid: 53,
+ ph: 'e', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nestableAsyncUnmatchedOuterEndEvent', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ // Unmatched END should produce a warning.
+ assert.isTrue(m.hasImportWarnings);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 2);
+ const unmatchedSlice = t.asyncSliceGroup.slices[0];
+ const slice = t.asyncSliceGroup.slices[1];
+ assert.strictEqual(unmatchedSlice.title, 'a');
+ assert.closeTo(0, unmatchedSlice.start, 1e-5);
+ assert.isTrue(unmatchedSlice.isTopLevel);
+ // Unmatched END event begins at the first event of that ID. In this
+ // case, the first event happens to be the same unmatched event.
+ assert.closeTo(0 / 1000, unmatchedSlice.duration, 1e-5);
+ assert.isUndefined(unmatchedSlice.args.x);
+ assert.isUndefined(unmatchedSlice.args.y);
+ assert.strictEqual(unmatchedSlice.args.z, 3);
+ assert.isDefined(unmatchedSlice.error);
+ assert.sameMembers(unmatchedSlice.subSlices, []);
+
+ assert.strictEqual(slice.title, 'b');
+ assert.isTrue(slice.isTopLevel);
+ assert.closeTo(1 / 1000, slice.start, 1e-5);
+ assert.closeTo(35 / 1000, slice.duration, 1e-5);
+ // Arguments should include both BEGIN and END event.
+ assert.strictEqual(slice.args.x, 1);
+ assert.strictEqual(slice.args.y, 2);
+ assert.sameMembers(slice.subSlices, []);
+ }
+
+ // Events are intentionally out-of-order.
+ const events = [
+ {name: 'b', args: {x: 1}, pid: 52, ts: 525, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'b', args: {y: 2}, pid: 52, ts: 560, cat: 'foo', tid: 53,
+ ph: 'e', id: 72},
+ {name: 'a', args: {z: 3}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'e', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nestableAsyncUnmatchedInnerEndEvent', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ // Unmatched END should produce a warning.
+ assert.isTrue(m.hasImportWarnings);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'a');
+ assert.isTrue(parentSlice.isTopLevel);
+ assert.closeTo(0, parentSlice.start, 1e-5);
+ assert.closeTo(41 / 1000, parentSlice.duration, 1e-5);
+ // Arguments should include both BEGIN and END event.
+ assert.strictEqual(parentSlice.args.x, 1);
+ assert.strictEqual(parentSlice.args.y, 2);
+
+ assert.isDefined(parentSlice.subSlices);
+ assert.strictEqual(parentSlice.subSlices.length, 2);
+ const subSliceInstant = parentSlice.subSlices[0];
+ const subSliceUnmatched = parentSlice.subSlices[1];
+ assert.strictEqual(subSliceInstant.title, 'c');
+ assert.isFalse(subSliceInstant.isTopLevel);
+ assert.strictEqual(subSliceUnmatched.title, 'b');
+ assert.isFalse(subSliceUnmatched.isTopLevel);
+ // Unmatched END begins at the first event of that ID.
+ assert.closeTo(0 / 1000, subSliceUnmatched.start, 1e-5);
+ assert.closeTo(1 / 1000, subSliceUnmatched.duration, 1e-5);
+ // Arguments should include both BEGIN and END event.
+ assert.isUndefined(subSliceUnmatched.args.x);
+ assert.isUndefined(subSliceUnmatched.args.y);
+ assert.strictEqual(subSliceUnmatched.args.z, 3);
+ assert.isDefined(subSliceUnmatched.error);
+
+ assert.sameMembers(subSliceUnmatched.subSlices, []);
+ assert.closeTo(1 / 1000, subSliceInstant.start, 1e-5);
+ assert.closeTo(0, subSliceInstant.duration, 1e-5);
+ assert.sameMembers(subSliceInstant.subSlices, []);
+ }
+
+ const events = [
+ {name: 'a', args: {x: 1}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'c', args: {}, pid: 52, ts: 525, cat: 'foo', tid: 53,
+ ph: 'n', id: 72},
+ {name: 'b', args: {z: 3}, pid: 52, ts: 525, cat: 'foo', tid: 53,
+ ph: 'e', id: 72},
+ {name: 'a', args: {y: 2}, pid: 52, ts: 565, cat: 'foo', tid: 53,
+ ph: 'e', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nestableAsyncSameIDDifferentCategory', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 2);
+ const eventASlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(eventASlice.title, 'EVENT_A');
+ assert.strictEqual(eventASlice.category, 'foo');
+ assert.strictEqual(eventASlice.id, 'foo::ptr:72');
+ assert.isTrue(eventASlice.isTopLevel);
+ assert.strictEqual(eventASlice.args.x, 1);
+ assert.sameMembers(eventASlice.subSlices, []);
+
+ const eventBSlice = t.asyncSliceGroup.slices[1];
+ assert.strictEqual(eventBSlice.title, 'EVENT_B');
+ assert.strictEqual(eventBSlice.category, 'bar');
+ assert.strictEqual(eventBSlice.id, 'bar::ptr:72');
+ assert.isTrue(eventBSlice.isTopLevel);
+ assert.strictEqual(eventBSlice.args.y, 2);
+ assert.sameMembers(eventBSlice.subSlices, []);
+ }
+
+ // Events with the same ID, but different categories should not be
+ // considered as nested.
+ const events = [
+ {name: 'EVENT_A', args: {}, pid: 52, ts: 500, cat: 'foo', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'EVENT_B', args: {y: 2}, pid: 52, ts: 550, cat: 'bar', tid: 53,
+ ph: 'b', id: 72},
+ {name: 'EVENT_B', args: {}, pid: 52, ts: 600, cat: 'bar', tid: 53,
+ ph: 'e', id: 72},
+ {name: 'EVENT_A', args: {x: 1}, pid: 52, ts: 650, cat: 'foo', tid: 53,
+ ph: 'e', id: 72}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('nestableAsyncStackFrame', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const slice = t.asyncSliceGroup.slices[0];
+
+ assert.strictEqual(slice.startStackFrame.title, 'main');
+ assert.strictEqual(slice.endStackFrame.title, 'frame7');
+ }
+
+ const events = {
+ traceEvents: [
+ {name: 'name', pid: 52, ts: 525, cat: 'foo', tid: 53,
+ ph: 'b', id: 72, sf: 1},
+ {name: 'name', pid: 52, ts: 560, cat: 'foo', tid: 53,
+ ph: 'e', id: 72, sf: 7}
+ ],
+ stackFrames: {
+ '1': {
+ category: 'm1',
+ name: 'main'
+ },
+ '7': {
+ category: 'm2',
+ name: 'frame7',
+ parent: '1'
+ }
+ }
+ };
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('processLocalAsync', function() {
+ function checkModel(m) {
+ let t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ let parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'a');
+ assert.strictEqual(parentSlice.category, 'foo');
+ assert.isTrue(parentSlice.isTopLevel);
+ assert.isDefined(parentSlice.subSlices);
+ assert.strictEqual(parentSlice.subSlices.length, 0);
+
+ t = m.processes[54].threads[55];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'b');
+ assert.strictEqual(parentSlice.category, 'foo');
+ assert.strictEqual(parentSlice.args.x, 1);
+ assert.strictEqual(parentSlice.args.y, 2);
+ assert.isTrue(parentSlice.isTopLevel);
+ assert.isDefined(parentSlice.subSlices);
+ assert.strictEqual(parentSlice.subSlices.length, 0);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'b', id2: {local: 72}},
+ {name: 'b', args: {x: 1}, pid: 54, ts: 525, cat: 'foo', tid: 55,
+ ph: 'b', id2: {local: 72}},
+ {name: 'b', args: {y: 2}, pid: 54, ts: 560, cat: 'foo', tid: 55,
+ ph: 'e', id2: {local: 72}},
+ {name: 'a', args: {}, pid: 52, ts: 565, cat: 'foo', tid: 53,
+ ph: 'e', id2: {local: 72}}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('globalAsync', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'a');
+ assert.strictEqual(parentSlice.category, 'foo');
+ assert.isTrue(parentSlice.isTopLevel);
+
+ assert.isDefined(parentSlice.subSlices);
+ assert.strictEqual(parentSlice.subSlices.length, 1);
+ const subSlice = parentSlice.subSlices[0];
+ assert.isFalse(subSlice.isTopLevel);
+ // Arguments should include both BEGIN and END event.
+ assert.strictEqual(subSlice.args.x, 1);
+ assert.strictEqual(subSlice.args.y, 2);
+ assert.sameMembers(subSlice.subSlices, []);
+
+ assert.isUndefined(m.processes[54].threads[55]);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53,
+ ph: 'b', id2: {global: 72}},
+ {name: 'b', args: {x: 1}, pid: 54, ts: 525, cat: 'foo', tid: 55,
+ ph: 'b', id2: {global: 72}},
+ {name: 'b', args: {y: 2}, pid: 54, ts: 560, cat: 'foo', tid: 55,
+ ph: 'e', id2: {global: 72}},
+ {name: 'a', args: {}, pid: 52, ts: 565, cat: 'foo', tid: 53,
+ ph: 'e', id2: {global: 72}}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importSamples', function() {
+ function checkModel(m) {
+ const p = m.processes[52];
+ assert.isDefined(p);
+ const t = p.threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.samples_.length, 4);
+ assert.strictEqual(t.samples_[0].start, 0.0);
+ assert.strictEqual(t.samples_[1].start, 0.0);
+ assert.closeTo(0.01, t.samples_[2].start, 1e-5);
+ assert.strictEqual(t.samples_[0].leafNode.title, 'a');
+ assert.strictEqual(t.samples_[1].leafNode.title, 'b');
+ assert.strictEqual(t.samples_[2].leafNode.title, 'c');
+ assert.strictEqual(t.samples_[3].leafNode, t.samples[0].leafNode);
+ assert.isFalse(m.hasImportWarnings);
+ }
+
+ const events = [
+ {name: 'a', args: {}, pid: 52, ts: 548, cat: 'test', tid: 53, ph: 'P'},
+ {name: 'b', args: {}, pid: 52, ts: 548, cat: 'test', tid: 53, ph: 'P'},
+ {name: 'c', args: {}, pid: 52, ts: 558, cat: 'test', tid: 53, ph: 'P'},
+ {name: 'a', args: {}, pid: 52, ts: 568, cat: 'test', tid: 53, ph: 'P'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importSamplesWithStackFrames', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[2];
+
+ assert.strictEqual(t.samples.length, 1);
+ assert.strictEqual(t.samples_[0].start, 0.0);
+ assert.strictEqual(t.samples_[0].leafNode.title, 'frame7');
+ assert.isFalse(m.hasImportWarnings);
+ }
+
+ const eventData = {
+ traceEvents: [
+ { name: 'a', args: {}, pid: 1, ts: 0, cat: 'test', tid: 2, ph: 'P', sf: 7 } // @suppress longLineCheck
+ ],
+ stackFrames: {
+ '1': {
+ category: 'm1',
+ name: 'main'
+ },
+ '7': {
+ category: 'm2',
+ name: 'frame7',
+ parent: '1'
+ }
+ }
+ };
+ checkParsedAndStreamInput(eventData, checkModel);
+ });
+
+ test('importSamplesMissingArgs', function() {
+ function checkModel(m) {
+ const p = m.processes[52];
+ assert.isDefined(p);
+ const t = p.threads[53];
+ assert.isDefined(t);
+ assert.isDefined(t);
+ assert.strictEqual(t.samples_.length, 3);
+ assert.isFalse(m.hasImportWarnings);
+ }
+
+ const events = [
+ {name: 'a', pid: 52, ts: 548, cat: 'test', tid: 53, ph: 'P'},
+ {name: 'b', pid: 52, ts: 548, cat: 'test', tid: 53, ph: 'P'},
+ {name: 'c', pid: 52, ts: 549, cat: 'test', tid: 53, ph: 'P'}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importV8Samples', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[2];
+
+ assert.isFalse(m.hasImportWarnings);
+ assert.strictEqual(t.samples.length, 2);
+
+ let sample = t.samples_[0];
+ assert.deepEqual(
+ sample.userFriendlyStack,
+ ['foo url: http://example.com/bar.js:22', 'bar url: unknown']);
+
+ sample = t.samples_[1];
+ assert.deepEqual(sample.userFriendlyStack, ['gc url: unknown']);
+ }
+
+ const eventData = {
+ traceEvents: [
+ { name: 'V8Sample', args: {data: {stack: ['0x2a574306061', '0x2a574306224'], vm_state: 'js'}}, pid: 1, ts: 4, cat: 'test', tid: 2, ph: 'P' }, // @suppress longLineCheck
+ { name: 'V8Sample', args: {data: {stack: [], vm_state: 'gc'}}, pid: 1, ts: 6, cat: 'test', tid: 2, ph: 'P' }, // @suppress longLineCheck
+ { name: 'JitCodeAdded', args: {data: {code_len: 2, name: 'LazyCompile:~foo http://example.com/bar.js:23', code_start: '0x2a574306060'}}, pid: 1, ts: 1, cat: 'test', tid: 2, ph: 'M' }, // @suppress longLineCheck
+ { name: 'JitCodeAdded', args: {data: {code_len: 20, name: 'bar', code_start: '0x2a574306220'}}, pid: 1, ts: 2, cat: 'test', tid: 2, ph: 'M' }, // @suppress longLineCheck
+ { name: 'JitCodeMoved', args: {data: {code_len: 2, old_code_start: '0x2a574306220', code_start: '0x2a574306222'}}, pid: 1, ts: 3, cat: 'test', tid: 2, ph: 'M' }, // @suppress longLineCheck
+ { name: 'JitCodeAdded', args: {data: {code_len: 20, name: 'baz', code_start: '0xffffffff9f90a1a0'}}, pid: 1, ts: 4, cat: 'test', tid: 2, ph: 'M' } // @suppress longLineCheck
+ ]
+ };
+ checkParsedAndStreamInput(eventData, checkModel);
+ });
+
+ test('importOldFormatV8Samples', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[2];
+
+ assert.isFalse(m.hasImportWarnings);
+ assert.strictEqual(t.samples.length, 2);
+
+ let sample = t.samples_[0];
+ assert.deepEqual(
+ sample.userFriendlyStack,
+ ['foo url: http://example.com/bar.js:22', 'bar url: unknown']);
+
+ sample = t.samples_[1];
+ assert.deepEqual(sample.userFriendlyStack, ['gc url: unknown']);
+ }
+
+ const eventData = {
+ traceEvents: [
+ { name: 'JitCodeAdded', args: {data: {code_len: 2, name: 'LazyCompile:~foo http://example.com/bar.js:23', code_start: '0x2a574306060'}}, pid: 1, ts: 0, cat: 'test', tid: 2, ph: 'I' }, // @suppress longLineCheck
+ { name: 'JitCodeAdded', args: {data: {code_len: 20, name: 'bar', code_start: '0x2a574306220'}}, pid: 1, ts: 0, cat: 'test', tid: 2, ph: 'I' }, // @suppress longLineCheck
+ { name: 'JitCodeMoved', args: {data: {code_len: 2, old_code_start: '0x2a574306220', code_start: '0x2a574306222'}}, pid: 1, ts: 0, cat: 'test', tid: 2, ph: 'I' }, // @suppress longLineCheck
+ { name: 'JitCodeAdded', args: {data: {code_len: 20, name: 'baz', code_start: '0xffffffff9f90a1a0'}}, pid: 1, ts: 0, cat: 'test', tid: 2, ph: 'I' }, // @suppress longLineCheck
+ { name: 'V8Sample', args: {data: {stack: ['0x2a574306061', '0x2a574306224']}}, pid: 1, ts: 0, cat: 'test', tid: 2, ph: 'P' }, // @suppress longLineCheck
+ { name: 'V8Sample', args: {data: {stack: [], vm_state: 'gc'}}, pid: 1, ts: 10, cat: 'test', tid: 2, ph: 'P' } // @suppress longLineCheck
+ ]
+ };
+ checkParsedAndStreamInput(eventData, checkModel);
+ });
+
+ test('importSimpleObject', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.bounds.min, 10);
+ assert.strictEqual(m.bounds.max, 50);
+ assert.isFalse(m.hasImportWarnings);
+
+ const p = m.processes[1];
+ assert.isDefined(p);
+
+ const i10 = p.objects.getObjectInstanceAt(
+ new ScopedId('ptr', '0x1000'), 10);
+ assert.strictEqual(i10.category, 'c');
+ assert.strictEqual(i10.creationTs, 10);
+ assert.strictEqual(i10.deletionTs, 50);
+ assert.strictEqual(i10.snapshots.length, 2);
+
+ const s15 = i10.snapshots[0];
+ assert.strictEqual(s15.ts, 15);
+ assert.strictEqual(s15.args, 15);
+
+ const s20 = i10.snapshots[1];
+ assert.strictEqual(s20.ts, 20);
+ assert.strictEqual(s20.args, 20);
+ }
+
+ const events = [
+ {ts: 10000, pid: 1, tid: 1, ph: 'N', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
+ {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: {snapshot: 15}}, // @suppress longLineCheck
+ {ts: 20000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: {snapshot: 20}}, // @suppress longLineCheck
+ {ts: 50000, pid: 1, tid: 1, ph: 'D', cat: 'c', id: '0x1000', name: 'a', args: {}} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('importImplicitObjects', function() {
+ function checkModel(m) {
+ const p1 = m.processes[1];
+
+ const iA = p1.objects.getObjectInstanceAt(
+ new ScopedId('ptr', '0x1000'), 10);
+ const subObjectInstances =
+ p1.objects.getAllInstancesByTypeName().subObject;
+
+ assert.strictEqual(subObjectInstances.length, 2);
+ const subObject1 = p1.objects.getObjectInstanceAt(
+ new ScopedId('ptr', '0x1'), 15);
+ assert.strictEqual(subObject1.name, 'subObject');
+ assert.strictEqual(subObject1.creationTs, 15);
+
+ assert.strictEqual(subObject1.snapshots.length, 2);
+ assert.strictEqual(subObject1.snapshots[0].ts, 15);
+ assert.strictEqual(subObject1.snapshots[0].args.foo, 1);
+ assert.strictEqual(subObject1.snapshots[1].ts, 20);
+ assert.strictEqual(subObject1.snapshots[1].args.foo, 2);
+
+ const subObject2 = p1.objects.getObjectInstanceAt(
+ new ScopedId('ptr', '0x2'), 20);
+ assert.strictEqual(subObject2.name, 'subObject');
+ assert.strictEqual(subObject2.creationTs, 20);
+ assert.strictEqual(subObject2.snapshots.length, 1);
+ assert.strictEqual(subObject2.snapshots[0].ts, 20);
+ }
+
+ const events = [
+ {ts: 10000, pid: 1, tid: 1, ph: 'N', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
+ {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a',
+ args: { snapshot: [
+ { id: 'subObject/0x1',
+ foo: 1
+ }
+ ]}},
+ {ts: 20000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a',
+ args: { snapshot: [
+ { id: 'subObject/0x1',
+ foo: 2
+ },
+ { id: 'subObject/0x2',
+ foo: 1
+ }
+ ]}}
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('importImplicitObjectWithCategoryOverride', function() {
+ function checkModel(m) {
+ const p1 = m.processes[1];
+
+ const iA = p1.objects.getObjectInstanceAt(
+ new ScopedId('ptr', '0x1000'), 10);
+ const subObjectInstances =
+ p1.objects.getAllInstancesByTypeName().subObject;
+
+ assert.strictEqual(subObjectInstances.length, 1);
+ }
+
+ const events = [
+ {ts: 10000, pid: 1, tid: 1, ph: 'N', cat: 'cat', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
+ {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'otherCat', id: '0x1000', name: 'a', // @suppress longLineCheck
+ args: { snapshot: [
+ { id: 'subObject/0x1',
+ cat: 'cat',
+ foo: 1
+ }
+ ]}}
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importImplicitObjectWithBaseTypeOverride', function() {
+ function checkModel(m) {
+ const p1 = m.processes[1];
+ assert.strictEqual(m.importWarnings.length, 0);
+
+ const iA = p1.objects.getObjectInstanceAt(
+ new ScopedId('ptr', '0x1000'), 10);
+ assert.strictEqual(iA.snapshots.length, 1);
+ }
+
+ const events = [
+ {ts: 10000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'PictureLayerImpl', args: { // @suppress longLineCheck
+ snapshot: {
+ base_type: 'LayerImpl'
+ }
+ }},
+ {ts: 50000, pid: 1, tid: 1, ph: 'D', cat: 'c', id: '0x1000', name: 'LayerImpl', args: {}} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importIDRefs', function() {
+ function checkModel(m) {
+ const p1 = m.processes[1];
+
+ const iA = p1.objects.getObjectInstanceAt(
+ new ScopedId('ptr', '0x1000'), 10);
+ const s15 = iA.getSnapshotAt(15);
+
+ const taskSlice = p1.threads[1].sliceGroup.slices[0];
+ assert.strictEqual(taskSlice.args.my_object, s15);
+ }
+
+ const events = [
+ // An object with two snapshots.
+ {ts: 10000, pid: 1, tid: 1, ph: 'N', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
+ {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: {snapshot: 15}}, // @suppress longLineCheck
+ {ts: 20000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: {snapshot: 20}}, // @suppress longLineCheck
+ {ts: 50000, pid: 1, tid: 1, ph: 'D', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
+
+ // A slice that references the object.
+ {ts: 17000, pid: 1, tid: 1, ph: 'B', cat: 'c', name: 'taskSlice', args: {my_object: {id_ref: '0x1000'}}}, // @suppress longLineCheck
+ {ts: 17500, pid: 1, tid: 1, ph: 'E', cat: 'c', name: 'taskSlice', args: {}} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('importIDRefsThatPointAtEachOther', function() {
+ function checkModel(m) {
+ const p1 = m.processes[1];
+
+ const iA = p1.objects.getObjectInstanceAt(
+ new ScopedId('ptr', '0x1000'), 15);
+ const iFoo = p1.objects.getObjectInstanceAt(
+ new ScopedId('ptr', '0x1001'), 15);
+ assert.isDefined(iA);
+ assert.isDefined(iFoo);
+
+ const a15 = iA.getSnapshotAt(15);
+ const foo15 = iFoo.getSnapshotAt(15);
+
+ const taskSlice = p1.threads[1].sliceGroup.slices[0];
+ assert.strictEqual(taskSlice.args.my_object, foo15);
+ }
+
+ const events = [
+ // An object.
+ {ts: 10000, pid: 1, tid: 1, ph: 'N', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
+ {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: { // @suppress longLineCheck
+ snapshot: { x: {
+ id: 'foo/0x1001',
+ value: 'bar'
+ }}}},
+ {ts: 50000, pid: 1, tid: 1, ph: 'D', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
+
+ // A slice that references the object.
+ {ts: 17000, pid: 1, tid: 1, ph: 'B', cat: 'c', name: 'taskSlice', args: {my_object: {id_ref: '0x1001'}}}, // @suppress longLineCheck
+ {ts: 17500, pid: 1, tid: 1, ph: 'E', cat: 'c', name: 'taskSlice', args: {}} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importArrayWithIDs', function() {
+ function checkModel(m) {
+ const p1 = m.processes[1];
+
+ const sA = p1.objects.getSnapshotAt(new ScopedId('ptr', '0x1000'), 15);
+ assert.isTrue(sA.args.x instanceof Array);
+ assert.strictEqual(sA.args.x.length, 3);
+ assert.isTrue(sA.args.x[0] instanceof tr.model.ObjectSnapshot);
+ assert.isTrue(sA.args.x[1] instanceof tr.model.ObjectSnapshot);
+ assert.isTrue(sA.args.x[2] instanceof tr.model.ObjectSnapshot);
+ }
+
+ const events = [
+ {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: { // @suppress longLineCheck
+ snapshot: { x: [
+ {id: 'foo/0x1001', value: 'bar1'},
+ {id: 'foo/0x1002', value: 'bar2'},
+ {id: 'foo/0x1003', value: 'bar3'}
+ ]}}}
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('importDoesNotMutateEventList', function() {
+ const events = [
+ // An object.
+ {ts: 10000, pid: 1, tid: 1, ph: 'N', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
+ {ts: 15000, pid: 1, tid: 1, ph: 'O', cat: 'c', id: '0x1000', name: 'a', args: { // @suppress longLineCheck
+ snapshot: {foo: 15}}},
+ {ts: 50000, pid: 1, tid: 1, ph: 'D', cat: 'c', id: '0x1000', name: 'a', args: {}}, // @suppress longLineCheck
+
+ // A slice that references the object.
+ {ts: 17000, pid: 1, tid: 1, ph: 'B', cat: 'c', name: 'taskSlice', args: {
+ my_object: {id_ref: '0x1000'}}
+ },
+ {ts: 17500, pid: 1, tid: 1, ph: 'E', cat: 'c', name: 'taskSlice', args: {}} // @suppress longLineCheck
+ ];
+
+ // The A type family exists to mutate the args list provided to
+ // snapshots.
+ function ASnapshot() {
+ tr.model.ObjectSnapshot.apply(this, arguments);
+ this.args.foo = 7;
+ }
+ ASnapshot.prototype = {
+ __proto__: tr.model.ObjectSnapshot.prototype
+ };
+
+ // Import event while the A types are registered, causing the
+ // arguments of the snapshots to be mutated.
+ tr.model.ObjectSnapshot.subTypes.register(ASnapshot, {typeName: 'a'});
+ const m = makeModel(events);
+ tr.model.ObjectSnapshot.subTypes.unregister(ASnapshot);
+ assert.isFalse(m.hasImportWarnings);
+
+ // Verify that the events array wasn't modified.
+ assert.deepEqual(
+ events[1].args,
+ {snapshot: {foo: 15}});
+ assert.deepEqual(
+ events[3].args,
+ {my_object: {id_ref: '0x1000'}});
+ });
+
+ test('importFlowEvent', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+
+ assert.isDefined(t);
+ assert.strictEqual(m.flowEvents.length, 2);
+ assert.strictEqual(m.flowIntervalTree.size, 2);
+
+ const f0 = m.flowEvents[0];
+ assert.strictEqual(f0.title, 'a');
+ assert.strictEqual(f0.category, 'foo');
+ assert.strictEqual(f0.id, 72);
+ assert.closeTo(f0.start, 0.001, 1e-5);
+ assert.closeTo(12 / 1000, f0.duration, 1e-5);
+ assert.strictEqual(f0.startSlice.title, 'aSlice');
+ assert.strictEqual(f0.endSlice.title, 'bSlice');
+
+ // TODO(nduca): Replace this assertion with something better when
+ // flow events don't create synthetic slices on their own.
+ assert.isDefined(f0.startSlice);
+ assert.isDefined(f0.endSlice);
+
+ const f1 = m.flowEvents[1];
+ assert.strictEqual(f1.title, f0.title);
+ assert.strictEqual(f1.category, f0.category);
+ assert.strictEqual(f1.id, f0.id);
+ assert.closeTo(20 / 1000, f1.duration, 1e-5);
+
+ assert.strictEqual(f1.startSlice.title, 'bSlice');
+ assert.strictEqual(f1.endSlice.title, 'cSlice');
+
+ assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
+ assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
+ assert.deepEqual(f1.startSlice.outFlowEvents, [f1]);
+ assert.deepEqual(f1.endSlice.inFlowEvents, [f1]);
+ }
+
+ const events = [
+ { name: 'aSlice', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 547, ph: 'B', args: {} }, // @suppress longLineCheck
+ { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {} }, // @suppress longLineCheck
+ { id: 72, pid: 52, tid: 53, ts: 549, ph: 'E', args: {} }, // @suppress longLineCheck
+
+ { name: 'bSlice', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 559, ph: 'B', args: {} }, // @suppress longLineCheck
+ { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 560, ph: 't', args: {} }, // @suppress longLineCheck
+ { id: 72, pid: 52, tid: 53, ts: 561, ph: 'E', args: {} }, // @suppress longLineCheck
+
+ { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 580, ph: 'f', args: {} }, // @suppress longLineCheck
+ { name: 'cSlice', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 581, ph: 'B', args: {} }, // @suppress longLineCheck
+ { id: 72, pid: 52, tid: 53, ts: 582, ph: 'E', args: {} } // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importOldFlowEventBindtoNext', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.flowEvents.length, 1);
+
+ const f0 = m.flowEvents[0];
+
+ assert.strictEqual(f0.title, 'flow');
+ assert.strictEqual(f0.category, 'foo');
+ assert.strictEqual(f0.id, 72);
+ assert.strictEqual(f0.start, .548);
+ assert.closeTo(32 / 1000, f0.duration, 1e-5);
+ assert.strictEqual(f0.startSlice.title, 'slice1');
+ assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
+ assert.strictEqual(f0.endSlice.title, 'slice3');
+ assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
+ }
+
+ // Old trace format without event.bp, and event.cat doesn't contain input
+ const events = [
+ { name: 'slice1', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', dur: 100}, // @suppress longLineCheck
+ { name: 'flow', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {}}, // @suppress longLineCheck
+
+ { name: 'flow', cat: 'foo', id: 72, pid: 70, tid: 71, ts: 580, ph: 'f', args: {}}, // @suppress longLineCheck
+ { name: 'slice2', cat: 'foo', pid: 70, tid: 71, ts: 570, ph: 'X', args: {}, dur: 100}, // @suppress longLineCheck
+ { name: 'slice3', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', args: {}, dur: 1000} // @suppress longLineCheck
+
+ ];
+ checkParsedAndStreamInput(events, checkModel, false, false);
+ });
+
+ test('importOldInputFlowEventBindtoParent', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.flowEvents.length, 1);
+
+ const f0 = m.flowEvents[0];
+
+ assert.strictEqual(f0.title, 'flow');
+ assert.strictEqual(f0.category, 'input');
+ assert.strictEqual(f0.id, 72);
+ assert.strictEqual(f0.start, .548);
+ assert.closeTo(32 / 1000, f0.duration, 1e-5);
+ assert.strictEqual(f0.startSlice.title, 'slice1');
+ assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
+ assert.strictEqual(f0.endSlice.title, 'slice2');
+ assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
+ }
+
+ // Old trace format without event.bp, but event.cat contains input
+ const events = [
+ { name: 'slice1', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', dur: 100}, // @suppress longLineCheck
+ { name: 'flow', cat: 'input', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {}}, // @suppress longLineCheck
+
+ { name: 'flow', cat: 'input', id: 72, pid: 70, tid: 71, ts: 580, ph: 'f', args: {}}, // @suppress longLineCheck
+ { name: 'slice2', cat: 'foo', pid: 70, tid: 71, ts: 570, ph: 'X', args: {}, dur: 100}, // @suppress longLineCheck
+ { name: 'slice3', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', args: {}, dur: 1000} // @suppress longLineCheck
+
+ ];
+ checkParsedAndStreamInput(events, checkModel, false, false);
+ });
+
+ test('importOldIPCFlowEventBindtoParent', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.flowEvents.length, 1);
+
+ const f0 = m.flowEvents[0];
+
+ assert.strictEqual(f0.title, 'flow');
+ assert.strictEqual(f0.category, 'disabled-by-default-ipc.flow');
+ assert.strictEqual(f0.id, 72);
+ assert.strictEqual(f0.start, .548);
+ assert.closeTo(32 / 1000, f0.duration, 1e-5);
+ assert.strictEqual(f0.startSlice.title, 'slice1');
+ assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
+ assert.strictEqual(f0.endSlice.title, 'slice2');
+ assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
+ }
+
+ // Old trace format without event.bp, but event.cat contains ipc.flow
+ const events = [
+ { name: 'slice1', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', dur: 100}, // @suppress longLineCheck
+ { name: 'flow', cat: 'disabled-by-default-ipc.flow', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {}}, // @suppress longLineCheck
+
+ { name: 'flow', cat: 'disabled-by-default-ipc.flow', id: 72, pid: 70, tid: 71, ts: 580, ph: 'f', args: {}}, // @suppress longLineCheck
+ { name: 'slice2', cat: 'foo', pid: 70, tid: 71, ts: 570, ph: 'X', args: {}, dur: 100}, // @suppress longLineCheck
+ { name: 'slice3', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', args: {}, dur: 1000} // @suppress longLineCheck
+
+ ];
+ checkParsedAndStreamInput(events, checkModel, false, false);
+ });
+
+ test('importNewFlowEventBindtoParent', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.flowEvents.length, 1);
+
+ const f0 = m.flowEvents[0];
+
+ assert.strictEqual(f0.title, 'flow');
+ assert.strictEqual(f0.category, 'foo');
+ assert.strictEqual(f0.id, 72);
+ assert.strictEqual(f0.start, .548);
+ assert.closeTo(32 / 1000, f0.duration, 1e-5);
+ assert.strictEqual(f0.startSlice.title, 'slice1');
+ assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
+ assert.strictEqual(f0.endSlice.title, 'slice2');
+ assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
+ }
+
+ // New trace format with event.bp
+ const events = [
+ { name: 'slice1', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', dur: 100}, // @suppress longLineCheck
+ { name: 'flow', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', bp: 'e', args: {}}, // @suppress longLineCheck
+
+ { name: 'flow', cat: 'foo', id: 72, pid: 70, tid: 71, ts: 580, ph: 'f', bp: 'e', args: {}}, // @suppress longLineCheck
+ { name: 'slice2', cat: 'foo', pid: 70, tid: 71, ts: 570, ph: 'X', args: {}, dur: 100}, // @suppress longLineCheck
+ { name: 'slice3', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', args: {}, dur: 1000} // @suppress longLineCheck
+
+ ];
+ checkParsedAndStreamInput(events, checkModel, false, false);
+ });
+
+ test('importNewFlowEventWithInvalidBindingPoint', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.flowEvents.length, 0);
+ }
+
+ // New trace format with event.bp, which however !== 'e'
+ const events = [
+ { name: 'slice1', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', dur: 100}, // @suppress longLineCheck
+ { name: 'flow', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', bp: 'z', args: {}}, // @suppress longLineCheck
+
+ { name: 'flow', cat: 'foo', id: 72, pid: 70, tid: 71, ts: 580, ph: 'f', bp: 'z', args: {}}, // @suppress longLineCheck
+ { name: 'slice2', cat: 'foo', pid: 70, tid: 71, ts: 570, ph: 'X', args: {}, dur: 100}, // @suppress longLineCheck
+ { name: 'slice3', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', args: {}, dur: 1000} // @suppress longLineCheck
+
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importFlowV2OnePair', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.flowEvents.length, 1);
+
+ const f0 = m.flowEvents[0];
+
+ assert.strictEqual(f0.startSlice.title, 'producer');
+ assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
+ assert.strictEqual(f0.endSlice.title, 'consumer');
+ assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
+ }
+
+ // Flow V2: one flow producer one flow consumer
+ const events = [
+ { name: 'producer', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', dur: 100, bind_id: '0xaaa', flow_out: true}, // @suppress longLineCheck
+ { name: 'consumer', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', dur: 1000, bind_id: '0xaaa', flow_in: true} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importFlowV2OneFlowStep', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.flowEvents.length, 2);
+
+ const f0 = m.flowEvents[0];
+ const f1 = m.flowEvents[1];
+
+ assert.strictEqual(f0.startSlice.title, 'producer');
+ assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
+ assert.strictEqual(f0.endSlice.title, 'step');
+ assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
+
+ assert.strictEqual(f1.startSlice.title, 'step');
+ assert.deepEqual(f1.startSlice.outFlowEvents, [f1]);
+ assert.strictEqual(f1.endSlice.title, 'consumer');
+ assert.deepEqual(f1.endSlice.inFlowEvents, [f1]);
+ }
+
+ // Flow V2: one flow producer one flow consumer
+ const events = [
+ { name: 'producer', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', dur: 100, bind_id: '0xaaa', flow_out: true}, // @suppress longLineCheck
+ { name: 'step', cat: 'foo', pid: 62, tid: 63, ts: 647, ph: 'X', dur: 100, bind_id: '0xaaa', flow_out: true, flow_in: true}, // @suppress longLineCheck
+ { name: 'consumer', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', dur: 1000, bind_id: '0xaaa', args: { 'queue_duration': 0}, flow_in: true} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importFlowV2MultipleConsumers', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.flowEvents.length, 2);
+
+ const f0 = m.flowEvents[0];
+ const f1 = m.flowEvents[1];
+
+ assert.strictEqual(f0.startSlice.title, 'producer');
+ assert.strictEqual(f1.startSlice.title, 'producer');
+
+ assert.strictEqual(f0.startSlice.outFlowEvents.length, 2);
+ assert.deepEqual(f0.startSlice.outFlowEvents, [f0, f1]);
+ assert.deepEqual(f1.startSlice.outFlowEvents, [f0, f1]);
+
+ assert.strictEqual(f0.endSlice.title, 'consumer1');
+ assert.strictEqual(f1.endSlice.title, 'consumer2');
+ assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
+ assert.deepEqual(f1.endSlice.inFlowEvents, [f1]);
+ }
+
+ // Flow V2: one flow producer multiple flow consumers
+ const events = [
+ { name: 'producer', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', dur: 100, bind_id: '0xaaa', flow_out: true}, // @suppress longLineCheck
+ { name: 'consumer1', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', dur: 1000, bind_id: '0xaaa', flow_in: true}, // @suppress longLineCheck
+ { name: 'consumer2', cat: 'foo', pid: 70, tid: 72, ts: 870, ph: 'X', dur: 1000, bind_id: '0xaaa', flow_in: true} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importFlowV2MultipleProducers', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.flowEvents.length, 1);
+ }
+
+ // Flow V2: multiple flow producers, which is not allowed
+ const events = [
+ { name: 'producer1', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', dur: 100, bind_id: '0xaaa', flow_out: true}, // @suppress longLineCheck
+ { name: 'producer2', cat: 'foo', pid: 52, tid: 54, ts: 567, ph: 'X', dur: 100, bind_id: '0xaaa', flow_out: true}, // @suppress longLineCheck
+ { name: 'consumer', cat: 'foo', pid: 70, tid: 71, ts: 770, ph: 'X', dur: 1000, bind_id: '0xaaa', flow_in: true} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ // This test creates a flow event that stops on the same timestamp that
+ // the 'X' event which it triggers begins.
+ test('importFlowEventOverlaps', function() {
+ function checkModel(m) {
+ const startT = m.processes[52].threads[53];
+ const endT = m.processes[70].threads[71];
+
+ assert.isDefined(startT);
+ assert.strictEqual(startT.sliceGroup.slices.length, 1);
+
+ assert.isDefined(endT);
+ assert.strictEqual(endT.sliceGroup.slices.length, 1);
+
+ assert.strictEqual(m.flowEvents.length, 1);
+
+ // f0 represents 's' to 'f'
+ const f0 = m.flowEvents[0];
+
+ assert.strictEqual(f0.title, 'PostTask');
+ assert.strictEqual(f0.category, 'foo');
+ assert.strictEqual(f0.id, 72);
+ assert.strictEqual(f0.start, .548);
+ assert.closeTo(32 / 1000, f0.duration, 1e-5);
+ assert.strictEqual(f0.startSlice.title, 'SomeTask');
+ assert.deepEqual(f0.startSlice.outFlowEvents, [f0]);
+ assert.strictEqual(f0.endSlice.title, 'RunTask');
+ assert.deepEqual(f0.endSlice.inFlowEvents, [f0]);
+
+ // TODO(nduca): Add assertions about the flow slices, esp that they were
+ // found correctly.
+ }
+
+ const events = [
+ { name: 'SomeTask', cat: 'foo', pid: 52, tid: 53, ts: 547, ph: 'X', dur: 100}, // @suppress longLineCheck
+ { name: 'PostTask', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {}}, // @suppress longLineCheck
+
+ { name: 'PostTask', cat: 'foo', id: 72, pid: 70, tid: 71, ts: 580, ph: 'f', args: { 'queue_duration': 0}}, // @suppress longLineCheck
+ // Note that RunTask has the same time-stamp as PostTask 'f'
+ { name: 'RunTask', cat: 'foo', pid: 70, tid: 71, ts: 580, ph: 'X', args: {'src_func': 'PostRunTask'}, dur: 1000} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('importOutOfOrderFlowEvent', function() {
+ function checkModel(m) {
+ const expected = [0.4, 0.0, 0.412];
+ assert.strictEqual(m.flowIntervalTree.size, 3);
+
+ const order = m.flowEvents.map(function(x) { return x.start; });
+ for (let i = 0; i < expected.length; ++i) {
+ assert.closeTo(expected[i], order[i], 1e-5);
+ }
+ }
+
+ const events = [
+ { name: 'SomeTask', cat: 'foo', pid: 52, tid: 53, ts: 548, ph: 'X', dur: 10}, // @suppress longLineCheck
+ { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {} }, // @suppress longLineCheck
+
+ { name: 'SomeTask', cat: 'foo', pid: 52, tid: 53, ts: 148, ph: 'X', dur: 10}, // @suppress longLineCheck
+ { name: 'b', cat: 'foo', id: 73, pid: 52, tid: 53, ts: 148, ph: 's', args: {} }, // @suppress longLineCheck
+
+ { name: 'b', cat: 'foo', id: 73, pid: 52, tid: 53, ts: 570, ph: 'f', args: {} }, // @suppress longLineCheck
+ { name: 'SomeTask', cat: 'foo', pid: 52, tid: 53, ts: 571, ph: 'X', dur: 10}, // @suppress longLineCheck
+
+ { name: 'SomeTask', cat: 'foo', pid: 52, tid: 53, ts: 560, ph: 'X', dur: 10}, // @suppress longLineCheck
+ { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 560, ph: 't', args: {} }, // @suppress longLineCheck
+
+ { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 580, ph: 'f', args: {} }, // @suppress longLineCheck
+ { name: 'SomeTask', cat: 'foo', pid: 52, tid: 53, ts: 581, ph: 'X', dur: 10} // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importCompleteEvent', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.numProcesses, 1);
+ const p = m.processes[52];
+ assert.isDefined(p);
+
+ assert.strictEqual(p.numThreads, 1);
+ const t = p.threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.slices.length, 3);
+ assert.strictEqual(t.tid, 53);
+
+ let slice = t.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'baz');
+ assert.closeTo(0, slice.start, 1e-5);
+ assert.closeTo(1 / 1000, slice.duration, 1e-5);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ slice = t.sliceGroup.slices[1];
+ assert.strictEqual(slice.title, 'b');
+ assert.strictEqual(slice.category, 'foo');
+ assert.closeTo((730 - 629) / 1000, slice.start, 1e-5);
+ assert.closeTo(20 / 1000, slice.duration, 1e-5);
+ assert.strictEqual(slice.subSlices.length, 1);
+
+ slice = t.sliceGroup.slices[2];
+ assert.strictEqual(slice.title, 'c');
+ assert.isTrue(slice.didNotFinish);
+ assert.closeTo(10 / 1000, slice.duration, 1e-5);
+ }
+
+ const events = [
+ { name: 'a', args: {}, pid: 52, ts: 629, dur: 1, cat: 'baz', tid: 53, ph: 'X' }, // @suppress longLineCheck
+ { name: 'b', args: {}, pid: 52, ts: 730, dur: 20, cat: 'foo', tid: 53, ph: 'X' }, // @suppress longLineCheck
+ { name: 'c', args: {}, pid: 52, ts: 740, cat: 'baz', tid: 53, ph: 'X' }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importFlowEventsWithStackFrame', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.flowEvents.length, 2);
+
+ const f0 = m.flowEvents[0];
+ assert.strictEqual(f0.startStackFrame.title, 'fn1');
+ assert.strictEqual(f0.endStackFrame.title, 'fn2');
+
+ const f1 = m.flowEvents[1];
+ assert.strictEqual(f1.startStackFrame.title, 'fn2');
+ assert.strictEqual(f1.endStackFrame.title, 'fn3');
+ }
+
+ const eventData = {
+ traceEvents: [
+ { name: 'aSlice', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 547, ph: 'B', args: {} }, // @suppress longLineCheck
+ { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 548, ph: 's', args: {}, sf: 1 }, // @suppress longLineCheck
+ { id: 72, pid: 52, tid: 53, ts: 549, ph: 'E', args: {} }, // @suppress longLineCheck
+
+ { name: 'bSlice', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 559, ph: 'B', args: {} }, // @suppress longLineCheck
+ { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 560, ph: 't', args: {}, sf: 2 }, // @suppress longLineCheck
+ { id: 72, pid: 52, tid: 53, ts: 561, ph: 'E', args: {} }, // @suppress longLineCheck
+
+ { name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 580, ph: 'f', args: {}, sf: 3 }, // @suppress longLineCheck
+ { name: 'cSlice', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 581, ph: 'B', args: {} }, // @suppress longLineCheck
+ { id: 72, pid: 52, tid: 53, ts: 582, ph: 'E', args: {} } // @suppress longLineCheck
+ ],
+ stackFrames: {
+ '1': {
+ category: 'm1',
+ name: 'fn1'
+ },
+ '2': {
+ category: 'm2',
+ name: 'fn2'
+ },
+ '3': {
+ category: 'm3',
+ name: 'fn3'
+ }
+ }
+ };
+ checkParsedAndStreamInput(eventData, checkModel);
+ });
+
+ test('importCompleteEventWithCpuDuration', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.numProcesses, 1);
+ const p = m.processes[52];
+ assert.isDefined(p);
+
+ assert.strictEqual(p.numThreads, 1);
+ const t = p.threads[53];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.slices.length, 3);
+ assert.strictEqual(t.tid, 53);
+
+ let slice = t.sliceGroup.slices[0];
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.category, 'baz');
+ assert.closeTo(0, slice.start, 1e-5);
+ assert.closeTo(1 / 1000, slice.duration, 1e-5);
+ assert.closeTo(12 / 1000, slice.cpuStart, 1e-5);
+ assert.closeTo(1 / 1000, slice.cpuDuration, 1e-5);
+ assert.strictEqual(slice.subSlices.length, 0);
+
+ slice = t.sliceGroup.slices[1];
+ assert.strictEqual(slice.title, 'b');
+ assert.strictEqual(slice.category, 'foo');
+ assert.closeTo((730 - 629) / 1000, slice.start, 1e-5);
+ assert.closeTo(20 / 1000, slice.duration, 1e-5);
+ assert.closeTo(110 / 1000, slice.cpuStart, 1e-5);
+ assert.closeTo(16 / 1000, slice.cpuDuration, 1e-5);
+ assert.strictEqual(slice.subSlices.length, 1);
+
+ slice = t.sliceGroup.slices[2];
+ assert.strictEqual(slice.title, 'c');
+ assert.isTrue(slice.didNotFinish);
+ assert.closeTo(10 / 1000, slice.duration, 1e-5);
+ }
+
+ const events = [
+ { name: 'a', args: {}, pid: 52, ts: 629, dur: 1, cat: 'baz', tid: 53, ph: 'X', tts: 12, tdur: 1 }, // @suppress longLineCheck
+ { name: 'b', args: {}, pid: 52, ts: 730, dur: 20, cat: 'foo', tid: 53, ph: 'X', tts: 110, tdur: 16 }, // @suppress longLineCheck
+ { name: 'c', args: {}, pid: 52, ts: 740, cat: 'baz', tid: 53, ph: 'X', tts: 115 } // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importNestedCompleteEventWithTightBounds', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+
+ const sA = findSliceNamed(t.sliceGroup, 'a');
+ const sB = findSliceNamed(t.sliceGroup, 'b');
+
+ assert.strictEqual(sA.title, 'a');
+ assert.strictEqual(sA.category, 'baz');
+ assert.strictEqual(sA.start, 244654227.065);
+ assert.strictEqual(sA.duration, 36.075);
+ assert.closeTo(0.03, sA.selfTime, 1e-5);
+
+ assert.strictEqual(sB.title, 'b');
+ assert.strictEqual(sB.category, 'foo');
+ assert.strictEqual(sB.start, 244654227.095);
+ assert.strictEqual(sB.duration, 36.045);
+
+ assert.strictEqual(sA.subSlices.length, 1);
+ assert.strictEqual(sA.subSlices[0], sB);
+ assert.strictEqual(sB.parentSlice, sA);
+ }
+
+ const events = [
+ { name: 'a', args: {}, pid: 52, ts: 244654227065, dur: 36075, cat: 'baz', tid: 53, ph: 'X' }, // @suppress longLineCheck
+ { name: 'b', args: {}, pid: 52, ts: 244654227095, dur: 36045, cat: 'foo', tid: 53, ph: 'X' } // @suppress longLineCheck
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+
+ test('importCompleteEventWithStackFrame', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[2];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.slices.length, 2);
+
+ const s0 = t.sliceGroup.slices[0];
+ assert.strictEqual(s0.startStackFrame.title, 'frame7');
+ assert.isUndefined(s0.endStackFrame);
+
+ const s1 = t.sliceGroup.slices[1];
+ assert.strictEqual(s1.startStackFrame.title, 'frame8');
+ assert.strictEqual(s1.endStackFrame.title, 'frame9');
+ }
+
+ const eventData = {
+ traceEvents: [
+ { name: 'a', args: {}, pid: 1, ts: 0, dur: 1, cat: 'baz', tid: 2, ph: 'X', sf: 7 }, // @suppress longLineCheck
+ { name: 'b', args: {}, pid: 1, ts: 5, dur: 1, cat: 'baz', tid: 2, ph: 'X', sf: 8, esf: 9 } // @suppress longLineCheck
+ ],
+ stackFrames: {
+ '1': {
+ category: 'm1',
+ name: 'main'
+ },
+ '7': {
+ category: 'm2',
+ name: 'frame7',
+ parent: '1'
+ },
+ '8': {
+ category: 'm2',
+ name: 'frame8',
+ parent: '1'
+ },
+ '9': {
+ category: 'm2',
+ name: 'frame9',
+ parent: '1'
+ }
+ }
+ };
+ checkParsedAndStreamInput(eventData, checkModel);
+ });
+
+ test('importAsyncEventWithSameTimestamp', function() {
+ function checkModel(m) {
+ const t = m.processes[52].threads[53];
+
+ assert.strictEqual(t.asyncSliceGroup.slices.length, 1);
+ const parentSlice = t.asyncSliceGroup.slices[0];
+ assert.strictEqual(parentSlice.title, 'a');
+ assert.strictEqual(parentSlice.category, 'foo');
+ assert.isTrue(parentSlice.isTopLevel);
+
+ assert.isDefined(parentSlice.subSlices);
+ const subSlices = parentSlice.subSlices;
+ assert.strictEqual(subSlices.length, 1000);
+ // Slices should be sorted according to 'ts'. And if 'ts' is the same,
+ // slices should keep the order that they were recorded.
+ for (let i = 0; i < 1000; i++) {
+ assert.strictEqual(i + 1, subSlices[i].args.seq);
+ assert.isFalse(subSlices[i].isTopLevel);
+ }
+ }
+
+ const events = [];
+ // Events are added with ts 0, 1, 1, 2, 2, 3, 3 ...500, 500, 1000
+ // and use 'seq' to track the order of when the event is recorded.
+ events.push({name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 0, ph: 'S', args: {'seq': 0}}); // @suppress longLineCheck
+
+ for (let i = 1; i <= 1000; i++) {
+ events.push({name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: Math.round(i / 2), ph: 'T', args: {'seq': i}}); // @suppress longLineCheck
+ }
+
+ events.push({name: 'a', cat: 'foo', id: 72, pid: 52, tid: 53, ts: 1000, ph: 'F', args: {'seq': 1001}}); // @suppress longLineCheck
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('sampleDataSimple', function() {
+ function checkModel(m) {
+ assert.isDefined(m.kernel.cpus[0]);
+ assert.strictEqual(m.getAllThreads().length, 1);
+
+ assert.strictEqual(m.samples.length, 3);
+
+ const t1 = m.processes[1].threads[1];
+ assert.strictEqual(t1.samples.length, 3);
+
+ const c0 = m.kernel.cpus[0];
+ const c1 = m.kernel.cpus[1];
+ assert.strictEqual(c0.samples.length, 2);
+ assert.strictEqual(c1.samples.length, 1);
+
+ assert.strictEqual(m.samples[0].cpu, c0);
+ assert.strictEqual(m.samples[0].thread, t1);
+ assert.strictEqual(m.samples[0].title, 'cycles:HG');
+ assert.strictEqual(m.samples[0].start, 1);
+ assert.deepEqual(
+ ['a_sub', 'a', 'main'],
+ m.samples[0].userFriendlyStack
+ .map(x => x.substr(0, x.indexOf('url') - 1)));
+ assert.strictEqual(m.samples[0].weight, 1);
+ }
+
+ const events = {
+ 'traceEvents': [],
+ 'stackFrames': {
+ '1': {
+ 'category': 'mod',
+ 'name': 'main'
+ },
+ '2': {
+ 'category': 'mod',
+ 'name': 'a',
+ 'parent': 1
+ },
+ '3': {
+ 'category': 'mod',
+ 'name': 'a_sub',
+ 'parent': 2
+ },
+ '4': {
+ 'category': 'mod',
+ 'name': 'b',
+ 'parent': 1
+ }
+ },
+ 'samples': [
+ {
+ 'cpu': 0, 'tid': 1, 'ts': 1000.0,
+ 'name': 'cycles:HG', 'sf': 3, 'weight': 1
+ },
+ {
+ 'cpu': 0, 'tid': 1, 'ts': 2000.0,
+ 'name': 'cycles:HG', 'sf': 2, 'weight': 1
+ },
+ {
+ 'cpu': 1, 'tid': 1, 'ts': 3000.0,
+ 'name': 'cycles:HG', 'sf': 3, 'weight': 1
+ }
+ ]
+ };
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('importMemoryDumps_verifyProcessAndGlobalMemoryDumpLinks', function() {
+ function checkModel(m) {
+ const p1 = m.getProcess(42);
+ const p2 = m.getProcess(43);
+ assert.isDefined(p1);
+ assert.isDefined(p2);
+
+ // Check that Model and Process objects contain the right dumps.
+ assert.strictEqual(m.globalMemoryDumps.length, 2);
+ assert.strictEqual(p1.memoryDumps.length, 2);
+ assert.strictEqual(p2.memoryDumps.length, 1);
+
+ assert.strictEqual(m.globalMemoryDumps[0].start, 10);
+ assert.strictEqual(p1.memoryDumps[0].start, 10);
+ assert.strictEqual(p2.memoryDumps[0].start, 11);
+ assert.strictEqual(m.globalMemoryDumps[0].duration, 1);
+ assert.strictEqual(p1.memoryDumps[0].duration, 0);
+ assert.strictEqual(p2.memoryDumps[0].duration, 0);
+
+ assert.strictEqual(m.globalMemoryDumps[1].start, 13);
+ assert.strictEqual(p1.memoryDumps[1].start, 13);
+ assert.strictEqual(m.globalMemoryDumps[1].duration, 0);
+ assert.strictEqual(p1.memoryDumps[1].duration, 0);
+
+ // Check that GlobalMemoryDump and ProcessMemoryDump objects are
+ // interconnected correctly.
+ assert.strictEqual(p1.memoryDumps[0],
+ m.globalMemoryDumps[0].processMemoryDumps[42]);
+ assert.strictEqual(p2.memoryDumps[0],
+ m.globalMemoryDumps[0].processMemoryDumps[43]);
+ assert.strictEqual(
+ p1.memoryDumps[0].globalMemoryDump, m.globalMemoryDumps[0]);
+ assert.strictEqual(
+ p2.memoryDumps[0].globalMemoryDump, m.globalMemoryDumps[0]);
+
+ assert.strictEqual(p1.memoryDumps[1],
+ m.globalMemoryDumps[1].processMemoryDumps[42]);
+ assert.strictEqual(
+ p1.memoryDumps[1].globalMemoryDump, m.globalMemoryDumps[1]);
+ }
+
+ const events = [
+ // 2 process memory dump events.
+ {
+ name: 'a',
+ pid: 42,
+ ts: 10000,
+ cat: 'test',
+ tid: 53,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '100'
+ }
+ }
+ }
+ },
+ {
+ name: 'b',
+ pid: 43,
+ ts: 11000,
+ cat: 'test',
+ tid: 54,
+ ph: 'v',
+ id: '0x0001',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '200'
+ }
+ }
+ }
+ },
+ // 1 process memory dump event.
+ {
+ name: 'd',
+ pid: 42,
+ ts: 13000,
+ cat: 'test',
+ tid: 56,
+ ph: 'v',
+ id: '0xfffffff12345678',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '300'
+ }
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('importMemoryDumps_totalResidentBytesOnly', function() {
+ function checkModel(m) {
+ const p = m.getProcess(42);
+ const d = p.memoryDumps[0];
+
+ assert.strictEqual(d.totals.residentBytes, 9007199254740991);
+ assert.isUndefined(d.totals.peakResidentBytes);
+ assert.isUndefined(d.totals.arePeakResidentBytesResettable);
+ assert.isUndefined(d.totals.platformSpecific);
+ assert.isUndefined(d.mostRecentVmRegions);
+ assert.lengthOf(d.memoryAllocatorDumps, 0);
+ }
+
+ const events = [
+ {
+ pid: 42,
+ ts: 10,
+ ph: 'v',
+ id: '0x01',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '1fffffffffffff'
+ }
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importMemoryDumps_withPeakResidentBytes', function() {
+ function checkModel(m) {
+ const p = m.getProcess(42);
+ const d = p.memoryDumps[0];
+
+ assert.strictEqual(d.totals.residentBytes, 9007199254740991);
+ assert.strictEqual(d.totals.peakResidentBytes, 13510798882111488);
+ assert.isTrue(d.totals.arePeakResidentBytesResettable);
+ assert.isUndefined(d.totals.platformSpecific);
+ assert.isUndefined(d.mostRecentVmRegions);
+ assert.lengthOf(d.memoryAllocatorDumps, 0);
+ }
+
+ const events = [
+ {
+ pid: 42,
+ ts: 10,
+ ph: 'v',
+ id: '0x01',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '1fffffffffffff',
+ peak_resident_set_bytes: '2fffffffffffff',
+ is_peak_rss_resetable: true
+ }
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importMemoryDumps_platformSpecificTotals', function() {
+ function checkModel(m) {
+ const p = m.getProcess(42);
+ const d = p.memoryDumps[0];
+
+ assert.strictEqual(d.totals.residentBytes, 9007199254740991);
+ assert.isUndefined(d.totals.peakResidentBytes);
+ assert.isUndefined(d.totals.arePeakResidentBytesResettable);
+ assert.deepEqual(d.totals.platformSpecific,
+ {private_bytes: 4503599627370495, shared_bytes: 4503599627370496});
+ assert.isUndefined(d.mostRecentVmRegions);
+ assert.lengthOf(d.memoryAllocatorDumps, 0);
+ }
+
+ const events = [
+ {
+ pid: 42,
+ ts: 10,
+ ph: 'v',
+ id: '0x01',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '1fffffffffffff',
+ private_bytes: 'fffffffffffff',
+ shared_bytes: '10000000000000'
+ }
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importMemoryDumps_vmRegions', function() {
+ function checkModel(m) {
+ const p = m.getProcess(42);
+ const d = p.memoryDumps[0];
+
+ checkVMRegions(d.vmRegions, [
+ {
+ startAddress: 240,
+ sizeInBytes: 336,
+ protectionFlags: VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_WRITE,
+ mappedFile: '[stack:20310]',
+ byteStats: {
+ privateCleanResident: 64,
+ privateDirtyResident: 32,
+ sharedCleanResident: 256,
+ sharedDirtyResident: 0,
+ proportionalResident: 158,
+ swapped: 80
+ }
+ },
+ {
+ startAddress: 848,
+ sizeInBytes: 592,
+ protectionFlags: VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_EXECUTE,
+ mappedFile: '/dev/ashmem/dalvik',
+ byteStats: {
+ proportionalResident: 205,
+ privateDirtyResident: 205,
+ swapped: 0
+ }
+ },
+ {
+ startAddress: 140673331539968,
+ sizeInBytes: 262144,
+ protectionFlags: VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_WRITE |
+ VMRegion.PROTECTION_FLAG_MAYSHARE,
+ mappedFile: '/run/shm/.org.chromium.Chromium.sqqN11 (deleted)',
+ byteStats: {
+ privateCleanResident: 0,
+ privateDirtyResident: 262144,
+ sharedCleanResident: 0,
+ sharedDirtyResident: 0,
+ proportionalResident: 262144,
+ swapped: 0
+ }
+ }
+ ]);
+
+ assert.strictEqual(d.totals.residentBytes, 0);
+ assert.isUndefined(d.totals.peakResidentBytes);
+ assert.isUndefined(d.totals.arePeakResidentBytesResettable);
+ assert.isUndefined(d.totals.platformSpecific);
+ assert.lengthOf(d.memoryAllocatorDumps, 0);
+ }
+
+ const events = [
+ {
+ name: 'some_dump_name',
+ pid: 42,
+ ts: 10,
+ cat: 'test',
+ tid: 53,
+ ph: 'v',
+ id: '000',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '0'
+ },
+ process_mmaps: {
+ vm_regions: [
+ {
+ sa: 'f0',
+ sz: '150',
+ pf: 6,
+ mf: '[stack:20310]',
+ bs: {
+ pss: '9e',
+ pc: '40',
+ pd: '20',
+ sc: '100',
+ sd: '0',
+ sw: '50'
+ }
+ },
+ {
+ sa: '350',
+ sz: '250',
+ pf: 5,
+ mf: '/dev/ashmem/dalvik',
+ bs: {
+ pss: 'cd',
+ pd: 'cd',
+ sc: undefined,
+ sw: '0'
+ }
+ },
+ {
+ sa: '7ff10ff4b000',
+ sz: '40000',
+ pf: 134,
+ mf: '/run/shm/.org.chromium.Chromium.sqqN11 (deleted)',
+ bs: {
+ pss: '40000',
+ pc: '0',
+ pd: '40000',
+ sc: '0',
+ sd: '0',
+ sw: '0'
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importMemoryDumps_explicitMemoryAllocatorDumps', function() {
+ function checkModel(m) {
+ const p = m.getProcess(42);
+ const d = p.memoryDumps[0];
+
+ assert.strictEqual(d.memoryAllocatorDumps.length, 2);
+
+ const oilpanRoot = d.getMemoryAllocatorDumpByFullName('oilpan');
+ const v8Root = d.getMemoryAllocatorDumpByFullName('v8');
+ assert.isDefined(oilpanRoot);
+ assert.isDefined(v8Root);
+ assert.include(d.memoryAllocatorDumps, oilpanRoot);
+ assert.include(d.memoryAllocatorDumps, v8Root);
+
+ checkDumpNumericsAndDiagnostics(oilpanRoot, {
+ 'objects_count': new Scalar(unitlessNumber_smallerIsBetter, 47),
+ 'size': 32768,
+ 'effective_size': 32768,
+ 'inner_size': 4096
+ }, {});
+ assert.strictEqual(oilpanRoot.children.length, 2);
+
+ const oilpanBucket1 = d.getMemoryAllocatorDumpByFullName(
+ 'oilpan/heap2/bucket1');
+ assert.isDefined(oilpanBucket1);
+ assert.strictEqual(oilpanBucket1.fullName, 'oilpan/heap2/bucket1');
+ assert.strictEqual(oilpanBucket1.name, 'bucket1');
+ checkDumpNumericsAndDiagnostics(oilpanBucket1, {
+ 'objects_count': new Scalar(unitlessNumber_smallerIsBetter, 31),
+ 'size': 8192,
+ 'effective_size': 8192,
+ 'inner_size': 8192
+ }, {});
+ assert.strictEqual(oilpanBucket1.children.length, 0);
+
+ assert.isDefined(oilpanBucket1.parent);
+ assert.strictEqual(oilpanBucket1.parent.fullName, 'oilpan/heap2');
+ assert.strictEqual(oilpanBucket1.parent.name, 'heap2');
+ assert.include(oilpanBucket1.parent.children, oilpanBucket1);
+
+ assert.isDefined(oilpanBucket1.parent.parent);
+ assert.strictEqual(oilpanBucket1.parent.parent, oilpanRoot);
+
+ assert.strictEqual(d.totals.residentBytes, 256);
+ assert.isUndefined(d.totals.peakResidentBytes);
+ assert.isUndefined(d.totals.arePeakResidentBytesResettable);
+ assert.isUndefined(d.totals.platformSpecific);
+ assert.isUndefined(d.mostRecentVmRegions);
+ }
+
+ const events = [
+ {
+ name: 'a',
+ pid: 42,
+ ts: 10,
+ cat: 'test',
+ tid: 53,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '100'
+ },
+ allocators: {
+ 'oilpan': {
+ guid: '1a',
+ attrs: {
+ objects_count: {
+ type: 'scalar', units: 'objects', value: '2f'
+ },
+ inner_size: {type: 'scalar', units: 'bytes', value: '1000'},
+ size: {type: 'scalar', units: 'bytes', value: '8000'}
+ }
+ },
+ 'oilpan/heap1': {
+ guid: '2b',
+ attrs: {
+ objects_count: {
+ type: 'scalar', units: 'objects', value: '3f'
+ },
+ inner_size: {type: 'scalar', units: 'bytes', value: '3000'},
+ size: {type: 'scalar', units: 'bytes', value: '4000'}
+ }
+ },
+ 'oilpan/heap2': {
+ guid: '3c',
+ attrs: {
+ objects_count: {
+ type: 'scalar', units: 'objects', value: '4f'
+ },
+ inner_size: {type: 'scalar', units: 'bytes', value: '4000'},
+ size: {type: 'scalar', units: 'bytes', value: '4000'}
+ }
+ },
+ 'oilpan/heap2/bucket1': {
+ // Deliberately missing GUID (to check that the importer does
+ // not skip memory allocator dump without GUID).
+ attrs: {
+ objects_count: {
+ type: 'scalar', units: 'objects', value: '1f'
+ },
+ inner_size: {type: 'scalar', units: 'bytes', value: '2000'},
+ size: {type: 'scalar', units: 'bytes', value: '2000'}
+ }
+ },
+ 'v8': {
+ guid: '5e',
+ attrs: {
+ objects_count: {
+ type: 'scalar', units: 'objects', value: '5f'
+ },
+ inner_size: {type: 'scalar', units: 'bytes', value: '5000'},
+ size: {type: 'scalar', units: 'bytes', value: '6000'}
+ }
+ }
+ }
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importMemoryDumps_implicitMemoryAllocatorDumps', function() {
+ function checkModel(m) {
+ const p = m.getProcess(42);
+ const d = p.memoryDumps[0];
+
+ assert.strictEqual(d.memoryAllocatorDumps.length, 2);
+
+ const oilpanRoot = d.getMemoryAllocatorDumpByFullName('oilpan');
+ const v8Root = d.getMemoryAllocatorDumpByFullName('v8');
+ assert.isDefined(oilpanRoot);
+ assert.isDefined(v8Root);
+ assert.include(d.memoryAllocatorDumps, oilpanRoot);
+ assert.include(d.memoryAllocatorDumps, v8Root);
+
+ checkDumpNumericsAndDiagnostics(oilpanRoot, {
+ 'objects_count': new Scalar(unitlessNumber_smallerIsBetter, 94),
+ 'size': 24576,
+ 'effective_size': 24576,
+ 'inner_size': 20480
+ }, {});
+ assert.strictEqual(oilpanRoot.children.length, 2);
+
+ const oilpanBucket1 = d.getMemoryAllocatorDumpByFullName(
+ 'oilpan/heap2/bucket1');
+ assert.isDefined(oilpanBucket1);
+ assert.strictEqual(oilpanBucket1.fullName, 'oilpan/heap2/bucket1');
+ assert.strictEqual(oilpanBucket1.name, 'bucket1');
+ checkDumpNumericsAndDiagnostics(oilpanBucket1, {
+ 'objects_count': new Scalar(unitlessNumber_smallerIsBetter, 31),
+ 'size': 8192,
+ 'effective_size': 8192,
+ 'inner_size': 8192
+ }, {});
+ assert.strictEqual(oilpanBucket1.children.length, 0);
+
+ assert.isDefined(oilpanBucket1.parent);
+ assert.strictEqual(oilpanBucket1.parent.fullName, 'oilpan/heap2');
+ assert.strictEqual(oilpanBucket1.parent.name, 'heap2');
+ assert.include(oilpanBucket1.parent.children, oilpanBucket1);
+
+ assert.isDefined(oilpanBucket1.parent.parent);
+ assert.strictEqual(oilpanBucket1.parent.parent, oilpanRoot);
+
+ assert.strictEqual(d.totals.residentBytes, 256);
+ assert.isUndefined(d.totals.peakResidentBytes);
+ assert.isUndefined(d.totals.arePeakResidentBytesResettable);
+ assert.isUndefined(d.totals.platformSpecific);
+ assert.isUndefined(d.mostRecentVmRegions);
+ }
+
+ const events = [
+ {
+ name: 'a',
+ pid: 42,
+ ts: 10,
+ cat: 'test',
+ tid: 53,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '100'
+ },
+ allocators: {
+ 'oilpan/heap1': {
+ guid: '999',
+ attrs: {
+ objects_count: {
+ type: 'scalar', units: 'objects', value: '3f'
+ },
+ inner_size: {type: 'scalar', units: 'bytes', value: '3000'},
+ size: {type: 'scalar', units: 'bytes', value: '4000'}
+ }
+ },
+ 'oilpan/heap2/bucket1': {
+ guid: '888',
+ attrs: {
+ objects_count: {
+ type: 'scalar', units: 'objects', value: '1f'
+ },
+ inner_size: {type: 'scalar', units: 'bytes', value: '2000'},
+ size: {type: 'scalar', units: 'bytes', value: '2000'}
+ }
+ },
+ 'v8': {
+ guid: '777',
+ attrs: {
+ objects_count: {
+ type: 'scalar', units: 'objects', value: '5f'
+ },
+ inner_size: {type: 'scalar', units: 'bytes', value: '5000'},
+ size: {type: 'scalar', units: 'bytes', value: '6000'}
+ }
+ }
+ }
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importMemoryDumps_globalMemoryAllocatorDumps', function() {
+ function checkModel(m) {
+ const p = m.getProcess(42);
+ const gmd = m.globalMemoryDumps[0];
+ const pmd = p.memoryDumps[0];
+
+ assert.isUndefined(gmd.totals);
+ assert.strictEqual(pmd.totals.residentBytes, 256);
+ assert.isUndefined(pmd.totals.peakResidentBytes);
+ assert.isUndefined(pmd.totals.arePeakResidentBytesResettable);
+ assert.isUndefined(pmd.totals.platformSpecific);
+
+ assert.isUndefined(gmd.mostRecentVmRegions);
+ assert.isUndefined(pmd.mostRecentVmRegions);
+
+ assert.strictEqual(gmd.memoryAllocatorDumps.length, 1);
+ assert.strictEqual(pmd.memoryAllocatorDumps.length, 1);
+
+ // Global memory allocator dumps.
+ const sharedBitmapManager = gmd.getMemoryAllocatorDumpByFullName(
+ 'shared_bitmap_manager');
+ assert.isDefined(sharedBitmapManager);
+ assert.include(gmd.memoryAllocatorDumps, sharedBitmapManager);
+
+ checkDumpNumericsAndDiagnostics(sharedBitmapManager, {
+ 'objects_count': new Scalar(unitlessNumber_smallerIsBetter, 31),
+ 'size': 8192,
+ 'effective_size': 8192,
+ 'inner_size': 8192
+ }, {});
+ assert.lengthOf(sharedBitmapManager.children, 1);
+
+ const bitmap2 = gmd.getMemoryAllocatorDumpByFullName(
+ 'shared_bitmap_manager/bitmap2');
+ assert.isDefined(bitmap2);
+ assert.include(sharedBitmapManager.children, bitmap2);
+ assert.strictEqual(bitmap2.parent, sharedBitmapManager);
+
+ checkDumpNumericsAndDiagnostics(bitmap2, {
+ 'objects_count': new Scalar(unitlessNumber_smallerIsBetter, 31),
+ 'size': 8192,
+ 'effective_size': 8192,
+ 'inner_size': 8192
+ }, { 'weather': 'sunny' });
+ assert.lengthOf(bitmap2.children, 0);
+
+ assert.isUndefined(gmd.getMemoryAllocatorDumpByFullName('tile_manager'));
+ assert.isUndefined(
+ gmd.getMemoryAllocatorDumpByFullName('tile_manager/tile1'));
+
+ // Process memory allocator dumps.
+ const tileManagerRoot = pmd.getMemoryAllocatorDumpByFullName(
+ 'tile_manager');
+ assert.isDefined(tileManagerRoot);
+ assert.include(pmd.memoryAllocatorDumps, tileManagerRoot);
+ assert.isUndefined(tileManagerRoot.parent);
+
+ checkDumpNumericsAndDiagnostics(tileManagerRoot, {
+ 'objects_count': new Scalar(unitlessNumber_smallerIsBetter, 63),
+ 'size': 16384,
+ 'effective_size': 16384,
+ 'inner_size': 12288
+ }, {});
+ assert.lengthOf(tileManagerRoot.children, 1);
+
+ const tile1 = pmd.getMemoryAllocatorDumpByFullName(
+ 'tile_manager/tile1');
+ assert.isDefined(tile1);
+ assert.include(tileManagerRoot.children, tile1);
+ assert.strictEqual(tile1.parent, tileManagerRoot);
+
+ checkDumpNumericsAndDiagnostics(tile1, {
+ 'objects_count': new Scalar(unitlessNumber_smallerIsBetter, 63),
+ 'size': 16384,
+ 'effective_size': 16384,
+ 'inner_size': 12288
+ }, { 'weather': 'rainy' });
+ assert.lengthOf(tile1.children, 0);
+
+ assert.isUndefined(
+ pmd.getMemoryAllocatorDumpByFullName('shared_bitmap_manager'));
+ assert.isUndefined(
+ pmd.getMemoryAllocatorDumpByFullName(
+ 'shared_bitmap_manager/bitmap2'));
+ }
+
+ const events = [
+ {
+ name: 'a',
+ pid: 42,
+ ts: 10,
+ cat: 'test',
+ tid: 53,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '100'
+ },
+ allocators: {
+ 'tile_manager/tile1': {
+ guid: '21',
+ attrs: {
+ objects_count: {
+ type: 'scalar', units: 'objects', value: '3f'
+ },
+ inner_size: {type: 'scalar', units: 'bytes', value: '3000'},
+ size: {type: 'scalar', units: 'bytes', value: '4000'},
+ weather: {type: 'string', units: '', value: 'rainy'}
+ }
+ },
+ 'global/shared_bitmap_manager/bitmap2': {
+ guid: '42',
+ attrs: {
+ objects_count: {
+ type: 'scalar', units: 'objects', value: '1f'
+ },
+ inner_size: {type: 'scalar', units: 'bytes', value: '2000'},
+ size: {type: 'scalar', units: 'bytes', value: '2000'},
+ weather: {type: 'string', units: '', value: 'sunny'}
+ }
+ }
+ }
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importMemoryDumps_memoryAllocatorDumpEdges', function() {
+ function checkModel(model) {
+ const browserProcess = model.getProcess(42);
+ const rendererProcess = model.getProcess(43);
+ const gpuProcess = model.getProcess(44);
+
+ assert.lengthOf(model.globalMemoryDumps, 1);
+ assert.lengthOf(browserProcess.memoryDumps, 1);
+ assert.lengthOf(rendererProcess.memoryDumps, 1);
+ assert.lengthOf(gpuProcess.memoryDumps, 1);
+
+ const globalDump = model.globalMemoryDumps[0];
+ const browserDump = browserProcess.memoryDumps[0];
+ const rendererDump = rendererProcess.memoryDumps[0];
+ const gpuDump = gpuProcess.memoryDumps[0];
+
+ // Global memory allocator dump.
+ assert.lengthOf(globalDump.memoryAllocatorDumps, 1);
+
+ const globalDumpShared = globalDump.getMemoryAllocatorDumpByFullName(
+ 'shared');
+ assert.isDefined(globalDumpShared);
+ assert.include(globalDump.memoryAllocatorDumps, globalDumpShared);
+ checkDumpNumericsAndDiagnostics(globalDumpShared, {
+ 'area': new Scalar(unitlessNumber_smallerIsBetter, 9)
+ }, { 'color': 'blue' });
+ assert.lengthOf(globalDumpShared.children, 0);
+ assert.isUndefined(globalDumpShared.parent);
+
+ assert.isUndefined(globalDumpShared.owns);
+ assert.lengthOf(globalDumpShared.ownedBy, 3);
+ assert.lengthOf(globalDumpShared.retains, 0);
+ assert.lengthOf(globalDumpShared.retainedBy, 0);
+
+ // Browser memory allocator dump.
+ assert.lengthOf(browserDump.memoryAllocatorDumps, 1);
+
+ const browserDumpLocal = browserDump.getMemoryAllocatorDumpByFullName(
+ 'local');
+ assert.isDefined(browserDumpLocal);
+ assert.include(browserDump.memoryAllocatorDumps, browserDumpLocal);
+ checkDumpNumericsAndDiagnostics(browserDumpLocal, {
+ 'area': new Scalar(unitlessNumber_smallerIsBetter, 9)
+ }, { 'color': 'blue', 'mood': 'very good' });
+ assert.lengthOf(browserDumpLocal.children, 0);
+ assert.isUndefined(browserDumpLocal.parent);
+
+ assert.isDefined(browserDumpLocal.owns);
+ assert.lengthOf(browserDumpLocal.ownedBy, 0);
+ assert.lengthOf(browserDumpLocal.retains, 0);
+ assert.lengthOf(browserDumpLocal.retainedBy, 0);
+
+ const browserDumpLocalOwnsLink = browserDumpLocal.owns;
+ assert.include(globalDumpShared.ownedBy, browserDumpLocalOwnsLink);
+ assert.strictEqual(browserDumpLocalOwnsLink.source, browserDumpLocal);
+ assert.strictEqual(browserDumpLocalOwnsLink.target, globalDumpShared);
+ assert.strictEqual(browserDumpLocalOwnsLink.importance, 0);
+
+ // Renderer memory allocator dump.
+ assert.lengthOf(rendererDump.memoryAllocatorDumps, 1);
+
+ const rendererDumpLocal = rendererDump.getMemoryAllocatorDumpByFullName(
+ 'local');
+ assert.isDefined(rendererDumpLocal);
+ assert.include(rendererDump.memoryAllocatorDumps, rendererDumpLocal);
+ checkDumpNumericsAndDiagnostics(rendererDumpLocal, {
+ 'area': new Scalar(unitlessNumber_smallerIsBetter, 9),
+ 'length': 3
+ }, { 'color': 'blue' });
+ assert.lengthOf(rendererDumpLocal.children, 0);
+ assert.isUndefined(rendererDumpLocal.parent);
+
+ assert.isDefined(rendererDumpLocal.owns);
+ assert.lengthOf(rendererDumpLocal.ownedBy, 0);
+ assert.lengthOf(rendererDumpLocal.retains, 0);
+ assert.lengthOf(rendererDumpLocal.retainedBy, 1);
+
+ const rendererDumpLocalOwnsLink = rendererDumpLocal.owns;
+ assert.include(globalDumpShared.ownedBy, rendererDumpLocalOwnsLink);
+ assert.strictEqual(rendererDumpLocalOwnsLink.source, rendererDumpLocal);
+ assert.strictEqual(rendererDumpLocalOwnsLink.target, globalDumpShared);
+ assert.strictEqual(rendererDumpLocalOwnsLink.importance, 1);
+
+ // GPU memory allocator dumps.
+ assert.lengthOf(gpuDump.memoryAllocatorDumps, 2);
+
+ const gpuDumpLocal1 = gpuDump.getMemoryAllocatorDumpByFullName('local1');
+ assert.isDefined(gpuDumpLocal1);
+ assert.include(gpuDump.memoryAllocatorDumps, gpuDumpLocal1);
+ checkDumpNumericsAndDiagnostics(gpuDumpLocal1, {
+ 'area': new Scalar(unitlessNumber_smallerIsBetter, 9)
+ }, { 'state': 'ON', 'color': 'blue' });
+ assert.lengthOf(gpuDumpLocal1.children, 0);
+ assert.isUndefined(gpuDumpLocal1.parent);
+
+ assert.isDefined(gpuDumpLocal1.owns);
+ assert.lengthOf(gpuDumpLocal1.ownedBy, 1);
+ assert.lengthOf(gpuDumpLocal1.retains, 1);
+ assert.lengthOf(gpuDumpLocal1.retainedBy, 0);
+
+ const gpuDumpLocal1OwnsLink = gpuDumpLocal1.owns;
+ assert.include(globalDumpShared.ownedBy, gpuDumpLocal1OwnsLink);
+ assert.strictEqual(gpuDumpLocal1OwnsLink.source, gpuDumpLocal1);
+ assert.strictEqual(gpuDumpLocal1OwnsLink.target, globalDumpShared);
+ assert.strictEqual(gpuDumpLocal1OwnsLink.importance, -1);
+
+ const gpuDumpLocal1RetainsLink = gpuDumpLocal1.retains[0];
+ assert.include(rendererDumpLocal.retainedBy, gpuDumpLocal1RetainsLink);
+ assert.strictEqual(gpuDumpLocal1RetainsLink.source, gpuDumpLocal1);
+ assert.strictEqual(gpuDumpLocal1RetainsLink.target, rendererDumpLocal);
+ assert.isUndefined(gpuDumpLocal1RetainsLink.importance);
+
+ const gpuDumpLocal2 = gpuDump.getMemoryAllocatorDumpByFullName('local2');
+ assert.isDefined(gpuDumpLocal2);
+ assert.include(gpuDump.memoryAllocatorDumps, gpuDumpLocal2);
+ checkDumpNumericsAndDiagnostics(gpuDumpLocal2, {
+ 'temperature': new Scalar(unitlessNumber_smallerIsBetter, 100)
+ }, {});
+ assert.lengthOf(gpuDumpLocal2.children, 0);
+ assert.isUndefined(gpuDumpLocal2.parent);
+
+ assert.isDefined(gpuDumpLocal2.owns);
+ assert.lengthOf(gpuDumpLocal2.ownedBy, 0);
+ assert.lengthOf(gpuDumpLocal2.retains, 0);
+ assert.lengthOf(gpuDumpLocal2.retainedBy, 0);
+
+ const gpuDumpLocal2OwnsLink = gpuDumpLocal2.owns;
+ assert.include(gpuDumpLocal1.ownedBy, gpuDumpLocal2OwnsLink);
+ assert.strictEqual(gpuDumpLocal2OwnsLink.source, gpuDumpLocal2);
+ assert.strictEqual(gpuDumpLocal2OwnsLink.target, gpuDumpLocal1);
+ assert.strictEqual(gpuDumpLocal2OwnsLink.importance, 1);
+ }
+
+ const events = [
+ {
+ name: 'browser',
+ pid: 42,
+ ts: 10,
+ cat: 'test',
+ tid: 53,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '100'
+ },
+ allocators: {
+ 'local': {
+ guid: '3',
+ attrs: {
+ mood: {type: 'string', units: '', value: 'very good'}
+ }
+ },
+ 'global/shared': {
+ guid: '7',
+ attrs: {
+ color: {type: 'string', units: '', value: 'blue'}
+ }
+ }
+ },
+ allocators_graph: [
+ {
+ source: '3',
+ target: '7',
+ type: 'ownership',
+ importance: 0
+ }
+ ]
+ }
+ }
+ },
+ {
+ name: 'renderer',
+ pid: 43,
+ ts: 11,
+ cat: 'test',
+ tid: 53,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '200'
+ },
+ allocators: {
+ 'local': {
+ guid: '4',
+ attrs: {
+ length: {type: 'scalar', units: 'bytes', value: '3'}
+ }
+ },
+ 'global/shared': {
+ guid: '7',
+ attrs: {
+ area: {type: 'scalar', units: 'sq ft', value: '9'}
+ }
+ }
+ },
+ allocators_graph: [
+ {
+ source: '4',
+ target: '7',
+ type: 'ownership',
+ importance: 1
+ }
+ ]
+ }
+ }
+ },
+ {
+ name: 'gpu',
+ pid: 44,
+ ts: 10.5,
+ cat: 'test',
+ tid: 53,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '300'
+ },
+ allocators: {
+ 'local1': {
+ guid: '5',
+ attrs: {
+ state: {type: 'string', units: '', value: 'ON'}
+ }
+ },
+ 'local2': {
+ guid: '6',
+ attrs: {
+ temperature: {type: 'scalar', units: 'C', value: '64'}
+ }
+ }
+ },
+ allocators_graph: [
+ {
+ source: '5',
+ target: '7',
+ type: 'ownership',
+ importance: -1
+ },
+ {
+ source: '6',
+ target: '5',
+ type: 'ownership',
+ importance: 1
+ },
+ {
+ source: '5',
+ target: '4',
+ type: 'retention'
+ }
+ ]
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importMemoryDumps_memoryAllocatorDumpsMissingFields', function() {
+ function checkModel(m) {
+ const p = m.getProcess(42);
+ const d = p.memoryDumps[0];
+
+ assert.strictEqual(d.memoryAllocatorDumps.length, 1);
+ const noCrashRoot = d.getMemoryAllocatorDumpByFullName('no_crash');
+ assert.lengthOf(noCrashRoot.children, 0);
+ checkDumpNumericsAndDiagnostics(noCrashRoot, {}, {});
+ assert.isUndefined(noCrashRoot.parent);
+ assert.isUndefined(noCrashRoot.guid);
+ }
+
+ const events = [
+ {
+ name: 'a',
+ pid: 42,
+ ts: 10,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ allocators: {
+ 'no_crash': {
+ /* Missing GUID and attributes. */
+ }
+ }
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importMemoryDumps_weakMemoryAllocatorDumps', function() {
+ function checkModel(m) {
+ const p = m.getProcess(42);
+ const d = p.memoryDumps[0];
+ const memoryAllocatorDumps = d.memoryAllocatorDumps;
+ assert.lengthOf(memoryAllocatorDumps, 6);
+
+ function checkDump(dump, expectedFullName, expectedGuid, expectedParent,
+ expectedChildCount, expectedOwnsLink, expectedOwnedByLinkCount) {
+ assert.strictEqual(dump.fullName, expectedFullName);
+ assert.strictEqual(dump.guid, expectedGuid);
+ assert.strictEqual(dump.parent, expectedParent);
+ assert.lengthOf(dump.children, expectedChildCount);
+ assert.strictEqual(dump.owns, expectedOwnsLink);
+ assert.lengthOf(dump.ownedBy, expectedOwnedByLinkCount);
+ assert.strictEqual(
+ d.getMemoryAllocatorDumpByFullName(expectedFullName), dump);
+ }
+
+ function checkOwnsLink(ownerDump, expectedTarget) {
+ assert.strictEqual(ownerDump.owns.source, ownerDump);
+ assert.strictEqual(ownerDump.owns.target, expectedTarget);
+ }
+
+ // Check root_sink/* dumps.
+ const rootSink = d.memoryAllocatorDumps[3];
+ checkDump(rootSink, 'root_sink', '100', undefined, 1, undefined, 2);
+ const childSink = rootSink.children[0];
+ checkDump(childSink, 'root_sink/child_sink', '200', rootSink, 1,
+ undefined, 1);
+ const descendantSink = childSink.children[0];
+ checkDump(descendantSink, 'root_sink/child_sink/descendant_sink', '300',
+ childSink, 0, undefined, 2);
+
+ // Check strong_root/* dumps.
+ const strongRoot = d.memoryAllocatorDumps[4];
+ checkDump(strongRoot, 'strong_root', '4', undefined, 0,
+ rootSink.ownedBy[0], 0);
+
+ // Check inferred_strong_root/* dumps.
+ const inferredStrongRoot = d.memoryAllocatorDumps[0];
+ checkDump(inferredStrongRoot, 'inferred_strong_root', undefined,
+ undefined, 1, undefined, 0);
+ const child2Strong = inferredStrongRoot.children[0];
+ checkDump(child2Strong, 'inferred_strong_root/child2_strong', '9',
+ inferredStrongRoot, 0, childSink.ownedBy[0], 0);
+
+ // Check inferred_strong_root2/* dumps.
+ const inferredStrongRoot2 = d.memoryAllocatorDumps[1];
+ checkDump(inferredStrongRoot2, 'inferred_strong_root2', undefined,
+ undefined, 1, undefined, 0);
+ const inferredStrongChild = inferredStrongRoot2.children[0];
+ checkDump(inferredStrongChild,
+ 'inferred_strong_root2/inferred_strong_child', undefined,
+ inferredStrongRoot2, 2, undefined, 0);
+ const desc1Strong = inferredStrongChild.children[0];
+ checkDump(desc1Strong,
+ 'inferred_strong_root2/inferred_strong_child/desc1_strong', '11',
+ inferredStrongChild, 0, descendantSink.ownedBy[0], 0);
+ const desc3Strong = inferredStrongChild.children[1];
+ checkDump(desc3Strong,
+ 'inferred_strong_root2/inferred_strong_child/desc3_strong', '13',
+ inferredStrongChild, 0, descendantSink.ownedBy[1], 0);
+
+ // Check strong_root2/* dumps.
+ const strongRoot2 = d.memoryAllocatorDumps[5];
+ checkDump(strongRoot2, 'strong_root2', '15', undefined, 0,
+ rootSink.ownedBy[1], 0);
+
+ // Check inferred_strong_root3/* dumps.
+ const inferredStrongRoot3 = d.memoryAllocatorDumps[2];
+ checkDump(inferredStrongRoot3, 'inferred_strong_root3', undefined,
+ undefined, 0, undefined, 0);
+
+ // Check the links.
+ checkOwnsLink(strongRoot, rootSink);
+ checkOwnsLink(child2Strong, childSink);
+ checkOwnsLink(desc1Strong, descendantSink);
+ checkOwnsLink(desc3Strong, descendantSink);
+ checkOwnsLink(strongRoot2, rootSink);
+
+ // Check that the removed weak dumps are not indexed.
+ [
+ 'weak_root',
+ 'weak_root/removed_child',
+ 'weak_root/inferred_removed_child',
+ 'weak_root/inferred_removed_child/removed_descendant',
+ 'strong_root/weak_child',
+ 'strong_root/inferred_weak_child/weak_descendant',
+ 'inferred_weak_root',
+ 'inferred_weak_root/inferred_weak_child',
+ 'inferred_weak_root/inferred_weak_child/weak_descendant',
+ 'inferred_strong_root/child1_weak',
+ 'inferred_strong_root/child3_weak',
+ 'inferred_strong_root2/inferred_strong_child/desc2_weak',
+ 'inferred_strong_root2/weak_child',
+ 'strong_root2/weak_child',
+ 'strong_root2/removed_descendant',
+ 'removed_root',
+ 'removed_root/removed_child',
+ 'inferred_strong_root3/removed_child'
+ ].forEach(function(fullName) {
+ assert.isUndefined(d.getMemoryAllocatorDumpByFullName(fullName));
+ });
+ }
+
+ const events = [
+ {
+ pid: 42,
+ ts: 10,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ allocators: {
+ // Sinks for ownership edges (to check that the correct ownership
+ // edges are removed).
+ 'root_sink': { guid: '100', attrs: {} },
+ 'root_sink/child_sink': { guid: '200', attrs: {} },
+ 'root_sink/child_sink/descendant_sink': {
+ guid: '300', attrs: {}
+ },
+
+ // Note: 'removed' in the name of a dump means that the dump will
+ // be removed despite being non-weak (strong), e.g. due to one of
+ // its ancestors being weak.
+
+ // All descendants of a weak root dump should be removed.
+ 'weak_root': { guid: '1', attrs: {}, flags: 1 },
+ 'weak_root/removed_child': { guid: '2', attrs: {} },
+ 'weak_root/inferred_removed_child/removed_descendant': {
+ guid: '3', attrs: {}, flags: 0
+ },
+
+ // A strong root should be kept even if all its descendants are
+ // weak.
+ 'strong_root': { guid: '4', attrs: {}, flags: 0 },
+ 'strong_root/weak_child': { guid: '5', attrs: {}, flags: 1 },
+ 'strong_root/inferred_weak_child/weak_descendant': {
+ guid: '6', attrs: {}, flags: 1
+ },
+
+ // All inferred ancestors of a weak descendant should be marked
+ // weak and, consequently, removed (provided that they don't have
+ // any non-weak descendants).
+ 'inferred_weak_root/inferred_weak_child/weak_descendant': {
+ guid: '7', attrs: {}, flags: 1
+ },
+
+ // An inferred dump should be marked non-weak if it has at least
+ // one strong descendant.
+ 'inferred_strong_root/child1_weak': {
+ guid: '8', attrs: {}, flags: 1
+ },
+ 'inferred_strong_root/child2_strong': {
+ guid: '9', attrs: {}
+ },
+ 'inferred_strong_root/child3_weak': {
+ guid: '10', attrs: {}, flags: 1
+ },
+ 'inferred_strong_root2/inferred_strong_child/desc1_strong': {
+ guid: '11', attrs: {}
+ },
+ 'inferred_strong_root2/inferred_strong_child/desc2_weak': {
+ guid: '12', attrs: {}, flags: 1
+ },
+ 'inferred_strong_root2/inferred_strong_child/desc3_strong': {
+ guid: '13', attrs: {}
+ },
+ 'inferred_strong_root2/weak_child': {
+ guid: '14', attrs: {}, flags: 1
+ },
+
+ // A desdendant dump should be removed if it has a weak ancestor.
+ 'strong_root2': { guid: '15', attrs: {} },
+ 'strong_root2/weak_child': { guid: '16', attrs: {}, flags: 1 },
+ 'strong_root2/weak_child/removed_descendant': {
+ guid: '17', attrs: {}
+ },
+
+ // Check that "weakness" also propagates across ownership edges.
+ 'removed_root': { guid: '18', attrs: {} },
+ 'removed_root/removed_child': {
+ guid: '19', attrs: {}
+ },
+ 'inferred_strong_root3/removed_child': {
+ guid: '20', attrs: {}
+ },
+ },
+ allocators_graph: [
+ { source: '1', target: '100', type: 'ownership' },
+ { source: '2', target: '200', type: 'ownership' },
+ { source: '3', target: '300', type: 'ownership' },
+
+ { source: '4', target: '100', type: 'ownership' }, // Kept.
+ { source: '5', target: '200', type: 'ownership' },
+ { source: '6', target: '300', type: 'ownership' },
+
+ { source: '7', target: '300', type: 'ownership' },
+
+ { source: '8', target: '200', type: 'ownership' },
+ { source: '9', target: '200', type: 'ownership' }, // Kept.
+ { source: '10', target: '200', type: 'ownership' },
+ { source: '11', target: '300', type: 'ownership' }, // Kept.
+ { source: '12', target: '300', type: 'ownership' },
+ { source: '13', target: '300', type: 'ownership' }, // Kept.
+ { source: '14', target: '200', type: 'ownership' },
+
+ { source: '15', target: '100', type: 'ownership' }, // Kept.
+ { source: '16', target: '200', type: 'ownership' },
+ { source: '17', target: '300', type: 'ownership' },
+
+ { source: '18', target: '3' /* not a sink */, type: 'ownership' },
+ { source: '19', target: '200', type: 'ownership' },
+ { source: '20', target: '19' /* not a sink */, type: 'ownership' }
+ ]
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importMemoryDumps_levelsOfDetail', function() {
+ function checkLevelsOfDetail(pmdSpecifications, expectedGlobalLevelOfDetail,
+ expectedProcessLevelsOfDetail, expectedHasWarnings) {
+ function checkModel(model) {
+ // Check GlobalMemoryDump level of detail.
+ assert.lengthOf(model.globalMemoryDumps, 1);
+ assert.strictEqual(model.globalMemoryDumps[0].levelOfDetail,
+ expectedGlobalLevelOfDetail);
+
+ // Check ProcessMemoryDumps levels of detail.
+ assert.lengthOf(Object.keys(model.processes),
+ expectedProcessLevelsOfDetail.length);
+ for (let i = 0; i < expectedProcessLevelsOfDetail.length; i++) {
+ const process = model.getProcess(i);
+ assert.lengthOf(process.memoryDumps, 1);
+ assert.strictEqual(process.memoryDumps[0].levelOfDetail,
+ expectedProcessLevelsOfDetail[i]);
+ }
+
+ assert.strictEqual(model.hasImportWarnings, expectedHasWarnings);
+ }
+
+ const events = [];
+ pmdSpecifications.forEach(function(singlePmdSpecifications, pid) {
+ singlePmdSpecifications.forEach(function(singlePmdSpecification) {
+ const dumps = {};
+ if (singlePmdSpecification.levelOfDetail !== undefined) {
+ dumps.level_of_detail = singlePmdSpecification.levelOfDetail;
+ }
+ if (singlePmdSpecification.vmRegions) {
+ dumps.process_mmaps = {
+ vm_regions: [
+ { sa: 'f0', sz: '150', pf: 6, mf: '[stack]', bs: { pss: 'ff'} }
+ ]
+ };
+ }
+ events.push({
+ name: 'process_' + pid,
+ pid,
+ ts: 10,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps
+ }
+ });
+ });
+ });
+ checkParsedAndStreamInput(events, checkModel);
+ }
+
+ // Legacy trace events (without levels of detail).
+ checkLevelsOfDetail([[{}]], LIGHT, [LIGHT], false);
+ checkLevelsOfDetail([[{ vmRegions: true }]], DETAILED, [DETAILED], false);
+ checkLevelsOfDetail(
+ [
+ [{}] /* raw composable PMD1 events */,
+ [{}, {}] /* raw composable PMD2 events */
+ ],
+ LIGHT /* expected GMD level of detail */,
+ [LIGHT, LIGHT] /* expected PMD levels of detail */,
+ false /* no warnings expected */);
+ checkLevelsOfDetail(
+ [
+ [{}, { vmRegions: true }, {}],
+ [{ vmRegions: true }, {}]
+ ],
+ DETAILED, [DETAILED, DETAILED], false);
+
+ // Well-formed traces events (VM regions should be irrelevant).
+ checkLevelsOfDetail(
+ [
+ [{ levelOfDetail: 'light'}],
+ [{ levelOfDetail: 'light', vmRegions: true }]
+ ],
+ LIGHT, [LIGHT, LIGHT], false);
+ checkLevelsOfDetail(
+ [
+ [
+ { levelOfDetail: 'detailed' }, { levelOfDetail: 'detailed' }
+ ],
+ [
+ { levelOfDetail: 'detailed', vmRegions: true }
+ ],
+ [
+ { levelOfDetail: 'detailed' },
+ { levelOfDetail: 'detailed', vmRegions: true },
+ { levelOfDetail: 'detailed' }
+ ]
+ ],
+ DETAILED, [DETAILED, DETAILED, DETAILED], false);
+
+ // Not so well-formed trace events.
+ checkLevelsOfDetail(
+ [
+ [{}, { levelOfDetail: 'detailed'}, {}]
+ ],
+ DETAILED, [DETAILED], true);
+ checkLevelsOfDetail(
+ [
+ [{ levelOfDetail: 'light' }],
+ [{}],
+ [{ levelOfDetail: 'detailed' }],
+ [{ levelOfDetail: 'light' }]],
+ DETAILED, [LIGHT, LIGHT, DETAILED, LIGHT], true);
+ checkLevelsOfDetail(
+ [
+ [{ levelOfDetail: 'light' }, { levelOfDetail: 'detailed' }],
+ [{}],
+ [{ levelOfDetail: 'light' }, {}]],
+ DETAILED, [DETAILED, LIGHT, LIGHT], true);
+ checkLevelsOfDetail(
+ [
+ [{ levelOfDetail: 'invalid' }, { levelOfDetail: 'light' }],
+ [{ levelOfDetail: 'invalid' }]],
+ LIGHT, [LIGHT, LIGHT], true);
+ });
+
+ test('importMemoryDumps_heapDumps_oldFormat', function() {
+ function checkModel(m) {
+ const p1 = m.getProcess(21);
+ const p2 = m.getProcess(42);
+ assert.lengthOf(m.globalMemoryDumps, 2);
+ assert.lengthOf(p1.memoryDumps, 2);
+ assert.lengthOf(p2.memoryDumps, 1);
+
+ // Stack frames.
+ const frameIdToTitle = {};
+ for (const [id, f] of Object.entries(m.stackFrames)) {
+ frameIdToTitle[id] = f.title;
+ }
+ assert.deepEqual(
+ frameIdToTitle,
+ {
+ 'p21:0': 'FrameView::layout',
+ 'p21:0:self': '<self>',
+ 'p21:1': 'MessageLoop::RunTask',
+ 'p42::self': '<self>',
+ 'p42:0': 'MessageLoop::RunTask',
+ 'p42:0:self': '<self>',
+ 'p42:1': 'TimerBase::run',
+ 'p42:TWO': 'ScheduledAction::execute',
+ 'p42:3': 'FunctionCall',
+ 'p42:3:self': '<self>',
+ 'p42:4': 'UpdateLayoutTree',
+ 'p42:4:self': '<self>',
+ 'p42:5': 'MessageLoop::JogTask',
+ 'p42:5:self': '<self>'
+ });
+
+ // 1. Process 21, first dump.
+ const pmd1 = p1.memoryDumps[0];
+ const hds1 = pmd1.heapDumps;
+ assert.sameMembers(Object.keys(hds1), ['partition_alloc']);
+
+ const partitionAllocDump1 = hds1.partition_alloc;
+ assert.strictEqual(partitionAllocDump1.processMemoryDump, pmd1);
+ assert.strictEqual(partitionAllocDump1.allocatorName, 'partition_alloc');
+ const partitionAllocEntries1 = partitionAllocDump1.entries;
+ assert.lengthOf(partitionAllocEntries1, 2);
+ checkHeapEntry(partitionAllocEntries1[0], partitionAllocDump1, 4096,
+ undefined /* root */, undefined /* sum over all types */);
+ checkHeapEntry(partitionAllocEntries1[1], partitionAllocDump1, 2748,
+ ['<self>', 'FrameView::layout', 'MessageLoop::RunTask']);
+
+ // 2. Process 21, second dump.
+ const pmd2 = p1.memoryDumps[1];
+ const hds2 = pmd2.heapDumps;
+ assert.sameMembers(Object.keys(hds2), ['partition_alloc']);
+
+ const partitionAllocDump2 = hds2.partition_alloc;
+ assert.strictEqual(partitionAllocDump2.processMemoryDump, pmd2);
+ assert.strictEqual(partitionAllocDump2.allocatorName, 'partition_alloc');
+ const partitionAllocEntries2 = partitionAllocDump2.entries;
+ assert.lengthOf(partitionAllocEntries2, 2);
+ checkHeapEntry(partitionAllocEntries2[0], partitionAllocDump2, 8192,
+ undefined /* root */, undefined /* sum over all types */);
+ checkHeapEntry(partitionAllocEntries2[1], partitionAllocDump2, 3567,
+ ['<self>', 'FrameView::layout', 'MessageLoop::RunTask'],
+ undefined /* sum over all types */);
+
+ // All heap dumps in Process 21 should use the same stack frames.
+ assert.strictEqual(
+ getFrame(partitionAllocEntries1[1], 0),
+ getFrame(partitionAllocEntries2[1], 0));
+
+ // 3. Process 42.
+ const pmd3 = p2.memoryDumps[0];
+ const hds3 = pmd3.heapDumps;
+ assert.sameMembers(Object.keys(hds3), ['partition_alloc', 'malloc']);
+
+ const partitionAllocDump3 = hds3.partition_alloc;
+ assert.strictEqual(partitionAllocDump3.processMemoryDump, pmd3);
+ assert.strictEqual(partitionAllocDump3.allocatorName, 'partition_alloc');
+ const partitionAllocEntries3 = partitionAllocDump3.entries;
+ assert.lengthOf(partitionAllocEntries3, 6);
+ checkHeapEntry(partitionAllocEntries3[0], partitionAllocDump3, 3043272,
+ undefined /* root */, 'blink::Event');
+ checkHeapEntry(partitionAllocEntries3[1], partitionAllocDump3, 6086545,
+ undefined /* root */, undefined /* sum over all types */);
+ checkHeapEntry(partitionAllocEntries3[2], partitionAllocDump3, 1521636,
+ undefined /* root */, 'blink::ContextLifecycleObserver*');
+ checkHeapEntry(partitionAllocEntries3[3], partitionAllocDump3, 5991638,
+ ['<self>'], undefined /* sum over all types */);
+ checkHeapEntry(partitionAllocEntries3[4], partitionAllocDump3, 6384,
+ ['<self>', 'UpdateLayoutTree', 'TimerBase::run',
+ 'MessageLoop::RunTask'], undefined /* sum over all types */);
+ checkHeapEntry(partitionAllocEntries3[5], partitionAllocDump3, 58280,
+ ['<self>', 'FunctionCall', 'ScheduledAction::execute',
+ 'TimerBase::run', 'MessageLoop::RunTask'],
+ undefined /* sum over all types */);
+
+ const mallocDump3 = hds3.malloc;
+ assert.strictEqual(mallocDump3.processMemoryDump, pmd3);
+ assert.strictEqual(mallocDump3.allocatorName, 'malloc');
+ const mallocEntries3 = mallocDump3.entries;
+ assert.lengthOf(mallocEntries3, 4);
+ checkHeapEntry(mallocEntries3[0], mallocDump3, 1929, undefined /* root */,
+ undefined /* sum over all types */);
+ checkHeapEntry(mallocEntries3[1], mallocDump3, 291,
+ ['<self>', 'MessageLoop::RunTask'],
+ undefined /* sum over all types */);
+ checkHeapEntry(mallocEntries3[2], mallocDump3, 1110,
+ ['<self>', 'MessageLoop::JogTask'],
+ undefined /* sum over all types */);
+ checkHeapEntry(mallocEntries3[3], mallocDump3, 205, undefined /* root */,
+ 'blink::ContextLifecycleObserver*');
+
+ // All heap dumps in Process 42 should use the same stack frames.
+ assert.strictEqual(
+ getFrame(partitionAllocEntries3[5], 3),
+ getFrame(partitionAllocEntries3[4], 2));
+ assert.strictEqual(
+ getFrame(mallocEntries3[1], 1),
+ getFrame(partitionAllocEntries3[4], 3));
+ }
+
+ const events = [ // Intentionally shuffled.
+ {
+ pid: 21,
+ ts: 9,
+ ph: 'v',
+ id: '0123',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ heaps: {
+ partition_alloc: {
+ entries: [
+ { size: '1000' },
+ { bt: '0', size: 'abc' }
+ ]
+ }
+ }
+ }
+ }
+ },
+ {
+ pid: 42,
+ ph: 'M',
+ name: 'stackFrames',
+ cat: 'memory-infra',
+ args: {
+ stackFrames: {
+ '0': { name: 'MessageLoop::RunTask' },
+ '1': { name: 'TimerBase::run', parent: '0' },
+ 'TWO': { name: 'ScheduledAction::execute', parent: '1' },
+ '3': { name: 'FunctionCall', parent: 'TWO' },
+ '4': { name: 'UpdateLayoutTree', parent: '1' },
+ '5': { name: 'MessageLoop::JogTask' }
+ }
+ }
+ },
+ {
+ pid: 42,
+ ph: 'M',
+ name: 'typeNames',
+ cat: 'memory-infra',
+ args: {
+ typeNames: {
+ // GCC.
+ '22': '[unknown]',
+ '23': 'testing::ManuallyAnnotatedMockClass',
+ '24': 'const char* WTF::getStringWithTypeName() [with T = ' +
+ 'blink::Event]',
+ '25': 'blink::ContextLifecycleObserver*',
+ '26': 'const char* WTF::getStringWithTypeName() [with T = ' +
+ 'blink::WebFrame*]'
+ }
+ }
+ },
+ {
+ pid: 42,
+ ts: 10,
+ ph: 'v',
+ id: '0123',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '0'
+ },
+ heaps: {
+ partition_alloc: {
+ entries: [
+ { type: '24', size: '2e6fc8' },
+ { size: '5cdf91' },
+ { type: '25', size: '1737e4' },
+ { bt: '', size: '5b6cd6' },
+ { bt: '4', size: '18f0' },
+ { bt: '3', size: 'e3a8' }
+ ]
+ },
+ malloc: {
+ entries: [
+ { size: '789' },
+ { bt: '0', size: '123' },
+ { bt: '5', size: '456' },
+ { type: '25', size: 'cd' }
+ ]
+ }
+ }
+ }
+ }
+ },
+ {
+ pid: 21,
+ ph: 'M',
+ name: 'stackFrames',
+ cat: 'memory-infra',
+ args: {
+ stackFrames: {
+ // Intentionally in reverse order.
+ '0': { name: 'FrameView::layout', parent: '1' },
+ '1': { name: 'MessageLoop::RunTask' }
+ }
+ }
+ },
+ {
+ pid: 21,
+ ts: 12,
+ ph: 'v',
+ id: '0987',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ heaps: {
+ partition_alloc: {
+ entries: [
+ { size: '2000' },
+ { bt: '0', size: 'def' }
+ ]
+ }
+ }
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('importMemoryDumps_heapDumps_newFormat', function() {
+ function checkModel(m) {
+ const p1 = m.getProcess(21);
+ const p2 = m.getProcess(42);
+ const p3 = m.getProcess(63);
+ const p4 = m.getProcess(84);
+ assert.lengthOf(m.globalMemoryDumps, 2);
+ assert.lengthOf(p1.memoryDumps, 2);
+ assert.lengthOf(p2.memoryDumps, 1);
+ assert.lengthOf(p3.memoryDumps, 1);
+ assert.lengthOf(p4.memoryDumps, 1);
+
+ // Stack frames.
+ const frameIdToTitle = {};
+ for (const [id, f] of Object.entries(m.stackFrames)) {
+ frameIdToTitle[id] = f.title;
+ }
+ assert.deepEqual(
+ frameIdToTitle,
+ {
+ 'p21:0': 'FrameView::layout',
+ 'p21:A': '<self>',
+ 'p21:1': 'MessageLoop::RunTask',
+ 'p42:-1': '<self>',
+ 'p42:0': 'MessageLoop::RunTask',
+ 'p42:0.5': '<self>',
+ 'p42:1': 'TimerBase::run',
+ 'p42:TWO': 'ScheduledAction::execute',
+ 'p42:2.72': '<self>',
+ 'p42:3': 'FunctionCall',
+ 'p42:\u03C0': '<self>',
+ 'p42:4': 'UpdateLayoutTree',
+ 'p42:FOUR-AND-A-BIT': '<self>',
+ 'p42:5': 'MessageLoop::JogTask',
+ 'p42:NaN': '<self>',
+ 'p84:5': 'MessageLoop::WalkTask'
+ });
+
+ // 1. Process 21, first dump.
+ const pmd1 = p1.memoryDumps[0];
+ const hds1 = pmd1.heapDumps;
+ assert.sameMembers(Object.keys(hds1), ['partition_alloc']);
+
+ const partitionAllocDump1 = hds1.partition_alloc;
+ assert.strictEqual(partitionAllocDump1.processMemoryDump, pmd1);
+ assert.strictEqual(partitionAllocDump1.allocatorName, 'partition_alloc');
+ const partitionAllocEntries1 = partitionAllocDump1.entries;
+ assert.lengthOf(partitionAllocEntries1, 2);
+ checkHeapEntry(partitionAllocEntries1[0], partitionAllocDump1, 4096,
+ undefined /* root */,
+ 'class v8::FunctionCallbackInfo<class v8::Value>');
+ checkHeapEntry(partitionAllocEntries1[1], partitionAllocDump1, 2748,
+ ['<self>', 'FrameView::layout', 'MessageLoop::RunTask'],
+ undefined /* sum over all types */);
+
+ // 2. Process 21, second dump.
+ const pmd2 = p1.memoryDumps[1];
+ const hds2 = pmd2.heapDumps;
+ assert.sameMembers(Object.keys(hds2), ['partition_alloc']);
+
+ const partitionAllocDump2 = hds2.partition_alloc;
+ assert.strictEqual(partitionAllocDump2.processMemoryDump, pmd2);
+ assert.strictEqual(partitionAllocDump2.allocatorName, 'partition_alloc');
+ const partitionAllocEntries2 = partitionAllocDump2.entries;
+ assert.lengthOf(partitionAllocEntries2, 3);
+ checkHeapEntry(partitionAllocEntries2[0], partitionAllocDump2, 8192,
+ undefined /* root */, undefined /* sum over all types */);
+ checkHeapEntry(partitionAllocEntries2[1], partitionAllocDump2, 3567,
+ ['<self>', 'FrameView::layout', 'MessageLoop::RunTask'],
+ 'class v8::FunctionCallbackInfo<class v8::Value>');
+ checkHeapEntry(partitionAllocEntries2[2], partitionAllocDump2, 4095,
+ ['FrameView::layout', 'MessageLoop::RunTask'],
+ undefined /* sum over all types */);
+
+ // All heap dumps in Process 21 should use the same stack frames.
+ assert.strictEqual(
+ getFrame(partitionAllocEntries1[1], 0),
+ getFrame(partitionAllocEntries2[1], 0));
+ assert.strictEqual(
+ getFrame(partitionAllocEntries2[2], 0),
+ getFrame(partitionAllocEntries2[1], 1));
+
+ // 3. Process 42.
+ const pmd3 = p2.memoryDumps[0];
+ const hds3 = pmd3.heapDumps;
+ assert.sameMembers(Object.keys(hds3), ['partition_alloc', 'malloc']);
+
+ const partitionAllocDump3 = hds3.partition_alloc;
+ assert.strictEqual(partitionAllocDump3.processMemoryDump, pmd3);
+ assert.strictEqual(partitionAllocDump3.allocatorName, 'partition_alloc');
+ const partitionAllocEntries3 = partitionAllocDump3.entries;
+ assert.lengthOf(partitionAllocEntries3, 7);
+ checkHeapEntry(partitionAllocEntries3[0], partitionAllocDump3, 6086545,
+ undefined /* root */, undefined /* sum over all types */);
+ checkHeapEntry(partitionAllocEntries3[1], partitionAllocDump3, 3043272,
+ undefined /* root */, 'blink::Event');
+ checkHeapEntry(partitionAllocEntries3[2], partitionAllocDump3, 1521636,
+ undefined /* root */, 'blink::ContextLifecycleObserver *');
+ checkHeapEntry(partitionAllocEntries3[3], partitionAllocDump3, 5991638,
+ ['<self>'], '[unknown]');
+ checkHeapEntry(partitionAllocEntries3[4], partitionAllocDump3, 6384,
+ ['<self>', 'UpdateLayoutTree', 'TimerBase::run',
+ 'MessageLoop::RunTask'], undefined /* sum over all types */, 256);
+ checkHeapEntry(partitionAllocEntries3[5], partitionAllocDump3, 3192,
+ ['<self>', 'UpdateLayoutTree', 'TimerBase::run',
+ 'MessageLoop::RunTask'], 'blink::WebFrame *');
+ checkHeapEntry(partitionAllocEntries3[6], partitionAllocDump3, 58280,
+ ['<self>', 'FunctionCall', 'ScheduledAction::execute',
+ 'TimerBase::run', 'MessageLoop::RunTask'],
+ undefined /* sum over all types */);
+
+ const mallocDump3 = hds3.malloc;
+ assert.strictEqual(mallocDump3.processMemoryDump, pmd3);
+ assert.strictEqual(mallocDump3.allocatorName, 'malloc');
+ const mallocEntries3 = mallocDump3.entries;
+ assert.lengthOf(mallocEntries3, 4);
+ checkHeapEntry(mallocEntries3[0], mallocDump3, 1929, undefined /* root */,
+ undefined /* sum over all types */, 80);
+ checkHeapEntry(mallocEntries3[1], mallocDump3, 291,
+ ['<self>', 'MessageLoop::RunTask'],
+ undefined /* sum over all types */, 96);
+ checkHeapEntry(mallocEntries3[2], mallocDump3, 1110,
+ ['<self>', 'MessageLoop::JogTask'],
+ undefined /* sum over all types */, 112);
+ checkHeapEntry(mallocEntries3[3], mallocDump3, 205,
+ ['FunctionCall', 'ScheduledAction::execute', 'TimerBase::run',
+ 'MessageLoop::RunTask'], 'blink::ContextLifecycleObserver *', 128);
+
+ // All heap dumps in Process 42 should use the same stack frames.
+ assert.strictEqual(
+ getFrame(partitionAllocEntries3[5], 0),
+ getFrame(partitionAllocEntries3[4], 0));
+ assert.strictEqual(
+ getFrame(partitionAllocEntries3[6], 3),
+ getFrame(partitionAllocEntries3[4], 2));
+ assert.strictEqual(
+ getFrame(mallocEntries3[1], 1),
+ getFrame(partitionAllocEntries3[4], 3));
+ assert.strictEqual(
+ getFrame(mallocEntries3[3], 0),
+ getFrame(partitionAllocEntries3[6], 1));
+
+ // 4. Process 63.
+ const pmd4 = p3.memoryDumps[0];
+ const hds4 = pmd4.heapDumps;
+ assert.sameMembers(Object.keys(hds4), ['winheap']);
+
+ const winheapDump = hds4.winheap;
+ assert.strictEqual(winheapDump.processMemoryDump, pmd4);
+ assert.strictEqual(winheapDump.allocatorName, 'winheap');
+ const winheapEntries = winheapDump.entries;
+ assert.lengthOf(winheapEntries, 1);
+ checkHeapEntry(winheapEntries[0], winheapDump, 65536,
+ undefined /* root */, undefined /* sum over all types */);
+
+ // 5. Process 84.
+ const pmd5 = p4.memoryDumps[0];
+ const hds5 = pmd5.heapDumps;
+ assert.sameMembers(Object.keys(hds5), ['malloc']);
+
+ const mallocDump4 = hds5.malloc;
+ assert.strictEqual(mallocDump4.processMemoryDump, pmd5);
+ assert.strictEqual(mallocDump4.allocatorName, 'malloc');
+ const mallocEntries4 = mallocDump4.entries;
+ assert.lengthOf(mallocEntries4, 1);
+ checkHeapEntry(mallocEntries4[0], mallocDump4, 43981,
+ ['MessageLoop::WalkTask'], 'content::Manually');
+ }
+
+ const events = [ // Intentionally shuffled.
+ {
+ pid: 21,
+ ts: 9,
+ ph: 'v',
+ id: '0123',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ heaps: {
+ partition_alloc: {
+ entries: [
+ { bt: '', type: '25', size: '1000' },
+ { bt: 'A', size: 'abc' }
+ ]
+ }
+ }
+ }
+ }
+ },
+ {
+ pid: 42,
+ ph: 'M',
+ name: 'stackFrames',
+ cat: 'memory-infra',
+ args: {
+ stackFrames: {
+ '-1': { name: '<self>' },
+ '0': { name: 'MessageLoop::RunTask' },
+ '0.5': { name: '<self>', parent: '0' },
+ '1': { name: 'TimerBase::run', parent: '0' },
+ 'TWO': { name: 'ScheduledAction::execute', parent: '1' },
+ '2.72': { name: '<self>', parent: 'TWO' },
+ '3': { name: 'FunctionCall', parent: 'TWO' },
+ '\u03C0': { name: '<self>', parent: '3' },
+ '4': { name: 'UpdateLayoutTree', parent: '1' },
+ 'FOUR-AND-A-BIT': { name: '<self>', parent: '4' },
+ '5': { name: 'MessageLoop::JogTask' },
+ 'NaN': { name: '<self>', parent: '5' }
+ }
+ }
+ },
+ {
+ pid: 42,
+ ph: 'M',
+ name: 'typeNames',
+ cat: 'memory-infra',
+ args: {
+ typeNames: {
+ // Clang.
+ '22': '[unknown]',
+ '23': 'testing::ManuallyAnnotatedMockClass',
+ '24': 'const char *WTF::getStringWithTypeName() [T = ' +
+ 'blink::Event]',
+ '25': 'blink::ContextLifecycleObserver *',
+ '26': 'const char *WTF::getStringWithTypeName() [T = ' +
+ 'blink::WebFrame *]'
+ }
+ }
+ },
+ {
+ pid: 42,
+ ts: 10,
+ ph: 'v',
+ id: '0123',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_totals: {
+ resident_set_bytes: '0'
+ },
+ heaps: {
+ partition_alloc: {
+ entries: [
+ { bt: '' /* root */, size: '5cdf91' },
+ { bt: '' /* root */, type: '24', size: '2e6fc8' },
+ { bt: '' /* root */, type: '25', size: '1737e4' },
+ { bt: '-1', type: '22', size: '5b6cd6' },
+ { bt: 'FOUR-AND-A-BIT', size: '18f0', count: '100' },
+ { bt: 'FOUR-AND-A-BIT', type: '26', size: 'c78' },
+ { bt: '\u03C0', size: 'e3a8' }
+ ]
+ },
+ malloc: {
+ entries: [
+ { bt: '', size: '789', count: '50' },
+ { bt: '0.5', size: '123', count: '60' },
+ { bt: 'NaN', size: '456', count: '70' },
+ { bt: '3', type: '25', size: 'cd', count: '80' }
+ ]
+ }
+ }
+ }
+ }
+ },
+ {
+ pid: 21,
+ ph: 'M',
+ name: 'stackFrames',
+ cat: 'memory-infra',
+ args: {
+ stackFrames: {
+ // Intentionally in reverse order.
+ 'A': { name: '<self>', parent: '0' },
+ '0': { name: 'FrameView::layout', parent: '1' },
+ '1': { name: 'MessageLoop::RunTask' }
+ }
+ }
+ },
+ {
+ pid: 21,
+ ts: 12,
+ ph: 'v',
+ id: '0987',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ heaps: {
+ winheap: {
+ entries: [] // Intentionally empty.
+ },
+ partition_alloc: {
+ entries: [
+ { bt: '', size: '2000' },
+ { bt: 'A', type: '25', size: 'def' },
+ { bt: '3' /* invalid */, size: 'aaa' },
+ { bt: 'A', type: '24' /* invalid */, size: 'bbb' },
+ { bt: '0', size: 'fff' }
+ ]
+ }
+ }
+ }
+ }
+ },
+ {
+ pid: 21,
+ ph: 'M',
+ name: 'typeNames',
+ cat: 'memory-infra',
+ args: {
+ typeNames: {
+ // Microsoft Visual C++.
+ '25': 'const char *__cdecl WTF::getStringWithTypeName<class ' +
+ 'v8::FunctionCallbackInfo<class v8::Value>>(void)'
+ }
+ }
+ },
+ {
+ pid: 63,
+ ph: 'M',
+ name: 'stackFrames',
+ cat: 'memory-infra',
+ args: {
+ stackFrames: {} // Intentionally empty.
+ }
+ },
+ {
+ pid: 63,
+ ph: 'M',
+ name: 'typeNames',
+ cat: 'memory-infra',
+ args: {
+ typeNames: {} // Intentionally empty.
+ }
+ },
+ {
+ pid: 63,
+ ts: 13,
+ ph: 'v',
+ id: '0987',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ heaps: {
+ winheap: {
+ entries: [
+ { bt: '', size: '10000' }
+ ]
+ }
+ }
+ }
+ }
+ },
+ {
+ pid: 84,
+ ph: 'M',
+ name: 'stackFrames',
+ cat: 'memory-infra',
+ args: {
+ stackFrames: {
+ '5': { name: 'MessageLoop::WalkTask' }
+ }
+ }
+ },
+ {
+ pid: 84,
+ ph: 'M',
+ name: 'typeNames',
+ cat: 'memory-infra',
+ args: {
+ typeNames: {
+ '0': '[unknown]',
+ '1': 'base::All',
+ '3': 'content::Manually',
+ '4': 'net::Annotated'
+ }
+ }
+ },
+ {
+ pid: 84,
+ ts: 14,
+ ph: 'v',
+ id: '0987',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ heaps: {
+ malloc: {
+ entries: [
+ { bt: '5', type: '3', size: 'abcd' }
+ ]
+ }
+ }
+ }
+ }
+ }
+ ];
+ // TODO(chiniforooshan): checkParsedAndStreamInput uses JSON.stringify and
+ // then stringToUint8Array for converting events to a trace stream. The
+ // current implementation of stringToUint8Array is not Unicode compatible.
+ const m = makeModel(events);
+ checkModel(m);
+ });
+
+ test('importMemoryDumps_composableDumps', function() {
+ function checkModel(m) {
+ function checkDumpTime(dump, expectedStart, expectedDuration) {
+ assert.closeTo(dump.start, expectedStart / 1000, 1e-5);
+ assert.closeTo(dump.duration, expectedDuration / 1000, 1e-5);
+ }
+
+ function checkLinkCounts(allocatorDump, expectedHasOwns,
+ expectedOwnedByCount, expectedRetainsCount, expectedRetainedByCount) {
+ assert.strictEqual(allocatorDump.owns !== undefined, expectedHasOwns);
+ assert.lengthOf(allocatorDump.ownedBy, expectedOwnedByCount);
+ assert.lengthOf(allocatorDump.retains, expectedRetainsCount);
+ assert.lengthOf(allocatorDump.retainedBy, expectedRetainedByCount);
+ }
+
+ // Check the overall structure of the model and memory dumps.
+ assert.sameMembers(Object.keys(m.processes), ['42', '68']);
+ assert.lengthOf(m.globalMemoryDumps, 2);
+ const gmd1 = m.globalMemoryDumps[0];
+ assert.strictEqual(gmd1.model, m);
+ checkDumpTime(gmd1, 9999, 6);
+ const gmd2 = m.globalMemoryDumps[1];
+ assert.strictEqual(gmd2.model, m);
+ checkDumpTime(gmd2, 10003, 0);
+
+ const p1 = m.getProcess(42);
+ assert.lengthOf(p1.memoryDumps, 2);
+ const pmd1 = p1.memoryDumps[0];
+ checkDumpTime(pmd1, 10000, 5);
+ assert.strictEqual(pmd1.globalMemoryDump, gmd1);
+ assert.strictEqual(pmd1.process, p1);
+ const pmd2 = p1.memoryDumps[1];
+ checkDumpTime(pmd2, 10003, 0);
+ assert.strictEqual(pmd2.globalMemoryDump, gmd2);
+ assert.strictEqual(pmd2.process, p1);
+
+ const p2 = m.getProcess(68);
+ assert.lengthOf(p2.memoryDumps, 1);
+ const pmd3 = p2.memoryDumps[0];
+ checkDumpTime(pmd3, 9999, 0);
+ assert.strictEqual(pmd3.globalMemoryDump, gmd1);
+ assert.strictEqual(pmd3.process, p2);
+
+ assert.deepEqual(gmd1.processMemoryDumps, {42: pmd1, 68: pmd3});
+ assert.deepEqual(gmd2.processMemoryDumps, {42: pmd2});
+
+ // Check the composed dump.
+ assert.strictEqual(pmd1.levelOfDetail, LIGHT);
+
+ const totals = pmd1.totals;
+ assert.strictEqual(totals.residentBytes, 256);
+ assert.isUndefined(totals.peakResidentBytes);
+ assert.isUndefined(totals.arePeakResidentBytesResettable);
+ assert.deepEqual(totals.platformSpecific, {private_bytes: 128});
+
+ const vmRegions = pmd1.vmRegions;
+ checkVMRegions(vmRegions, [
+ {
+ mappedFile: '[stack:20310]',
+ startAddress: 240,
+ sizeInBytes: 336,
+ protectionFlags: VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_WRITE,
+ byteStats: {
+ proportionalResident: 158
+ }
+ }
+ ]);
+
+ const memoryAllocatorDumps = pmd1.memoryAllocatorDumps;
+ assert.lengthOf(memoryAllocatorDumps, 2);
+
+ const local1Dump = pmd1.getMemoryAllocatorDumpByFullName('local1');
+ assert.strictEqual(memoryAllocatorDumps[0], local1Dump);
+ assert.strictEqual(local1Dump.fullName, 'local1');
+ assert.isUndefined(local1Dump.parent);
+ assert.lengthOf(local1Dump.children, 0);
+ checkDumpNumericsAndDiagnostics(local1Dump, {},
+ { 'A': 'blue', 'B': 'red' });
+ checkLinkCounts(local1Dump, true /* owns */, 0 /* owned by */,
+ 0 /* retains */, 0 /* retained by */);
+
+ const local2Dump = pmd1.getMemoryAllocatorDumpByFullName('local2');
+ assert.strictEqual(memoryAllocatorDumps[1], local2Dump);
+ assert.strictEqual(local2Dump.fullName, 'local2');
+ assert.isUndefined(local2Dump.parent);
+ assert.lengthOf(local2Dump.children, 0);
+ checkDumpNumericsAndDiagnostics(local2Dump, {}, { 'B': 'yellow' });
+ checkLinkCounts(local2Dump, false /* owns */, 0 /* owned by */,
+ 1 /* retains */, 0 /* retained by */);
+
+ const heapDumps = pmd1.heapDumps;
+ assert.sameMembers(Object.keys(heapDumps), ['partition_alloc']);
+ const heapDump = heapDumps.partition_alloc;
+ assert.strictEqual(heapDump.processMemoryDump, pmd1);
+ assert.strictEqual(heapDump.allocatorName, 'partition_alloc');
+ const entries = heapDump.entries;
+ assert.lengthOf(entries, 1);
+ assert.strictEqual(entries[0].heapDump, heapDump);
+ assert.strictEqual(
+ entries[0].leafStackFrame.title, 'MessageLoop::RunTask');
+ assert.strictEqual(entries[0].objectTypeName, 'cc::SurfaceFactory');
+ assert.strictEqual(entries[0].size, 1280);
+
+ // Check the other dumps.
+ assert.strictEqual(pmd2.levelOfDetail, DETAILED);
+ assert.isUndefined(pmd2.vmRegions);
+ assert.lengthOf(pmd2.memoryAllocatorDumps, 1);
+ const otherLocal1Dump = pmd2.getMemoryAllocatorDumpByFullName('local1');
+ assert.strictEqual(otherLocal1Dump, pmd2.memoryAllocatorDumps[0]);
+ assert.strictEqual(otherLocal1Dump.fullName, 'local1');
+ assert.isUndefined(otherLocal1Dump.parent);
+ checkDumpNumericsAndDiagnostics(otherLocal1Dump, { 'A': 2989 }, {});
+ assert.isUndefined(pmd2.heapDumps);
+ checkLinkCounts(otherLocal1Dump, false /* owns */, 1 /* owned by */,
+ 0 /* retains */, 0 /* retained by */);
+
+ assert.strictEqual(pmd3.levelOfDetail, DETAILED);
+ const otherVmRegions = pmd3.vmRegions;
+ checkVMRegions(otherVmRegions, [
+ {
+ mappedFile: '/dev/ashmem/dalvik',
+ startAddress: 848,
+ sizeInBytes: 592,
+ protectionFlags: VMRegion.PROTECTION_FLAG_READ |
+ VMRegion.PROTECTION_FLAG_EXECUTE,
+ byteStats: {
+ privateDirtyResident: 205
+ }
+ }
+ ]);
+ assert.lengthOf(pmd3.memoryAllocatorDumps, 0);
+ assert.isUndefined(pmd3.heapDumps);
+
+ // Check the global dumps.
+ assert.lengthOf(gmd1.memoryAllocatorDumps, 2);
+ const shared1Dump = gmd1.getMemoryAllocatorDumpByFullName('shared1');
+ assert.strictEqual(shared1Dump, gmd1.memoryAllocatorDumps[0]);
+ assert.strictEqual(shared1Dump.fullName, 'shared1');
+ assert.isUndefined(shared1Dump.parent);
+ checkDumpNumericsAndDiagnostics(shared1Dump, {},
+ { 'A': 'purple', 'B': 'green' });
+ checkLinkCounts(shared1Dump, false /* owns */, 1 /* owned by */,
+ 0 /* retains */, 0 /* retained by */);
+ const shared2Dump = gmd1.getMemoryAllocatorDumpByFullName('shared2');
+ assert.strictEqual(shared2Dump, gmd1.memoryAllocatorDumps[1]);
+ assert.strictEqual(shared2Dump.fullName, 'shared2');
+ assert.isUndefined(shared2Dump.parent);
+ checkDumpNumericsAndDiagnostics(shared2Dump, {}, { 'A': 'cyan' });
+ checkLinkCounts(shared2Dump, false /* owns */, 0 /* owned by */,
+ 0 /* retains */, 1 /* retained by */);
+
+ assert.lengthOf(gmd2.memoryAllocatorDumps, 1);
+ const otherShared1Dump = gmd2.getMemoryAllocatorDumpByFullName('shared1');
+ assert.strictEqual(otherShared1Dump, gmd2.memoryAllocatorDumps[0]);
+ assert.strictEqual(otherShared1Dump.fullName, 'shared1');
+ assert.isUndefined(otherShared1Dump.parent);
+ checkDumpNumericsAndDiagnostics(otherShared1Dump, {}, { 'A': 'brown' });
+ checkLinkCounts(otherShared1Dump, true /* owns */, 0 /* owned by */,
+ 0 /* retains */, 0 /* retained by */);
+
+ // Check the edges.
+ const ownershipLink = local1Dump.owns;
+ assert.strictEqual(shared1Dump.ownedBy[0], ownershipLink);
+ assert.strictEqual(ownershipLink.source, local1Dump);
+ assert.strictEqual(ownershipLink.target, shared1Dump);
+ assert.strictEqual(ownershipLink.importance, 1);
+
+ const retentionLink = local2Dump.retains[0];
+ assert.strictEqual(shared2Dump.retainedBy[0], retentionLink);
+ assert.strictEqual(retentionLink.source, local2Dump);
+ assert.strictEqual(retentionLink.target, shared2Dump);
+ assert.isUndefined(retentionLink.importance);
+
+ const otherOwnershipLink = otherShared1Dump.owns;
+ assert.strictEqual(otherLocal1Dump.ownedBy[0], otherOwnershipLink);
+ assert.strictEqual(otherOwnershipLink.source, otherShared1Dump);
+ assert.strictEqual(otherOwnershipLink.target, otherLocal1Dump);
+ assert.isUndefined(otherOwnershipLink.importance);
+ }
+
+ // Split PMD1 over multiple PMD trace events, all of which should be merged
+ // by the importer. Also inject some PMD trace events with different PIDs
+ // or dump IDs, which should *not* be merged with the rest.
+ const events = [
+ { // Heap dumps.
+ pid: 42,
+ ts: 10000,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ level_of_detail: 'light',
+ heaps: {
+ partition_alloc: {
+ entries: [
+ { bt: '99', type: '888', size: '500' }
+ ]
+ }
+ }
+ }
+ }
+ },
+ { // PMD2 with a different dump id (should not be merged).
+ pid: 42,
+ ts: 10003, // Same PID, so it will end up in the same process.
+ ph: 'v',
+ id: '0x0002',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ level_of_detail: 'detailed',
+ allocators: {
+ 'local1': {
+ guid: '3',
+ attrs: {
+ A: {type: 'scalar', units: 'bytes', value: '0xBAD'}
+ }
+ },
+ 'global/shared1': {
+ guid: '4',
+ attrs: {
+ A: {type: 'string', units: '', value: 'brown'}
+ }
+ }
+ },
+ allocators_graph: [
+ {
+ source: '4',
+ target: '3',
+ type: 'ownership'
+ }
+ ]
+ }
+ }
+ },
+ { // Stack frames (required for heap dumps).
+ pid: 42,
+ ph: 'M',
+ name: 'stackFrames',
+ cat: 'memory-infra',
+ args: {
+ stackFrames: {
+ '99': { name: 'MessageLoop::RunTask' }
+ }
+ }
+ },
+ { // Allocator dumps.
+ pid: 42,
+ ts: 10001,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ level_of_detail: 'light',
+ allocators: {
+ 'local1': {
+ guid: '3',
+ attrs: {
+ A: {type: 'string', units: '', value: 'blue'}
+ }
+ },
+ 'global/shared1': {
+ guid: '7',
+ attrs: {
+ A: {type: 'string', units: '', value: 'purple'}
+ }
+ },
+ 'global/shared2': {
+ guid: '8',
+ attrs: {
+ A: {type: 'string', units: '', value: 'cyan'}
+ }
+ }
+ }
+ }
+ }
+ },
+ { // PMD3 with a different PID (should not be merged).
+ pid: 68,
+ ts: 9999,
+ ph: 'v',
+ id: '0x0001', // Same dump ID, so it will end up in the same GMD.
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ process_mmaps: {
+ vm_regions: [
+ {
+ sa: '350',
+ sz: '250',
+ pf: 5,
+ mf: '/dev/ashmem/dalvik',
+ bs: {
+ pd: 'cd'
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ { // VM regions.
+ pid: 42,
+ ts: 10005,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ level_of_detail: 'light',
+ process_mmaps: {
+ vm_regions: [
+ {
+ sa: 'f0',
+ sz: '150',
+ pf: 6,
+ mf: '[stack:20310]',
+ bs: {
+ pss: '9e'
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ { // Totals.
+ pid: 42,
+ ts: 10003,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ level_of_detail: 'light',
+ process_totals: {
+ resident_set_bytes: '100',
+ private_bytes: '80' // OS-specific total.
+ }
+ }
+ }
+ },
+ { // Allocator dump edges.
+ pid: 42,
+ ts: 10004,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ level_of_detail: 'light',
+ allocators_graph: [
+ {
+ source: '4',
+ target: '8',
+ type: 'retention'
+ }
+ ]
+ }
+ }
+ },
+ { // Object type names (required for heap dumps).
+ pid: 42,
+ ph: 'M',
+ name: 'typeNames',
+ cat: 'memory-infra',
+ args: {
+ typeNames: {
+ // GCC.
+ '888': 'const char* WTF::getStringWithTypeName() [with T = ' +
+ 'cc::SurfaceFactory]'
+ }
+ }
+ },
+ { // Allocator dumps and dump edges (should be merged).
+ pid: 42,
+ ts: 10002,
+ ph: 'v',
+ id: '0x0001',
+ cat: 'memory-infra',
+ args: {
+ dumps: {
+ level_of_detail: 'light',
+ allocators: {
+ 'local1': {
+ guid: '3',
+ attrs: {
+ B: {type: 'string', units: '', value: 'red'}
+ }
+ },
+ 'local2': {
+ guid: '4',
+ attrs: {
+ B: {type: 'string', units: '', value: 'yellow'}
+ }
+ },
+ 'global/shared1': {
+ guid: '7',
+ attrs: {
+ B: {type: 'string', units: '', value: 'green'}
+ }
+ }
+ },
+ allocators_graph: [
+ {
+ source: '3',
+ target: '7',
+ type: 'ownership',
+ importance: 1
+ }
+ ]
+ }
+ }
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('importThreadInstantSliceWithStackFrame', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[2];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.slices.length, 1);
+
+ const s0 = t.sliceGroup.slices[0];
+ assert.strictEqual(s0.startStackFrame.title, 'frame7');
+ assert.isUndefined(s0.endStackFrame);
+ }
+
+ const eventData = {
+ traceEvents: [
+ { name: 'a', args: {}, pid: 1, ts: 0, cat: 'baz', tid: 2, ph: 'I', s: 't', sf: 7 } // @suppress longLineCheck
+ ],
+ stackFrames: {
+ '1': {
+ category: 'm1',
+ name: 'main'
+ },
+ '7': {
+ category: 'm2',
+ name: 'frame7',
+ parent: '1'
+ }
+ }
+ };
+ checkParsedAndStreamInput(eventData, checkModel);
+ });
+
+ test('importDurationEventsWithStackFrames', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[2];
+ assert.isDefined(t);
+ assert.strictEqual(t.sliceGroup.slices.length, 1);
+
+ const s0 = t.sliceGroup.slices[0];
+ assert.strictEqual(s0.startStackFrame.title, 'frame7');
+ assert.strictEqual(s0.endStackFrame.title, 'frame8');
+ }
+
+ const eventData = {
+ traceEvents: [
+ { name: 'a', args: {}, pid: 1, ts: 0, cat: 'baz', tid: 2, ph: 'B', sf: 7 }, // @suppress longLineCheck
+ { name: 'b', args: {}, pid: 1, ts: 5, cat: 'baz', tid: 2, ph: 'E', sf: 8 } // @suppress longLineCheck
+ ],
+ stackFrames: {
+ '1': {
+ category: 'm1',
+ name: 'main'
+ },
+ '7': {
+ category: 'm2',
+ name: 'frame7',
+ parent: '1'
+ },
+ '8': {
+ category: 'm2',
+ name: 'frame8',
+ parent: '1'
+ }
+ }
+ };
+ checkParsedAndStreamInput(eventData, checkModel);
+ });
+
+ test('annotationParsing', function() {
+ const yComponents1 = [{stableId: '52.53', yPercentOffset: 0.5},
+ {stableId: '52', yPercentOffset: 0.3}];
+ const yComponents2 = [{stableId: '52.53', yPercentOffset: 0.7},
+ {stableId: '52', yPercentOffset: 0.4}];
+ const location1 = new tr.model.Location(0.1, yComponents1);
+ const location2 = new tr.model.Location(0.2, yComponents2);
+
+ function checkModel(m) {
+ const annotations = m.getAllAnnotations();
+ assert.strictEqual(annotations.length, 3);
+
+ assert.isTrue(annotations[0] instanceof tr.model.XMarkerAnnotation);
+ assert.strictEqual(annotations[0].timestamp, 12);
+
+ assert.isTrue(annotations[1] instanceof tr.model.RectAnnotation);
+ assert.deepEqual(annotations[1].startLocation, location1);
+ assert.deepEqual(annotations[1].endLocation, location2);
+
+ assert.isTrue(
+ annotations[2] instanceof tr.model.CommentBoxAnnotation);
+ assert.strictEqual(annotations[2].text, 'test');
+ assert.deepEqual(annotations[2].location, location1);
+ }
+
+ const eventData = { traceEvents: [
+ {name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53, ph: 'B'}],
+ traceAnnotations: [
+ {typeName: 'xmarker', args: {timestamp: 12}},
+ {typeName: 'rect', args: {
+ start: location1.toDict(), end: location2.toDict()}},
+ {typeName: 'comment_box', args: {text: 'test',
+ location: location1.toDict()}}
+ ]};
+ checkParsedAndStreamInput(eventData, checkModel);
+ });
+
+ test('importDisplayTimeUnit', function() {
+ function checkModel(m) {
+ assert.strictEqual(m.intrinsicTimeUnit, tr.b.TimeDisplayModes.ns);
+ }
+
+ const eventData = {
+ traceEvents: [
+ {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'}],
+ displayTimeUnit: 'ns'
+ };
+ checkParsedAndStreamInput(eventData, checkModel);
+ const m = makeModel(JSON.stringify(eventData));
+ checkModel(m);
+ });
+
+ test('extractBattorSubTraces', function() {
+ const battorLog = '# BattOr\n# voltage range [0.000000, 7196.484161] mV\n' +
+ '#current range [11.898481, 2110.900916] mA\n' +
+ '# sample_rate=10000Hz, gain=30.257143x\n' +
+ '# filpot_pos=3, amppot_pos=35, timer_ovf=399, timer_div=4 ovs_bits=0\n' + // @suppress longLineCheck
+ '0.0 1040.3 3984.2\n' +
+ '0.1 1081.3 3987.8\n' +
+ '0.2 1092.6 3987.8\n' +
+ '0.3 1070.0 3987.8\n' +
+ '0.4 1017.7 3994.8\n';
+
+ const eventData = {
+ traceEvents: [
+ { name: 'a', args: {}, pid: 1, ts: 0, cat: 'baz', tid: 2, ph: 'B', sf: 7 }, // @suppress longLineCheck
+ { name: 'b', args: {}, pid: 1, ts: 5, cat: 'baz', tid: 2, ph: 'E', sf: 8 } // @suppress longLineCheck
+ ],
+ powerTraceAsString: battorLog
+ };
+ let m = new tr.Model();
+ let importer = new tr.e.importer.TraceEventImporter(m, eventData);
+ let subTraces = importer.extractSubtraces();
+ assert.isTrue(subTraces instanceof Array);
+ assert.strictEqual(subTraces.length, 1);
+ assert.strictEqual(subTraces[0], battorLog);
+
+ m = new tr.Model();
+ const stream = new tr.b.InMemoryTraceStream(
+ stringToUint8Array(JSON.stringify(eventData)), false);
+ importer = new tr.e.importer.TraceEventImporter(m, stream);
+ subTraces = importer.extractSubtraces();
+ assert.isTrue(subTraces instanceof Array);
+ assert.strictEqual(subTraces.length, 1);
+ assert.strictEqual(subTraces[0], battorLog);
+ });
+
+ test('metadataParsing', function() {
+ const metadataValue = {value: {}};
+
+ function checkModel(m) {
+ assert.isTrue(m.metadata instanceof Array);
+ assert.strictEqual(m.metadata.length, 1);
+ assert.strictEqual(m.metadata[0].name, 'metadata');
+ assert.deepEqual(m.metadata[0].value, metadataValue);
+ }
+
+ const eventData = {
+ traceEvents: [
+ { name: 'a', args: {}, pid: 1, ts: 0, cat: 'baz', tid: 2, ph: 'B', sf: 7 }, // @suppress longLineCheck
+ { name: 'b', args: {}, pid: 1, ts: 5, cat: 'baz', tid: 2, ph: 'E', sf: 8 } // @suppress longLineCheck
+ ],
+ metadata: metadataValue
+ };
+ checkParsedAndStreamInput(eventData, checkModel);
+ });
+
+ test('importTraceConfigAsMetadata', function() {
+ const traceConfigData = {
+ enable_argument_filter: false,
+ enable_sampling: false,
+ enable_systrace: false,
+ record_mode: 'record-until-full'
+ };
+
+ function checkModel(m) {
+ assert.isTrue(m.metadata instanceof Array);
+ assert.strictEqual(m.metadata.length, 1);
+ assert.strictEqual(m.metadata[0].name, 'TraceConfig');
+ assert.deepEqual(m.metadata[0].value, traceConfigData);
+ }
+
+ const eventData = {
+ traceEvents: [
+ { name: 'TraceConfig', args: { value: traceConfigData },
+ pid: 2, ts: 0, tid: 1, ph: 'M', cat: '__metadata' },
+ ],
+ };
+ checkParsedAndStreamInput(eventData, checkModel);
+ });
+
+ test('importMarks', function() {
+ function checkModel(m) {
+ const p = m.processes[1];
+ const t = p.threads[2];
+ assert.lengthOf(t.sliceGroup.slices, 2);
+
+ const s0 = t.sliceGroup.slices[0];
+ assert.strictEqual(s0.title, 'mark1');
+ assert.strictEqual(s0.start, 0);
+
+ const s1 = t.sliceGroup.slices[1];
+ assert.strictEqual(s1.title, 'mark2');
+ assert.strictEqual(s1.start, .01);
+ }
+
+ const eventData = {
+ traceEvents: [
+ { name: 'mark1', args: {}, pid: 1, tid: 2, ts: 0, tts: 0, cat: 'blink.user_timing', ph: 'R' }, // @suppress longLineCheck
+ { name: 'mark2', args: {}, pid: 1, tid: 2, ts: 10, tts: 10, cat: 'blink.user_timing', ph: 'R' } // @suppress longLineCheck
+ ]
+ };
+ checkParsedAndStreamInput(eventData, checkModel);
+ });
+
+ test('createNestableAsyncSlicesForUserTimingWithoutArgs', function() {
+ function checkModel(m) {
+ assert(m.numProcesses, 1);
+ const p = m.processes[1];
+ assert.isDefined(p);
+ assert.strictEqual(p.numThreads, 1);
+ const t = p.threads[2];
+ const asyncSliceGroup = t.asyncSliceGroup;
+ assert.strictEqual(asyncSliceGroup.length, 2);
+ for (let i = 0; i < asyncSliceGroup.length; ++i) {
+ assert.isTrue(asyncSliceGroup.slices[i] instanceof MeasureAsyncSlice);
+ }
+
+ const groupA = asyncSliceGroup.slices[0];
+ assert.strictEqual(groupA.viewSubGroupTitle, 'A');
+ assert.strictEqual(groupA.title, 'a1');
+ assert.strictEqual(groupA.subSlices.length, 0);
+ const groupB = asyncSliceGroup.slices[1];
+ assert.strictEqual(groupB.viewSubGroupTitle, 'B');
+ assert.strictEqual(groupB.title, 'b1');
+ assert.strictEqual(groupB.subSlices.length, 2);
+ const groupBSubSlice1 = groupB.subSlices[0];
+ assert.strictEqual(groupBSubSlice1.viewSubGroupTitle, 'B');
+ assert.strictEqual(groupBSubSlice1.title, 'b2');
+ assert.strictEqual(groupBSubSlice1.subSlices.length, 1);
+ assert.strictEqual(groupBSubSlice1.subSlices[0].viewSubGroupTitle, 'B');
+ assert.strictEqual(groupBSubSlice1.subSlices[0].title, 'b3');
+ assert.strictEqual(groupBSubSlice1.subSlices[0].subSlices.length, 0);
+ const groupBSubSlice2 = groupB.subSlices[1];
+ assert.strictEqual(groupBSubSlice2.viewSubGroupTitle, 'B');
+ assert.strictEqual(groupBSubSlice2.title, 'b4');
+ assert.strictEqual(groupBSubSlice2.subSlices.length, 0);
+ }
+
+ /**
+ * Structure of this async slices
+ *
+ * Group A:
+ *
+ * |__________|
+ * a1
+ *
+ * Group B:
+ *
+ * |______________________________|
+ * b1
+ * |__________||_|
+ * b2 b4
+ * |_|
+ * b3
+ **/
+ const events = [
+ {
+ name: 'A:a1',
+ args: {params: ''},
+ pid: 1,
+ ts: 100,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 3,
+ ph: 'b'
+ },
+ {
+ name: 'A:a1',
+ args: {params: ''},
+ pid: 1,
+ ts: 110,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 3,
+ ph: 'e'
+ },
+ {
+ name: 'B:b1',
+ args: {params: ''},
+ pid: 1,
+ ts: 120,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 4,
+ ph: 'b'
+ },
+ {
+ name: 'B:b2',
+ args: {params: ''},
+ pid: 1,
+ ts: 130,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 5,
+ ph: 'b'
+ },
+ {
+ name: 'B:b3',
+ args: {params: ''},
+ pid: 1,
+ ts: 131,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 5,
+ ph: 'b'
+ },
+ {
+ name: 'B:b3',
+ args: {params: ''},
+ pid: 1,
+ ts: 132,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 5,
+ ph: 'e'
+ },
+ {
+ name: 'B:b2',
+ args: {params: ''},
+ pid: 1,
+ ts: 140,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 5,
+ ph: 'e'
+ },
+ {
+ name: 'B:b4',
+ args: {params: ''},
+ pid: 1,
+ ts: 141,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 5,
+ ph: 'b'
+ },
+ {
+ name: 'B:b4',
+ args: {params: ''},
+ pid: 1,
+ ts: 142,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 5,
+ ph: 'e'
+ },
+ {
+ name: 'B:b1',
+ args: {params: ''},
+ pid: 1,
+ ts: 150,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 4,
+ ph: 'e'
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('createNestableAsyncSlicesForUserTimingWithArgs', function() {
+ function checkModel(m) {
+ assert(m.numProcesses, 1);
+ const p = m.processes[1];
+ assert.isDefined(p);
+ assert.strictEqual(p.numThreads, 1);
+ const t = p.threads[2];
+ const asyncSliceGroup = t.asyncSliceGroup;
+ assert.strictEqual(asyncSliceGroup.length, 2);
+ for (let i = 0; i < asyncSliceGroup.length; ++i) {
+ assert.isTrue(asyncSliceGroup.slices[i] instanceof MeasureAsyncSlice);
+ }
+
+ const a1 = asyncSliceGroup.slices[0];
+ assert.strictEqual(a1.viewSubGroupTitle, 'A');
+ assert.strictEqual(a1.title, 'a1');
+ assert.strictEqual(a1.subSlices.length, 0);
+ assert.deepEqual(a1.args, {a: 1});
+ const a2 = asyncSliceGroup.slices[1];
+ assert.strictEqual(a2.viewSubGroupTitle, 'A');
+ assert.strictEqual(a2.title, 'a2');
+ assert.strictEqual(a2.subSlices.length, 0);
+ assert.deepEqual(a2.args, {a: 2, b: 2});
+ }
+
+ /**
+ * Structure of this async slices
+ *
+ * Group A:
+ *
+ * |__________| |__________|
+ * a1 a2
+ *
+ * a1.args = {a: 1}
+ * a2.args = {a: 2, b: 2}
+ **/
+ const events = [
+ {
+ name: 'A:a1/eyJhIjoxfQ==',
+ args: {params: ''},
+ pid: 1,
+ ts: 100,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 3,
+ ph: 'b'
+ },
+ {
+ name: 'A:a1/eyJhIjoxfQ==',
+ args: {params: ''},
+ pid: 1,
+ ts: 110,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 3,
+ ph: 'e'
+ },
+ {
+ name: 'A:a2/eyJhIjoyLCJiIjoyfQ==',
+ args: {params: ''},
+ pid: 1,
+ ts: 120,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 4,
+ ph: 'b'
+ },
+ {
+ name: 'A:a2/eyJhIjoyLCJiIjoyfQ==',
+ args: {params: ''},
+ pid: 1,
+ ts: 130,
+ cat: 'blink.user_timing',
+ tid: 2,
+ id: 4,
+ ph: 'e'
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('UserTimingAsyncSlicesWithNormalAsyncSlices', function() {
+ function checkModel(m) {
+ assert(m.numProcesses, 1);
+ const p = m.processes[1];
+ assert.isDefined(p);
+ assert.strictEqual(p.numThreads, 1);
+ const t = p.threads[2];
+ const asyncSliceGroup = t.asyncSliceGroup;
+ assert.strictEqual(asyncSliceGroup.length, 3);
+ assert.isTrue(asyncSliceGroup.slices[0] instanceof MeasureAsyncSlice);
+ assert.isFalse(asyncSliceGroup.slices[1] instanceof MeasureAsyncSlice);
+ assert.isFalse(asyncSliceGroup.slices[2] instanceof MeasureAsyncSlice);
+
+ const a1 = asyncSliceGroup.slices[0];
+ assert.strictEqual(a1.viewSubGroupTitle, 'A');
+ assert.strictEqual(a1.title, 'a1');
+ assert.strictEqual(a1.subSlices.length, 1);
+ const a2 = a1.subSlices[0];
+ assert.strictEqual(a2.viewSubGroupTitle, 'A');
+ assert.strictEqual(a2.title, 'a2');
+ assert.strictEqual(a2.subSlices.length, 0);
+ const B = asyncSliceGroup.slices[1];
+ assert.strictEqual(B.viewSubGroupTitle, 'B');
+ assert.strictEqual(B.title, 'B');
+ assert.strictEqual(B.subSlices.length, 0);
+ const C = asyncSliceGroup.slices[2];
+ assert.strictEqual(C.viewSubGroupTitle, 'C');
+ assert.strictEqual(C.title, 'C');
+ assert.strictEqual(C.subSlices.length, 0);
+ }
+
+ /**
+ * Structure of user timing async slices
+ *
+ * Group A:
+ *
+ * |__________|
+ * a1
+ * |__|
+ * a2
+ *
+ * B
+ * |__|
+ * B
+ * C
+ * |_|
+ * C
+ **/
+ const events = [
+ {
+ name: 'A:a1', args: {params: ''}, pid: 1, ts: 1,
+ cat: 'blink.user_timing', tid: 2, id: 3, ph: 'b'
+ },
+ {
+ name: 'A:a1', args: {params: ''}, pid: 1, ts: 11,
+ cat: 'blink.user_timing', tid: 2, id: 3, ph: 'e'
+ },
+ {
+ name: 'A:a2', args: {params: ''}, pid: 1, ts: 2,
+ cat: 'blink.user_timing', tid: 2, id: 4, ph: 'b'
+ },
+ {
+ name: 'A:a2', args: {params: ''}, pid: 1, ts: 4,
+ cat: 'blink.user_timing', tid: 2, id: 4, ph: 'e'
+ },
+ {
+ name: 'B', args: {}, pid: 1, ts: 9, cat: 'foo',
+ tid: 2, ph: 'b', id: 5
+ },
+ {
+ name: 'B', args: {}, pid: 1, ts: 11, cat: 'foo',
+ tid: 2, ph: 'e', id: 5
+ },
+ {
+ name: 'C', args: {}, pid: 1, ts: 12, cat: 'foo',
+ tid: 2, ph: 'b', id: 6
+ },
+ {
+ name: 'C', args: {}, pid: 1, ts: 13, cat: 'foo',
+ tid: 2, ph: 'e', id: 6
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('UserTimingAsyncSlicesParsingMeasureNameShouldNotFail', function() {
+ function checkModel(m) {
+ assert(m.numProcesses, 1);
+ const p = m.processes[1];
+ assert.isDefined(p);
+ assert.strictEqual(p.numThreads, 1);
+ const t = p.threads[2];
+ const asyncSliceGroup = t.asyncSliceGroup;
+
+ assert.strictEqual(asyncSliceGroup.length, 1);
+ assert.isTrue(asyncSliceGroup.slices[0] instanceof MeasureAsyncSlice);
+
+ const slice = asyncSliceGroup.slices[0];
+ assert.strictEqual(slice.viewSubGroupTitle, '<extended@component');
+ assert.strictEqual(slice.title,
+ 'shared@dropdown/dropdown-options::ember30> (Rendering: initial)');
+ assert.strictEqual(slice.subSlices.length, 3);
+ assert.strictEqual(slice.subSlices[0].title,
+ 'shared@dropdown/dropdown-options::ember30>:didReceiveAttrs:34');
+ assert.strictEqual(slice.subSlices[1].title,
+ 'shared@dropdown/dropdown-options::ember30>:willRender:35');
+ assert.strictEqual(slice.subSlices[2].title,
+ 'shared@dropdown/dropdown-options::ember30>:willInsertElement:36');
+ }
+
+ // events from ember-perf-timeline addon performance.measure() calls
+ const events = [
+ {
+ pid: 1, tid: 2, ts: 2695561803887, ph: 'b', cat: 'blink.user_timing',
+ name: '\u003Cextended@component:shared@dropdown/dropdown-options::ember30>:didReceiveAttrs:34', // @suppress longLineCheck
+ args: {}, tts: 10533196, id: '0x9e9a4a'
+ },
+ {
+ pid: 1, tid: 2, ts: 2695561803907, ph: 'e', cat: 'blink.user_timing',
+ name: '\u003Cextended@component:shared@dropdown/dropdown-options::ember30>:didReceiveAttrs:34', // @suppress longLineCheck
+ args: {}, tts: 10533202, id: '0x9e9a4a'
+ },
+ {
+ pid: 1, tid: 2, ts: 2695561803987, ph: 'b', cat: 'blink.user_timing',
+ name: '\u003Cextended@component:shared@dropdown/dropdown-options::ember30>:willRender:35', // @suppress longLineCheck
+ args: {}, tts: 10533295, id: '0xd0fc77'
+ },
+ {
+ pid: 1, tid: 2, ts: 2695561804002, ph: 'e', cat: 'blink.user_timing',
+ name: '\u003Cextended@component:shared@dropdown/dropdown-options::ember30>:willRender:35', // @suppress longLineCheck
+ args: {}, tts: 10533300, id: '0xd0fc77'
+ },
+ {
+ pid: 1, tid: 2, ts: 2695561804252, ph: 'b', cat: 'blink.user_timing',
+ name: '\u003Cextended@component:shared@dropdown/dropdown-options::ember30>:willInsertElement:36', // @suppress longLineCheck
+ args: {}, tts: 10533557, id: '0x5dfb0b'
+ },
+ {
+ pid: 1, tid: 2, ts: 2695561804262, ph: 'e', cat: 'blink.user_timing',
+ name: '\u003Cextended@component:shared@dropdown/dropdown-options::ember30>:willInsertElement:36', // @suppress longLineCheck
+ args: {}, tts: 10533561, id: '0x5dfb0b'
+ },
+ {
+ pid: 1, tid: 2, ts: 2695561803852, ph: 'b', cat: 'blink.user_timing',
+ name: '\u003Cextended@component:shared@dropdown/dropdown-options::ember30> (Rendering: initial)', // @suppress longLineCheck
+ args: {}, tts: 10533951, id: '0xc824e5'
+ },
+ {
+ pid: 1, tid: 2, ts: 2695561804652, ph: 'e', cat: 'blink.user_timing',
+ name: '\u003Cextended@component:shared@dropdown/dropdown-options::ember30> (Rendering: initial)', // @suppress longLineCheck
+ args: {}, tts: 10533956, id: '0xc824e5'
+ }
+ ];
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('clockSync_requesterWithoutMetadata', function() {
+ const events = [{
+ name: 'clock_sync', args: {sync_id: 'abc', issue_ts: 5000},
+ pid: 1, ts: 15000, cat: '__metadata', tid: 2, ph: 'c'
+ }];
+
+ const io = new tr.importer.ImportOptions();
+ const m = new tr.Model();
+ const i = new tr.importer.Import(m, io);
+
+ m.clockSyncManager.addClockSyncMarker(
+ ClockDomainId.LINUX_FTRACE_GLOBAL, 'abc', 15);
+
+ i.importTraces([events]);
+
+ // The clock sync happened in UNKNOWN_CHROME_LEGACY at 10ms and in
+ // LINUX_FTRACE_GLOBAL at 15ms. The transformer should shift the ftrace
+ // timestamps back by 5ms.
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY)(5),
+ 5);
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ ClockDomainId.LINUX_FTRACE_GLOBAL)(15),
+ 10);
+ assert.isFalse(m.hasImportWarnings);
+ });
+
+ test('clockSync_requesterWithoutMetadata_stream', function() {
+ const events = [{
+ name: 'clock_sync', args: {sync_id: 'abc', issue_ts: 5000},
+ pid: 1, ts: 15000, cat: '__metadata', tid: 2, ph: 'c'
+ }];
+ const stream = new tr.b.InMemoryTraceStream(
+ stringToUint8Array(JSON.stringify(events)), false);
+
+ const io = new tr.importer.ImportOptions();
+ const m = new tr.Model();
+ const i = new tr.importer.Import(m, io);
+
+ m.clockSyncManager.addClockSyncMarker(
+ ClockDomainId.LINUX_FTRACE_GLOBAL, 'abc', 15);
+
+ i.importTraces([stream]);
+
+ // The clock sync happened in UNKNOWN_CHROME_LEGACY at 10ms and in
+ // LINUX_FTRACE_GLOBAL at 15ms. The transformer should shift the ftrace
+ // timestamps back by 5ms.
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY)(5),
+ 5);
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ ClockDomainId.LINUX_FTRACE_GLOBAL)(15),
+ 10);
+ assert.isFalse(m.hasImportWarnings);
+ });
+
+
+ test('clockSync_requesterWithMetadata', function() {
+ function checkModel(m) {
+ // Make sure that our clock domain was chosen as the model.
+ assert.strictEqual(
+ m.clockSyncManager
+ .getModelTimeTransformer(ClockDomainId.BATTOR)(.005),
+ .005);
+ assert.isFalse(m.hasImportWarnings);
+ }
+
+ const events = {
+ traceEvents: [{
+ name: 'clock_sync', args: {sync_id: 'abc', issue_ts: 5},
+ pid: 1, ts: 15, cat: '__metadata', tid: 2, ph: 'c'
+ }],
+ metadata: {
+ 'clock-domain': ClockDomainId.BATTOR
+ }
+ };
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('clockSync_recipient', function() {
+ function checkModel(m) {
+ m.clockSyncManager.addClockSyncMarker(
+ ClockDomainId.LINUX_CLOCK_MONOTONIC, 'abc', 15);
+
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ ClockDomainId.TELEMETRY)(10),
+ 10);
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ ClockDomainId.LINUX_CLOCK_MONOTONIC)(10),
+ 10);
+ }
+
+ const events = {
+ traceEvents: [{
+ name: 'clock_sync', args: {sync_id: 'abc'}, pid: 1, ts: 15000,
+ cat: '__metadata', tid: 2, ph: 'c'
+ }],
+ metadata: {
+ 'clock-domain': ClockDomainId.TELEMETRY
+ }
+ };
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('clockSync_missingSyncId', function() {
+ const events = [{
+ name: 'clock_sync', args: {issue_ts: 5},
+ pid: 1, ts: 15, cat: '__metadata', tid: 2, ph: 'c'
+ }];
+ checkParsedAndStreamInput(events, m => assert.isTrue(m.hasImportWarnings));
+ });
+
+ test('clockSync_legacyChrome', function() {
+ function checkModel(m) {
+ m.clockSyncManager.addClockSyncMarker(
+ ClockDomainId.TELEMETRY, 'abc', 15);
+
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ ClockDomainId.UNKNOWN_CHROME_LEGACY)(10),
+ 10);
+ assert.strictEqual(
+ m.clockSyncManager.getModelTimeTransformer(
+ ClockDomainId.TELEMETRY)(15),
+ 10);
+ }
+
+ const events = {
+ traceEvents: [
+ {
+ name: 'ClockSyncEvent.abc', ts: 5000,
+ cat: 'blink.user_timing', pid: 1, tid: 2, ph: 'S'
+ },
+ {
+ name: 'ClockSyncEvent.abc', ts: 15000,
+ cat: 'blink.user_timing', pid: 1, tid: 2, ph: 'F'
+ }
+ ]
+ };
+ checkParsedAndStreamInput(events, checkModel);
+ });
+
+ test('clockSync_legacyChromeThrowsWithInconsistentSyncID', function() {
+ const events = {
+ traceEvents: [
+ {
+ name: 'ClockSyncEvent.abc', ts: 5000,
+ cat: 'blink.user_timing', pid: 1, tid: 2, ph: 'S'
+ },
+ {
+ name: 'ClockSyncEvent.def', ts: 15000,
+ cat: 'blink.user_timing', pid: 1, tid: 2, ph: 'F'
+ }
+ ]
+ };
+
+ assert.throws(() => makeModel(events),
+ 'Inconsistent clock sync ID of legacy Chrome clock sync events');
+ // TODO(chiniforooshan): When the input is a trace stream, it is processed
+ // using the Oboe.js library. Oboe.js wraps exceptions thrown from callbacks
+ // and uses setTimeout to throw them in another event loop so that stream
+ // parsing is not interrupted.
+ //
+ // The importer's behaviour should be the same for different types of the
+ // same input. We should either modify Oboe.js to throw right away, or wrap
+ // exceptions in the importer, too.
+ });
+
+ test('importV8SamplesInStreamingFormat', function() {
+ function checkModel(m) {
+ const samples = m.samples;
+ assert.strictEqual(m.samples.length, 4);
+ assert.deepEqual(samples.map(sample => sample.start.toFixed(3)), [
+ '0.011',
+ '0.012',
+ '0.015',
+ '0.018'
+ ]);
+ assert.deepEqual(samples[0].userFriendlyStack, [
+ 'b url: b.js:1:2 Deoptimized reason: a reason',
+ 'a url: a.js:1:2'
+ ]);
+ assert.deepEqual(samples[1].userFriendlyStack, [
+ 'a url: a.js:1:2'
+ ]);
+ assert.deepEqual(samples[2].userFriendlyStack, [
+ 'd url: d.js:2:3 Deoptimized reason: another reason',
+ 'b url: b.js:1:2 Deoptimized reason: a reason',
+ 'a url: a.js:1:2'
+ ]);
+ assert.deepEqual(samples[3].userFriendlyStack, [
+ 'c url: c.js:2:3',
+ 'a url: a.js:1:2'
+ ]);
+ }
+
+ const events = {
+ 'traceEvents': [
+ {'pid': 1, 'tid': 2, 'ts': 3, 'ph': 'P',
+ 'cat': 'disabled-by-default-v8.cpu_profiler',
+ 'name': 'Profile',
+ 'args': {'data': {'startTime': 10}},
+ 'id': 123
+ },
+ {'pid': 1, 'tid': 3, 'ts': 4, 'ph': 'P',
+ 'cat': 'disabled-by-default-v8.cpu_profiler',
+ 'name': 'Profile',
+ 'args': {
+ 'data': {
+ 'timeDeltas': [1, 1],
+ 'cpuProfile': {
+ 'nodes': [
+ // eslint-disable-next-line
+ {'callFrame': {'functionName': '(root)', 'scriptId': 0}, 'id': 1},
+ // eslint-disable-next-line
+ {'callFrame': {'functionName': 'a', 'url': 'a.js', 'scriptId': 1, 'lineNumber': 1, 'columnNumber': 2}, 'id': 2, 'parent': 1},
+ // eslint-disable-next-line
+ {'callFrame': {'functionName': 'b', 'url': 'b.js', 'scriptId': 2, 'lineNumber': 1, 'columnNumber': 2}, 'deoptReason': 'a reason', 'id': 3, 'parent': 2}
+ ],
+ 'samples': [3, 2]
+ }
+ }
+ },
+ 'id': 123
+ },
+ {'pid': 1, 'tid': 3, 'ts': 4, 'ph': 'P',
+ 'cat': 'disabled-by-default-v8.cpu_profiler',
+ 'name': 'Profile',
+ 'args': {
+ 'data': {
+ 'timeDeltas': [3, 3],
+ 'cpuProfile': {
+ 'nodes': [
+ // eslint-disable-next-line
+ {'callFrame': {'functionName': 'c', 'url': 'c.js', 'scriptId': 3, 'lineNumber': 2, 'columnNumber': 3}, 'id': 4, 'parent': 2},
+ // eslint-disable-next-line
+ {'callFrame': {'functionName': 'd', 'url': 'd.js', 'scriptId': 4, 'lineNumber': 2, 'columnNumber': 3}, 'deoptReason': 'another reason', 'id': 5, 'parent': 3}
+ ],
+ 'samples': [5, 4]
+ }
+ }
+ },
+ 'id': 123
+ }
+ ]
+ };
+ checkParsedAndStreamInput(events, checkModel, false);
+ });
+
+ test('scopedIdForEvent_defaultScopeAsyncEvent', function() {
+ const event = {pid: 1, ph: 'b', id: '0x1000'};
+ const id = tr.e.importer.TraceEventImporter.scopedIdForEvent_(event);
+ assert.isDefined(id);
+ assert.strictEqual('0x1000', id.id);
+ assert.strictEqual('ptr', id.scope);
+ assert.isUndefined(id.pid);
+ });
+
+ test('scopedIdForEvent_defaultScopeContextEvent', function() {
+ const event = {pid: 1, ph: '(', id: '0x1000'};
+ const id = tr.e.importer.TraceEventImporter.scopedIdForEvent_(event);
+ assert.isDefined(id);
+ assert.strictEqual('0x1000', id.id);
+ assert.strictEqual('ptr', id.scope);
+ assert.strictEqual(1, id.pid);
+ });
+
+ test('scopedIdForEvent_defaultScopeProcessLocalId', function() {
+ const event = {pid: 1, ph: 'b', id2: {local: '0x1000'}};
+ const id = tr.e.importer.TraceEventImporter.scopedIdForEvent_(event);
+ assert.isDefined(id);
+ assert.strictEqual('0x1000', id.id);
+ assert.strictEqual('ptr', id.scope);
+ assert.strictEqual(1, id.pid);
+ });
+
+ test('scopedIdForEvent_defaultScopeGlobalId', function() {
+ const event = {pid: 1, ph: '(', id2: {global: '0x1000'}};
+ const id = tr.e.importer.TraceEventImporter.scopedIdForEvent_(event);
+ assert.isDefined(id);
+ assert.strictEqual('0x1000', id.id);
+ assert.strictEqual('ptr', id.scope);
+ assert.isUndefined(id.pid);
+ });
+
+ test('scopedIdForEvent_explicitScopeGlobalId', function() {
+ const event = {pid: 1, ph: '(', id2: {global: '0x1000'}, scope: 'scope'};
+ const id = tr.e.importer.TraceEventImporter.scopedIdForEvent_(event);
+ assert.isDefined(id);
+ assert.strictEqual('0x1000', id.id);
+ assert.strictEqual('scope', id.scope);
+ assert.isUndefined(id.pid);
+ });
+
+ // TODO(nduca): one slice, two threads
+ // TODO(nduca): one slice, two pids
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/codemap.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/codemap.html
new file mode 100644
index 00000000000..23fe39b7e37
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/codemap.html
@@ -0,0 +1,383 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/v8/splaytree.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Map addresses to dynamically created functions.
+ */
+tr.exportTo('tr.e.importer.v8', function() {
+ /**
+ * Constructs a mapper that maps addresses into code entries.
+ *
+ * @constructor
+ */
+ function CodeMap() {
+ /**
+ * Dynamic code entries. Used for JIT compiled code.
+ */
+ this.dynamics_ = new tr.e.importer.v8.SplayTree();
+
+ /**
+ * Name generator for entries having duplicate names.
+ */
+ this.dynamicsNameGen_ = new tr.e.importer.v8.CodeMap.NameGenerator();
+
+ /**
+ * Static code entries. Used for statically compiled code.
+ */
+ this.statics_ = new tr.e.importer.v8.SplayTree();
+
+ /**
+ * Libraries entries. Used for the whole static code libraries.
+ */
+ this.libraries_ = new tr.e.importer.v8.SplayTree();
+
+ /**
+ * Map of memory pages occupied with static code.
+ */
+ this.pages_ = [];
+ }
+
+ /**
+ * The number of alignment bits in a page address.
+ */
+ CodeMap.PAGE_ALIGNMENT = 12;
+
+ /**
+ * Page size in bytes.
+ */
+ CodeMap.PAGE_SIZE = 1 << CodeMap.PAGE_ALIGNMENT;
+
+ /**
+ * Adds a dynamic (i.e. moveable and discardable) code entry.
+ *
+ * @param {number} start The starting address.
+ * @param {CodeMap.CodeEntry} codeEntry Code entry object.
+ */
+ CodeMap.prototype.addCode = function(start, codeEntry) {
+ this.deleteAllCoveredNodes_(this.dynamics_, start, start + codeEntry.size);
+ this.dynamics_.insert(start, codeEntry);
+ };
+
+ /**
+ * Moves a dynamic code entry. Throws an exception if there is no dynamic
+ * code entry with the specified starting address.
+ *
+ * @param {number} from The starting address of the entry being moved.
+ * @param {number} to The destination address.
+ */
+ CodeMap.prototype.moveCode = function(from, to) {
+ const removedNode = this.dynamics_.remove(from);
+ this.deleteAllCoveredNodes_(this.dynamics_, to,
+ to + removedNode.value.size);
+ this.dynamics_.insert(to, removedNode.value);
+ };
+
+ /**
+ * Discards a dynamic code entry. Throws an exception if there is no dynamic
+ * code entry with the specified starting address.
+ *
+ * @param {number} start The starting address of the entry being deleted.
+ */
+ CodeMap.prototype.deleteCode = function(start) {
+ const removedNode = this.dynamics_.remove(start);
+ };
+
+ /**
+ * Adds a library entry.
+ *
+ * @param {number} start The starting address.
+ * @param {CodeMap.CodeEntry} codeEntry Code entry object.
+ */
+ CodeMap.prototype.addLibrary = function(
+ start, codeEntry) {
+ this.markPages_(start, start + codeEntry.size);
+ this.libraries_.insert(start, codeEntry);
+ };
+
+ /**
+ * Adds a static code entry.
+ *
+ * @param {number} start The starting address.
+ * @param {CodeMap.CodeEntry} codeEntry Code entry object.
+ */
+ CodeMap.prototype.addStaticCode = function(
+ start, codeEntry) {
+ this.statics_.insert(start, codeEntry);
+ };
+
+ /**
+ * @private
+ */
+ CodeMap.prototype.markPages_ = function(start, end) {
+ for (let addr = start; addr <= end;
+ addr += CodeMap.PAGE_SIZE) {
+ this.pages_[addr >>> CodeMap.PAGE_ALIGNMENT] = 1;
+ }
+ };
+
+ /**
+ * @private
+ */
+ CodeMap.prototype.deleteAllCoveredNodes_ = function(tree, start, end) {
+ const toDelete = [];
+ let addr = end - 1;
+ while (addr >= start) {
+ const node = tree.findGreatestLessThan(addr);
+ if (!node) break;
+ const start2 = node.key;
+ const end2 = start2 + node.value.size;
+ if (start2 < end && start < end2) toDelete.push(start2);
+ addr = start2 - 1;
+ }
+ for (let i = 0, l = toDelete.length; i < l; ++i) tree.remove(toDelete[i]);
+ };
+
+ /**
+ * @private
+ */
+ CodeMap.prototype.isAddressBelongsTo_ = function(addr, node) {
+ return addr >= node.key && addr < (node.key + node.value.size);
+ };
+
+ /**
+ * @private
+ */
+ CodeMap.prototype.findInTree_ = function(tree, addr) {
+ const node = tree.findGreatestLessThan(addr);
+ return node && this.isAddressBelongsTo_(addr, node) ? node.value : null;
+ };
+
+ /**
+ * Finds a code entry that contains the specified address in static libraries.
+ *
+ * @param {number} addr Address.
+ */
+ CodeMap.prototype.findEntryInLibraries = function(addr) {
+ const pageAddr = addr >>> CodeMap.PAGE_ALIGNMENT;
+ if (pageAddr in this.pages_) {
+ return this.findInTree_(this.libraries_, addr);
+ }
+ return undefined;
+ };
+
+ /**
+ * Finds a code entry that contains the specified address. Both static and
+ * dynamic code entries are considered.
+ *
+ * @param {number} addr Address.
+ */
+ CodeMap.prototype.findEntry = function(addr) {
+ const pageAddr = addr >>> CodeMap.PAGE_ALIGNMENT;
+ if (pageAddr in this.pages_) {
+ // Static code entries can contain "holes" of unnamed code.
+ // In this case, the whole library is assigned to this address.
+ return this.findInTree_(this.statics_, addr) ||
+ this.findInTree_(this.libraries_, addr);
+ }
+ const min = this.dynamics_.findMin();
+ const max = this.dynamics_.findMax();
+ if (max !== null && addr < (max.key + max.value.size) && addr >= min.key) {
+ const dynaEntry = this.findInTree_(this.dynamics_, addr);
+ if (dynaEntry === null) return null;
+ // Dedupe entry name.
+ if (!dynaEntry.nameUpdated_) {
+ dynaEntry.name = this.dynamicsNameGen_.getName(dynaEntry.name);
+ dynaEntry.nameUpdated_ = true;
+ }
+ return dynaEntry;
+ }
+ return null;
+ };
+
+ /**
+ * Returns a dynamic code entry using its starting address.
+ *
+ * @param {number} addr Address.
+ */
+ CodeMap.prototype.findDynamicEntryByStartAddress =
+ function(addr) {
+ const node = this.dynamics_.find(addr);
+ return node ? node.value : null;
+ };
+
+ /**
+ * Returns an array of all dynamic code entries.
+ */
+ CodeMap.prototype.getAllDynamicEntries = function() {
+ return this.dynamics_.exportValues();
+ };
+
+ /**
+ * Returns an array of pairs of all dynamic code entries and their addresses.
+ */
+ CodeMap.prototype.getAllDynamicEntriesWithAddresses = function() {
+ return this.dynamics_.exportKeysAndValues();
+ };
+
+ /**
+ * Returns an array of all static code entries.
+ */
+ CodeMap.prototype.getAllStaticEntries = function() {
+ return this.statics_.exportValues();
+ };
+
+ /**
+ * Returns an array of all libraries entries.
+ */
+ CodeMap.prototype.getAllLibrariesEntries = function() {
+ return this.libraries_.exportValues();
+ };
+
+ /**
+ * Enum for code state regarding its dynamic optimization.
+ *
+ * @enum {number}
+ */
+ CodeMap.CodeState = {
+ COMPILED: 0,
+ OPTIMIZABLE: 1,
+ OPTIMIZED: 2
+ };
+
+ /**
+ * Creates a code entry object.
+ *
+ * @param {number} size Code entry size in bytes.
+ * @param {string=} opt_name Code entry name.
+ * @constructor
+ */
+ CodeMap.CodeEntry = function(size, opt_name, opt_type) {
+ this.id = tr.b.GUID.allocateSimple();
+ this.size = size;
+ this.name_ = opt_name || '';
+ this.type = opt_type || '';
+ this.nameUpdated_ = false;
+ };
+
+ CodeMap.CodeEntry.prototype = {
+ __proto__: Object.prototype,
+
+ get name() {
+ return this.name_;
+ },
+
+ set name(value) {
+ this.name_ = value;
+ },
+
+ toString() {
+ this.name_ + ': ' + this.size.toString(16);
+ }
+ };
+
+ CodeMap.CodeEntry.TYPE = {
+ SHARED_LIB: 'SHARED_LIB',
+ CPP: 'CPP'
+ };
+
+ /**
+ * Creates a dynamic code entry.
+ *
+ * @param {number} size Code size.
+ * @param {string} type Code type.
+ * @param {CodeMap.FunctionEntry} func Shared function entry.
+ * @param {CodeMap.CodeState} state Code optimization state.
+ * @constructor
+ */
+ CodeMap.DynamicFuncCodeEntry = function(size, type, func, state) {
+ CodeMap.CodeEntry.call(this, size, '', type);
+ this.func = func;
+ this.state = state;
+ };
+
+ CodeMap.DynamicFuncCodeEntry.STATE_PREFIX = ['', '~', '*'];
+
+ CodeMap.DynamicFuncCodeEntry.prototype = {
+ __proto__: CodeMap.CodeEntry.prototype,
+
+ /**
+ * Returns node name.
+ */
+ get name() {
+ return CodeMap.DynamicFuncCodeEntry.STATE_PREFIX[this.state] +
+ this.func.name;
+ },
+
+ set name(value) {
+ this.name_ = value;
+ },
+
+ /**
+ * Returns raw node name (without type decoration).
+ */
+ getRawName() {
+ return this.func.getName();
+ },
+
+ isJSFunction() {
+ return true;
+ },
+
+ toString() {
+ return this.type + ': ' + this.name + ': ' + this.size.toString(16);
+ }
+ };
+
+ /**
+ * Creates a shared function object entry.
+ *
+ * @param {string} name Function name.
+ * @constructor
+ */
+ CodeMap.FunctionEntry = function(name) {
+ CodeMap.CodeEntry.call(this, 0, name);
+ };
+
+ CodeMap.FunctionEntry.prototype = {
+ __proto__: CodeMap.CodeEntry.prototype,
+
+ /**
+ * Returns node name.
+ */
+ get name() {
+ let name = this.name_;
+ if (name.length === 0) {
+ name = '<anonymous>';
+ } else if (name.charAt(0) === ' ') {
+ // An anonymous function with location: " aaa.js:10".
+ name = '<anonymous>' + name;
+ }
+ return name;
+ },
+
+ set name(value) {
+ this.name_ = value;
+ }
+ };
+
+ CodeMap.NameGenerator = function() {
+ this.knownNames_ = {};
+ };
+
+ CodeMap.NameGenerator.prototype.getName = function(name) {
+ if (!(name in this.knownNames_)) {
+ this.knownNames_[name] = 0;
+ return name;
+ }
+ const count = ++this.knownNames_[name];
+ return name + ' {' + count + '}';
+ };
+ return {
+ CodeMap,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/log_reader.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/log_reader.html
new file mode 100644
index 00000000000..70ac3349b81
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/log_reader.html
@@ -0,0 +1,213 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/base/base.html">
+<script>
+'use strict';
+
+/**
+ * @fileoverview Log Reader is used to process log file produced by V8.
+ */
+tr.exportTo('tr.e.importer.v8', function() {
+ /**
+ * Creates a CSV lines parser.
+ */
+ function CsvParser() { }
+
+ /**
+ * A regex for matching a CSV field.
+ * @private
+ */
+ CsvParser.CSV_FIELD_RE_ = /^"((?:[^"]|"")*)"|([^,]*)/;
+
+ /**
+ * A regex for matching a double quote.
+ * @private
+ */
+ CsvParser.DOUBLE_QUOTE_RE_ = /""/g;
+
+ /**
+ * Parses a line of CSV-encoded values. Returns an array of fields.
+ *
+ * @param {string} line Input line.
+ */
+ CsvParser.prototype.parseLine = function(line) {
+ const fieldRe = CsvParser.CSV_FIELD_RE_;
+ const doubleQuoteRe = CsvParser.DOUBLE_QUOTE_RE_;
+ let pos = 0;
+ const endPos = line.length;
+ const fields = [];
+ if (endPos > 0) {
+ do {
+ const fieldMatch = fieldRe.exec(line.substr(pos));
+ if (typeof fieldMatch[1] === 'string') {
+ const field = fieldMatch[1];
+ pos += field.length + 3; // Skip comma and quotes.
+ fields.push(field.replace(doubleQuoteRe, '"'));
+ } else {
+ // The second field pattern will match anything, thus
+ // in the worst case the match will be an empty string.
+ const field = fieldMatch[2];
+ pos += field.length + 1; // Skip comma.
+ fields.push(field);
+ }
+ } while (pos <= endPos);
+ }
+ return fields;
+ };
+
+ /**
+ * Base class for processing log files.
+ *
+ * @param {Array.<Object>} dispatchTable A table used for parsing and
+ * processing log records.
+ *
+ * @constructor
+ */
+ function LogReader(dispatchTable) {
+ /**
+ * @type {Array.<Object>}
+ */
+ this.dispatchTable_ = dispatchTable;
+
+ /**
+ * Current line.
+ * @type {number}
+ */
+ this.lineNum_ = 0;
+
+ /**
+ * CSV lines parser.
+ * @type {CsvParser}
+ */
+ this.csvParser_ = new CsvParser();
+ }
+
+ /**
+ * Used for printing error messages.
+ *
+ * @param {string} str Error message.
+ */
+ LogReader.prototype.printError = function(str) {
+ // Do nothing.
+ };
+
+ /**
+ * Processes a portion of V8 profiler event log.
+ *
+ * @param {string} chunk A portion of log.
+ */
+ LogReader.prototype.processLogChunk = function(chunk) {
+ this.processLog_(chunk.split('\n'));
+ };
+
+ /**
+ * Processes a line of V8 profiler event log.
+ *
+ * @param {string} line A line of log.
+ */
+ LogReader.prototype.processLogLine = function(line) {
+ this.processLog_([line]);
+ };
+
+ /**
+ * Processes stack record.
+ *
+ * @param {number} pc Program counter.
+ * @param {number} func JS Function.
+ * @param {Array.<string>} stack String representation of a stack.
+ * @return {Array.<number>} Processed stack.
+ */
+ LogReader.prototype.processStack = function(pc, func, stack) {
+ const fullStack = func ? [pc, func] : [pc];
+ let prevFrame = pc;
+ for (let i = 0, n = stack.length; i < n; ++i) {
+ const frame = stack[i];
+ const firstChar = frame.charAt(0);
+ if (firstChar === '+' || firstChar === '-') {
+ // An offset from the previous frame.
+ prevFrame += parseInt(frame, 16);
+ fullStack.push(prevFrame);
+ // Filter out possible 'overflow' string.
+ } else if (firstChar !== 'o') {
+ fullStack.push(parseInt(frame, 16));
+ }
+ }
+ return fullStack;
+ };
+
+ /**
+ * Returns whether a particular dispatch must be skipped.
+ *
+ * @param {!Object} dispatch Dispatch record.
+ * @return {boolean} True if dispatch must be skipped.
+ */
+ LogReader.prototype.skipDispatch = function(dispatch) {
+ return false;
+ };
+
+ /**
+ * Does a dispatch of a log record.
+ *
+ * @param {Array.<string>} fields Log record.
+ * @private
+ */
+ LogReader.prototype.dispatchLogRow_ = function(fields) {
+ // Obtain the dispatch.
+ const command = fields[0];
+ if (!(command in this.dispatchTable_)) return;
+
+ const dispatch = this.dispatchTable_[command];
+
+ if (dispatch === null || this.skipDispatch(dispatch)) {
+ return;
+ }
+
+ // Parse fields.
+ const parsedFields = [];
+ for (let i = 0; i < dispatch.parsers.length; ++i) {
+ const parser = dispatch.parsers[i];
+ if (parser === null) {
+ parsedFields.push(fields[1 + i]);
+ } else if (typeof parser === 'function') {
+ parsedFields.push(parser(fields[1 + i]));
+ } else {
+ // var-args
+ parsedFields.push(fields.slice(1 + i));
+ break;
+ }
+ }
+
+ // Run the processor.
+ dispatch.processor.apply(this, parsedFields);
+ };
+
+ /**
+ * Processes log lines.
+ *
+ * @param {Array.<string>} lines Log lines.
+ * @private
+ */
+ LogReader.prototype.processLog_ = function(lines) {
+ for (let i = 0, n = lines.length; i < n; ++i, ++this.lineNum_) {
+ const line = lines[i];
+ if (!line) {
+ continue;
+ }
+ try {
+ const fields = this.csvParser_.parseLine(line);
+ this.dispatchLogRow_(fields);
+ } catch (e) {
+ this.printError('line ' + (this.lineNum_ + 1) + ': ' +
+ (e.message || e));
+ }
+ }
+ };
+ return {
+ LogReader,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/splaytree.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/splaytree.html
new file mode 100644
index 00000000000..3498b15658d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/splaytree.html
@@ -0,0 +1,295 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/base/base.html">
+<script>
+'use strict';
+
+/**
+ * @fileoverview Splay tree used by CodeMap.
+ */
+tr.exportTo('tr.e.importer.v8', function() {
+ /**
+ * Constructs a Splay tree. A splay tree is a self-balancing binary
+ * search tree with the additional property that recently accessed
+ * elements are quick to access again. It performs basic operations
+ * such as insertion, look-up and removal in O(log(n)) amortized time.
+ *
+ * @constructor
+ */
+ function SplayTree() { }
+
+ /**
+ * Pointer to the root node of the tree.
+ *
+ * @type {SplayTree.Node}
+ * @private
+ */
+ SplayTree.prototype.root_ = null;
+
+ /**
+ * @return {boolean} Whether the tree is empty.
+ */
+ SplayTree.prototype.isEmpty = function() {
+ return !this.root_;
+ };
+
+ /**
+ * Inserts a node into the tree with the specified key and value if
+ * the tree does not already contain a node with the specified key. If
+ * the value is inserted, it becomes the root of the tree.
+ *
+ * @param {number} key Key to insert into the tree.
+ * @param {*} value Value to insert into the tree.
+ */
+ SplayTree.prototype.insert = function(key, value) {
+ if (this.isEmpty()) {
+ this.root_ = new SplayTree.Node(key, value);
+ return;
+ }
+ // Splay on the key to move the last node on the search path for
+ // the key to the root of the tree.
+ this.splay_(key);
+ if (this.root_.key === key) {
+ return;
+ }
+ const node = new SplayTree.Node(key, value);
+ if (key > this.root_.key) {
+ node.left = this.root_;
+ node.right = this.root_.right;
+ this.root_.right = null;
+ } else {
+ node.right = this.root_;
+ node.left = this.root_.left;
+ this.root_.left = null;
+ }
+ this.root_ = node;
+ };
+
+
+ /**
+ * Removes a node with the specified key from the tree if the tree
+ * contains a node with this key. The removed node is returned. If the
+ * key is not found, an exception is thrown.
+ *
+ * @param {number} key Key to find and remove from the tree.
+ * @return {SplayTree.Node} The removed node.
+ */
+ SplayTree.prototype.remove = function(key) {
+ if (this.isEmpty()) {
+ throw Error('Key not found: ' + key);
+ }
+ this.splay_(key);
+ if (this.root_.key !== key) {
+ throw Error('Key not found: ' + key);
+ }
+ const removed = this.root_;
+ if (!this.root_.left) {
+ this.root_ = this.root_.right;
+ } else {
+ const right = this.root_.right;
+ this.root_ = this.root_.left;
+ // Splay to make sure that the new root has an empty right child.
+ this.splay_(key);
+ // Insert the original right child as the right child of the new
+ // root.
+ this.root_.right = right;
+ }
+ return removed;
+ };
+
+
+ /**
+ * Returns the node having the specified key or null if the tree doesn't
+ * contain a node with the specified key.
+ *
+ *
+ * @param {number} key Key to find in the tree.
+ * @return {SplayTree.Node} Node having the specified key.
+ */
+ SplayTree.prototype.find = function(key) {
+ if (this.isEmpty()) return null;
+ this.splay_(key);
+ return this.root_.key === key ? this.root_ : null;
+ };
+
+ /**
+ * @return {SplayTree.Node} Node having the minimum key value.
+ */
+ SplayTree.prototype.findMin = function() {
+ if (this.isEmpty()) return null;
+ let current = this.root_;
+ while (current.left) {
+ current = current.left;
+ }
+ return current;
+ };
+
+ /**
+ * @return {SplayTree.Node} Node having the maximum key value.
+ */
+ SplayTree.prototype.findMax = function(opt_startNode) {
+ if (this.isEmpty()) return null;
+ let current = opt_startNode || this.root_;
+ while (current.right) {
+ current = current.right;
+ }
+ return current;
+ };
+
+ /**
+ * @return {SplayTree.Node} Node having the maximum key value that
+ * is less or equal to the specified key value.
+ */
+ SplayTree.prototype.findGreatestLessThan = function(key) {
+ if (this.isEmpty()) return null;
+ // Splay on the key to move the node with the given key or the last
+ // node on the search path to the top of the tree.
+ this.splay_(key);
+ // Now the result is either the root node or the greatest node in
+ // the left subtree.
+ if (this.root_.key <= key) {
+ return this.root_;
+ }
+ if (this.root_.left) {
+ return this.findMax(this.root_.left);
+ }
+ return null;
+ };
+
+ /**
+ * @return {Array<*>} An array containing all the values of tree's nodes
+ * paired with keys.
+ *
+ */
+ SplayTree.prototype.exportKeysAndValues = function() {
+ const result = [];
+ this.traverse_(function(node) { result.push([node.key, node.value]); });
+ return result;
+ };
+
+ /**
+ * @return {Array<*>} An array containing all the values of tree's nodes.
+ */
+ SplayTree.prototype.exportValues = function() {
+ const result = [];
+ this.traverse_(function(node) { result.push(node.value); });
+ return result;
+ };
+
+ /**
+ * Perform the splay operation for the given key. Moves the node with
+ * the given key to the top of the tree. If no node has the given
+ * key, the last node on the search path is moved to the top of the
+ * tree. This is the simplified top-down splaying algorithm from:
+ * "Self-adjusting Binary Search Trees" by Sleator and Tarjan
+ *
+ * @param {number} key Key to splay the tree on.
+ * @private
+ */
+ SplayTree.prototype.splay_ = function(key) {
+ if (this.isEmpty()) return;
+ // Create a dummy node. The use of the dummy node is a bit
+ // counter-intuitive: The right child of the dummy node will hold
+ // the L tree of the algorithm. The left child of the dummy node
+ // will hold the R tree of the algorithm. Using a dummy node, left
+ // and right will always be nodes and we avoid special cases.
+ const dummy = new SplayTree.Node(null, null);
+ let left = dummy;
+ let right = dummy;
+ let current = this.root_;
+ while (true) {
+ if (key < current.key) {
+ if (!current.left) {
+ break;
+ }
+ if (key < current.left.key) {
+ // Rotate right.
+ const tmp = current.left;
+ current.left = tmp.right;
+ tmp.right = current;
+ current = tmp;
+ if (!current.left) {
+ break;
+ }
+ }
+ // Link right.
+ right.left = current;
+ right = current;
+ current = current.left;
+ } else if (key > current.key) {
+ if (!current.right) {
+ break;
+ }
+ if (key > current.right.key) {
+ // Rotate left.
+ const tmp = current.right;
+ current.right = tmp.left;
+ tmp.left = current;
+ current = tmp;
+ if (!current.right) {
+ break;
+ }
+ }
+ // Link left.
+ left.right = current;
+ left = current;
+ current = current.right;
+ } else {
+ break;
+ }
+ }
+ // Assemble.
+ left.right = current.left;
+ right.left = current.right;
+ current.left = dummy.right;
+ current.right = dummy.left;
+ this.root_ = current;
+ };
+
+ /**
+ * Performs a preorder traversal of the tree.
+ *
+ * @param {function(SplayTree.Node)} f Visitor function.
+ * @private
+ */
+ SplayTree.prototype.traverse_ = function(f) {
+ const nodesToVisit = [this.root_];
+ while (nodesToVisit.length > 0) {
+ const node = nodesToVisit.shift();
+ if (node === null) continue;
+ f(node);
+ nodesToVisit.push(node.left);
+ nodesToVisit.push(node.right);
+ }
+ };
+
+ /**
+ * Constructs a Splay tree node.
+ *
+ * @param {number} key Key.
+ * @param {*} value Value.
+ */
+ SplayTree.Node = function(key, value) {
+ this.key = key;
+ this.value = value;
+ };
+
+ /**
+ * @type {SplayTree.Node}
+ */
+ SplayTree.Node.prototype.left = null;
+
+ /**
+ * @type {SplayTree.Node}
+ */
+ SplayTree.Node.prototype.right = null;
+
+ return {
+ SplayTree,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/v8_log_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/v8_log_importer.html
new file mode 100644
index 00000000000..74b140beafe
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/v8_log_importer.html
@@ -0,0 +1,393 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/color_scheme.html">
+<link rel="import" href="/tracing/extras/importer/v8/codemap.html">
+<link rel="import" href="/tracing/extras/importer/v8/log_reader.html">
+<link rel="import" href="/tracing/extras/v8/v8_cpu_profile_node.html">
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/profile_tree.html">
+<link rel="import" href="/tracing/model/slice.html">
+
+<script>
+
+'use strict';
+
+/**
+ * @fileoverview V8LogImporter imports v8.log files into the provided model.
+ */
+tr.exportTo('tr.e.importer.v8', function() {
+ const CodeEntry = tr.e.importer.v8.CodeMap.CodeEntry;
+ const CodeMap = tr.e.importer.v8.CodeMap;
+ const ColorScheme = tr.b.ColorScheme;
+ const DynamicFuncCodeEntry = tr.e.importer.v8.CodeMap.DynamicFuncCodeEntry;
+ const FunctionEntry = tr.e.importer.v8.CodeMap.FunctionEntry;
+ const ProfileNodeType = tr.model.ProfileNode.subTypes.getConstructor(
+ undefined, 'legacySample');
+
+ function V8LogImporter(model, eventData) {
+ this.importPriority = 3;
+ this.model_ = model;
+
+ this.logData_ = eventData;
+
+ this.code_map_ = new CodeMap();
+ this.v8_timer_thread_ = undefined;
+ this.v8_thread_ = undefined;
+
+ this.profileTree_ = new tr.model.ProfileTree();
+ // We predefine a unknown Profile Node so that when a stack address
+ // can't be symbolized, we make it attach to the unknown Profile Node.
+ this.profileTree_.add(new ProfileNodeType(
+ -1, {
+ url: '',
+ functionName: 'unknown'
+ }
+ ));
+
+ // We reconstruct the stack timeline from ticks.
+ this.v8_stack_timeline_ = [];
+ }
+
+ const kV8BinarySuffixes = ['/d8', '/libv8.so'];
+
+
+ const TimerEventDefaultArgs = {
+ 'V8.Execute': { pause: false, no_execution: false},
+ 'V8.External': { pause: false, no_execution: true},
+ 'V8.CompileFullCode': { pause: true, no_execution: true},
+ 'V8.RecompileSynchronous': { pause: true, no_execution: true},
+ 'V8.RecompileParallel': { pause: false, no_execution: false},
+ 'V8.CompileEval': { pause: true, no_execution: true},
+ 'V8.Parse': { pause: true, no_execution: true},
+ 'V8.PreParse': { pause: true, no_execution: true},
+ 'V8.ParseLazy': { pause: true, no_execution: true},
+ 'V8.GCScavenger': { pause: true, no_execution: true},
+ 'V8.GCCompactor': { pause: true, no_execution: true},
+ 'V8.GCContext': { pause: true, no_execution: true}
+ };
+
+ /**
+ * @return {boolean} Whether obj is a V8 log string.
+ */
+ V8LogImporter.canImport = function(eventData) {
+ if (typeof(eventData) !== 'string' && !(eventData instanceof String)) {
+ return false;
+ }
+
+ return eventData.substring(0, 11) === 'v8-version,' ||
+ eventData.substring(0, 12) === 'timer-event,' ||
+ eventData.substring(0, 5) === 'tick,' ||
+ eventData.substring(0, 15) === 'shared-library,' ||
+ eventData.substring(0, 9) === 'profiler,' ||
+ eventData.substring(0, 14) === 'code-creation,';
+ };
+
+ V8LogImporter.prototype = {
+
+ __proto__: tr.importer.Importer.prototype,
+
+ get importerName() {
+ return 'V8LogImporter';
+ },
+
+ processTimerEvent_(name, startInUs, lengthInUs) {
+ const args = TimerEventDefaultArgs[name];
+ if (args === undefined) return;
+ const startInMs = tr.b.convertUnit(startInUs,
+ tr.b.UnitPrefixScale.METRIC.MICRO,
+ tr.b.UnitPrefixScale.METRIC.MILLI);
+ const lengthInMs = tr.b.convertUnit(lengthInUs,
+ tr.b.UnitPrefixScale.METRIC.MICRO,
+ tr.b.UnitPrefixScale.METRIC.MILLI);
+ const colorId = ColorScheme.getColorIdForGeneralPurposeString(name);
+ const slice = new tr.model.ThreadSlice('v8', name, colorId, startInMs,
+ args, lengthInMs);
+ this.v8_timer_thread_.sliceGroup.pushSlice(slice);
+ },
+
+ processTimerEventStart_(name, startInUs) {
+ const args = TimerEventDefaultArgs[name];
+ if (args === undefined) return;
+ const startInMs = tr.b.convertUnit(startInUs,
+ tr.b.UnitPrefixScale.METRIC.MICRO,
+ tr.b.UnitPrefixScale.METRIC.MILLI);
+ this.v8_timer_thread_.sliceGroup.beginSlice('v8', name, startInMs, args);
+ },
+
+ processTimerEventEnd_(name, endInUs) {
+ const endInMs = tr.b.convertUnit(endInUs,
+ tr.b.UnitPrefixScale.METRIC.MICRO,
+ tr.b.UnitPrefixScale.METRIC.MILLI);
+ this.v8_timer_thread_.sliceGroup.endSlice(endInMs);
+ },
+
+ processCodeCreateEvent_(type, kind, address, size, name,
+ maybeFunc) {
+ function parseState(s) {
+ switch (s) {
+ case '': return CodeMap.CodeState.COMPILED;
+ case '~': return CodeMap.CodeState.OPTIMIZABLE;
+ case '*': return CodeMap.CodeState.OPTIMIZED;
+ }
+ throw new Error('unknown code state: ' + s);
+ }
+
+ if (maybeFunc.length) {
+ const funcAddr = parseInt(maybeFunc[0]);
+ const state = parseState(maybeFunc[1]);
+ let func = this.code_map_.findDynamicEntryByStartAddress(funcAddr);
+ if (!func) {
+ func = new FunctionEntry(name);
+ func.kind = kind;
+ this.code_map_.addCode(funcAddr, func);
+ } else if (func.name !== name) {
+ // Function object has been overwritten with a new one.
+ func.name = name;
+ }
+ let entry = this.code_map_.findDynamicEntryByStartAddress(address);
+ if (entry) {
+ if (entry.size === size && entry.func === func) {
+ // Entry state has changed.
+ entry.state = state;
+ }
+ } else {
+ entry = new DynamicFuncCodeEntry(size, type, func, state);
+ entry.kind = kind;
+ this.code_map_.addCode(address, entry);
+ }
+ } else {
+ const codeEntry = new CodeEntry(size, name);
+ codeEntry.kind = kind;
+ this.code_map_.addCode(address, codeEntry);
+ }
+ },
+
+ processCodeMoveEvent_(from, to) {
+ this.code_map_.moveCode(from, to);
+ },
+
+ processCodeDeleteEvent_(address) {
+ this.code_map_.deleteCode(address);
+ },
+
+ processSharedLibrary_(name, start, end) {
+ const codeEntry = new CodeEntry(end - start, name,
+ CodeEntry.TYPE.SHARED_LIB);
+ codeEntry.kind = -3; // External code kind.
+ for (let i = 0; i < kV8BinarySuffixes.length; i++) {
+ const suffix = kV8BinarySuffixes[i];
+ if (name.indexOf(suffix, name.length - suffix.length) >= 0) {
+ codeEntry.kind = -1; // V8 runtime code kind.
+ break;
+ }
+ }
+ this.code_map_.addLibrary(start, codeEntry);
+ },
+
+ processCppSymbol_(address, size, name) {
+ const codeEntry = new CodeEntry(size, name, CodeEntry.TYPE.CPP);
+ codeEntry.kind = -1;
+ this.code_map_.addStaticCode(address, codeEntry);
+ },
+
+ processTickEvent_(pc, startInUs, isExternalCallback,
+ tosOrExternalCallback, vmstate, stack) {
+ const startInMs = tr.b.convertUnit(startInUs,
+ tr.b.UnitPrefixScale.METRIC.MICRO,
+ tr.b.UnitPrefixScale.METRIC.MILLI);
+
+ function findChildWithEntryID(stackFrame, entryID) {
+ for (let i = 0; i < stackFrame.children.length; i++) {
+ if (stackFrame.children[i].entryID === entryID) {
+ return stackFrame.children[i];
+ }
+ }
+ return undefined;
+ }
+
+ function processStack(pc, func, stack) {
+ const fullStack = func ? [pc, func] : [pc];
+ let prevFrame = pc;
+ for (let i = 0, n = stack.length; i < n; ++i) {
+ const frame = stack[i];
+ const firstChar = frame.charAt(0);
+ if (firstChar === '+' || firstChar === '-') {
+ // An offset from the previous frame.
+ prevFrame += parseInt(frame, 16);
+ fullStack.push(prevFrame);
+ // Filter out possible 'overflow' string.
+ } else if (firstChar !== 'o') {
+ fullStack.push(parseInt(frame, 16));
+ }
+ // Otherwise, they will be skipped.
+ }
+ return fullStack;
+ }
+
+ if (isExternalCallback) {
+ // Don't use PC when in external callback code, as it can point
+ // inside callback's code, and we will erroneously report
+ // that a callback calls itself. Instead use tosOrExternalCallback,
+ // as simply resetting PC will produce unaccounted ticks.
+ pc = tosOrExternalCallback;
+ tosOrExternalCallback = 0;
+ } else if (tosOrExternalCallback) {
+ // Find out, if top of stack was pointing inside a JS function
+ // meaning that we have encountered a frameless invocation.
+ const funcEntry = this.code_map_.findEntry(tosOrExternalCallback);
+ if (!funcEntry ||
+ !funcEntry.isJSFunction ||
+ !funcEntry.isJSFunction()) {
+ tosOrExternalCallback = 0;
+ }
+ }
+
+ let processedStack = processStack(pc, tosOrExternalCallback, stack);
+ let node = undefined;
+ let lastNode = undefined;
+ // v8 log stacks are inverted, leaf first and the root at the end.
+ processedStack = processedStack.reverse();
+ for (let i = 0, n = processedStack.length; i < n; i++) {
+ const frame = processedStack[i];
+ if (!frame) break;
+ const entry = this.code_map_.findEntry(frame);
+
+ if (!entry && i !== 0) {
+ continue;
+ }
+
+ let sourceInfo = undefined;
+ if (entry && entry.type === CodeEntry.TYPE.CPP) {
+ const libEntry = this.code_map_.findEntryInLibraries(frame);
+ if (libEntry) {
+ sourceInfo = {
+ file: libEntry.name
+ };
+ }
+ }
+ const entryId = entry ? entry.id : -1;
+ node = this.profileTree_.getNode(entryId);
+ if (node === undefined) {
+ node = this.profileTree_.add(new ProfileNodeType(
+ entryId, {
+ functionName: entry.name,
+ url: sourceInfo ? sourceInfo.file : '',
+ lineNumber: sourceInfo ? sourceInfo.line : undefined,
+ columnNumber: sourceInfo ? sourceInfo.column : undefined,
+ scriptId: sourceInfo ? sourceInfo.scriptId : undefined
+ }, lastNode));
+ }
+ lastNode = node;
+ }
+ this.model_.samples.push(new tr.model.Sample(
+ startInMs, 'V8 PC', node, this.v8_thread_, undefined, 1));
+ },
+
+ processDistortion_(distortionInPicoseconds) {
+ // Do nothing.
+ },
+
+ processPlotRange_(start, end) {
+ // Do nothing.
+ },
+
+ processV8Version_(major, minor, build, patch, candidate) {
+ // Do nothing.
+ },
+
+ /**
+ * Walks through the events_ list and outputs the structures discovered to
+ * model_.
+ */
+ importEvents() {
+ const logreader = new tr.e.importer.v8.LogReader(
+ { 'timer-event': {
+ parsers: [null, parseInt, parseInt],
+ processor: this.processTimerEvent_.bind(this)
+ },
+ 'shared-library': {
+ parsers: [null, parseInt, parseInt],
+ processor: this.processSharedLibrary_.bind(this)
+ },
+ 'timer-event-start': {
+ parsers: [null, parseInt],
+ processor: this.processTimerEventStart_.bind(this)
+ },
+ 'timer-event-end': {
+ parsers: [null, parseInt],
+ processor: this.processTimerEventEnd_.bind(this)
+ },
+ 'code-creation': {
+ parsers: [null, parseInt, parseInt, parseInt, null, 'var-args'],
+ processor: this.processCodeCreateEvent_.bind(this)
+ },
+ 'code-move': {
+ parsers: [parseInt, parseInt],
+ processor: this.processCodeMoveEvent_.bind(this)
+ },
+ 'code-delete': {
+ parsers: [parseInt],
+ processor: this.processCodeDeleteEvent_.bind(this)
+ },
+ 'cpp': {
+ parsers: [parseInt, parseInt, null],
+ processor: this.processCppSymbol_.bind(this)
+ },
+ 'tick': {
+ parsers: [parseInt, parseInt, parseInt, parseInt, parseInt,
+ 'var-args'],
+ processor: this.processTickEvent_.bind(this)
+ },
+ 'distortion': {
+ parsers: [parseInt],
+ processor: this.processDistortion_.bind(this)
+ },
+ 'plot-range': {
+ parsers: [parseInt, parseInt],
+ processor: this.processPlotRange_.bind(this)
+ },
+ 'v8-version': {
+ parsers: [parseInt, parseInt, parseInt, parseInt, parseInt],
+ processor: this.processV8Version_.bind(this)
+ }
+ });
+
+ this.v8_timer_thread_ =
+ this.model_.getOrCreateProcess(-32).getOrCreateThread(1);
+ this.v8_timer_thread_.name = 'V8 Timers';
+ this.v8_thread_ =
+ this.model_.getOrCreateProcess(-32).getOrCreateThread(2);
+ this.v8_thread_.name = 'V8';
+
+ const lines = this.logData_.split('\n');
+ for (let i = 0; i < lines.length; i++) {
+ logreader.processLogLine(lines[i]);
+ }
+
+ function addSlices(slices, thread) {
+ for (let i = 0; i < slices.length; i++) {
+ const duration = slices[i].end - slices[i].start;
+ const slice = new tr.model.ThreadSlice('v8', slices[i].name,
+ ColorScheme.getColorIdForGeneralPurposeString(slices[i].name),
+ slices[i].start, {}, duration);
+ thread.sliceGroup.pushSlice(slice);
+ addSlices(slices[i].children, thread);
+ }
+ }
+ addSlices(this.v8_stack_timeline_, this.v8_thread_);
+ }
+ };
+
+ tr.importer.Importer.register(V8LogImporter);
+
+ return {
+ V8LogImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/v8_log_importer_test.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/v8_log_importer_test.html
new file mode 100644
index 00000000000..e9db2f92334
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/v8/v8_log_importer_test.html
@@ -0,0 +1,290 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/v8/v8_log_importer.html">
+<link rel="import" href="/tracing/extras/v8_config.html">
+
+<script>
+
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const V8LogImporter = tr.e.importer.v8.V8LogImporter;
+
+ function newModel(events) {
+ return tr.c.TestUtils.newModelWithEvents([events], {
+ shiftWorldToZero: false
+ });
+ }
+
+ test('tickEventInSharedLibrary', function() {
+ const lines = [
+ 'shared-library,"/usr/lib/libc++.1.dylib",0x99d8aae0,0x99dce729',
+ 'tick,0x99d8aae4,12158,0,0x0,5'];
+ const m = newModel(lines.join('\n'));
+ const p = m.processes[-32];
+ const t = p.findAllThreadsNamed('V8')[0];
+ assert.strictEqual(t.samples.length, 1);
+ assert.strictEqual(t.samples[0].title, 'V8 PC');
+ assert.strictEqual(t.samples[0].start, 12158 / 1000);
+ assert.strictEqual(t.samples[0].leafNode.title, '/usr/lib/libc++.1.dylib');
+ });
+
+ test('tickEventInGeneratedCode', function() {
+ const lines = [
+ 'shared-library,"/usr/lib/libc++.1.dylib",0x99d8aae0,0x99dce729',
+ 'code-creation,Stub,2,0x5b60ce80,1259,"StringAddStub"',
+ 'tick,0x5b60ce84,12158,0,0x0,5'];
+ const m = newModel(lines.join('\n'));
+ const p = m.processes[-32];
+ const threads = p.findAllThreadsNamed('V8');
+ const t = threads[0];
+ assert.strictEqual(t.samples.length, 1);
+ assert.strictEqual(t.samples[0].leafNode.title, 'StringAddStub');
+ });
+
+ test('tickEventInUknownCode', function() {
+ const lines = [
+ 'shared-library,"/usr/lib/libc++.1.dylib",0x99d8aae0,0x99dce729',
+ 'code-creation,Stub,2,0x5b60ce80,1259,"StringAddStub"',
+ 'tick,0x4,0xbff02f08,12158,0,0x0,5'];
+ const m = newModel(lines.join('\n'));
+ const p = m.processes[-32];
+ const threads = p.findAllThreadsNamed('V8');
+ const t = threads[0];
+ assert.strictEqual(t.samples.length, 1);
+ assert.strictEqual(t.samples[0].leafNode.title, 'unknown');
+ });
+
+ test('tickEventWithStack', function() {
+ const lines = [
+ 'code-creation,LazyCompile,0,0x2905d0c0,1800,"InstantiateFunction native apinatives.js:26:29",0x56b19124,', // @suppress longLineCheck
+ 'tick,0x7fc6fe34,528674,0,0x3,0,0x2905d304'];
+ const m = newModel(lines.join('\n'));
+ const p = m.processes[-32];
+ const t = p.findAllThreadsNamed('V8')[0];
+ assert.strictEqual(t.samples.length, 1);
+ assert.deepEqual(
+ ['InstantiateFunction native apinatives.js:26:29 url: unknown'],
+ t.samples[0].userFriendlyStack);
+ });
+
+ test('twoTickEventsWithStack', function() {
+ const lines = [
+ 'code-creation,LazyCompile,0,0x2905d0c0,1800,"InstantiateFunction native apinatives.js:26:29",0x56b19124,', // @suppress longLineCheck
+ 'tick,0x7fc6fe34,528674,0,0x3,0,0x2905d304',
+ 'tick,0x7fd2a534,536213,0,0x81d8d080,0,0x2905d304'];
+ const m = newModel(lines.join('\n'));
+ const p = m.processes[-32];
+ const t = p.findAllThreadsNamed('V8')[0];
+ assert.strictEqual(t.samples.length, 2);
+ assert.strictEqual(t.samples[0].start, 528674 / 1000);
+ assert.strictEqual(t.samples[1].start, 536213 / 1000);
+ assert.deepEqual(
+ ['InstantiateFunction native apinatives.js:26:29 url: unknown'],
+ t.samples[0].userFriendlyStack);
+ assert.deepEqual(
+ ['InstantiateFunction native apinatives.js:26:29 url: unknown'],
+ t.samples[1].userFriendlyStack);
+ assert.strictEqual(t.samples[0].leafNode,
+ t.samples[1].leafNode);
+ });
+
+ test('twoTickEventsWithTwoStackFrames', function() {
+ const lines = [
+ 'code-creation,LazyCompile,0,0x2904d560,876,"Instantiate native apinatives.js:9:21",0x56b190c8,~', // @suppress longLineCheck
+ 'code-creation,LazyCompile,0,0x2905d0c0,1800,"InstantiateFunction native apinatives.js:26:29",0x56b19124,', // @suppress longLineCheck
+ 'tick,0x7fc6fe34,528674,0,0x3,0,0x2905d304,0x2904d6e8',
+ 'tick,0x7fd2a534,536213,0,0x81d8d080,0,0x2905d304,0x2904d6e8'];
+ const m = newModel(lines.join('\n'));
+ const p = m.processes[-32];
+ const t = p.findAllThreadsNamed('V8')[0];
+ assert.strictEqual(t.samples.length, 2);
+
+ assert.strictEqual(t.samples[0].start, 528674 / 1000);
+ assert.strictEqual(t.samples[1].start, 536213 / 1000);
+ assert.deepEqual(
+ ['InstantiateFunction native apinatives.js:26:29 url: unknown',
+ '~Instantiate native apinatives.js:9:21 url: unknown'],
+ t.samples[0].userFriendlyStack);
+ assert.deepEqual(
+ ['InstantiateFunction native apinatives.js:26:29 url: unknown',
+ '~Instantiate native apinatives.js:9:21 url: unknown'],
+ t.samples[1].userFriendlyStack);
+
+ assert.strictEqual(t.samples[0].leafNode, t.samples[1].leafNode);
+ });
+
+ test('threeTickEventsWithTwoStackFrames', function() {
+ const lines = [
+ 'code-creation,LazyCompile,0,0x2904d560,876,"Instantiate native apinatives.js:9:21",0x56b190c8,~', // @suppress longLineCheck
+ 'code-creation,LazyCompile,0,0x2905d0c0,1800,"InstantiateFunction native apinatives.js:26:29",0x56b19124,', // @suppress longLineCheck
+ 'tick,0x7fd7f75c,518328,0,0x81d86da8,2,0x2904d6e8',
+ 'tick,0x7fc6fe34,528674,0,0x3,0,0x2905d304,0x2904d6e8',
+ 'tick,0x7fd2a534,536213,0,0x81d8d080,0,0x2905d304,0x2904d6e8'];
+ const m = newModel(lines.join('\n'));
+ const p = m.processes[-32];
+ const threads = p.findAllThreadsNamed('V8');
+
+ const t = threads[0];
+ assert.strictEqual(t.samples.length, 3);
+ assert.strictEqual(t.samples[0].start, 518328 / 1000);
+ assert.strictEqual(t.samples[1].start, 528674 / 1000);
+ assert.strictEqual(t.samples[2].start, 536213 / 1000);
+ assert.deepEqual(
+ ['~Instantiate native apinatives.js:9:21 url: unknown'],
+ t.samples[0].userFriendlyStack);
+ assert.deepEqual(
+ ['InstantiateFunction native apinatives.js:26:29 url: unknown',
+ '~Instantiate native apinatives.js:9:21 url: unknown'],
+ t.samples[1].userFriendlyStack);
+ assert.deepEqual(
+ ['InstantiateFunction native apinatives.js:26:29 url: unknown',
+ '~Instantiate native apinatives.js:9:21 url: unknown'],
+ t.samples[2].userFriendlyStack);
+
+ const topLevelNode = t.samples[0].leafNode;
+ const leafNode = t.samples[1].leafNode;
+ assert.strictEqual(t.samples[2].leafNode, leafNode);
+ assert.strictEqual(topLevelNode, leafNode.parentNode);
+ });
+
+ test('twoSubStacks', function() {
+ const lines = [
+ 'code-creation,LazyCompile,0,0x2904d560,876,"Instantiate native apinatives.js:9:21",0x56b190c8,~', // @suppress longLineCheck
+ 'code-creation,LazyCompile,0,0x2905d0c0,1800,"InstantiateFunction native apinatives.js:26:29",0x56b19124,', // @suppress longLineCheck
+ 'tick,0x7fd7f75c,518328,0,0x81d86da8,2,0x2904d6e8',
+ 'tick,0x7fc6fe34,528674,0,0x3,0,0x2905d304,0x2904d6e8',
+ 'tick,0x7fd2a534,536213,0,0x81d8d080,0,0x2905d304,0x2904d6e8',
+ 'code-creation,Script,0,0x2906a7c0,792,"http://www.google.com/",0x5b12fe50,~', // @suppress longLineCheck
+ 'tick,0xb6f51d30,794049,0,0xb6f7b368,2,0x2906a914',
+ 'tick,0xb6f51d30,799145,0,0xb6f7b368,0,0x2906a914'
+ ];
+ const m = newModel(lines.join('\n'));
+ const p = m.processes[-32];
+ const threads = p.findAllThreadsNamed('V8');
+ const t = threads[0];
+ assert.strictEqual(t.samples.length, 5);
+
+ assert.strictEqual(t.samples[0].start, 518328 / 1000);
+ assert.strictEqual(t.samples[1].start, 528674 / 1000);
+ assert.strictEqual(t.samples[2].start, 536213 / 1000);
+ assert.strictEqual(t.samples[3].start, 794049 / 1000);
+ assert.strictEqual(t.samples[4].start, 799145 / 1000);
+
+ assert.deepEqual(
+ ['~Instantiate native apinatives.js:9:21 url: unknown'],
+ t.samples[0].userFriendlyStack);
+ assert.deepEqual(
+ ['InstantiateFunction native apinatives.js:26:29 url: unknown',
+ '~Instantiate native apinatives.js:9:21 url: unknown'],
+ t.samples[1].userFriendlyStack);
+ assert.deepEqual(
+ ['InstantiateFunction native apinatives.js:26:29 url: unknown',
+ '~Instantiate native apinatives.js:9:21 url: unknown'],
+ t.samples[2].userFriendlyStack);
+ assert.deepEqual(['~http://www.google.com/ url: unknown'],
+ t.samples[3].userFriendlyStack);
+ assert.deepEqual(['~http://www.google.com/ url: unknown'],
+ t.samples[4].userFriendlyStack);
+
+ const firstNode = t.samples[0].leafNode;
+ const firstChildNode = t.samples[1].leafNode;
+ assert.strictEqual(firstChildNode, t.samples[2].leafNode);
+ assert.strictEqual(firstNode, firstChildNode.parentNode);
+
+ const secondNode = t.samples[3].leafNode;
+ assert.strictEqual(secondNode, t.samples[4].leafNode);
+ assert.isUndefined(secondNode.parentNode);
+ });
+
+ test('timerEventSliceCreation', function() {
+ const lines = ['timer-event,"V8.External",38189483,3'];
+ const m = newModel(lines.join('\n'));
+ const p = m.processes[-32];
+ const threads = p.findAllThreadsNamed('V8 Timers');
+ assert.isDefined(threads);
+ assert.strictEqual(threads.length, 1);
+ const t = threads[0];
+ assert.strictEqual(t.sliceGroup.length, 1);
+ });
+
+ test('processThreadCreation', function() {
+ const lines = ['timer-event,"V8.External",38189483,3'];
+ const m = newModel(lines.join('\n'));
+ assert.isDefined(m);
+ const p = m.processes[-32];
+ assert.isDefined(p);
+ const threads = p.findAllThreadsNamed('V8 Timers');
+ assert.isDefined(threads);
+ assert.strictEqual(1, threads.length);
+ const t = threads[0];
+ assert.strictEqual('V8 Timers', t.name);
+ });
+
+ test('canImport', function() {
+ assert.isTrue(V8LogImporter.canImport(
+ 'timer-event,"V8.External",38189483,3'));
+ assert.isTrue(V8LogImporter.canImport('v8-version,4,3,66,0,0'));
+ assert.isFalse(V8LogImporter.canImport(''));
+ assert.isFalse(V8LogImporter.canImport([]));
+ });
+
+ test('CppSymbolsProcess', function() {
+ const lines = [
+ 'shared-library,"/usr/loca/v8/out/native/d8",0x00400000, 0x01860000',
+ 'cpp,0x00600000,100,"v8::internal::Heap::AllocateByteArray"',
+ 'cpp,0x00700000,100,"v8::internal::Utf8StringKey::AsHandle"',
+ 'tick,0x00600010,121,0,0x0,5',
+ 'tick,0x00700010,122,0,0x0,5',
+ 'tick,0x00800010,123,0,0x0,5'];
+ const m = newModel(lines.join('\n'));
+ const p = m.processes[-32];
+ const t = p.findAllThreadsNamed('V8')[0];
+ assert.strictEqual(t.samples.length, 3);
+ assert.strictEqual(t.samples[0].leafNode.title,
+ 'v8::internal::Heap::AllocateByteArray');
+ assert.strictEqual(t.samples[1].leafNode.title,
+ 'v8::internal::Utf8StringKey::AsHandle');
+ assert.strictEqual(t.samples[2].leafNode.title,
+ '/usr/loca/v8/out/native/d8');
+ });
+
+ test('CppSymbolsWithJsStack', function() {
+ const lines = [
+ 'shared-library,"/usr/loca/v8/out/native/d8",0x00400000, 0x01860000',
+ 'cpp,0x00600000,100,"v8::internal::Heap::AllocateByteArray"',
+ 'cpp,0x00700000,100,"v8::internal::Utf8StringKey::AsHandle"',
+ 'code-creation,LazyCompile,0,0x2905d0c0,1800,"InstantiateFunction native apinatives.js:26:29",0x56b19124,', // @suppress longLineCheck
+ 'tick,0x00700010,1,0,0x3,0,0x2905d304',
+ 'tick,0x00600010,3,0,0x3,0,0x2905d306',
+ 'tick,0x00800010,5,0,0x3,0,0x2905d306',
+ 'tick,0x01860010,5,0,0x3,0,0x2905d306'];
+ const m = newModel(lines.join('\n'));
+ const p = m.processes[-32];
+ const t = p.findAllThreadsNamed('V8')[0];
+ assert.strictEqual(t.samples.length, 4);
+ assert.deepEqual(t.samples[0].userFriendlyStack, [
+ 'v8::internal::Utf8StringKey::AsHandle url: /usr/loca/v8/out/native/d8',
+ 'InstantiateFunction native apinatives.js:26:29 url: unknown'
+ ]);
+ assert.deepEqual(t.samples[1].userFriendlyStack, [
+ 'v8::internal::Heap::AllocateByteArray url: /usr/loca/v8/out/native/d8',
+ 'InstantiateFunction native apinatives.js:26:29 url: unknown'
+ ]);
+ assert.deepEqual(t.samples[2].userFriendlyStack, [
+ '/usr/loca/v8/out/native/d8 url: unknown',
+ 'InstantiateFunction native apinatives.js:26:29 url: unknown'
+ ]);
+ assert.deepEqual(t.samples[3].userFriendlyStack, [
+ 'InstantiateFunction native apinatives.js:26:29 url: unknown'
+ ]);
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/importer/zip_importer.html b/chromium/third_party/catapult/tracing/tracing/extras/importer/zip_importer.html
new file mode 100644
index 00000000000..db16134a838
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/importer/zip_importer.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/importer/gzip_importer.html">
+<link rel="import" href="/tracing/extras/importer/jszip.html">
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview ZipImporter inflates zip compressed data and passes it along
+ * to an actual importer.
+ */
+tr.exportTo('tr.e.importer', function() {
+ function ZipImporter(model, eventData) {
+ if (eventData instanceof ArrayBuffer) {
+ eventData = new Uint8Array(eventData);
+ }
+ this.model_ = model;
+ this.eventData_ = eventData;
+ }
+
+ /**
+ * @param {eventData} string Possibly zip compressed data.
+ * @return {boolean} Whether eventData looks like zip compressed data.
+ */
+ ZipImporter.canImport = function(eventData) {
+ let header;
+ if (eventData instanceof ArrayBuffer) {
+ header = new Uint8Array(eventData.slice(0, 2));
+ } else if (typeof(eventData) === 'string' || eventData instanceof String) {
+ header = [eventData.charCodeAt(0), eventData.charCodeAt(1)];
+ } else {
+ return false;
+ }
+ return header[0] === 'P'.charCodeAt(0) && header[1] === 'K'.charCodeAt(0);
+ };
+
+ ZipImporter.prototype = {
+ __proto__: tr.importer.Importer.prototype,
+
+ get importerName() {
+ return 'ZipImporter';
+ },
+
+ isTraceDataContainer() {
+ return true;
+ },
+
+ extractSubtraces() {
+ const zip = new JSZip(this.eventData_);
+ const subtraces = [];
+ for (const idx in zip.files) {
+ subtraces.push(zip.files[idx].asBinary());
+ }
+ return subtraces;
+ }
+ };
+
+ tr.importer.Importer.register(ZipImporter);
+
+ return {
+ ZipImporter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/lean_config.html b/chromium/third_party/catapult/tracing/tracing/extras/lean_config.html
new file mode 100644
index 00000000000..b39e51073d9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/lean_config.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<!--
+The lean config is just enough to import uncompressed, trace-event-formatted
+json blobs.
+-->
+
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/measure/measure.html b/chromium/third_party/catapult/tracing/tracing/extras/measure/measure.html
new file mode 100644
index 00000000000..05836554eda
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/measure/measure.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/measure/measure_async_slice.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/measure/measure_async_slice.html b/chromium/third_party/catapult/tracing/tracing/extras/measure/measure_async_slice.html
new file mode 100644
index 00000000000..d05d25999e9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/measure/measure_async_slice.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/model/async_slice.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.measure', function() {
+ const AsyncSlice = tr.model.AsyncSlice;
+
+ // Split on first : with an optional `/` + base64 suffix
+ const MEASURE_NAME_REGEX = /([^\/:]+):(.*?)(?:\/([A-Za-z0-9+/]+=?=?))?$/;
+
+ function MeasureAsyncSlice() {
+ this.groupTitle_ = 'Ungrouped Measure';
+ const matched = MEASURE_NAME_REGEX.exec(arguments[1]);
+ if (matched !== null) {
+ arguments[1] = matched[2];
+ this.groupTitle_ = matched[1];
+ }
+ AsyncSlice.apply(this, arguments);
+ }
+
+ MeasureAsyncSlice.prototype = {
+ __proto__: AsyncSlice.prototype,
+
+ get viewSubGroupTitle() {
+ return this.groupTitle_;
+ },
+
+ get title() {
+ return this.title_;
+ },
+
+ set title(title) {
+ this.title_ = title;
+ }
+ };
+
+ AsyncSlice.subTypes.register(
+ MeasureAsyncSlice,
+ {
+ categoryParts: ['blink.user_timing']
+ });
+
+ return {
+ MEASURE_NAME_REGEX,
+ MeasureAsyncSlice,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/measure/measure_async_slice_test.html b/chromium/third_party/catapult/tracing/tracing/extras/measure/measure_async_slice_test.html
new file mode 100644
index 00000000000..cd5de5db878
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/measure/measure_async_slice_test.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/measure/measure.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const AsyncSlice = tr.model.AsyncSlice;
+ const MeasureAsyncSlice = tr.e.measure.MeasureAsyncSlice;
+
+ test('basic', function() {
+ const s = new MeasureAsyncSlice(
+ 'blink.user_timing', 'createImports', 7, 0, {});
+ s.duration = 100;
+
+ assert.strictEqual(AsyncSlice.subTypes.getConstructor(
+ 'blink.user_timing', 'createImports'),
+ MeasureAsyncSlice);
+ assert.strictEqual(s.viewSubGroupTitle, 'Ungrouped Measure');
+ });
+
+ test('import', function() {
+ const events = [
+ {name: 'createImports', args: {}, pid: 1, ts: 100,
+ cat: 'blink.user_timing', tid: 2, ph: 'b', id: 71},
+ {name: 'createImports', args: {}, pid: 1, ts: 200,
+ cat: 'blink.user_timing', tid: 2, ph: 'e', id: 71}
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([events]);
+ const t2 = m.getOrCreateProcess(1).getOrCreateThread(2);
+ assert.strictEqual(t2.asyncSliceGroup.length, 1);
+ assert.instanceOf(t2.asyncSliceGroup.slices[0], MeasureAsyncSlice);
+ });
+
+ test('groupMeasureAsyncSlice', function() {
+ const events = [
+ {name: 'createImports', args: {}, pid: 1, ts: 100,
+ cat: 'blink.user_timing', tid: 2, ph: 'b', id: 71},
+ {name: 'createImports', args: {}, pid: 1, ts: 200,
+ cat: 'blink.user_timing', tid: 2, ph: 'e', id: 71},
+ {name: 'runAutoClosers', args: {}, pid: 1, ts: 210,
+ cat: 'blink.user_timing', tid: 2, ph: 'b', id: 72},
+ {name: 'runAutoClosers', args: {}, pid: 1, ts: 310,
+ cat: 'blink.user_timing', tid: 2, ph: 'e', id: 72}
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([events]);
+ const t2 = m.getOrCreateProcess(1).getOrCreateThread(2);
+ assert.strictEqual(t2.asyncSliceGroup.length, 2);
+ assert.strictEqual(t2.asyncSliceGroup.slices[0].title, 'createImports');
+ assert.strictEqual(t2.asyncSliceGroup.slices[1].title, 'runAutoClosers');
+ assert.instanceOf(t2.asyncSliceGroup.slices[0], MeasureAsyncSlice);
+ assert.instanceOf(t2.asyncSliceGroup.slices[1], MeasureAsyncSlice);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/memory/lowmemory_auditor.html b/chromium/third_party/catapult/tracing/tracing/extras/memory/lowmemory_auditor.html
new file mode 100644
index 00000000000..678393e9d43
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/memory/lowmemory_auditor.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<!--
+ Copyright 2017 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/math/math.html">
+<link rel="import" href="/tracing/core/auditor.html">
+<link rel="import" href="/tracing/model/alert.html">
+
+<script>
+'use strict';
+tr.exportTo('tr.e.audits', function() {
+ /**
+ * Auditor that analyzes the model and annotates low memory events.
+ */
+ class LowMemoryAuditor extends tr.c.Auditor {
+ constructor(model) {
+ super();
+ this.model_ = model;
+ }
+
+ runAnnotate() {
+ this.model_.device.lowMemoryEvents = this.getLowMemoryEvents_();
+ }
+
+ /**
+ * Returns a list of low memory killer events.
+ */
+ getLowMemoryEvents_() {
+ const model = this.model_;
+ const result = [];
+ for (const process of model.getAllProcesses()) {
+ for (const e of process.getDescendantEvents()) {
+ // low memory killer events are 0-duration events.
+ if (!(e instanceof tr.model.ThreadSlice) || e.duration !== 0) {
+ continue;
+ }
+
+ if (e.category !== 'lowmemory') {
+ continue;
+ }
+
+ result.push(e);
+ }
+ }
+ return result;
+ }
+ }
+
+ tr.c.Auditor.register(LowMemoryAuditor);
+
+ return {
+ LowMemoryAuditor
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/net/net.html b/chromium/third_party/catapult/tracing/tracing/extras/net/net.html
new file mode 100644
index 00000000000..3a246c930c0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/net/net.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/net/net_async_slice.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/net/net_async_slice.html b/chromium/third_party/catapult/tracing/tracing/extras/net/net_async_slice.html
new file mode 100644
index 00000000000..fe3008bf55f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/net/net_async_slice.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<link rel="import" href="/tracing/model/async_slice.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.net', function() {
+ const AsyncSlice = tr.model.AsyncSlice;
+
+ function NetAsyncSlice() {
+ AsyncSlice.apply(this, arguments);
+ this.url_ = undefined;
+ this.byteCount_ = undefined;
+ // Boolean variables indicating whether we have computed corresponding
+ // fields. Computing these fields needs iteration through all sub-slices and
+ // so recomputation will be costly.
+ this.isTitleComputed_ = false;
+ this.isUrlComputed_ = false;
+ }
+
+ NetAsyncSlice.prototype = {
+ __proto__: AsyncSlice.prototype,
+
+ get viewSubGroupTitle() {
+ return 'NetLog';
+ },
+
+ get title() {
+ if (this.isTitleComputed_ || !this.isTopLevel) {
+ return this.title_;
+ }
+
+ if (this.url !== undefined && this.url.length > 0) {
+ // Set the title so we do not have to recompute when it is redrawn.
+ this.title_ = this.url;
+ } else if (this.args !== undefined &&
+ this.args.source_type !== undefined) {
+ // We do not have a URL, use the source type as the title.
+ this.title_ = this.args.source_type;
+ }
+ this.isTitleComputed_ = true;
+ return this.title_;
+ },
+
+ set title(title) {
+ this.title_ = title;
+ },
+
+ // A recursive helper function that gets the url param of a slice or its
+ // nested subslices if there is one.
+ get url() {
+ if (this.isUrlComputed_) {
+ return this.url_;
+ }
+ if (this.args !== undefined && this.args.params !== undefined &&
+ this.args.params.url !== undefined) {
+ this.url_ = this.args.params.url;
+ } else if (this.subSlices !== undefined && this.subSlices.length > 0) {
+ for (let i = 0; i < this.subSlices.length && ! this.url_; i++) {
+ if (this.subSlices[i].url !== undefined) {
+ this.url_ = this.subSlices[i].url;
+ }
+ }
+ }
+ this.isUrlComputed_ = true;
+ return this.url_;
+ },
+
+ get byteCount() {
+ if (this.byteCount_ !== undefined) {
+ return this.byteCount_;
+ }
+
+ this.byteCount_ = 0;
+ if ((this.originalTitle === 'URL_REQUEST_JOB_FILTERED_BYTES_READ' ||
+ this.originalTitle === 'URL_REQUEST_JOB_BYTES_READ') &&
+ this.args !== undefined && this.args.params !== undefined &&
+ this.args.params.byte_count !== undefined) {
+ this.byteCount_ = this.args.params.byte_count;
+ }
+ for (let i = 0; i < this.subSlices.length; i++) {
+ this.byteCount_ += this.subSlices[i].byteCount;
+ }
+ return this.byteCount_;
+ }
+ };
+
+ AsyncSlice.subTypes.register(
+ NetAsyncSlice,
+ {
+ categoryParts: ['netlog', 'disabled-by-default-netlog']
+ });
+
+ return {
+ NetAsyncSlice,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/net/net_async_slice_test.html b/chromium/third_party/catapult/tracing/tracing/extras/net/net_async_slice_test.html
new file mode 100644
index 00000000000..13f2a938189
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/net/net_async_slice_test.html
@@ -0,0 +1,195 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/extras/net/net.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const AsyncSlice = tr.model.AsyncSlice;
+ const NetAsyncSlice = tr.e.net.NetAsyncSlice;
+
+ test('basic', function() {
+ const s = new NetAsyncSlice('netlog', 'HTTP_STREAM_JOB', 7, 0, {});
+ s.duration = 100;
+
+ assert.strictEqual(
+ AsyncSlice.subTypes.getConstructor('netlog', 'HTTP_STREAM_JOB'),
+ NetAsyncSlice);
+ assert.strictEqual(s.viewSubGroupTitle, 'NetLog');
+ });
+
+ test('import', function() {
+ const events = [
+ {name: 'HTTP_STREAM_JOB', args: {}, pid: 1, ts: 100, cat: 'netlog', tid: 2, ph: 'b', id: 71}, // @suppress longLineCheck
+ {name: 'HTTP_STREAM_JOB', args: {}, pid: 1, ts: 200, cat: 'netlog', tid: 2, ph: 'e', id: 71} // @suppress longLineCheck
+ ];
+ const m = tr.c.TestUtils.newModelWithEvents([events]);
+ const t2 = m.getOrCreateProcess(1).getOrCreateThread(2);
+ assert.strictEqual(t2.asyncSliceGroup.length, 1);
+ assert.instanceOf(t2.asyncSliceGroup.slices[0], NetAsyncSlice);
+ });
+
+ test('ExposeURLBasic', function() {
+ const slice = new NetAsyncSlice('', 'a', 0, 1,
+ {params: {url: 'https://google.com'},
+ source_type: 'b'}, 0, true);
+ // Make sure isTopLevel is populated in the constructor.
+ assert.isTrue(slice.isTopLevel);
+ // URL is exposed as the title of the parent slice.
+ assert.strictEqual(slice.title, 'https://google.com');
+ assert.strictEqual(slice.url, 'https://google.com');
+ });
+
+ test('ExposeURLNested', function() {
+ const slice = new NetAsyncSlice(
+ '', 'a', 0, 1, {params: {}, source_type: 'HELLO'}, 1, true);
+ const childSlice = new NetAsyncSlice('', 'b', 0, 1,
+ {params: {url: 'http://test.url'}});
+ slice.subSlices = [childSlice];
+ // Make sure isTopLevel is populated in the constructor.
+ assert.isTrue(slice.isTopLevel);
+ assert.isFalse(childSlice.isTopLevel);
+ // URL is exposed as the title of the parent slice.
+ assert.strictEqual(slice.title, 'http://test.url');
+ assert.strictEqual(slice.title, 'http://test.url');
+ assert.strictEqual(childSlice.title, 'b');
+ assert.strictEqual(childSlice.url, 'http://test.url');
+ });
+
+ test('ExposeURLNestedNoURL', function() {
+ const slice = new NetAsyncSlice('', 'a', 0, 1, {params: {}}, 1, true);
+ const childSlice = new NetAsyncSlice('', 'b', 0, 1, {params: {}});
+ slice.subSlices = [childSlice];
+ // Make sure isTopLevel is populated in the constructor.
+ assert.isTrue(slice.isTopLevel);
+ assert.isFalse(childSlice.isTopLevel);
+ // URL is exposed as the title of the parent slice.
+ assert.strictEqual(slice.title, 'a');
+ assert.strictEqual(slice.url, undefined);
+ assert.strictEqual(childSlice.title, 'b');
+ assert.strictEqual(childSlice.url, undefined);
+ });
+
+ test('ExposeURLNestedBothChildrenHaveURL', function() {
+ const slice = new NetAsyncSlice('', 'a', 0, 1, {params: {}}, 1, true);
+ const childSlice1 = new NetAsyncSlice('', 'b', 0, 1,
+ {params: {url: 'http://test.url.net'}});
+ const childSlice2 = new NetAsyncSlice('', 'c', 0, 1,
+ {params: {url: 'http://test.url.com'}});
+ slice.subSlices = [childSlice1, childSlice2];
+ // Make sure isTopLevel is populated in the constructor.
+ assert.isTrue(slice.isTopLevel);
+ assert.isFalse(childSlice1.isTopLevel);
+ assert.isFalse(childSlice2.isTopLevel);
+ // Parent should take the first url param that it finds.
+ assert.strictEqual(slice.title, 'http://test.url.net');
+ assert.strictEqual(childSlice1.title, 'b');
+ assert.strictEqual(childSlice2.title, 'c');
+ });
+
+ test('ExposeURLNestedBothParentAndChildHaveURL', function() {
+ const slice = new NetAsyncSlice('', 'a', 0, 1,
+ {params: {url: 'parent123.url.com'}}, 1,
+ true);
+ const childSlice1 = new NetAsyncSlice('', 'b', 0, 1,
+ {params: {url: 'http://test.url.net'}});
+ const childSlice2 = new NetAsyncSlice('', 'c', 0, 1);
+
+ slice.subSlices = [childSlice1, childSlice2];
+ // Make sure isTopLevel is populated in the constructor.
+ assert.isTrue(slice.isTopLevel);
+ assert.isFalse(childSlice1.isTopLevel);
+ assert.isFalse(childSlice2.isTopLevel);
+ // Parent should take its own url param if there is one.
+ assert.strictEqual(slice.title, 'parent123.url.com');
+ assert.strictEqual(childSlice1.title, 'b');
+ assert.strictEqual(childSlice2.title, 'c');
+ });
+
+ test('ExposeURLDoNotComputeUrlTwice', function() {
+ const slice = new NetAsyncSlice('', 'a', 0, 1, {params: {}}, 1, true);
+ const childSlice1 = new NetAsyncSlice('', 'b', 0, 1,
+ {params: {url: 'http://test.url.net'}});
+ const childSlice2 = new NetAsyncSlice('', 'c', 0, 1);
+
+ slice.subSlices = [childSlice1, childSlice2];
+ // Make sure isTopLevel is populated in the constructor.
+ assert.isTrue(slice.isTopLevel);
+ assert.isFalse(childSlice1.isTopLevel);
+ assert.isFalse(childSlice2.isTopLevel);
+ // Parent should take its child's url param.
+ assert.strictEqual(slice.title, 'http://test.url.net');
+ assert.strictEqual(childSlice1.title, 'b');
+ assert.strictEqual(childSlice2.title, 'c');
+ // Now if we change the url param of the child, the parent's title should
+ // remain the same. We do not recompute.
+ childSlice1.args.params.url = 'example.com';
+ assert.strictEqual(slice.title, 'http://test.url.net');
+ assert.strictEqual(childSlice1.title, 'b');
+ assert.strictEqual(childSlice2.title, 'c');
+ });
+
+ test('ExposeSourceTypeAsTitle', function() {
+ const slice = new NetAsyncSlice('', 'a', 0, 1,
+ {params: {}, source_type: 'UDP_SOCKET'}, 1,
+ true);
+ const childSlice1 = new NetAsyncSlice('', 'b', 0, 1,
+ {params: {}, source_type: 'SOCKET'});
+ const childSlice2 = new NetAsyncSlice('', 'c', 0, 1);
+
+ slice.subSlices = [childSlice1, childSlice2];
+ // Make sure isTopLevel is populated in the constructor.
+ assert.isTrue(slice.isTopLevel);
+ assert.isFalse(childSlice1.isTopLevel);
+ assert.isFalse(childSlice2.isTopLevel);
+ // Parent should take its own source_type.
+ assert.strictEqual(slice.title, 'UDP_SOCKET');
+ assert.strictEqual(childSlice1.title, 'b');
+ assert.strictEqual(childSlice2.title, 'c');
+ });
+
+ test('ByteCountBasic', function() {
+ const slice = new NetAsyncSlice('', 'URL_REQUEST_JOB_BYTES_READ', 0, 1,
+ {params: {byte_count: 12}}, 0, true);
+ assert.strictEqual(slice.byteCount, 12);
+ });
+
+ test('NoByteCount', function() {
+ const slice = new NetAsyncSlice('', 'a', 0, 1, {}, 0, true);
+ assert.strictEqual(slice.byteCount, 0);
+ });
+
+ test('ByteCountNested', function() {
+ const slice = new NetAsyncSlice('', 'URL_REQUEST_JOB_FILTERED_BYTES_READ',
+ 0, 1, {params: {byte_count: 12}}, 0, true);
+ const childSlice = new NetAsyncSlice('', 'URL_REQUEST_JOB_BYTES_READ', 0, 1,
+ {params: {byte_count: 50}});
+ slice.subSlices = [childSlice];
+ assert.strictEqual(slice.byteCount, 62);
+ assert.strictEqual(childSlice.byteCount, 50);
+ });
+
+ test('ByteCountTwoChildren', function() {
+ const slice = new NetAsyncSlice(
+ '', 'URL_REQUEST_JOB_FILTERED_BYTES_READ', 0, 1,
+ {params: {byte_count: 12}}, 0, true);
+ const childSlice1 = new NetAsyncSlice(
+ '', 'URL_REQUEST_JOB_BYTES_READ', 0, 1, {params: {byte_count: 50}});
+ const childSlice2 = new NetAsyncSlice(
+ '', 'URL_REQUEST_JOB_BYTES_READ', 0, 1, {params: {byte_count: 20}});
+ slice.subSlices = [childSlice1, childSlice2];
+ assert.strictEqual(slice.byteCount, 82);
+ assert.strictEqual(childSlice1.byteCount, 50);
+ assert.strictEqual(childSlice2.byteCount, 20);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/OWNERS b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/OWNERS
new file mode 100644
index 00000000000..25b1b8fc5e8
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/OWNERS
@@ -0,0 +1,2 @@
+dskiba@chromium.org
+erikchen@chromium.org
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/__init__.py b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/__init__.py
new file mode 100644
index 00000000000..a22a6ee39a9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/addr2line-pdb.exe.sha1 b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/addr2line-pdb.exe.sha1
new file mode 100644
index 00000000000..7b6ab903155
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/addr2line-pdb.exe.sha1
@@ -0,0 +1 @@
+4e236e6ea0d9f95067eafcb6a49fa85960253cd7 \ No newline at end of file
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/linux_trace_v2_presymbolization.json.gz.sha1 b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/linux_trace_v2_presymbolization.json.gz.sha1
new file mode 100644
index 00000000000..a9a23ea8f9a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/linux_trace_v2_presymbolization.json.gz.sha1
@@ -0,0 +1 @@
+cf9861aae9dc507e779fe7b34a810848d1c1b288 \ No newline at end of file
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/mac_trace_v2_presymbolization.json.gz.sha1 b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/mac_trace_v2_presymbolization.json.gz.sha1
new file mode 100644
index 00000000000..bdd769b990d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/mac_trace_v2_presymbolization.json.gz.sha1
@@ -0,0 +1 @@
+4bbc69a25b711b5a9488936382298063e240ffa7 \ No newline at end of file
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/windows_trace_v1_presymbolization.json.gz.sha1 b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/windows_trace_v1_presymbolization.json.gz.sha1
new file mode 100644
index 00000000000..0243761389e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/windows_trace_v1_presymbolization.json.gz.sha1
@@ -0,0 +1 @@
+4f63b242a7bab2266b76bee3e528c7d7c1e3882f \ No newline at end of file
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/windows_trace_v2_presymbolization.json.gz.sha1 b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/windows_trace_v2_presymbolization.json.gz.sha1
new file mode 100644
index 00000000000..29027e5dd72
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/data/windows_trace_v2_presymbolization.json.gz.sha1
@@ -0,0 +1 @@
+520410cd56479aa54321eee65be5676acc2e8ed7 \ No newline at end of file
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace.py b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace.py
new file mode 100755
index 00000000000..e90b9ce5e30
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace.py
@@ -0,0 +1,1707 @@
+#!/usr/bin/env python
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+# pylint: disable=too-many-lines
+
+"""
+This script processes trace files and symbolizes stack frames generated by
+Chrome's native heap profiler. This script assumes that the Chrome binary
+referenced in the trace contains symbols, and is the same binary used to emit
+the trace.
+
+=== Overview ===
+
+Trace file is essentially a giant JSON array of dictionaries (events).
+Events have some predefined keys (e.g. 'pid'), but otherwise are free to
+have anything inside. Trace file contains events from all Chrome processes
+that were sampled during tracing period.
+
+This script cares only about memory dump events generated with memory-infra
+category enabled.
+
+When Chrome native heap profiling is enabled, some memory dump events
+include the following extra information:
+
+* (Per allocator) Information about live allocations at the moment of the
+ memory dump (the information includes backtraces, types / categories,
+ sizes, and counts of allocations). There are several allocators in
+ Chrome: e.g. malloc, blink_gc, partition_alloc.
+
+* (Per process) Stack frame tree of all functions that called allocators
+ above.
+
+This script does the following:
+
+1. Parses the given trace file (loads JSON).
+2. Finds memory dump events and parses stack frame tree for each process.
+3. Finds stack frames that have PC addresses instead of function names.
+4. Symbolizes PCs and modifies loaded JSON.
+5. Writes modified JSON back to the file.
+
+The script supports trace files from the following platforms:
+ * Android (the script itself must be run on Linux)
+ * Linux
+ * macOS
+ * Windows
+
+Important note - the script doesn't check that it symbolizes same binaries
+that were used at the time trace was taken. I.e. if you take a trace, change
+and rebuild Chrome binaries, the script will blindly use the new binaries.
+
+=== Details ===
+
+There are two formats of heap profiler information: legacy and modern. The
+main differences relevant to this script are:
+
+* In the modern format the stack frame tree, type name mapping, and string
+ mapping nodes are dumped incrementally. These nodes are dumped in each
+ memory dump event and carry updates that occurred since the last event.
+
+ For example, let's say that when the first memory dump event is generated
+ we only know about a function foo() (called from main()) allocating objects
+ of type "int":
+
+ {
+ "args": {
+ "dumps": {
+ "heaps_v2": {
+ "maps": {
+ "nodes": [
+ { "id": 1, "name_sid": 1 },
+ { "id": 2, "parent": 1, "name_sid": 3 },
+ ],
+ "types": [
+ { "id": 1, "name_sid": 2 },
+ ],
+ "strings": [
+ { "id": 1, "string": "main()" },
+ { "id": 2, "string": "int" },
+ { "id": 3, "string": "foo()" },
+ ]
+ },
+ "allocators": { ...live allocations per allocator... },
+ ...
+ },
+ ...
+ }
+ },
+ ...
+ }
+
+ Here:
+ * 'nodes' node encodes stack frame tree
+ * 'types' node encodes type name mappings
+ * 'strings' node encodes string mapping (explained below)
+
+ Then, by the time second memory dump even is generated, we learn about
+ bar() (called from main()), which also allocated "int" objects. Only the
+ new information is dumped, i.e. bar() stack frame:
+
+ {
+ "args": {
+ "dumps": {
+ "heaps_v2": {
+ "maps": {
+ "nodes": [
+ { "id": 2, "parent": 1, "name_sid": 4 },
+ ],
+ "types": [],
+ "strings": [
+ { "id": 4, "string": "bar()" },
+ ]
+ },
+ "allocators": { ...live allocations per allocator... },
+ ...
+ },
+ ...
+ }
+ },
+ ...
+ }
+
+ Note that 'types' node is empty, since there were no updates. All three
+ nodes ('nodes', types', and 'strings') can be empty if there were no updates
+ to them.
+
+ For simplicity, when the script updates incremental nodes, it puts updated
+ content in the first node, and clears all others. I.e. the following stack
+ frame nodes:
+
+ 'nodes': [
+ { "id": 1, "name_sid": 1 },
+ { "id": 2, "parent": 1, "name_sid": 2 },
+ ]
+ 'nodes': [
+ { "id": 3, "parent": 2, "name_sid": 3 },
+ ]
+ 'nodes': [
+ { "id": 4, "parent": 3, "name_sid": 4 },
+ { "id": 5, "parent": 1, "name_sid": 5 },
+ ]
+
+ After symbolization are written as:
+
+ 'nodes': [
+ { "id": 1, "name_sid": 1 },
+ { "id": 2, "parent": 1, "name_sid": 2 },
+ { "id": 3, "parent": 2, "name_sid": 3 },
+ { "id": 4, "parent": 3, "name_sid": 4 },
+ { "id": 5, "parent": 1, "name_sid": 5 },
+ ]
+ 'nodes': []
+ 'nodes': []
+
+
+* In contrast, in the legacy format stack frame tree and type mappings are
+ dumped separately from memory dump events, once per process.
+
+ Here is how trace file with two memory dump events looks like in the
+ legacy format:
+
+ {
+ "args": {
+ "dumps": {
+ "heaps": { ...live allocations per allocator... },
+ ...
+ }
+ },
+ ...
+ }
+
+ {
+ "args": {
+ "dumps": {
+ "heaps": { ...live allocations per allocator... },
+ ...
+ }
+ },
+ ...
+ }
+
+ {
+ "args": {
+ "typeNames": {
+ 1: "int",
+ }
+ },
+ "cat": "__metadata",
+ "name": "typeNames",
+ ...
+ }
+
+ {
+ "args": {
+ "stackFrames": {
+ 1: { "name": "main" },
+ 2: { "name": "foo", "parent": 1 },
+ 3: { "name": "bar", "parent": 1 },
+ }
+ },
+ "cat": "__metadata",
+ "name": "stackFrames",
+ ...
+ }
+
+
+* Another change in the modern format is 'strings' node, which was added
+ to deduplicate stack frame names (mainly for trace file size reduction).
+ For consistency 'types' node also uses string mappings.
+
+
+See crbug.com/708930 for more information about the modern format.
+"""
+
+from __future__ import print_function
+
+import argparse
+import bisect
+import collections
+import gzip
+import json
+import os
+import re
+import shutil
+import subprocess
+import sys
+import tarfile
+import zipfile
+import tempfile
+
+import symbols.elf_symbolizer as elf_symbolizer
+
+from tracing.extras.symbolizer import symbolize_trace_atos_regex
+from tracing.extras.symbolizer import symbolize_trace_macho_reader
+
+import py_utils.cloud_storage as cloud_storage
+
+_UNNAMED_FILE = 'unnamed'
+
+
+class NodeWrapper(object):
+ """Wraps an event data node(s).
+
+ A node is a reference into a trace event JSON. Wrappers parse nodes to
+ provide convenient APIs and update nodes when asked to propagate changes
+ back (see ApplyModifications() below).
+
+ Here is an example of legacy metadata event that contains stack frame tree:
+
+ {
+ "args": {
+ "stackFrames": { ... }
+ },
+ "cat": "__metadata",
+ "name": "stackFrames",
+ "ph": "M",
+ ...
+ }
+
+ When this event is encountered, a reference to the "stackFrames" dictionary
+ is obtained and passed down to a specific wrapped class, which knows how to
+ parse / update the dictionary.
+
+ There are two parsing patterns depending on whether node is serialized
+ incrementally:
+
+ * If node is not incremental, then parsing is done by __init__(),
+ see MemoryMap for an example.
+
+ * If node is incremental, then __init__() does nothing, and instead
+ ParseNext() method is called when next node (from a next event) is
+ encountered.
+
+ Some wrappers can also modify nodes they parsed. In such cases they have
+ additional APIs:
+
+ * 'modified' flag, which indicates whether the wrapper was changed.
+
+ * 'ApplyModifications' method, which propagates changes made to the wrapper
+ back to nodes. Successful invocation of ApplyModifications() resets
+ 'modified' flag.
+
+ """
+ pass
+
+
+class MemoryMap(NodeWrapper):
+ """Wraps 'process_mmaps' node.
+
+ 'process_mmaps' node contains information about file mappings.
+
+ "process_mmaps": {
+ "vm_regions": [
+ {
+ "mf": "<file_path>",
+ "sa": "<start_address>",
+ "sz": "<size>",
+ ...
+ },
+ ...
+ ]
+ }
+ """
+
+ class Region(object):
+ def __init__(self, start_address, size, file_path, file_offset):
+ self._start_address = start_address
+ self._size = size
+ self._file_path = file_path if file_path else _UNNAMED_FILE
+ self._file_offset = file_offset
+ self._code_id = None
+
+ @property
+ def start_address(self):
+ return self._start_address
+
+ @property
+ def end_address(self):
+ return self._start_address + self._size
+
+ @property
+ def size(self):
+ return self._size
+
+ @property
+ def code_id(self):
+ return self._code_id
+
+ @property
+ def file_path(self):
+ return self._file_path
+
+ @property
+ def file_offset(self):
+ return self._file_offset
+
+ @file_offset.setter
+ def file_offset(self, value):
+ self._file_offset = value
+
+ def __cmp__(self, other):
+ if isinstance(other, type(self)):
+ other_start_address = other._start_address
+ elif isinstance(other, (long, int)):
+ other_start_address = other
+ else:
+ raise Exception('Cannot compare with %s' % type(other))
+ if self._start_address < other_start_address:
+ return -1
+ elif self._start_address > other_start_address:
+ return 1
+ else:
+ return 0
+
+ def __repr__(self):
+ return 'Region(0x{:X} - 0x{:X}, {})'.format(
+ self.start_address, self.end_address, self.file_path)
+
+ def __init__(self, process_mmaps_node):
+ regions = []
+ for region_node in process_mmaps_node['vm_regions']:
+ file_offset = long(region_node['fo'], 16) if 'fo' in region_node else 0
+ file_path = region_node['mf'].replace(" (deleted)", "")
+ region = self.Region(long(region_node['sa'], 16),
+ long(region_node['sz'], 16),
+ file_path,
+ file_offset)
+ # Keep track of code-identifier when present.
+ if 'ts' in region_node and 'sz' in region_node:
+ region._code_id = "%08X%X" % (long(region_node['ts'], 16), region.size)
+ regions.append(region)
+
+ regions.sort()
+
+ # Iterate through the regions in order. If two regions border each other,
+ # and have the same file_path [or at least one of them is unnamed], but the
+ # latter region has file_offset == 0, then set the file_offset of the latter
+ # region to be former_region.file_offset + former_region.size.
+ #
+ # Rationale: Semantically, we want file_offset to be the distance between
+ # the base address of the region and the base address of the module [which
+ # breakpad symbols use as a relative-zero]. Technically, this is called
+ # slide (macOS) and load bias (ELF). See
+ # https://chromium-review.googlesource.com/c/chromium/src/+/568413#message-01cf829007882eea8c9d3403871814c4f336d16d
+ # for more details.
+ # Chrome does not emit slide or load bias. This usually doesn't make a
+ # difference because the TEXT segment usually has a slide or load bias of 0.
+ # In the rare cases that it doesn't [observed on Chrome Linux official
+ # builds], this heuristic correctly computes it.
+ #
+ # This hack relies on the assumption that all regions with the same name are
+ # mapped from the same file. Each region's file_offset should be computed
+ # based on the first region's base address.
+ last_region_with_file_path = {}
+ for region in regions:
+ if (region.file_path in last_region_with_file_path and
+ region.file_offset == 0):
+ region.file_offset = (
+ region.start_address -
+ last_region_with_file_path[region.file_path].start_address)
+ if (region.file_path and
+ region.file_path not in last_region_with_file_path):
+ last_region_with_file_path[region.file_path] = region
+
+ # Copy regions without duplicates and check for overlaps.
+ self._regions = []
+ previous_region = None
+ for region in regions:
+ if previous_region is not None:
+ if region == previous_region:
+ continue
+ if region.start_address < previous_region.end_address:
+ print('Regions {} and {} overlap.'.format(previous_region, region))
+ previous_region = region
+ self._regions.append(region)
+
+ @property
+ def regions(self):
+ return self._regions
+
+ def FindRegion(self, address):
+ """Finds region containing |address|. Returns None if none found."""
+
+ region_index = bisect.bisect_right(self._regions, address) - 1
+ if region_index >= 0:
+ region = self._regions[region_index]
+ if address >= region.start_address and address < region.end_address:
+ return region
+ return None
+
+
+class UnsupportedHeapDumpVersionError(Exception):
+ """Helper exception class to signal unsupported heap dump version."""
+
+ def __init__(self, version):
+ message = 'Unsupported heap dump version: {}'.format(version)
+ super(UnsupportedHeapDumpVersionError, self).__init__(message)
+
+
+class StringMap(NodeWrapper):
+ """Wraps all 'strings' nodes for a process.
+
+ 'strings' node contains incremental mappings between integer ids and strings.
+
+ "strings": [
+ {
+ "id": <string_id>,
+ "string": <string>
+ },
+ ...
+ ]
+ """
+
+ def __init__(self):
+ self._modified = False
+ self._strings_nodes = []
+ self._string_by_id = {}
+ self._id_by_string = {}
+ self._max_string_id = 0
+
+ @property
+ def modified(self):
+ """Returns True if the wrapper was modified (see NodeWrapper)."""
+ return self._modified
+
+ @property
+ def string_by_id(self):
+ return self._string_by_id
+
+ def ParseNext(self, heap_dump_version, strings_node):
+ """Parses and interns next node (see NodeWrapper)."""
+
+ if heap_dump_version != Trace.HEAP_DUMP_VERSION_1:
+ raise UnsupportedHeapDumpVersionError(heap_dump_version)
+
+ self._strings_nodes.append(strings_node)
+ for string_node in strings_node:
+ self._Insert(string_node['id'], string_node['string'])
+
+ def Clear(self):
+ """Clears all string mappings."""
+ if self._string_by_id:
+ self._modified = True
+
+ self._string_by_id = {}
+ self._id_by_string = {}
+ self._max_string_id = 0
+
+ def AddString(self, string):
+ """Adds a string (if it doesn't exist) and returns its integer id."""
+ string_id = self._id_by_string.get(string)
+ if string_id is None:
+ string_id = self._max_string_id + 1
+ self._Insert(string_id, string)
+ self._modified = True
+ return string_id
+
+ def ApplyModifications(self):
+ """Propagates modifications back to nodes (see NodeWrapper)."""
+ if not self.modified:
+ return
+
+ assert self._strings_nodes, 'no nodes'
+
+ # Serialize into the first node, and clear all others.
+
+ for strings_node in self._strings_nodes:
+ del strings_node[:]
+ strings_node = self._strings_nodes[0]
+ for string_id, string in self._string_by_id.items():
+ strings_node.append({'id': string_id, 'string': string})
+
+ self._modified = False
+
+ def _Insert(self, string_id, string):
+ self._id_by_string[string] = string_id
+ self._string_by_id[string_id] = string
+ self._max_string_id = max(self._max_string_id, string_id)
+
+
+class TypeNameMap(NodeWrapper):
+ """Wraps all 'types' nodes for a process.
+
+ 'types' nodes encode mappings between integer type ids and integer
+ string ids (from 'strings' nodes).
+
+ "types": [
+ {
+ "id": <type_id>,
+ "name_sid": <name_string_id>
+ }
+ ...
+ ]
+
+ For simplicity string ids are translated into strings during parsing,
+ and then translated back to ids in ApplyModifications().
+ """
+ def __init__(self):
+ self._modified = False
+ self._type_name_nodes = []
+ self._name_by_id = {}
+ self._id_by_name = {}
+ self._max_type_id = 0
+
+ @property
+ def modified(self):
+ """Returns True if the wrapper was modified (see NodeWrapper)."""
+ return self._modified
+
+ @property
+ def name_by_id(self):
+ """Returns {id -> name} dict (must not be changed directly)."""
+ return self._name_by_id
+
+ def ParseNext(self, heap_dump_version, type_name_node, string_map):
+ """Parses and interns next node (see NodeWrapper).
+
+ |string_map| - A StringMap object to use to translate string ids
+ to strings.
+ """
+ if heap_dump_version != Trace.HEAP_DUMP_VERSION_1:
+ raise UnsupportedHeapDumpVersionError(heap_dump_version)
+
+ self._type_name_nodes.append(type_name_node)
+ for type_node in type_name_node:
+ self._Insert(type_node['id'],
+ string_map.string_by_id[type_node['name_sid']])
+
+ def AddType(self, type_name):
+ """Adds a type name (if it doesn't exist) and returns its id."""
+ type_id = self._id_by_name.get(type_name)
+ if type_id is None:
+ type_id = self._max_type_id + 1
+ self._Insert(type_id, type_name)
+ self._modified = True
+ return type_id
+
+ def ApplyModifications(self, string_map, force=False):
+ """Propagates modifications back to nodes.
+
+ |string_map| - A StringMap object to use to translate strings to ids.
+ |force| - Whether to propagate changes regardless of 'modified' flag.
+ """
+ if not self.modified and not force:
+ return
+
+ assert self._type_name_nodes, 'no nodes'
+
+ # Serialize into the first node, and clear all others.
+
+ for types_node in self._type_name_nodes:
+ del types_node[:]
+ types_node = self._type_name_nodes[0]
+ for type_id, type_name in self._name_by_id.items():
+ types_node.append({
+ 'id': type_id,
+ 'name_sid': string_map.AddString(type_name)})
+
+ self._modified = False
+
+ def _Insert(self, type_id, type_name):
+ self._id_by_name[type_name] = type_id
+ self._name_by_id[type_id] = type_name
+ self._max_type_id = max(self._max_type_id, type_id)
+
+
+class StackFrameMap(NodeWrapper):
+ """ Wraps stack frame tree nodes for a process.
+
+ For the legacy format this wrapper expects a single 'stackFrames' node
+ (which comes from metadata event):
+
+ "stackFrames": {
+ "<frame_id>": {
+ "name": "<frame_name>"
+ "parent": "<parent_frame_id>"
+ },
+ ...
+ }
+
+ For the modern format this wrapper expects several 'nodes' nodes:
+
+ "nodes": [
+ {
+ "id": <frame_id>,
+ "parent": <parent_frame_id>,
+ "name_sid": <name_string_id>
+ },
+ ...
+ ]
+
+ In both formats frame name is a string. Native heap profiler generates
+ specially formatted frame names (e.g. "pc:10eb78dba") for function
+ addresses (PCs). Inner Frame class below parses name and extracts PC,
+ if it's there.
+ """
+ class Frame(object):
+ def __init__(self, frame_id, name, parent_frame_id):
+ self._modified = False
+ self._id = frame_id
+ self._name = name
+ self._pc = self._ParsePC(name)
+ self._parent_id = parent_frame_id
+ self._ext = None
+
+ @property
+ def modified(self):
+ """Returns True if the frame was modified.
+
+ For example changing frame's name sets this flag (since the change
+ needs to be propagated back to nodes).
+ """
+ return self._modified
+
+ @property
+ def id(self):
+ """Frame id (integer)."""
+ return self._id
+
+ @property
+ def pc(self):
+ """Parsed (integer) PC of the frame, or None."""
+ return self._pc
+
+ @property
+ def name(self):
+ """Name of the frame (see above)."""
+ return self._name
+
+ @name.setter
+ def name(self, value):
+ """Changes the name. Doesn't affect value of |pc|."""
+ self._modified = True
+ self._name = value
+
+ @property
+ def parent_id(self):
+ """Parent frame id (integer)."""
+ return self._parent_id
+
+ _PC_TAG = 'pc:'
+
+ def _ParsePC(self, name):
+ if not name.startswith(self._PC_TAG):
+ return None
+ return long(name[len(self._PC_TAG):], 16)
+
+ def _ClearModified(self):
+ self._modified = False
+
+ def __init__(self):
+ self._modified = False
+ self._heap_dump_version = None
+ self._stack_frames_nodes = []
+ self._frame_by_id = {}
+
+ @property
+ def modified(self):
+ """Returns True if the wrapper or any of its frames were modified."""
+ return (self._modified or
+ any(f.modified for f in self._frame_by_id.values()))
+
+ @property
+ def frame_by_id(self):
+ """Returns {id -> frame} dict (must not be modified directly)."""
+ return self._frame_by_id
+
+ def ParseNext(self, heap_dump_version, stack_frames_node, string_map):
+ """Parses the next stack frames node (see NodeWrapper).
+
+ For the modern format |string_map| is used to translate string ids
+ to strings.
+ """
+
+ frame_by_id = {}
+ if heap_dump_version == Trace.HEAP_DUMP_VERSION_LEGACY:
+ if self._stack_frames_nodes:
+ raise Exception('Legacy stack frames node is expected only once.')
+ for frame_id, frame_node in stack_frames_node.items():
+ frame = self.Frame(frame_id,
+ frame_node['name'],
+ frame_node.get('parent'))
+ frame_by_id[frame.id] = frame
+ else:
+ if heap_dump_version != Trace.HEAP_DUMP_VERSION_1:
+ raise UnsupportedHeapDumpVersionError(heap_dump_version)
+ for frame_node in stack_frames_node:
+ frame = self.Frame(frame_node['id'],
+ string_map.string_by_id[frame_node['name_sid']],
+ frame_node.get('parent'))
+ frame_by_id[frame.id] = frame
+
+ self._heap_dump_version = heap_dump_version
+ self._stack_frames_nodes.append(stack_frames_node)
+
+ self._frame_by_id.update(frame_by_id)
+
+ def ApplyModifications(self, string_map, force=False):
+ """Applies modifications back to nodes (see NodeWrapper)."""
+
+ if not self.modified and not force:
+ return
+
+ assert self._stack_frames_nodes, 'no nodes'
+ if self._heap_dump_version == Trace.HEAP_DUMP_VERSION_LEGACY:
+ assert string_map is None, \
+ 'string_map should not be used with the legacy format'
+
+ # Serialize frames into the first node, clear all others.
+
+ for frames_node in self._stack_frames_nodes:
+ if self._heap_dump_version == Trace.HEAP_DUMP_VERSION_LEGACY:
+ frames_node.clear()
+ else:
+ del frames_node[:]
+
+ frames_node = self._stack_frames_nodes[0]
+ for frame in self._frame_by_id.values():
+ if self._heap_dump_version == Trace.HEAP_DUMP_VERSION_LEGACY:
+ frame_node = {'name': frame.name}
+ frames_node[frame.id] = frame_node
+ else:
+ frame_node = {
+ 'id': frame.id,
+ 'name_sid': string_map.AddString(frame.name)
+ }
+ frames_node.append(frame_node)
+ if frame.parent_id is not None:
+ frame_node['parent'] = frame.parent_id
+ frame._ClearModified()
+
+ self._modified = False
+
+
+class Trace(NodeWrapper):
+ """Wrapper for the root trace node (i.e. the trace JSON itself).
+
+ This wrapper parses select nodes from memory-infra events and groups
+ parsed data per-process (see inner Process class below).
+ """
+
+ # Indicates legacy heap dump format.
+ HEAP_DUMP_VERSION_LEGACY = 'Legacy'
+
+ # Indicates variation of a modern heap dump format.
+ HEAP_DUMP_VERSION_1 = 1
+
+ class Process(object):
+ """Collection of per-process data and wrappers."""
+
+ def __init__(self, pid):
+ self._pid = pid
+ self._name = None
+ self._memory_map = None
+ self._stack_frame_map = StackFrameMap()
+ self._type_name_map = TypeNameMap()
+ self._string_map = StringMap()
+ self._heap_dump_version = None
+
+ @property
+ def modified(self):
+ return self._stack_frame_map.modified or self._type_name_map.modified
+
+ @property
+ def pid(self):
+ return self._pid
+
+ @property
+ def name(self):
+ return self._name
+
+ @property
+ def unique_name(self):
+ """Returns string that includes both process name and its pid."""
+ name = self._name if self._name else 'UnnamedProcess'
+ return '{}({})'.format(name, self._pid)
+
+ @property
+ def memory_map(self):
+ return self._memory_map
+
+ @property
+ def stack_frame_map(self):
+ return self._stack_frame_map
+
+ @property
+ def type_name_map(self):
+ return self._type_name_map
+
+ def ApplyModifications(self):
+ """Calls ApplyModifications() on contained wrappers."""
+ if self._heap_dump_version == Trace.HEAP_DUMP_VERSION_LEGACY:
+ self._stack_frame_map.ApplyModifications(None)
+ else:
+ if self._stack_frame_map.modified or self._type_name_map.modified:
+ self._string_map.Clear()
+ self._stack_frame_map.ApplyModifications(self._string_map, force=True)
+ self._type_name_map.ApplyModifications(self._string_map, force=True)
+ self._string_map.ApplyModifications()
+
+ def __init__(self, trace_node):
+ self._trace_node = trace_node
+ self._processes = []
+ self._heap_dump_version = None
+ self._os = None
+ self._version = None
+ self._is_chromium = True
+ self._is_64bit = False
+ self._is_win = False
+ self._is_mac = False
+ self._is_linux = False
+ self._is_cros = False
+ self._is_android = False
+
+ # Misc per-process information needed only during parsing.
+ class ProcessExt(object):
+ def __init__(self, pid):
+ self.process = Trace.Process(pid)
+ self.mapped_entry_names = set()
+ self.process_mmaps_node = None
+ self.seen_strings_node = False
+
+ process_ext_by_pid = {}
+
+ if isinstance(trace_node, dict):
+ metadata = trace_node['metadata']
+ product_version = metadata['product-version']
+ # product-version has the form "Chrome/60.0.3103.0"
+ self._version = product_version.split('/', 1)[-1]
+ self._os = metadata['os-name']
+
+ self._is_win = re.search('windows', metadata['os-name'], re.IGNORECASE)
+ self._is_mac = re.search('mac', metadata['os-name'], re.IGNORECASE)
+ self._is_linux = re.search('linux', metadata['os-name'], re.IGNORECASE)
+ self._is_cros = re.search('cros', metadata['os-name'], re.IGNORECASE)
+ self._is_android = re.search(
+ 'android', metadata['os-name'], re.IGNORECASE)
+
+ self._is_64bit = (
+ re.search('x86_64', metadata['os-arch'], re.IGNORECASE) and
+ not re.search('WOW64', metadata['user-agent'], re.IGNORECASE))
+
+ # Android traces produced via 'chrome://inspect/?tracing#devices' are
+ # just list of events.
+ events = trace_node if isinstance(trace_node, list) \
+ else trace_node['traceEvents']
+ for event in events:
+ name = event.get('name')
+ if not name:
+ continue
+
+ pid = event['pid']
+ process_ext = process_ext_by_pid.get(pid)
+ if process_ext is None:
+ process_ext = ProcessExt(pid)
+ process_ext_by_pid[pid] = process_ext
+ process = process_ext.process
+
+ phase = event['ph']
+ if phase == self._EVENT_PHASE_METADATA:
+ if name == 'process_name':
+ process._name = event['args']['name']
+ elif name == 'stackFrames':
+ process._stack_frame_map.ParseNext(
+ self._UseHeapDumpVersion(self.HEAP_DUMP_VERSION_LEGACY),
+ event['args']['stackFrames'],
+ process._string_map)
+ elif phase == self._EVENT_PHASE_MEMORY_DUMP:
+ dumps = event['args']['dumps']
+ process_mmaps = dumps.get('process_mmaps')
+ if process_mmaps:
+ # We want the most recent memory map, so parsing happens later
+ # once we finished reading all events.
+ process_ext.process_mmaps_node = process_mmaps
+ heaps = dumps.get('heaps_v2')
+ if heaps:
+ version = self._UseHeapDumpVersion(heaps['version'])
+ maps = heaps.get('maps')
+ if maps:
+ process_ext.mapped_entry_names.update(maps.keys())
+ types = maps.get('types')
+ stack_frames = maps.get('nodes')
+ strings = maps.get('strings')
+ if (strings is None and (types or stack_frames)
+ and not process_ext.seen_strings_node):
+ # ApplyModifications() for TypeNameMap and StackFrameMap puts
+ # everything into the first node and depends on StringMap. So
+ # we need to make sure that 'strings' node is there if any of
+ # other two nodes present.
+ strings = []
+ maps['strings'] = strings
+ if strings is not None:
+ process_ext.seen_strings_node = True
+ process._string_map.ParseNext(version, strings)
+ if types:
+ process._type_name_map.ParseNext(
+ version, types, process._string_map)
+ if stack_frames:
+ process._stack_frame_map.ParseNext(
+ version, stack_frames, process._string_map)
+
+ self._processes = []
+ for pe in process_ext_by_pid.values():
+ pe.process._heap_dump_version = self._heap_dump_version
+ if pe.process_mmaps_node:
+ # Now parse the most recent memory map.
+ pe.process._memory_map = MemoryMap(pe.process_mmaps_node)
+ self._processes.append(pe.process)
+
+ @property
+ def node(self):
+ """Root node (that was passed to the __init__)."""
+ return self._trace_node
+
+ @property
+ def modified(self):
+ """Returns True if trace file needs to be updated.
+
+ Before writing trace JSON back to a file ApplyModifications() needs
+ to be called.
+ """
+ return any(p.modified for p in self._processes)
+
+ @property
+ def processes(self):
+ return self._processes
+
+ @property
+ def heap_dump_version(self):
+ return self._heap_dump_version
+
+ @property
+ def version(self):
+ return self._version
+
+ @property
+ def os(self):
+ return self._os
+
+ @property
+ def is_chromium(self):
+ return self._is_chromium
+
+ @is_chromium.setter
+ def is_chromium(self, new_value):
+ self._is_chromium = new_value
+
+ @property
+ def is_mac(self):
+ return self._is_mac
+
+ @property
+ def is_win(self):
+ return self._is_win
+
+ @property
+ def is_linux(self):
+ return self._is_linux
+
+ @property
+ def is_android(self):
+ return self._is_android
+
+ @property
+ def is_64bit(self):
+ return self._is_64bit
+
+ @property
+ def library_name(self):
+ return self._trace_node['metadata'].get('chrome-library-name')
+
+ def ApplyModifications(self):
+ """Propagates modifications back to the trace JSON."""
+ for process in self._processes:
+ process.ApplyModifications()
+ assert not self.modified, 'still modified'
+
+ # Relevant trace event phases from Chromium's
+ # src/base/trace_event/common/trace_event_common.h.
+ _EVENT_PHASE_METADATA = 'M'
+ _EVENT_PHASE_MEMORY_DUMP = 'v'
+
+ def _UseHeapDumpVersion(self, version):
+ if self._heap_dump_version is None:
+ self._heap_dump_version = version
+ return version
+ elif self._heap_dump_version != version:
+ raise Exception(
+ ("Inconsistent trace file: first saw '{}' heap dump version, "
+ "then '{}'.").format(self._heap_dump_version, version))
+ else:
+ return version
+
+
+class SymbolizableFile(object):
+ """Holds file path, addresses to symbolize and stack frames to update.
+
+ This class is a link between ELFSymbolizer and a trace file: it specifies
+ what to symbolize (addresses) and what to update with the symbolization
+ result (frames).
+ """
+ def __init__(self, file_path, code_id):
+ self.path = file_path
+ self.symbolizable_path = file_path # path to use for symbolization
+ self.code_id = code_id
+ self.frames_by_address = collections.defaultdict(list)
+ self.skip_symbolization = False
+ self.has_breakpad_symbols = False
+
+
+def ResolveSymbolizableFiles(processes):
+ """Resolves and groups PCs into list of SymbolizableFiles.
+
+ As part of the grouping process, this function resolves PC from each stack
+ frame to the corresponding mmap region. Stack frames that failed to resolve
+ are symbolized with '<unresolved>'.
+ """
+ symfile_by_path = {}
+ for process in processes:
+ if not process.memory_map:
+ continue
+ for frame in process.stack_frame_map.frame_by_id.values():
+ if frame.pc is None:
+ continue
+ region = process.memory_map.FindRegion(frame.pc)
+ if region is None:
+ frame.name = '<unresolved>'
+ continue
+
+ symfile = symfile_by_path.get(region.file_path)
+ if symfile is None:
+ file_path = region.file_path
+ symfile = SymbolizableFile(file_path, region.code_id)
+ symfile_by_path[symfile.path] = symfile
+
+ relative_pc = frame.pc - region.start_address + region.file_offset
+ symfile.frames_by_address[relative_pc].append(frame)
+
+ return symfile_by_path.values()
+
+
+def FindInSystemPath(binary_name):
+ paths = os.environ['PATH'].split(os.pathsep)
+ for path in paths:
+ binary_path = os.path.join(path, binary_name)
+ if os.path.isfile(binary_path):
+ return binary_path
+ return None
+
+
+class BreakpadSymbolsModule(object):
+ """Encapsulates Breakpad logic for symbols of a specific module."""
+
+ def __init__(self, filename):
+ super(BreakpadSymbolsModule, self).__init__()
+ self.filename = filename
+ self.files = []
+ self.symbols = {}
+ self.arch = None
+ self.debug_id = None
+ self.code_id = None
+ self.binary = None
+
+ def Parse(self):
+ # see: https://chromium.googlesource.com/breakpad/breakpad/+/master/docs/symbol_files.md
+ with open(self.filename) as fp:
+ for line in fp:
+ fragments = line.rstrip().split()
+ if fragments[0] == 'MODULE':
+ # MODULE mac x86_64 A7001116478B33F18FF9BEDE9F615F190 t
+ self.arch = fragments[2]
+ self.debug_id = fragments[3]
+ self.binary = ' '.join(fragments[4:])
+ elif fragments[0] == 'INFO' and fragments[1] == 'CODE_ID':
+ # INFO CODE_ID 595D00BD31F0000 chrome.dll
+ self.code_id = fragments[2]
+ elif fragments[0] == 'FILE':
+ # FILE 0 /b/c/b/mac64/src/out/Release/../../base/at_exit.cc
+ self.files.append(' '.join(fragments[2:]))
+ elif fragments[0] == 'PUBLIC':
+ # PUBLIC db60 0 base::mac::CallWithEHFrame(void () block_pointer)
+ self.symbols[int(fragments[1], 16)] = ' '.join(fragments[3:])
+ elif fragments[0] == 'FUNC':
+ # FUNC 567e0 264 0 Cr_z_fill_window_sse
+ self.symbols[int(fragments[1], 16)] = ' '.join(fragments[4:])
+
+
+class Symbolizer(object):
+ """Encapsulates platform-specific symbolization logic."""
+
+ def __init__(self, addr2line_executable):
+ self.is_mac = sys.platform == 'darwin'
+ self.is_win = sys.platform == 'win32'
+ if self.is_mac:
+ self.binary = 'atos'
+ self._matcher = symbolize_trace_atos_regex.AtosRegexMatcher()
+ elif self.is_win:
+ self.binary = 'addr2line-pdb.exe'
+ else:
+ self.binary = 'addr2line'
+
+ if addr2line_executable and os.path.isfile(addr2line_executable):
+ self.symbolizer_path = addr2line_executable
+ else:
+ self.symbolizer_path = FindInSystemPath(self.binary)
+
+ self.breakpad_modules = {}
+
+ def _SymbolizeLinuxAndAndroid(self, symfile):
+ def _SymbolizerCallback(sym_info, frames):
+ # Unwind inline chain to the top.
+ while sym_info.inlined_by:
+ sym_info = sym_info.inlined_by
+
+ symbolized_name = (sym_info.name if sym_info.name else
+ '<{}>'.format(symfile.path))
+ for frame in frames:
+ frame.name = symbolized_name
+
+ symbolizer = elf_symbolizer.ELFSymbolizer(symfile.symbolizable_path,
+ self.symbolizer_path,
+ _SymbolizerCallback,
+ inlines=True)
+
+ for address, frames in symfile.frames_by_address.items():
+ # SymbolizeAsync() asserts that the type of address is int. We operate
+ # on longs (since they are raw pointers possibly from 64-bit processes).
+ # It's OK to cast here because we're passing relative PC, which should
+ # always fit into int.
+ symbolizer.SymbolizeAsync(int(address), frames)
+
+ symbolizer.Join()
+
+ def _SymbolizeMac(self, symfile):
+ load_address = (symbolize_trace_macho_reader.
+ ReadMachOTextLoadAddress(symfile.symbolizable_path))
+ assert load_address is not None
+
+ address_os_file, address_file_path = tempfile.mkstemp()
+ try:
+ with os.fdopen(address_os_file, 'w') as address_file:
+ for address in symfile.frames_by_address.keys():
+ address_file.write('{:x} '.format(address + load_address))
+
+ cmd = [self.symbolizer_path, '-arch', 'x86_64', '-l',
+ '0x%x' % load_address, '-o', symfile.symbolizable_path,
+ '-f', address_file_path]
+ output_array = subprocess.check_output(cmd).split('\n')
+
+ for i, frames in enumerate(symfile.frames_by_address.values()):
+ symbolized_name = self._matcher.Match(output_array[i])
+ for frame in frames:
+ frame.name = symbolized_name
+ finally:
+ os.remove(address_file_path)
+
+ def _SymbolizeWin(self, symfile):
+ """Invoke symbolizer binary on windows and write all input in one go.
+
+ Unlike linux, on windows, symbolization talks through a shared system
+ service that handles communication with the NT symbol servers. This
+ creates an explicit serialization (and therefor lock contention) of
+ any process using the symbol API for files do not have a local PDB.
+
+ Thus, even though the windows symbolizer binary can be make command line
+ compatible with the POSIX addr2line interface, parallelizing the
+ symbolization does not yield the same performance effects. Running
+ just one symbolizer seems good enough for now. Can optimize later
+ if this becomes a bottleneck.
+ """
+ cmd = [self.symbolizer_path, '--functions', '--demangle', '--exe',
+ symfile.symbolizable_path]
+
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
+ stderr=None)
+ addrs = ["%x" % relative_pc for relative_pc in
+ symfile.frames_by_address.keys()]
+ (stdout_data, _) = proc.communicate('\n'.join(addrs))
+ # On windows, lines may contain '\r' character: e.g. "RtlUserThreadStart\r".
+ stdout_data.replace('\r', '')
+ stdout_data = stdout_data.split('\n')
+
+ # This is known to be in the same order as stderr_data.
+ for i, addr in enumerate(addrs):
+ for frame in symfile.frames_by_address[int(addr, 16)]:
+ # Output of addr2line with --functions is always 2 outputs per
+ # symbol, function name followed by source line number. Only grab
+ # the function name as line info is not always available.
+ frame.name = stdout_data[i * 2]
+
+ def _SymbolizeBreakpad(self, symfile):
+ module_filename = symfile.symbolizable_path
+ module = BreakpadSymbolsModule(module_filename)
+ module.Parse()
+
+ if module.code_id and symfile.code_id and module.code_id != symfile.code_id:
+ print("Warning: Code identifiers do not match for %s" % symfile.path)
+ print(" from trace file: %s" % symfile.code_id)
+ print(" from debug file: %s" % module.code_id)
+ return
+
+ addresses = symfile.frames_by_address.keys()
+ addresses.sort()
+
+ symbols_addresses = module.symbols.keys()
+ symbols_addresses.sort()
+ symbols_addresses.append(float('inf'))
+
+ offset = 0
+ skipped_addresses = 0
+ for symbol_offset in range(1, len(symbols_addresses)):
+ symbol_address_start = symbols_addresses[symbol_offset - 1]
+ symbol_address_end = symbols_addresses[symbol_offset]
+ resolved_symbol = module.symbols[symbol_address_start]
+ while (offset < len(addresses) and
+ addresses[offset] < symbol_address_end):
+ if addresses[offset] >= symbol_address_start:
+ for frame in symfile.frames_by_address[addresses[offset]]:
+ frame.name = resolved_symbol
+ else:
+ skipped_addresses = skipped_addresses + 1
+ offset = offset + 1
+
+ if skipped_addresses:
+ print("warning: %d unsymbolized symbols!" % skipped_addresses)
+
+ def SymbolizeSymfile(self, symfile):
+ if symfile.skip_symbolization:
+ for address, frames in symfile.frames_by_address.items():
+ unsymbolized_name = ('<' + os.path.basename(symfile.symbolizable_path)
+ + '>')
+ # Only append the address if there's a library.
+ if symfile.symbolizable_path != _UNNAMED_FILE:
+ unsymbolized_name += ' + ' + str(hex(address))
+
+ for frame in frames:
+ frame.name = unsymbolized_name
+ return
+
+ if symfile.has_breakpad_symbols:
+ self._SymbolizeBreakpad(symfile)
+ elif self.is_mac:
+ self._SymbolizeMac(symfile)
+ elif self.is_win:
+ self._SymbolizeWin(symfile)
+ else:
+ self._SymbolizeLinuxAndAndroid(symfile)
+
+ def IsSymbolizableFile(self, file_path):
+ if self.is_win:
+ extension = os.path.splitext(file_path)[1].lower()
+ return extension in ['.dll', '.exe']
+ else:
+ result = subprocess.check_output(['file', '-0', file_path])
+ type_string = result[result.find('\0') + 1:]
+ return bool(re.match(r'.*(ELF|Mach-O) (32|64)-bit\b.*',
+ type_string, re.DOTALL))
+
+
+def SymbolizeFiles(symfiles, symbolizer):
+ """Symbolizes each file in the given list of SymbolizableFiles
+ and updates stack frames with symbolization results."""
+
+ if not symfiles:
+ print('Nothing to symbolize.')
+ return
+
+ print('Symbolizing...')
+
+ def _SubPrintf(message, *args):
+ print((' ' + message).format(*args))
+
+ for symfile in symfiles:
+ problem = None
+ if symfile.skip_symbolization:
+ pass
+ elif (symfile.has_breakpad_symbols and
+ os.path.isabs(symfile.symbolizable_path) and
+ os.path.isfile(symfile.symbolizable_path)):
+ pass
+ elif not os.path.isabs(symfile.symbolizable_path):
+ problem = 'not a file'
+ elif not os.path.isfile(symfile.symbolizable_path):
+ problem = "file doesn't exist"
+ elif not symbolizer.IsSymbolizableFile(symfile.symbolizable_path):
+ problem = 'file is not symbolizable'
+ if problem:
+ _SubPrintf("Problem with '{}': {}.",
+ symfile.symbolizable_path,
+ problem)
+ symfile.skip_symbolization = True
+
+ _SubPrintf('Symbolizing {} PCs from {}...',
+ len(symfile.frames_by_address),
+ symfile.symbolizable_path)
+
+ symbolizer.SymbolizeSymfile(symfile)
+
+
+# Subpath of output path where unstripped libraries are stored.
+ANDROID_UNSTRIPPED_SUBPATH = 'lib.unstripped'
+
+
+def RemapAndroidFiles(symfiles, output_path, chrome_soname):
+ for symfile in symfiles:
+ filename = os.path.basename(symfile.path)
+ if os.path.splitext(filename)[1] == '.so':
+ symfile.symbolizable_path = os.path.join(
+ output_path, ANDROID_UNSTRIPPED_SUBPATH, filename)
+ elif os.path.splitext(filename)[1] == '.apk' and chrome_soname:
+ # If there is any pc in .apk memory map, then just assume it is from
+ # chroms.so since we memory map libraries from apk directly. This does
+ # not work for component builds.
+ symfile.symbolizable_path = os.path.join(
+ output_path, ANDROID_UNSTRIPPED_SUBPATH, chrome_soname)
+ else:
+ # Clobber file path to trigger "not a file" problem in SymbolizeFiles().
+ # Without this, files won't be symbolized with "file not found" problem,
+ # which is not accurate.
+ symfile.symbolizable_path = 'android://{}'.format(symfile.path)
+
+
+def RemapMacFiles(symfiles, symbol_base_directory, version,
+ only_symbolize_chrome_symbols):
+ suffix = ("Google Chrome Framework.dSYM/Contents/Resources/DWARF/"
+ "Google Chrome Framework")
+ symbol_sub_dir = os.path.join(symbol_base_directory, version)
+ symbolizable_path = os.path.join(symbol_sub_dir, suffix)
+
+ for symfile in symfiles:
+ if symfile.path.endswith("Google Chrome Framework"):
+ symfile.symbolizable_path = symbolizable_path
+ elif only_symbolize_chrome_symbols:
+ symfile.skip_symbolization = True
+
+
+def RemapWinFiles(symfiles, symbol_base_directory, version, is64bit,
+ only_symbolize_chrome_symbols):
+ folder = "win64" if is64bit else "win"
+ symbol_sub_dir = os.path.join(symbol_base_directory,
+ "chrome-" + folder + "-" + version)
+ for symfile in symfiles:
+ image = os.path.join(symbol_sub_dir, os.path.basename(symfile.path))
+ symbols = image + ".pdb"
+ if os.path.isfile(image) and os.path.isfile(symbols):
+ symfile.symbolizable_path = image
+ elif only_symbolize_chrome_symbols:
+ symfile.skip_symbolization = True
+
+
+def RemapBreakpadModules(symfiles, symbolizer, only_symbolize_chrome_symbols):
+ for symfile in symfiles:
+ image = os.path.basename(symfile.path).lower()
+ # Looked if the image has Breakpad symbols. Breakpad symbols are generated
+ # for Chrome modules for official builds.
+ if image in symbolizer.breakpad_modules:
+ symfile.symbolizable_path = symbolizer.breakpad_modules[image]
+ symfile.has_breakpad_symbols = True
+ elif only_symbolize_chrome_symbols:
+ symfile.skip_symbolization = True
+
+
+def SymbolizeTrace(options, trace, symbolizer):
+ symfiles = ResolveSymbolizableFiles(trace.processes)
+
+ if options.use_breakpad_symbols:
+ RemapBreakpadModules(symfiles, symbolizer,
+ options.only_symbolize_chrome_symbols)
+ else:
+ if trace.is_android:
+ if not options.output_directory:
+ sys.exit('The trace file appears to be from Android. Please '
+ 'specify output directory to properly symbolize it.')
+ RemapAndroidFiles(symfiles, os.path.abspath(options.output_directory),
+ trace.library_name)
+
+ if not trace.is_chromium:
+ if symbolizer.is_mac:
+ RemapMacFiles(symfiles, options.symbol_base_directory, trace.version,
+ options.only_symbolize_chrome_symbols)
+ if symbolizer.is_win:
+ RemapWinFiles(symfiles, options.symbol_base_directory, trace.version,
+ trace.is_64bit, options.only_symbolize_chrome_symbols)
+
+ SymbolizeFiles(symfiles, symbolizer)
+
+
+def FetchAndExtractBreakpadSymbols(symbol_base_directory,
+ breakpad_info_folder,
+ trace,
+ symbolizer,
+ cloud_storage_bucket):
+
+ if breakpad_info_folder:
+ # Using local symbols from |breakpad_info_folder|.
+ symbol_sub_dir = breakpad_info_folder
+ else:
+ # Fetching the symbols from GCS (OS dependent).
+ if trace.is_win:
+ folder = 'win64-pgo' if trace.is_64bit else 'win-pgo'
+ elif trace.is_mac:
+ folder = 'mac64'
+ elif trace.is_linux:
+ folder = 'linux64'
+ else:
+ raise Exception('OS not supported for Breakpad symbolization (%s/%s)' %
+ (trace.os, trace.version))
+
+ gsc_folder = 'desktop-*/' + trace.version + '/' + folder
+ gcs_file = gsc_folder + '/breakpad-info'
+
+ symbol_sub_dir = os.path.join(symbol_base_directory,
+ 'breakpad-info_' + trace.version + '_' + folder)
+ zip_path = symbol_sub_dir + '/breakpad-info.zip'
+
+ # Check whether symbols are already downloaded and extracted.
+ if not os.path.isdir(symbol_sub_dir):
+ if cloud_storage.Exists(cloud_storage_bucket, gcs_file + '.zip'):
+ # Some version, like mac, doesn't have the .zip extension.
+ gcs_file = gcs_file + '.zip'
+ elif not cloud_storage.Exists(cloud_storage_bucket, gcs_file):
+ print("Can't find symbols on GCS " + gcs_file + ".")
+ return False
+ print('Downloading symbols files from GCS, please wait.')
+ cloud_storage.Get(cloud_storage_bucket, gcs_file, zip_path)
+
+ with zipfile.ZipFile(zip_path, 'r') as zip_file:
+ zip_file.extractall(symbol_sub_dir)
+ os.remove(zip_path)
+
+ # Parse breakpad module header (first line) and register known modules.
+ for root, _, filenames in os.walk(symbol_sub_dir):
+ for filename in filenames:
+ full_filename = os.path.abspath(os.path.join(root, filename))
+ with open(full_filename, 'r') as file_handle:
+ first_line = file_handle.readline()
+ fragments = first_line.rstrip().split()
+ if fragments[0] == 'MODULE':
+ binary = ' '.join(fragments[4:]).lower()
+ module_name, extension = os.path.splitext(binary)
+ if extension == ".pdb":
+ binary = module_name
+ symbolizer.breakpad_modules[binary] = full_filename
+
+ return True
+
+
+def OpenTraceFile(file_path, mode):
+ if file_path.endswith('.gz'):
+ return gzip.open(file_path, mode + 'b')
+ else:
+ return open(file_path, mode + 't')
+
+
+def FetchAndExtractSymbolsMac(symbol_base_directory, version,
+ cloud_storage_bucket):
+ def GetLocalPath(base_dir, version):
+ return os.path.join(base_dir, version + ".tar.bz2")
+ def GetSymbolsPath(version):
+ return "desktop-*/" + version + "/mac64/Google Chrome.dSYM.tar.bz2"
+ def ExtractSymbolTarFile(symbol_sub_dir, symbol_tar_file):
+ os.makedirs(symbol_sub_dir)
+ with tarfile.open(os.path.expanduser(symbol_tar_file), "r:bz2") as tar:
+ tar.extractall(symbol_sub_dir)
+
+ symbol_sub_dir = os.path.join(symbol_base_directory, version)
+ if os.path.isdir(symbol_sub_dir):
+ return True
+
+ bzip_path = GetLocalPath(symbol_base_directory, version)
+ if not os.path.isfile(bzip_path):
+ if not cloud_storage.Exists(cloud_storage_bucket, GetSymbolsPath(version)):
+ print("Can't find symbols on GCS '%s'." % version)
+ return False
+ print("Downloading symbols files from GCS, please wait.")
+ cloud_storage.Get(cloud_storage_bucket, GetSymbolsPath(version), bzip_path)
+
+ ExtractSymbolTarFile(symbol_sub_dir, bzip_path)
+ os.remove(bzip_path)
+ return True
+
+
+def FetchAndExtractSymbolsWin(symbol_base_directory, version, is64bit,
+ cloud_storage_bucket):
+ def DownloadAndExtractZipFile(zip_path, source, destination):
+ if not os.path.isfile(zip_path):
+ if not cloud_storage.Exists(cloud_storage_bucket, source):
+ print("Can't find symbols on GCS '%s'." % version)
+ return False
+ print("Downloading symbols files from GCS, please wait.")
+ cloud_storage.Get(cloud_storage_bucket, source, zip_path)
+ if not os.path.isfile(zip_path):
+ print("Can't download symbols on GCS.")
+ return False
+ with zipfile.ZipFile(zip_path, "r") as zip_file:
+ for member in zip_file.namelist():
+ filename = os.path.basename(member)
+ # Skip directories.
+ if not filename:
+ continue
+ # Extract archived files.
+ source = zip_file.open(member)
+ target = file(os.path.join(destination, filename), "wb")
+ with source, target:
+ shutil.copyfileobj(source, target)
+
+ folder = "win64" if is64bit else "win"
+ # Clang build (M61+)
+ folder_suffix = "-clang"
+ gcs_folder = "desktop-*/" + version + "/" + folder + folder_suffix + "/"
+ if not cloud_storage.Exists(cloud_storage_bucket, gcs_folder):
+ # MSVC build (before M61)
+ folder_suffix = "-pgo"
+ gcs_folder = "desktop-*/" + version + "/" + folder + folder_suffix + "/"
+
+ symbol_sub_dir = os.path.join(symbol_base_directory,
+ "chrome-" + folder + "-" + version)
+ if os.path.isdir(symbol_sub_dir):
+ return True
+
+ os.makedirs(symbol_sub_dir)
+ DownloadAndExtractZipFile(
+ os.path.join(symbol_base_directory,
+ "chrome-" + folder + "-" + version + "-syms.zip"),
+ gcs_folder + "chrome-win32-syms.zip",
+ symbol_sub_dir)
+ DownloadAndExtractZipFile(
+ os.path.join(symbol_base_directory,
+ "chrome-" + folder + "-" + version + ".zip"),
+ gcs_folder + "chrome-" + folder + folder_suffix + ".zip",
+ symbol_sub_dir)
+
+ return True
+
+# Suffix used for backup files.
+BACKUP_FILE_TAG = '.BACKUP'
+
+def main(args):
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ 'file',
+ help='Trace file to symbolize (.json or .json.gz)')
+
+ parser.add_argument(
+ '--no-backup', dest='backup', action='store_false',
+ help="Don't create {} files".format(BACKUP_FILE_TAG))
+
+ parser.add_argument(
+ '--is-local-build', action='store_true',
+ help="Indicate that the memlog trace is from a local build of Chromium.")
+
+ parser.add_argument(
+ '--output-directory',
+ help='The path to the build output directory, such as out/Debug.')
+
+ parser.add_argument(
+ '--only-symbolize-chrome-symbols',
+ action='store_true',
+ help='Prevents symbolization of non-Chrome [system] symbols.')
+
+ parser.add_argument(
+ '--cloud-storage-bucket', default='chrome-unsigned',
+ help="Bucket that holds symbols for official Chrome builds. "
+ "Used by tests, which don't have access to the default bucket.")
+
+ parser.add_argument(
+ '--addr2line-executable', default=None,
+ help="The path to the executable used to convert address to line."
+ "Default uses the executable found in the PATH environment variable."
+ "Used by tests, which don't have the executable.")
+
+ parser.add_argument(
+ '--use-breakpad-symbols',
+ action='store_true',
+ help='Use breakpad symbols files for symbolisation.')
+
+ parser.add_argument(
+ '--breakpad-symbols-directory', default=None,
+ help='A path to a directory containing breakpad symbols.')
+
+ home_dir = os.path.expanduser('~')
+ default_dir = os.path.join(home_dir, "symbols")
+ parser.add_argument(
+ '--symbol-base-directory',
+ default=default_dir,
+ help='Directory where symbols are downloaded and cached.')
+
+ options = parser.parse_args(args)
+
+ symbolizer = Symbolizer(options.addr2line_executable)
+ if (symbolizer.symbolizer_path is None and
+ not options.use_breakpad_symbols):
+ sys.exit("Can't symbolize - no %s in PATH." % symbolizer.binary)
+
+ trace_file_path = options.file
+
+ print('Reading trace file...')
+ with OpenTraceFile(trace_file_path, 'r') as trace_file:
+ trace = Trace(json.load(trace_file))
+ print('Trace loaded for %s/%s' % (trace.os, trace.version))
+
+ trace.is_chromium = options.is_local_build
+
+ # Perform some sanity checks.
+ if (trace.is_win and sys.platform != 'win32' and
+ not options.use_breakpad_symbols):
+ print("Cannot symbolize a windows trace on this architecture!")
+ return False
+
+ # If the trace is from Chromium, assume that symbols are already present.
+ # Otherwise the trace is from Google Chrome. Assume that this is not a local
+ # build of Google Chrome with symbols, and that we need to fetch symbols
+ # from gcs.
+ if trace.is_chromium or options.output_directory:
+ if options.use_breakpad_symbols and options.breakpad_symbols_directory:
+ # Local build with local symbols.
+ FetchAndExtractBreakpadSymbols(
+ options.symbol_base_directory,
+ options.breakpad_symbols_directory,
+ trace, symbolizer,
+ options.cloud_storage_bucket)
+ else:
+ has_symbols = False
+ if options.use_breakpad_symbols:
+ # Official build, using Breakpad symbolization.
+ has_symbols = FetchAndExtractBreakpadSymbols(
+ options.symbol_base_directory,
+ options.breakpad_symbols_directory,
+ trace, symbolizer,
+ options.cloud_storage_bucket)
+ else:
+ # Official build, using native symbolization.
+ if symbolizer.is_mac:
+ has_symbols = FetchAndExtractSymbolsMac(options.symbol_base_directory,
+ trace.version,
+ options.cloud_storage_bucket)
+ elif symbolizer.is_win:
+ has_symbols = FetchAndExtractSymbolsWin(options.symbol_base_directory,
+ trace.version, trace.is_64bit,
+ options.cloud_storage_bucket)
+ else:
+ raise Exception('OS not supported for native symbolization (%s/%s)' %
+ (trace.os, trace.version))
+ if not has_symbols:
+ print('Cannot fetch symbols from GCS')
+ return False
+
+ SymbolizeTrace(options, trace, symbolizer)
+
+ if trace.modified:
+ trace.ApplyModifications()
+
+ if options.backup:
+ backup_file_path = trace_file_path + BACKUP_FILE_TAG
+ print('Backing up trace file to {}'.format(backup_file_path))
+ os.rename(trace_file_path, backup_file_path)
+
+ print('Updating the trace file...')
+ with OpenTraceFile(trace_file_path, 'w') as trace_file:
+ trace_file.write(json.dumps(trace.node))
+ else:
+ print('No modifications were made - not updating the trace file.')
+ return True
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_atos_regex.py b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_atos_regex.py
new file mode 100644
index 00000000000..65356c3efa3
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_atos_regex.py
@@ -0,0 +1,36 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import re
+
+
+class AtosRegexMatcher(object):
+ def __init__(self):
+ """
+ Atos output has two useful forms:
+ 1. <name> (in <library name>) (<filename>:<linenumber>)
+ 2. <name> (in <library name>) + <symbol offset>
+ And two less useful forms:
+ 3. <address>
+ 4. <address> (in <library name>)
+ e.g.
+ 1. -[SKTGraphicView drawRect:] (in Sketch) (SKTGraphicView.m:445)
+ 2. malloc (in libsystem_malloc.dylib) + 42
+ 3. 0x4a12
+ 4. 0x00000d9a (in Chromium)
+
+ We don't bother checking for the latter two, and just return the full
+ output.
+ """
+ self._regex1 = re.compile(r"(.*) \(in (.+)\) \((.+):(\d+)\)")
+ self._regex2 = re.compile(r"(.*) \(in (.+)\) \+ (\d+)")
+
+ def Match(self, text):
+ result = self._regex1.match(text)
+ if result:
+ return result.group(1)
+ result = self._regex2.match(text)
+ if result:
+ return result.group(1)
+ return text
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_atos_regex_unittest.py b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_atos_regex_unittest.py
new file mode 100644
index 00000000000..28b488badde
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_atos_regex_unittest.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import sys
+import unittest
+
+from . import symbolize_trace_atos_regex
+
+
+class AtosRegexTest(unittest.TestCase):
+ def testRegex(self):
+ if sys.platform != "darwin":
+ return
+ matcher = symbolize_trace_atos_regex.AtosRegexMatcher()
+ text = "-[SKTGraphicView drawRect:] (in Sketch) (SKTGraphicView.m:445)"
+ output = matcher.Match(text)
+ self.assertEqual("-[SKTGraphicView drawRect:]", output)
+
+ text = "malloc (in libsystem_malloc.dylib) + 42"
+ output = matcher.Match(text)
+ self.assertEqual("malloc", output)
+
+ expected_output = (
+ "content::CacheStorage::MatchAllCaches(std::__1::unique_ptr<content::Se"
+ "rviceWorkerFetchRequest, std::__1::default_delete<content::ServiceWork"
+ "erFetchRequest> >, content::CacheStorageCacheQueryParams const&, base:"
+ ":Callback<void (content::CacheStorageError, std::__1::unique_ptr<conte"
+ "nt::ServiceWorkerResponse, std::__1::default_delete<content::ServiceWo"
+ "rkerResponse> >, std::__1::unique_ptr<storage::BlobDataHandle, std::__"
+ "1::default_delete<storage::BlobDataHandle> >), (base::internal::CopyMo"
+ "de)1, (base::internal::RepeatMode)1> const&)"
+ )
+ text = expected_output + " (in Chromium Framework) (ref_counted.h:322)"
+ output = matcher.Match(text)
+ self.assertEqual(expected_output, output)
+
+ text = "0x4a12"
+ output = matcher.Match(text)
+ self.assertEqual(text, output)
+
+ text = "0x00000d9a (in Chromium)"
+ output = matcher.Match(text)
+ self.assertEqual(text, output)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_end_to_end_test_slow.py b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_end_to_end_test_slow.py
new file mode 100755
index 00000000000..0a416800596
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_end_to_end_test_slow.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# pylint: disable=line-too-long
+# To upload test files, use the command:
+# <path_to_depot_tools>/upload_to_google_storage.py --bucket chrome-partner-telemetry <path_to_data_dir>/linux_trace_v2_breakpad_postsymbolization.json.gz
+#
+# To run this test suite, use ./tracing/bin/run_symbolizer_tests
+
+from __future__ import print_function
+
+import json
+import os
+import shutil
+import sys
+import tempfile
+import unittest
+
+from tracing.extras.symbolizer import symbolize_trace
+
+_THIS_DIR_PATH = os.path.abspath(os.path.dirname(__file__))
+_TRACING_DIR = os.path.abspath(
+ os.path.join(_THIS_DIR_PATH,
+ os.path.pardir,
+ os.path.pardir,
+ os.path.pardir))
+_PY_UTILS_PATH = os.path.abspath(os.path.join(
+ _TRACING_DIR,
+ os.path.pardir,
+ 'common',
+ 'py_utils'))
+sys.path.append(_PY_UTILS_PATH)
+import py_utils.cloud_storage as cloud_storage # pylint: disable=wrong-import-position
+
+
+def _DownloadFromCloudStorage(path):
+ print('Downloading %s from gcs.' % (path))
+ cloud_storage.GetIfChanged(path, cloud_storage.PARTNER_BUCKET)
+
+
+class SymbolizeTraceEndToEndTest(unittest.TestCase):
+ def _ValidateTrace(self, trace_path, expectations):
+ with symbolize_trace.OpenTraceFile(trace_path, 'r') as trace_file:
+ trace = symbolize_trace.Trace(json.load(trace_file))
+ # Find the browser process.
+ browser = None
+ for process in trace.processes:
+ if process.name == expectations['process']:
+ browser = process
+ self.assertTrue(browser)
+
+ # Look for a frame with a symbolize name, and check that it has the right
+ # parent.
+ frames = browser.stack_frame_map.frame_by_id
+ exact = expectations['frame_exact']
+ found = False
+ for _, frame in frames.items():
+ if frame.name.strip() == exact['frame_name']:
+ parent_id = frame.parent_id
+ if frames[parent_id].name.strip() == exact['parent_name']:
+ found = True
+ break
+
+ self.assertTrue(found)
+
+
+ def _RunSymbolizationOnTrace(self, pre_symbolization, expectations,
+ extra_options):
+ trace_presymbolization_path = os.path.join(
+ _THIS_DIR_PATH, 'data', pre_symbolization)
+ _DownloadFromCloudStorage(trace_presymbolization_path)
+ self.assertTrue(os.path.exists(trace_presymbolization_path))
+
+ temporary_fd, temporary_trace = tempfile.mkstemp(suffix='.json.gz')
+
+ symbolization_options = ['--only-symbolize-chrome-symbols',
+ '--no-backup',
+ '--cloud-storage-bucket',
+ cloud_storage.PARTNER_BUCKET,
+ temporary_trace]
+
+ symbolization_options.extend(extra_options)
+
+ # On windows, a pre-built version of addr2line-pdb is provided.
+ if sys.platform == 'win32':
+ addr2line_path = os.path.join(
+ _THIS_DIR_PATH, 'data', 'addr2line-pdb.exe')
+ _DownloadFromCloudStorage(addr2line_path)
+ self.assertTrue(os.path.exists(addr2line_path))
+ symbolization_options += ['--addr2line-executable', addr2line_path]
+
+ # Execute symbolization and compare results with the expected trace.
+ try:
+ shutil.copy(trace_presymbolization_path, temporary_trace)
+ self.assertTrue(symbolize_trace.main(symbolization_options))
+ self._ValidateTrace(temporary_trace, expectations)
+ finally:
+ os.close(temporary_fd)
+ if os.path.exists(temporary_trace):
+ os.remove(temporary_trace)
+
+
+ def testMacv2(self):
+ if sys.platform != 'darwin':
+ return
+ # The corresponding macOS Chrome symbols must be uploaded to
+ # "gs://chrome-partner-telemetry/desktop-symbolizer-test/66.0.3334.0/mac64/Google Chrome.dSYM.tar.bz2"
+ # since the waterfall bots do not have access to the chrome-unsigned bucket.
+ expectations = {}
+ expectations['process'] = 'Browser'
+ expectations['frame_exact'] = {
+ 'parent_name': 'ProfileImpl::OnPrefsLoaded(Profile::CreateMode, bool)',
+ 'frame_name': 'ProfileImpl::OnLocaleReady()'
+ }
+ self._RunSymbolizationOnTrace(
+ 'mac_trace_v2_presymbolization.json.gz',
+ expectations, [])
+
+ def testMacv2Breakpad(self):
+ # The corresponding macOS Chrome symbols must be uploaded to
+ # "gs://chrome-partner-telemetry/desktop-symbolizer-test/66.0.3334.0/mac64/breakpad-info"
+ # since the waterfall bots do not have access to the chrome-unsigned bucket.
+ expectations = {}
+ expectations['process'] = 'Browser'
+ expectations['frame_exact'] = {
+ 'parent_name': 'ProfileImpl::OnPrefsLoaded(Profile::CreateMode, bool)',
+ 'frame_name': 'ProfileImpl::OnLocaleReady()'
+ }
+ self._RunSymbolizationOnTrace(
+ 'mac_trace_v2_presymbolization.json.gz',
+ expectations, ['--use-breakpad-symbols'])
+
+ def testWin64v1(self):
+ if sys.platform != 'win32':
+ return
+
+ expectations = {}
+ expectations['process'] = 'Browser'
+ expectations['frame_exact'] = {
+ 'parent_name': 'ChromeMain',
+ 'frame_name': 'content::ContentMain'
+ }
+ # The corresponding Win64 Chrome symbols must be uploaded to
+ # "gs://chrome-partner-telemetry/desktop-symbolizer-test/61.0.3130.0/"
+ # "win64-pgo/chrome-win32-syms.zip"
+ # and the corresponding executables to
+ # "gs://chrome-partner-telemetry/desktop-symbolizer-test/61.0.3130.0/"
+ # "win64-pgo/chrome-win64-pgo.zip"
+ # since the waterfall bots do not have access to the chrome-unsigned bucket.
+ self._RunSymbolizationOnTrace('windows_trace_v1_presymbolization.json.gz',
+ expectations,
+ [])
+
+ def testWin64v2(self):
+ if sys.platform != 'win32':
+ return
+
+ expectations = {}
+ expectations['process'] = 'Browser'
+ expectations['frame_exact'] = {
+ 'parent_name': 'base::MessagePumpWin::Run',
+ 'frame_name': 'base::MessagePumpForUI::DoRunLoop'
+ }
+ # The corresponding Win64 Chrome symbols must be uploaded to
+ # "gs://chrome-partner-telemetry/desktop-symbolizer-test/61.0.3142.0/"
+ # "win64-pgo/chrome-win32-syms.zip"
+ # and the corresponding executables to
+ # "gs://chrome-partner-telemetry/desktop-symbolizer-test/61.0.3142.0/"
+ # "win64-pgo/chrome-win64-pgo.zip"
+ # since the waterfall bots do not have access to the chrome-unsigned bucket.
+ self._RunSymbolizationOnTrace('windows_trace_v2_presymbolization.json.gz',
+ expectations,
+ [])
+
+
+ def testLinuxv2(self):
+ # The corresponding Linux breakpad symbols must be uploaded to
+ # "gs://chrome-partner-telemetry/desktop-symbolizer-test/64.0.3282.24/linux64/breakpad-info.zip"
+ # since the waterfall bots do not have access to the chrome-unsigned bucket.
+ expectations = {}
+ expectations['process'] = 'Renderer'
+ expectations['frame_exact'] = {
+ 'parent_name': 'cc::LayerTreeSettings::LayerTreeSettings(cc::LayerTreeSettings const&)',
+ 'frame_name': 'viz::ResourceSettings::ResourceSettings(viz::ResourceSettings const&)'
+ }
+ self._RunSymbolizationOnTrace(
+ 'linux_trace_v2_presymbolization.json.gz',
+ expectations,
+ ['--use-breakpad-symbols'])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader.py b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader.py
new file mode 100644
index 00000000000..ea8a6366671
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader.py
@@ -0,0 +1,26 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import re
+import subprocess
+
+
+def ReadMachOTextLoadAddress(file_name):
+ """
+ This function returns the load address of the TEXT segment of a Mach-O file.
+ """
+ regex = re.compile(r".* vmaddr 0x([\dabcdef]*)")
+ cmd = ["otool", "-l", file_name]
+ output = subprocess.check_output(cmd).split('\n')
+ for i in range(len(output) - 3):
+ # It's possible to use a regex here instead, but these conditionals are much
+ # clearer.
+ if ("cmd LC_SEGMENT_64" in output[i] and
+ "cmdsize" in output[i + 1] and
+ "segname __TEXT" in output[i + 2] and
+ "vmaddr" in output[i + 3]):
+ result = regex.match(output[i + 3])
+ assert result
+ return int(result.group(1), 16)
+ return None
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader_unittest.py b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader_unittest.py
new file mode 100644
index 00000000000..f0058abda51
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_macho_reader_unittest.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import sys
+import unittest
+
+from . import symbolize_trace_macho_reader
+
+
+class AtosRegexTest(unittest.TestCase):
+ def testRegex(self):
+ if sys.platform != "darwin":
+ return
+ file_name = "/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit"
+ result = symbolize_trace_macho_reader.ReadMachOTextLoadAddress(file_name)
+ self.assertNotEqual(None, result)
+
+ file_name = "/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa"
+ result = symbolize_trace_macho_reader.ReadMachOTextLoadAddress(file_name)
+ self.assertNotEqual(None, result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_unittest.py b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_unittest.py
new file mode 100755
index 00000000000..1b9b7b912c5
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/symbolizer/symbolize_trace_unittest.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+from . import symbolize_trace
+
+class StackFrameMapTest(unittest.TestCase):
+
+ def assertStackFrame(self, stack_frame_map, frame_id, name, parent_id=None):
+ self.assertTrue(frame_id in stack_frame_map.frame_by_id)
+ frame = stack_frame_map.frame_by_id[frame_id]
+ self.assertEqual(name, frame.name)
+ self.assertEqual(parent_id, frame.parent_id)
+
+ def testParseNext(self):
+ string_map = symbolize_trace.StringMap()
+ stack_frame_map = symbolize_trace.StackFrameMap()
+
+ # Check that ParseNext() actually parses anything.
+ stack_frame_map.ParseNext(
+ symbolize_trace.Trace.HEAP_DUMP_VERSION_1,
+ [
+ {'id': 1, 'name_sid': string_map.AddString('main')},
+ {'id': 45, 'name_sid': string_map.AddString('foo'), 'parent': 1},
+ ],
+ string_map)
+ self.assertStackFrame(stack_frame_map, 1, 'main')
+ self.assertStackFrame(stack_frame_map, 45, 'foo', parent_id=1)
+
+ # Check that ParseNext() retains all previously parsed frames.
+ stack_frame_map.ParseNext(
+ symbolize_trace.Trace.HEAP_DUMP_VERSION_1,
+ [
+ {'id': 33, 'name_sid': string_map.AddString('bar')},
+ ],
+ string_map)
+ self.assertStackFrame(stack_frame_map, 1, 'main')
+ self.assertStackFrame(stack_frame_map, 45, 'foo', parent_id=1)
+ self.assertStackFrame(stack_frame_map, 33, 'bar')
+
+ def testParseNextLegacy(self):
+ stack_frame_map = symbolize_trace.StackFrameMap()
+
+ stack_frame_map.ParseNext(
+ symbolize_trace.Trace.HEAP_DUMP_VERSION_LEGACY,
+ {
+ 1: {'name': 'main'},
+ 45: {'name': 'foo', 'parent': 1},
+ },
+ string_map=None)
+ self.assertStackFrame(stack_frame_map, 1, 'main')
+ self.assertStackFrame(stack_frame_map, 45, 'foo', parent_id=1)
+
+ # When parsing legacy format, ParseNext() is expected to be called once.
+ self.assertRaises(
+ Exception,
+ stack_frame_map.ParseNext,
+ symbolize_trace.Trace.HEAP_DUMP_VERSION_LEGACY,
+ {
+ 33: {'name': 'bar'},
+ },
+ string_map=None)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/system_stats/system_stats_snapshot.html b/chromium/third_party/catapult/tracing/tracing/extras/system_stats/system_stats_snapshot.html
new file mode 100644
index 00000000000..4b23769967e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/system_stats/system_stats_snapshot.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/util.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.system_stats', function() {
+ const ObjectSnapshot = tr.model.ObjectSnapshot;
+
+ /**
+ * @constructor
+ */
+ function SystemStatsSnapshot(objectInstance, ts, args) {
+ ObjectSnapshot.apply(this, arguments);
+ this.objectInstance = objectInstance;
+ this.ts = ts;
+ this.args = args;
+ this.stats_ = args;
+ }
+
+ SystemStatsSnapshot.prototype = {
+ __proto__: ObjectSnapshot.prototype,
+
+ initialize() {
+ if (this.args.length === 0) {
+ throw new Error('No system stats snapshot data.');
+ }
+ this.stats_ = this.args;
+ },
+
+ getStats() {
+ return this.stats_;
+ },
+
+ setStats(stats) {
+ this.stats_ = stats;
+ }
+ };
+
+ ObjectSnapshot.subTypes.register(
+ SystemStatsSnapshot,
+ {typeName: 'base::TraceEventSystemStatsMonitor::SystemStats'});
+
+ return {
+ SystemStatsSnapshot,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/systrace_config.html b/chromium/third_party/catapult/tracing/tracing/extras/systrace_config.html
new file mode 100644
index 00000000000..7711959fcc7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/systrace_config.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/android/android_auditor.html">
+<link rel="import" href="/tracing/extras/cpu/cpu_usage_auditor.html">
+<link rel="import" href="/tracing/extras/importer/android/atrace_process_dump_importer.html">
+<link rel="import" href="/tracing/extras/importer/android/event_log_importer.html">
+<link rel="import" href="/tracing/extras/importer/android/process_data_importer.html">
+<link rel="import" href="/tracing/extras/importer/battor_importer.html">
+<link rel="import" href="/tracing/extras/importer/ddms_importer.html">
+<link rel="import" href="/tracing/extras/importer/linux_perf/ftrace_importer.html">
+<link rel="import" href="/tracing/extras/memory/lowmemory_auditor.html">
+<link rel="import" href="/tracing/extras/vsync/vsync_auditor.html">
+<link rel="import" href="/tracing/importer/import.html">
+<link rel="import" href="/tracing/model/model.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/tquery/context.html b/chromium/third_party/catapult/tracing/tracing/extras/tquery/context.html
new file mode 100644
index 00000000000..8c893206543
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/tquery/context.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/utils.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.tquery', function() {
+ function Context() {
+ this.event = undefined;
+ this.ancestors = [];
+ }
+
+ Context.prototype = {
+ push(event) {
+ const ctx = new Context();
+ ctx.ancestors = this.ancestors.slice();
+ ctx.ancestors.push(event);
+ return ctx;
+ },
+
+ pop(event) {
+ const ctx = new Context();
+ ctx.event = this.ancestors[this.ancestors.length - 1];
+ ctx.ancestors = this.ancestors.slice(0, this.ancestors.length - 1);
+ return ctx;
+ }
+ };
+
+ return {
+ Context,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter.html b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter.html
new file mode 100644
index 00000000000..62126e3fc0c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/scripting_object.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.tquery', function() {
+ function Filter() {
+ tr.c.ScriptingObject.call(this);
+ }
+
+ Filter.normalizeFilterExpression = function(filterExpression) {
+ // Shortcut: naked strings and regexps can be used to match against slice
+ // titles.
+ if (filterExpression instanceof String ||
+ typeof(filterExpression) === 'string' ||
+ filterExpression instanceof RegExp) {
+ const filter = new tr.e.tquery.FilterHasTitle(filterExpression);
+ return filter;
+ }
+ return filterExpression;
+ };
+
+ Filter.prototype = {
+ __proto__: tr.c.ScriptingObject.prototype,
+
+ evaluate(context) {
+ throw new Error('Not implemented');
+ },
+
+ matchValue_(value, expected) {
+ if (expected instanceof RegExp) {
+ return expected.test(value);
+ } else if (expected instanceof Function) {
+ return expected(value);
+ }
+ return value === expected;
+ }
+ };
+
+ return {
+ Filter,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_all_of.html b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_all_of.html
new file mode 100644
index 00000000000..89f187100cc
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_all_of.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/scripting_controller.html">
+<link rel="import" href="/tracing/extras/tquery/filter.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.tquery', function() {
+ function FilterAllOf(opt_subExpressions) {
+ tr.e.tquery.Filter.call(this);
+ this.subExpressions = opt_subExpressions || [];
+ }
+
+ FilterAllOf.prototype = {
+ __proto__: tr.e.tquery.Filter.prototype,
+
+ set subExpressions(exprs) {
+ this.subExpressions_ = [];
+ for (let i = 0; i < exprs.length; i++) {
+ this.subExpressions_.push(
+ tr.e.tquery.Filter.normalizeFilterExpression(exprs[i]));
+ }
+ },
+
+ get subExpressions() {
+ return this.subExpressions_;
+ },
+
+ evaluate(context) {
+ if (!this.subExpressions.length) return true;
+ for (let i = 0; i < this.subExpressions.length; i++) {
+ if (!this.subExpressions[i].evaluate(context)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ };
+ tr.c.ScriptingObjectRegistry.register(
+ function() {
+ const exprs = [];
+ for (let i = 0; i < arguments.length; i++) {
+ exprs.push(arguments[i]);
+ }
+ return new FilterAllOf(exprs);
+ },
+ {
+ name: 'allOf'
+ }
+ );
+ return {
+ FilterAllOf,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_any_of.html b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_any_of.html
new file mode 100644
index 00000000000..3303adc53c9
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_any_of.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/scripting_controller.html">
+<link rel="import" href="/tracing/extras/tquery/filter.html">
+<link rel="import" href="/tracing/extras/tquery/filter_not.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.tquery', function() {
+ function FilterAnyOf(opt_subExpressions) {
+ tr.e.tquery.Filter.call(this);
+ this.subExpressions = opt_subExpressions || [];
+ }
+
+ FilterAnyOf.prototype = {
+ __proto__: tr.e.tquery.Filter.prototype,
+
+ set subExpressions(exprs) {
+ this.subExpressions_ = [];
+ for (let i = 0; i < exprs.length; i++) {
+ this.subExpressions_.push(
+ tr.e.tquery.Filter.normalizeFilterExpression(exprs[i]));
+ }
+ },
+
+ get subExpressions() {
+ return this.subExpressions_;
+ },
+
+ evaluate(context) {
+ if (!this.subExpressions.length) return true;
+ for (let i = 0; i < this.subExpressions.length; i++) {
+ if (this.subExpressions[i].evaluate(context)) return true;
+ }
+ return false;
+ }
+ };
+ tr.c.ScriptingObjectRegistry.register(
+ function() {
+ const exprs = Array.prototype.slice.call(arguments);
+ return new FilterAnyOf(exprs);
+ },
+ {
+ name: 'anyOf'
+ }
+ );
+ tr.c.ScriptingObjectRegistry.register(
+ function() {
+ const exprs = Array.prototype.slice.call(arguments);
+ return new tr.e.tquery.FilterNot(new FilterAnyOf(exprs));
+ },
+ {
+ name: 'noneOf'
+ }
+ );
+ return {
+ FilterAnyOf,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_has_ancestor.html b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_has_ancestor.html
new file mode 100644
index 00000000000..98597ccb2da
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_has_ancestor.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/scripting_controller.html">
+<link rel="import" href="/tracing/extras/tquery/filter.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.tquery', function() {
+ function FilterHasAncestor(opt_subExpression) {
+ this.subExpression = opt_subExpression;
+ }
+
+ FilterHasAncestor.prototype = {
+ __proto__: tr.e.tquery.Filter.prototype,
+
+ set subExpression(expr) {
+ this.subExpression_ = tr.e.tquery.Filter.normalizeFilterExpression(expr);
+ },
+
+ get subExpression() {
+ return this.subExpression_;
+ },
+
+ evaluate(context) {
+ if (!this.subExpression) {
+ return context.ancestors.length > 0;
+ }
+ while (context.ancestors.length) {
+ context = context.pop();
+ if (this.subExpression.evaluate(context)) return true;
+ }
+ return false;
+ }
+ };
+ tr.c.ScriptingObjectRegistry.register(
+ function(subExpression) {
+ return new FilterHasAncestor(subExpression);
+ },
+ {
+ name: 'hasAncestor'
+ }
+ );
+ return {
+ FilterHasAncestor,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_has_duration.html b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_has_duration.html
new file mode 100644
index 00000000000..ab5828e2dda
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_has_duration.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/scripting_controller.html">
+<link rel="import" href="/tracing/extras/tquery/filter.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.tquery', function() {
+ function FilterHasDuration(minValueOrExpected, opt_maxValue) {
+ if (minValueOrExpected !== undefined && opt_maxValue !== undefined) {
+ this.minValue = minValueOrExpected;
+ this.maxValue = opt_maxValue;
+ } else {
+ this.expected = minValueOrExpected;
+ }
+ }
+
+ FilterHasDuration.prototype = {
+ __proto__: tr.e.tquery.Filter.prototype,
+
+ evaluate(context) {
+ if (context.event.duration === undefined) return false;
+ if (this.minValue !== undefined && this.maxValue !== undefined) {
+ return context.event.duration >= this.minValue &&
+ context.event.duration <= this.maxValue;
+ }
+ return this.matchValue_(context.event.duration, this.expected);
+ }
+ };
+ tr.c.ScriptingObjectRegistry.register(
+ function(minValueOrExpected, opt_maxValue) {
+ return new FilterHasDuration(minValueOrExpected, opt_maxValue);
+ },
+ {
+ name: 'hasDuration'
+ }
+ );
+ return {
+ FilterHasDuration,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_has_title.html b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_has_title.html
new file mode 100644
index 00000000000..dcd4c8ac79b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_has_title.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/scripting_controller.html">
+<link rel="import" href="/tracing/extras/tquery/filter.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.tquery', function() {
+ function FilterHasTitle(expected) {
+ tr.e.tquery.Filter.call(this);
+ this.expected = expected;
+ }
+
+ FilterHasTitle.prototype = {
+ __proto__: tr.e.tquery.Filter.prototype,
+
+ evaluate(context) {
+ return this.matchValue_(context.event.title, this.expected);
+ }
+ };
+ tr.c.ScriptingObjectRegistry.register(
+ function(expected) {
+ const filter = new tr.e.tquery.FilterHasTitle(expected);
+ return filter;
+ },
+ {
+ name: 'hasTitle'
+ }
+ );
+
+ return {
+ FilterHasTitle,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_is_top_level.html b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_is_top_level.html
new file mode 100644
index 00000000000..24fd66d8aa3
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_is_top_level.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/scripting_controller.html">
+<link rel="import" href="/tracing/extras/tquery/filter.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.tquery', function() {
+ function FilterIsTopLevel(opt_subExpression) {
+ this.subExpression = opt_subExpression;
+ }
+
+ FilterIsTopLevel.prototype = {
+ __proto__: tr.e.tquery.Filter.prototype,
+
+ set subExpression(expr) {
+ this.subExpression_ = tr.e.tquery.Filter.normalizeFilterExpression(expr);
+ },
+
+ get subExpression() {
+ return this.subExpression_;
+ },
+
+ evaluate(context) {
+ if (context.ancestors.length > 0) return false;
+ if (!this.subExpression) return true;
+ return this.subExpression.evaluate(context);
+ }
+ };
+ tr.c.ScriptingObjectRegistry.register(
+ function(subExpression) {
+ return new FilterIsTopLevel(subExpression);
+ },
+ {
+ name: 'isTopLevel'
+ }
+ );
+ return {
+ FilterIsTopLevel,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_not.html b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_not.html
new file mode 100644
index 00000000000..d3014376113
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/tquery/filter_not.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/scripting_controller.html">
+<link rel="import" href="/tracing/extras/tquery/filter.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.tquery', function() {
+ function FilterNot(subExpression) {
+ tr.e.tquery.Filter.call(this);
+ this.subExpression = subExpression;
+ }
+
+ FilterNot.prototype = {
+ __proto__: tr.e.tquery.Filter.prototype,
+
+ set subExpression(expr) {
+ this.subExpression_ = tr.e.tquery.Filter.normalizeFilterExpression(expr);
+ },
+
+ get subExpression() {
+ return this.subExpression_;
+ },
+
+ evaluate(context) {
+ return !this.subExpression.evaluate(context);
+ }
+ };
+ tr.c.ScriptingObjectRegistry.register(
+ function() {
+ const exprs = Array.prototype.slice.call(arguments);
+ if (exprs.length !== 1) {
+ throw new Error('not() must have exactly one subexpression');
+ }
+ return new FilterNot(exprs[0]);
+ },
+ {
+ name: 'not'
+ }
+ );
+ return {
+ FilterNot,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/tquery/tquery.html b/chromium/third_party/catapult/tracing/tracing/extras/tquery/tquery.html
new file mode 100644
index 00000000000..c4b5d19e995
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/tquery/tquery.html
@@ -0,0 +1,188 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/task.html">
+<link rel="import" href="/tracing/core/filter.html">
+<link rel="import" href="/tracing/core/scripting_object.html">
+<link rel="import" href="/tracing/extras/tquery/context.html">
+<link rel="import" href="/tracing/extras/tquery/filter_all_of.html">
+<link rel="import" href="/tracing/extras/tquery/filter_any_of.html">
+<link rel="import" href="/tracing/extras/tquery/filter_has_ancestor.html">
+<link rel="import" href="/tracing/extras/tquery/filter_has_duration.html">
+<link rel="import" href="/tracing/extras/tquery/filter_has_title.html">
+<link rel="import" href="/tracing/extras/tquery/filter_is_top_level.html">
+<link rel="import" href="/tracing/extras/tquery/filter_not.html">
+<link rel="import" href="/tracing/model/event_set.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.tquery', function() {
+ function addEventTreeToSelection(selection, event) {
+ selection.push(event);
+ if (!event.subSlices) return;
+ event.subSlices.forEach(
+ addEventTreeToSelection.bind(undefined, selection));
+ }
+
+ function TQuery(model) {
+ tr.c.ScriptingObject.call(this);
+
+ this.model_ = model;
+ this.parent_ = undefined;
+ this.filterExpression_ = undefined;
+ // Memoized filtering result.
+ this.selection_ = undefined;
+ }
+
+ TQuery.prototype = {
+ __proto__: tr.c.ScriptingObject.prototype,
+
+ onModelChanged(model) {
+ this.model_ = model;
+ this.selection_ = undefined;
+ },
+
+ get brushingStateController() {
+ return this.brushingStateController_;
+ },
+
+ // Append a new filter expression to this query and return a query node
+ // that represents the result.
+ filter(filterExpression) {
+ const result = new TQuery(this.model_);
+ result.parent_ = this;
+ result.filterExpression_ =
+ tr.e.tquery.Filter.normalizeFilterExpression(filterExpression);
+ return result;
+ },
+
+ // Creates a graph of {Task} objects which will compute the selections for
+ // this filter object and all of its parents. The return value is an object
+ // with the following fields:
+ // - rootTask: {Task} which should be executed to kick off processing for
+ // the entire task graph.
+ // - lastTask: The final {Task} of the graph. Can be used by the caller to
+ // enqueue additional processing at the end.
+ // - lastNode: The last filter object in the task. It's selection property
+ // will contain the filtering result once |finalTask|
+ // completes.
+ createFilterTaskGraph_() {
+ // List of nodes in order from the current one to the root.
+ const nodes = [this];
+ while (nodes[nodes.length - 1].parent_) {
+ nodes.push(nodes[nodes.length - 1].parent_);
+ }
+
+ const rootTask = new tr.b.Task();
+ let lastTask = rootTask;
+ let node;
+ for (let i = nodes.length - 1; i >= 0; i--) {
+ node = nodes[i];
+ // Reuse any memoized result.
+ if (node.selection_ !== undefined) continue;
+ node.selection_ = new tr.model.EventSet();
+ if (node.parent_ === undefined) {
+ // If this is the root, start by collecting all objects from the
+ // model.
+ lastTask = lastTask.after(
+ this.selectEverythingAsTask_(node.selection_));
+ } else {
+ // Otherwise execute the filter expression for this node and fill
+ // in its selection.
+ const prevNode = nodes[i + 1];
+ lastTask = this.createFilterTaskForNode_(lastTask, node, prevNode);
+ }
+ }
+ return {rootTask, lastTask, lastNode: node};
+ },
+
+ createFilterTaskForNode_(lastTask, node, prevNode) {
+ return lastTask.after(function() {
+ // TODO(skyostil): Break into subtasks.
+ node.evaluateFilterExpression_(
+ prevNode.selection_, node.selection_);
+ }, this);
+ },
+
+ // Applies the result of a filter expression for a given event and all
+ // of its subslices and adds the matching events to an output selection.
+ evaluateFilterExpression_(inputSelection, outputSelection) {
+ const seenEvents = {};
+ inputSelection.forEach(function(event) {
+ const context = new tr.e.tquery.Context();
+ context.event = event;
+ this.evaluateFilterExpressionForEvent_(
+ context, inputSelection, outputSelection, seenEvents);
+ }.bind(this));
+ },
+
+ evaluateFilterExpressionForEvent_(
+ context, inputSelection, outputSelection, seenEvents) {
+ const event = context.event;
+ if (inputSelection.contains(event) && !seenEvents[event.guid]) {
+ seenEvents[event.guid] = true;
+ if (!this.filterExpression_ ||
+ this.filterExpression_.evaluate(context)) {
+ outputSelection.push(event);
+ }
+ }
+ if (!event.subSlices) return;
+ context = context.push(event);
+ for (let i = 0; i < event.subSlices.length; i++) {
+ context.event = event.subSlices[i];
+ this.evaluateFilterExpressionForEvent_(
+ context, inputSelection, outputSelection, seenEvents);
+ }
+ },
+
+ // Returns a task that fills the given selection with everything in the
+ // model.
+ selectEverythingAsTask_(selection) {
+ const filterTask = new tr.b.Task();
+ for (const container of this.model_.getDescendantEventContainers()) {
+ filterTask.subTask(() => {
+ for (const event of container.childEvents()) {
+ addEventTreeToSelection(selection, event);
+ }
+ }, this);
+ }
+ return filterTask;
+ },
+
+ // Returns a promise which will resolve into a {EventSet} representing the
+ // result of this query.
+ ready() {
+ return new Promise(function(resolve, reject) {
+ const graph = this.createFilterTaskGraph_();
+ graph.lastTask = graph.lastTask.after(function() {
+ resolve(this.selection_);
+ }, this);
+ tr.b.Task.RunWhenIdle(graph.rootTask);
+ }.bind(this));
+ },
+
+ get selection() {
+ if (this.selection_ === undefined) {
+ const graph = this.createFilterTaskGraph_();
+ tr.b.Task.RunSynchronously(graph.rootTask);
+ }
+ return this.selection_;
+ }
+ };
+ tr.c.ScriptingObjectRegistry.register(
+ new TQuery(),
+ {
+ name: '$t'
+ }
+ );
+
+ return {
+ TQuery,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/tquery/tquery_test.html b/chromium/third_party/catapult/tracing/tracing/extras/tquery/tquery_test.html
new file mode 100644
index 00000000000..1ecd7e6255a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/tquery/tquery_test.html
@@ -0,0 +1,247 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/core/scripting_controller.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/tquery/tquery.html">
+<link rel="import" href="/tracing/model/model.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function createTestModel(sliceCount) {
+ const slices = [];
+ for (let i = 0; i < sliceCount; i++) {
+ slices.push(tr.c.TestUtils.newSliceEx({start: 1, duration: 2}));
+ }
+ return createTestModelWithSlices(slices);
+ }
+
+ function createTestModelWithSlices(slices) {
+ const model = new tr.Model();
+ const cpu = model.kernel.getOrCreateCpu(1);
+ for (let i = 0; i < slices.length; i++) {
+ cpu.slices.push(slices[i]);
+ }
+ return model;
+ }
+
+ function getScriptObject(name) {
+ const typeInfos = tr.c.ScriptingObjectRegistry.getAllRegisteredTypeInfos();
+ for (let i = 0; i < typeInfos.length; i++) {
+ if (typeInfos[i].metadata.name === name) {
+ return typeInfos[i].constructor;
+ }
+ }
+ }
+
+ test('tqueryAsyncSelection', function() {
+ const model = createTestModel(3);
+ const tquery = new tr.e.tquery.TQuery(model);
+
+ return tquery.ready().then(function(selection) {
+ assert.strictEqual(selection.length, 3);
+ });
+ });
+
+ test('tquerySyncSelection', function() {
+ let model = createTestModel(3);
+ const tquery = new tr.e.tquery.TQuery(model);
+
+ assert.strictEqual(tquery.selection.length, 3);
+
+ // Selection should get reset when the model changes.
+ model = createTestModel(5);
+ tquery.onModelChanged(model);
+ assert.strictEqual(tquery.selection.length, 5);
+ });
+
+ test('tqueryPassThroughFiltering', function() {
+ const model = new createTestModel(3);
+ const tquery = new tr.e.tquery.TQuery(model);
+
+ const result = tquery.filter().filter().selection;
+ assert.strictEqual(result.length, 3);
+ });
+
+ test('tqueryFilterHasTitle', function() {
+ const hasTitle = getScriptObject('hasTitle');
+ const model = createTestModelWithSlices([
+ {guid: 1, title: 'a'},
+ {guid: 2, title: 'b'},
+ {guid: 3, title: 'c'}
+ ]);
+ const tquery = new tr.e.tquery.TQuery(model);
+
+ let result = tquery.filter(hasTitle('a')).selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 1);
+
+ result = tquery.filter('b').selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 2);
+
+ result = tquery.filter(/^c$/).selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 3);
+ });
+
+ test('tqueryFilterHasAncestor', function() {
+ const hasAncestor = getScriptObject('hasAncestor');
+ const model = createTestModelWithSlices([
+ {guid: 1, title: 'a'},
+ {guid: 2, title: 'b', subSlices: [{guid: 4}]},
+ {guid: 3, title: 'c'}
+ ]);
+ const tquery = new tr.e.tquery.TQuery(model);
+
+ let result = tquery.filter(hasAncestor('b')).selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 4);
+
+ result = tquery.filter(hasAncestor()).selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 4);
+
+ result = tquery.filter(hasAncestor('a')).selection;
+ assert.strictEqual(result.length, 0);
+ });
+
+ test('tqueryFilterAllOf', function() {
+ const allOf = getScriptObject('allOf');
+ const model = createTestModelWithSlices([
+ {guid: 1, title: 'a1'},
+ {guid: 2, title: 'b1'},
+ {guid: 3, title: 'c1'}
+ ]);
+ const tquery = new tr.e.tquery.TQuery(model);
+
+ let result = tquery.filter(allOf('a1')).selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 1);
+
+ result = tquery.filter(allOf('a1', /1/)).selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 1);
+
+ result = tquery.filter(allOf()).selection;
+ assert.strictEqual(result.length, 3);
+ });
+
+ test('tqueryFilterAnyOf', function() {
+ const anyOf = getScriptObject('anyOf');
+ const model = createTestModelWithSlices([
+ {guid: 1, title: 'a'},
+ {guid: 2, title: 'b'},
+ {guid: 3, title: 'c'}
+ ]);
+ const tquery = new tr.e.tquery.TQuery(model);
+
+ let result = tquery.filter(anyOf('a', 'b')).selection;
+ assert.strictEqual(result.length, 2);
+ let nextGuid = 1;
+ for (const event of result) {
+ assert.strictEqual(event.guid, nextGuid++);
+ }
+
+ result = tquery.filter(anyOf('not there', 'a')).selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 1);
+
+ result = tquery.filter(anyOf()).selection;
+ assert.strictEqual(result.length, 3);
+ });
+
+ test('tqueryFilterIsTopLevel', function() {
+ const isTopLevel = getScriptObject('isTopLevel');
+ const model = createTestModelWithSlices([
+ {guid: 1, title: 'a'},
+ {guid: 2, title: 'b', subSlices: [{guid: 4}]},
+ {guid: 3, title: 'c'}
+ ]);
+ const tquery = new tr.e.tquery.TQuery(model);
+
+ let result = tquery.filter(isTopLevel()).selection;
+ assert.strictEqual(result.length, 3);
+ let nextGuid = 1;
+ for (const event of result) {
+ assert.strictEqual(event.guid, nextGuid++);
+ }
+
+ result = tquery.filter(isTopLevel('a')).selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 1);
+ });
+
+ test('tqueryFilterHasDuration', function() {
+ const hasDuration = getScriptObject('hasDuration');
+ const model = createTestModelWithSlices([
+ {guid: 1, title: 'a', duration: 1},
+ {guid: 2, title: 'b', duration: 2},
+ {guid: 3, title: 'c', duration: 3},
+ {guid: 4, title: 'no duration'}
+ ]);
+ const tquery = new tr.e.tquery.TQuery(model);
+
+ let result = tquery.filter(hasDuration(1.5, 2.5)).selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 2);
+
+ result = tquery.filter(hasDuration(3, Infinity)).selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 3);
+
+ result = tquery.filter(hasDuration(-1, 0)).selection;
+ assert.strictEqual(result.length, 0);
+
+ result = tquery.filter(hasDuration(function(d) {
+ return d > 2;
+ })).selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 3);
+ });
+
+ test('tqueryFilterNot', function() {
+ const not = getScriptObject('not');
+ const model = createTestModelWithSlices([
+ {guid: 1, title: 'a'},
+ {guid: 2, title: 'b'},
+ {guid: 3, title: 'c'}
+ ]);
+ const tquery = new tr.e.tquery.TQuery(model);
+
+ const result = tquery.filter(not(/a/)).selection;
+ assert.strictEqual(result.length, 2);
+ let nextGuid = 2;
+ for (const event of result) {
+ assert.strictEqual(event.guid, nextGuid++);
+ }
+
+ // Test a not() without any subexpressions.
+ assert.throws(function() { not(); });
+
+ // Test a not() with too many subexpressions.
+ assert.throws(function() { not(/a/, /b/); });
+ });
+
+ test('tqueryFilterNoneOf', function() {
+ const noneOf = getScriptObject('noneOf');
+ const model = createTestModelWithSlices([
+ {guid: 1, title: 'a'},
+ {guid: 2, title: 'b'},
+ {guid: 3, title: 'c'}
+ ]);
+ const tquery = new tr.e.tquery.TQuery(model);
+
+ const result = tquery.filter(noneOf(/a/, /b/)).selection;
+ assert.strictEqual(result.length, 1);
+ assert.strictEqual(tr.b.getOnlyElement(result).guid, 3);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/v8/ic_stats_entry.html b/chromium/third_party/catapult/tracing/tracing/extras/v8/ic_stats_entry.html
new file mode 100644
index 00000000000..6717b80302d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/v8/ic_stats_entry.html
@@ -0,0 +1,209 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/v8/v8_ic_stats_thread_slice.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.v8', function() {
+ const IC_STATS_PROPERTIES = [
+ 'type', 'category', 'scriptName', 'filePosition', 'state', 'isNative',
+ 'map', 'propertiesMode', 'numberOfOwnProperties', 'instanceType'
+ ];
+
+ class ICStatsEntry {
+ constructor(obj) {
+ this.type_ = obj.type;
+ if (this.type_.includes('Store')) {
+ this.category_ = 'Store';
+ } else if (this.type_.includes('Load')) {
+ this.category_ = 'Load';
+ }
+ this.state_ = obj.state;
+ if (obj.functionName) {
+ this.functionName_ = obj.optimized ? '*' : '~';
+ this.functionName_ += obj.functionName.length === 0 ?
+ '(anonymous function)' : obj.functionName;
+ }
+ this.offset_ = obj.offset;
+ this.scriptName_ = obj.scriptName ? obj.scriptName : 'unknown';
+ this.isNative_ = obj.scriptName && obj.scriptName.includes('native');
+ this.lineNum_ = obj.lineNum ? obj.lineNum : 'unknown';
+ this.filePosition_ = this.scriptName_ + ':' + this.lineNum_;
+ if (this.functionName_) {
+ this.filePosition_ += ' ' + this.functionName_ + '+' + this.offset_;
+ }
+ this.constructor_ = obj.constructor ? false : true;
+ this.map_ = obj.map;
+ if (this.map_) {
+ this.propertiesMode_ = obj.dict === 1 ? 'slow' : 'fast';
+ } else {
+ this.propertiesMode_ = 'unknown';
+ }
+ this.numberOfOwnProperties_ = obj.own;
+ this.instanceType_ = obj.instanceType;
+ this.key_ = obj.key;
+ }
+
+ get type() {
+ return this.type_;
+ }
+
+ get category() {
+ return this.category_;
+ }
+
+ get state() {
+ return this.state_;
+ }
+
+ get functionName() {
+ return this.functionName_;
+ }
+
+ get offset() {
+ return this.offset_;
+ }
+
+ get scriptName() {
+ return this.scriptName_;
+ }
+
+ get isNative() {
+ return this.isNative_;
+ }
+
+ get lineNumber() {
+ return this.lineNum_;
+ }
+
+ get isConstructor() {
+ return this.constructor_;
+ }
+
+ get map() {
+ return this.map_;
+ }
+
+ get propertiesMode() {
+ return this.propertiesMode_;
+ }
+
+ get numberOfOwnProperties() {
+ return this.numberOfOwnProperties_;
+ }
+
+ get instanceType() {
+ return this.instanceType_;
+ }
+
+ get filePosition() {
+ return this.filePosition_;
+ }
+ }
+
+ class ICStatsEntryGroup {
+ constructor(property, key) {
+ this.property_ = property;
+ this.key_ = key;
+ this.percentage_ = 0;
+ this.entries_ = [];
+ // This is map used to cache all sub grouping result
+ this.subGroup_ = undefined;
+ }
+
+ static groupBy(groups, entries, property) {
+ for (const entry of entries) {
+ const key = entry[property];
+ let group = groups.get(key);
+ if (!group) {
+ group = new ICStatsEntryGroup(property, key);
+ groups.set(key, group);
+ }
+ group.add(entry);
+ }
+ for (const group of groups.values()) {
+ group.percentage = group.length / entries.length;
+ }
+ }
+
+ add(entry) {
+ this.entries_.push(entry);
+ }
+
+ createSubGroup() {
+ if (this.subGroup_) return this.subGroup_;
+ this.subGroup_ = new Map();
+ for (const property of IC_STATS_PROPERTIES) {
+ if (property === this.property_) continue;
+ const groups = new Map();
+ this.subGroup_.set(property, groups);
+ ICStatsEntryGroup.groupBy(groups, this.entries_, property);
+ }
+ return this.subGroup_;
+ }
+
+ get entries() {
+ return this.entries_;
+ }
+
+ get key() {
+ return this.key_;
+ }
+
+ get length() {
+ return this.entries_.length;
+ }
+
+ get percentage() {
+ return this.percentage_;
+ }
+
+ set percentage(value) {
+ this.percentage_ = value;
+ }
+ }
+
+ class ICStatsCollection {
+ constructor() {
+ this.entries_ = [];
+ // Used to cache property grouping result.
+ this.groupedEntries_ = new Map();
+ }
+
+ add(entry) {
+ this.entries_.push(entry);
+ }
+
+ groupBy(property) {
+ if (this.groupedEntries_.has(property)) {
+ return Array.from(this.groupedEntries_.get(property).values());
+ }
+ const groups = new Map();
+ this.groupedEntries_.set(property, groups);
+ ICStatsEntryGroup.groupBy(groups, this.entries_, property);
+ return Array.from(groups.values());
+ }
+
+ get entries() {
+ return this.entries_;
+ }
+
+ get length() {
+ return this.entries_.length;
+ }
+ }
+
+ return {
+ IC_STATS_PROPERTIES,
+ ICStatsEntry,
+ ICStatsEntryGroup,
+ ICStatsCollection,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/v8/ic_stats_entry_test.html b/chromium/third_party/catapult/tracing/tracing/extras/v8/ic_stats_entry_test.html
new file mode 100644
index 00000000000..1cb0f851552
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/v8/ic_stats_entry_test.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/v8/ic_stats_entry.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const obj1 = {
+ type: 'StoreIC',
+ functionName: 'f',
+ offset: 10,
+ scriptName: 'a.js',
+ lineNum: 1,
+ state: '(0->.)',
+ map: '0x000000000A',
+ dict: 0,
+ own: 3
+ };
+ const obj2 = {
+ type: 'LoadIC',
+ functionName: 'saveBuiltin',
+ offset: 11,
+ scriptName: 'extensions::SafeBuiltins',
+ lineNum: 2,
+ state: '(0->.)',
+ map: '0x00000000AA',
+ dict: 1,
+ own: 0
+ };
+ const obj3 = {
+ type: 'CompareIC',
+ functionName: 'InnerArrayForEach',
+ offset: 12,
+ scriptName: 'native array.js',
+ lineNum: 3,
+ state: '((UNINITIALIZED+UNINITIALIZED=UNINITIALIZED)->(SMI+SMI=SMI))#LT',
+ map: '0x0000000AAA'
+ };
+ const obj4 = {
+ type: 'StoreIC',
+ functionName: 'bar',
+ offset: 13,
+ scriptName: 'a.js',
+ lineNum: 4,
+ state: '(0->.)',
+ map: '0x000000AAAA',
+ dict: 1,
+ own: 7
+ };
+
+ test('ICStatsEntry', function() {
+ const entry1 = new tr.e.v8.ICStatsEntry(obj1);
+ assert.strictEqual(entry1.type, 'StoreIC');
+ assert.strictEqual(entry1.category, 'Store');
+ assert.strictEqual(entry1.state, '(0->.)');
+ assert.strictEqual(entry1.filePosition, 'a.js:1 ~f+10');
+ assert.strictEqual(entry1.isNative, false);
+ assert.strictEqual(entry1.propertiesMode, 'fast');
+ assert.strictEqual(entry1.numberOfOwnProperties, 3);
+
+ const entry2 = new tr.e.v8.ICStatsEntry(obj4);
+ assert.strictEqual(entry2.type, 'StoreIC');
+ assert.strictEqual(entry2.category, 'Store');
+ assert.strictEqual(entry2.state, '(0->.)');
+ assert.strictEqual(entry2.filePosition, 'a.js:4 ~bar+13');
+ assert.strictEqual(entry2.isNative, false);
+ assert.strictEqual(entry2.propertiesMode, 'slow');
+ assert.strictEqual(entry2.numberOfOwnProperties, 7);
+
+ const entry3 = new tr.e.v8.ICStatsEntry(obj3);
+ assert.strictEqual(entry3.isNative, true);
+ });
+
+ test('ICStatsEntryCollection', function() {
+ const entryCollection = new tr.e.v8.ICStatsCollection();
+ entryCollection.add(new tr.e.v8.ICStatsEntry(obj1));
+ entryCollection.add(new tr.e.v8.ICStatsEntry(obj2));
+ entryCollection.add(new tr.e.v8.ICStatsEntry(obj3));
+ entryCollection.add(new tr.e.v8.ICStatsEntry(obj4));
+
+ assert.strictEqual(entryCollection.length, 4);
+ });
+
+ test('ICStatsEntryGroup', function() {
+ const entryCollection = new tr.e.v8.ICStatsCollection();
+ entryCollection.add(new tr.e.v8.ICStatsEntry(obj1));
+ entryCollection.add(new tr.e.v8.ICStatsEntry(obj2));
+ entryCollection.add(new tr.e.v8.ICStatsEntry(obj3));
+ entryCollection.add(new tr.e.v8.ICStatsEntry(obj4));
+
+ const entryGroups = entryCollection.groupBy('type');
+ assert.strictEqual(entryGroups[0].length, 2);
+ assert.strictEqual(entryGroups[0].key, 'StoreIC');
+ assert.strictEqual(entryGroups[1].key, 'LoadIC');
+ assert.strictEqual(entryGroups[2].key, 'CompareIC');
+
+ const subGroups = entryGroups[0].createSubGroup();
+ assert.strictEqual(subGroups.size, 9);
+ const filePositionGroup = subGroups.get('filePosition');
+ assert.deepEqual(Array.from(filePositionGroup.keys()), [
+ 'a.js:1 ~f+10', 'a.js:4 ~bar+13'
+ ]);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/v8/runtime_stats_entry.html b/chromium/third_party/catapult/tracing/tracing/extras/v8/runtime_stats_entry.html
new file mode 100644
index 00000000000..9895f33096f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/v8/runtime_stats_entry.html
@@ -0,0 +1,221 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/v8/v8_thread_slice.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.v8', function() {
+ class RuntimeStatsEntry {
+ /**
+ * @param time is in microseconds.
+ */
+ constructor(name, count, time) {
+ this.name_ = name;
+ this.count_ = count;
+ this.time_ = time;
+ }
+
+ get name() {
+ return this.name_;
+ }
+
+ get count() {
+ return this.count_;
+ }
+
+ get time() {
+ return this.time_;
+ }
+
+ addSample(count, time) {
+ this.count_ += count;
+ this.time_ += time;
+ }
+ }
+
+ class RuntimeStatsGroup extends RuntimeStatsEntry {
+ constructor(name, matchRegex) {
+ super(name, 0, 0);
+ this.regex_ = matchRegex;
+ this.entries_ = new Map();
+ }
+
+ match(name) {
+ return this.regex_ && name.match(this.regex_);
+ }
+
+ add(entry) {
+ const value = this.entries_.get(entry.name);
+ if (value !== undefined) {
+ value.addSample(entry.count, entry.time);
+ } else {
+ this.entries_.set(entry.name, entry);
+ }
+ this.count_ += entry.count;
+ this.time_ += entry.time;
+ }
+
+ get values() {
+ return Array.from(this.entries_.values());
+ }
+ }
+
+ class RuntimeStatsGroupCollection {
+ constructor() {
+ this.blink_cpp_group_ =
+ new RuntimeStatsGroup('Blink C++', /.*Callback.*/);
+ this.api_group_ = new RuntimeStatsGroup('API', /.*API.*/);
+
+ this.groups_ = [
+ new RuntimeStatsGroup('Total'),
+ new RuntimeStatsGroup('IC', /.*IC_.*/),
+ new RuntimeStatsGroup('Optimize-Background',
+ /(.*OptimizeConcurrent.*)|RecompileConcurrent.*/),
+ new RuntimeStatsGroup('Optimize',
+ /StackGuard|.*Optimize.*|.*Deoptimize.*|Recompile.*/),
+ new RuntimeStatsGroup('Compile-Background',
+ /(.*CompileBackground.*)/),
+ new RuntimeStatsGroup('Compile', /(^Compile.*)|(.*_Compile.*)/),
+ new RuntimeStatsGroup('Parse-Background', /.*ParseBackground.*/),
+ new RuntimeStatsGroup('Parse', /.*Parse.*/),
+ this.blink_cpp_group_,
+ this.api_group_,
+ new RuntimeStatsGroup('GC-Background-Marking',
+ /.*GC.MC.BACKGROUND.*MARKING.*/),
+ new RuntimeStatsGroup('GC-Background-Sweeping',
+ /.*GC.MC.BACKGROUND.*SWEEPING.*/),
+ new RuntimeStatsGroup('GC-Background-Scavenger',
+ /.*GC.SCAVENGER.BACKGROUND.*/),
+ new RuntimeStatsGroup('GC-Background-MinorMC',
+ /.*GC.MINOR_MC.BACKGROUND.*/),
+ new RuntimeStatsGroup('GC-Background-MajorMC',
+ /.*GC.MC.BACKGROUND.*/),
+ new RuntimeStatsGroup('GC-Background-Other', /.*GC.*BACKGROUND.*/),
+ new RuntimeStatsGroup('GC', /GC|AllocateInTargetSpace/),
+ new RuntimeStatsGroup('JavaScript', /JS_Execution/),
+ new RuntimeStatsGroup('V8 C++', /.*/)
+ ];
+
+ this.blink_group_collection_ = null;
+ }
+
+ addSlices(slices) {
+ const blinkEntries = [];
+ for (const slice of slices) {
+ if (!(slice instanceof tr.e.v8.V8ThreadSlice)) return;
+ let runtimeCallStats;
+ try {
+ runtimeCallStats = JSON.parse(slice.runtimeCallStats);
+ } catch (e) {
+ runtimeCallStats = slice.runtimeCallStats;
+ }
+ if (runtimeCallStats === undefined) continue;
+ for (const [name, stat] of Object.entries(runtimeCallStats)) {
+ // Blink RCS stats go to a separate table
+ if (name.match(/Blink_.*/)) {
+ // This counter is used to avoid counting time spent in V8 as time
+ // spent doing Blink C++, and interferes with total and percentage
+ // calculations, so we skip this counter.
+ if (name === 'Blink_V8') continue;
+ const entry = new RuntimeStatsEntry(name, stat[0], stat[1]);
+ blinkEntries.push(entry);
+ continue;
+ }
+
+ // Skip the 'Total' group
+ for (let i = 1; i < this.groups_.length; ++i) {
+ if (this.groups_[i].match(name)) {
+ if (stat.length !== 2) break;
+ const entry = new RuntimeStatsEntry(name, stat[0], stat[1]);
+ this.groups_[0].addSample(stat[0], stat[1]);
+ this.groups_[i].add(entry);
+ break;
+ }
+ }
+ }
+ }
+
+ this.blink_group_collection_ =
+ new BlinkRuntimeStatsGroupCollection(blinkEntries);
+ }
+
+ get totalTime() {
+ return this.groups_[0].time;
+ }
+
+ get totalCount() {
+ return this.groups_[0].count;
+ }
+
+ get runtimeGroups() {
+ return this.groups_;
+ }
+
+ get blinkRCSGroupCollection() {
+ return this.blink_group_collection_;
+ }
+
+ get blinkCppTotalTime() {
+ // Include API time because Blink RCS times also include V8 API times
+ return this.blink_cpp_group_.time + this.api_group_.time;
+ }
+ }
+
+ class BlinkRuntimeStatsGroupCollection {
+ constructor(entries) {
+ this.groups_ = [
+ new RuntimeStatsGroup('Blink_Bindings', /^Blink_Bindings_(.*)/),
+ new RuntimeStatsGroup('Blink_GC', /^Blink_GC_(.*)/),
+ new RuntimeStatsGroup('Blink_Layout', /^Blink_Layout_(.*)/),
+ new RuntimeStatsGroup('Blink_Parsing', /^Blink_Parsing_(.*)/),
+ new RuntimeStatsGroup('Blink_Style', /^Blink_Style_(.*)/),
+ new RuntimeStatsGroup('Blink_Callbacks', /^Blink_(.*)/)
+ ];
+ this.total_group_ = new RuntimeStatsGroup('Blink_Total', /.*/);
+
+ for (const entry of entries) {
+ for (const group of this.groups_) {
+ if (group.match(entry.name)) {
+ // Strip out category prefix
+ const newEntry = new RuntimeStatsEntry(
+ 'Blink_' + group.match(entry.name)[1], entry.count, entry.time);
+ group.add(newEntry);
+ this.total_group_.addSample(entry.count, entry.time);
+ break;
+ }
+ }
+ }
+ }
+
+ get runtimeGroups() {
+ return this.groups_.concat(this.total_group_);
+ }
+
+ get values() {
+ return this.groups_.reduce(
+ (values, group) => values.concat(group.values), []);
+ }
+
+ get totalTime() {
+ return this.total_group_.time;
+ }
+
+ get totalCount() {
+ return this.total_group_.count;
+ }
+ }
+
+ return {
+ BlinkRuntimeStatsGroupCollection,
+ RuntimeStatsEntry,
+ RuntimeStatsGroup,
+ RuntimeStatsGroupCollection,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/v8/runtime_stats_entry_test.html b/chromium/third_party/catapult/tracing/tracing/extras/v8/runtime_stats_entry_test.html
new file mode 100644
index 00000000000..aa5aa39ee55
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/v8/runtime_stats_entry_test.html
@@ -0,0 +1,322 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/v8/runtime_stats_entry.html">
+<link rel="import" href="/tracing/extras/v8/v8_thread_slice.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('RuntimeStatsEntry', function() {
+ const entry = new tr.e.v8.RuntimeStatsEntry('IC_Entry', 5, 1234);
+ assert.strictEqual(entry.name, 'IC_Entry');
+ assert.strictEqual(entry.count, 5);
+ assert.strictEqual(entry.time, 1234);
+
+ entry.addSample(37, 8766);
+ assert.strictEqual(entry.name, 'IC_Entry');
+ assert.strictEqual(entry.count, 42);
+ assert.strictEqual(entry.time, 10000);
+
+ entry.addSample(58, 1);
+ assert.strictEqual(entry.name, 'IC_Entry');
+ assert.strictEqual(entry.count, 100);
+ assert.strictEqual(entry.time, 10001);
+ });
+
+ test('RuntimeStatsGroup', function() {
+ const group = new tr.e.v8.RuntimeStatsGroup('IC', /.*IC_.*/);
+ assert.notEqual(group.match('IC_Entry'), undefined);
+ assert.notEqual(group.match('CallbackIC_Entry'), undefined);
+ assert.strictEqual(group.match('Callback_Entry'), null);
+
+ const entry1a = new tr.e.v8.RuntimeStatsEntry('IC_Entry', 5, 1234);
+ group.add(entry1a);
+ assert.deepEqual(group.values, [entry1a]);
+ const entry2 = new tr.e.v8.RuntimeStatsEntry('IC_Entry2', 2, 2048);
+ group.add(entry2);
+ assert.deepEqual(group.values, [entry1a, entry2]);
+ const entry1b = new tr.e.v8.RuntimeStatsEntry('IC_Entry', 37, 8766);
+ group.add(entry1b);
+ const entry1sum = new tr.e.v8.RuntimeStatsEntry('IC_Entry', 42, 10000);
+ assert.deepEqual(group.values, [entry1sum, entry2]);
+ });
+
+ function checkRuntimeGroup_(group, name, count, time) {
+ assert.strictEqual(group.name, name);
+ assert.strictEqual(group.count, count);
+ assert.strictEqual(group.time, time);
+ }
+
+ test('RuntimeStatsGroupCollection', function() {
+ const slices = [];
+ slices.push(tr.c.TestUtils.newSliceEx({
+ title: 'V8.Execute',
+ start: 0,
+ end: 10,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 11],
+ HandleApiCall: [2, 22],
+ CompileFullCode: [3, 33],
+ LoadIC_Miss: [4, 44],
+ ParseLazy: [5, 55],
+ OptimizeCode: [6, 66],
+ FunctionCallback: [7, 77],
+ AllocateInTargetSpace: [8, 88],
+ API_Object_Get: [9, 99],
+ RecompileConcurrent: [10, 100],
+ }
+ }
+ }));
+ slices.push(tr.c.TestUtils.newSliceEx({
+ title: 'V8.newInstance',
+ start: 0,
+ end: 10,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [2, 22],
+ HandleApiCall: [3, 33],
+ CompileFullCode: [4, 44],
+ LoadIC_Miss: [5, 55],
+ ParseLazy: [6, 66],
+ OptimizeCode: [7, 77],
+ FunctionCallback: [8, 88],
+ AllocateInTargetSpace: [9, 99],
+ API_Object_Get: [1, 11],
+ ParseBackgroundFunctionLiteral: [2, 22],
+ CompileBackgroundIgnition: [3, 33],
+ RecompileConcurrent: [5, 50],
+ }
+ }
+ }));
+
+ const groupCollection = new tr.e.v8.RuntimeStatsGroupCollection();
+ groupCollection.addSlices(slices);
+ assert.strictEqual(groupCollection.totalTime, 1195);
+
+ const groups = groupCollection.runtimeGroups;
+ assert.deepEqual(groups.length, 19);
+ checkRuntimeGroup_(groups[0], 'Total', 110, 1195);
+ checkRuntimeGroup_(groups[1], 'IC', 9, 99);
+ checkRuntimeGroup_(groups[2], 'Optimize-Background', 15, 150);
+ checkRuntimeGroup_(groups[3], 'Optimize', 13, 143);
+ checkRuntimeGroup_(groups[4], 'Compile-Background', 3, 33);
+ checkRuntimeGroup_(groups[5], 'Compile', 7, 77);
+ checkRuntimeGroup_(groups[6], 'Parse-Background', 2, 22);
+ checkRuntimeGroup_(groups[7], 'Parse', 11, 121);
+ checkRuntimeGroup_(groups[8], 'Blink C++', 15, 165);
+ checkRuntimeGroup_(groups[9], 'API', 10, 110);
+ checkRuntimeGroup_(groups[10], 'GC-Background-Marking', 0, 0);
+ checkRuntimeGroup_(groups[11], 'GC-Background-Sweeping', 0, 0);
+ checkRuntimeGroup_(groups[12], 'GC-Background-Scavenger', 0, 0);
+ checkRuntimeGroup_(groups[13], 'GC-Background-MinorMC', 0, 0);
+ checkRuntimeGroup_(groups[14], 'GC-Background-MajorMC', 0, 0);
+ checkRuntimeGroup_(groups[15], 'GC-Background-Other', 0, 0);
+ checkRuntimeGroup_(groups[16], 'GC', 17, 187);
+ checkRuntimeGroup_(groups[17], 'JavaScript', 3, 33);
+ checkRuntimeGroup_(groups[18], 'V8 C++', 5, 55);
+ });
+
+ test('BlinkRuntimeStatsGroupCollection', function() {
+ const slices = [];
+ slices.push(tr.c.TestUtils.newSliceEx({
+ title: 'V8.Execute',
+ start: 0,
+ end: 10,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [1, 11],
+ HandleApiCall: [2, 22],
+ CompileFullCode: [3, 33],
+ LoadIC_Miss: [4, 44],
+ ParseLazy: [5, 55],
+ OptimizeCode: [6, 66],
+ FunctionCallback: [7, 77],
+ AllocateInTargetSpace: [8, 88],
+ API_Object_Get: [9, 99],
+ RecompileConcurrent: [10, 100],
+ }
+ }
+ }));
+ slices.push(tr.c.TestUtils.newSliceEx({
+ title: 'V8.newInstance',
+ start: 0,
+ end: 10,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {
+ 'runtime-call-stats': {
+ JS_Execution: [2, 22],
+ HandleApiCall: [3, 33],
+ CompileFullCode: [4, 44],
+ LoadIC_Miss: [5, 55],
+ ParseLazy: [6, 66],
+ OptimizeCode: [7, 77],
+ FunctionCallback: [8, 88],
+ AllocateInTargetSpace: [9, 99],
+ API_Object_Get: [1, 11],
+ ParseBackgroundFunctionLiteral: [2, 22],
+ CompileBackgroundIgnition: [3, 33],
+ RecompileConcurrent: [5, 50],
+ }
+ }
+ }));
+ slices.push(tr.c.TestUtils.newSliceEx({
+ title: 'BlinkRuntimeCallStats',
+ start: 0,
+ end: 10,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'disabled-by-default-v8.runtime_stats',
+ args: {
+ 'runtime-call-stats': {
+ Blink_Layout_UpdateLayout: [10, 25],
+ Blink_GC_CollectGarbage: [10, 20],
+ Blink_Style_UpdateStyle: [15, 30],
+ Blink_Parsing_ParseHTML: [5, 60],
+ Blink_WindowSetTimeout: [10, 15],
+ Blink_V8: [5, 100],
+ }
+ }
+ }));
+ slices.push(tr.c.TestUtils.newSliceEx({
+ title: 'BlinkRuntimeCallStats',
+ start: 0,
+ end: 10,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'disabled-by-default-v8.runtime_stats',
+ args: {
+ 'runtime-call-stats': {
+ Blink_Layout_UpdateLayout: [5, 10],
+ Blink_GC_CollectGarbage: [20, 50],
+ Blink_Parsing_ParseHTML: [10, 60],
+ Blink_Bindings_CreateWrapper: [100, 5],
+ Blink_NodeAppendChild: [20, 20],
+ Blink_V8: [2, 50],
+ }
+ }
+ }));
+
+ const groupCollection = new tr.e.v8.RuntimeStatsGroupCollection();
+ groupCollection.addSlices(slices);
+ assert.strictEqual(groupCollection.totalTime, 1195);
+
+ const groups = groupCollection.runtimeGroups;
+ assert.deepEqual(groups.length, 19);
+ checkRuntimeGroup_(groups[0], 'Total', 110, 1195);
+ checkRuntimeGroup_(groups[1], 'IC', 9, 99);
+ checkRuntimeGroup_(groups[2], 'Optimize-Background', 15, 150);
+ checkRuntimeGroup_(groups[3], 'Optimize', 13, 143);
+ checkRuntimeGroup_(groups[4], 'Compile-Background', 3, 33);
+ checkRuntimeGroup_(groups[5], 'Compile', 7, 77);
+ checkRuntimeGroup_(groups[6], 'Parse-Background', 2, 22);
+ checkRuntimeGroup_(groups[7], 'Parse', 11, 121);
+ checkRuntimeGroup_(groups[8], 'Blink C++', 15, 165);
+ checkRuntimeGroup_(groups[9], 'API', 10, 110);
+ checkRuntimeGroup_(groups[10], 'GC-Background-Marking', 0, 0);
+ checkRuntimeGroup_(groups[11], 'GC-Background-Sweeping', 0, 0);
+ checkRuntimeGroup_(groups[12], 'GC-Background-Scavenger', 0, 0);
+ checkRuntimeGroup_(groups[13], 'GC-Background-MinorMC', 0, 0);
+ checkRuntimeGroup_(groups[14], 'GC-Background-MajorMC', 0, 0);
+ checkRuntimeGroup_(groups[15], 'GC-Background-Other', 0, 0);
+ checkRuntimeGroup_(groups[16], 'GC', 17, 187);
+ checkRuntimeGroup_(groups[17], 'JavaScript', 3, 33);
+ checkRuntimeGroup_(groups[18], 'V8 C++', 5, 55);
+
+ const blinkGroupCollection = groupCollection.blinkRCSGroupCollection;
+ const blinkGroups = blinkGroupCollection.runtimeGroups;
+ checkRuntimeGroup_(blinkGroups[0], 'Blink_Bindings', 100, 5);
+ checkRuntimeGroup_(blinkGroups[1], 'Blink_GC', 30, 70);
+ checkRuntimeGroup_(blinkGroups[2], 'Blink_Layout', 15, 35);
+ checkRuntimeGroup_(blinkGroups[3], 'Blink_Parsing', 15, 120);
+ checkRuntimeGroup_(blinkGroups[4], 'Blink_Style', 15, 30);
+ checkRuntimeGroup_(blinkGroups[5], 'Blink_Callbacks', 30, 35);
+
+ assert.strictEqual(205, blinkGroupCollection.totalCount);
+ assert.strictEqual(295, blinkGroupCollection.totalTime);
+
+ assert.strictEqual('Blink_UpdateLayout', blinkGroups[2].values[0].name);
+ assert.strictEqual('Blink_CollectGarbage', blinkGroups[1].values[0].name);
+ });
+
+ test('RuntimeStatsBackgroundGC', function() {
+ const slices = [];
+ slices.push(tr.c.TestUtils.newSliceEx({
+ title: 'V8.Execute',
+ start: 0,
+ end: 700,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {
+ 'runtime-call-stats': {
+ GC_BACKGROUND_UNMAPPER: [1, 10],
+ GC_SCAVENGER_BACKGROUND_SCAVENGE_PARALLEL: [10, 100],
+ GC_MINOR_MC_BACKGROUND_MARKING: [20, 200],
+ GC_MINOR_MC_BACKGROUND_EVACUATE_COPY: [30, 300],
+ GC_MC_BACKGROUND_MARKING: [40, 400],
+ GC_MC_BACKGROUND_SWEEPING: [50, 500],
+ GC_MC_BACKGROUND_EVACUATE_COPY: [60, 600],
+ GC_MC_BACKGROUND_EVACUATE_UPDATE_POINTERS: [70, 700]
+ }
+ }
+ }));
+ slices.push(tr.c.TestUtils.newSliceEx({
+ title: 'V8.newInstance',
+ start: 1000,
+ end: 8000,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {
+ 'runtime-call-stats': {
+ GC_SCAVENGER_BACKGROUND_SCAVENGE_PARALLEL: [100, 1000],
+ GC_MINOR_MC_BACKGROUND_MARKING: [200, 2000],
+ GC_MINOR_MC_BACKGROUND_EVACUATE_COPY: [300, 3000],
+ GC_MC_BACKGROUND_MARKING: [400, 4000],
+ GC_MC_BACKGROUND_SWEEPING: [500, 5000],
+ GC_MC_BACKGROUND_EVACUATE_COPY: [600, 6000],
+ GC_MC_BACKGROUND_EVACUATE_UPDATE_POINTERS: [700, 7000]
+ }
+ }
+ }));
+ const groupCollection = new tr.e.v8.RuntimeStatsGroupCollection();
+ groupCollection.addSlices(slices);
+ assert.strictEqual(groupCollection.totalTime, 28000 + 2800 + 10);
+
+ const groups = groupCollection.runtimeGroups;
+ assert.deepEqual(groups.length, 19);
+ checkRuntimeGroup_(groups[0], 'Total', 2800 + 280 + 1, 28000 + 2800 + 10);
+ checkRuntimeGroup_(groups[1], 'IC', 0, 0);
+ checkRuntimeGroup_(groups[2], 'Optimize-Background', 0, 0);
+ checkRuntimeGroup_(groups[3], 'Optimize', 0, 0);
+ checkRuntimeGroup_(groups[4], 'Compile-Background', 0, 0);
+ checkRuntimeGroup_(groups[5], 'Compile', 0, 0);
+ checkRuntimeGroup_(groups[6], 'Parse-Background', 0, 0);
+ checkRuntimeGroup_(groups[7], 'Parse', 0, 0);
+ checkRuntimeGroup_(groups[8], 'Blink C++', 0, 0);
+ checkRuntimeGroup_(groups[9], 'API', 0, 0);
+ checkRuntimeGroup_(groups[10], 'GC-Background-Marking', 440, 4400);
+ checkRuntimeGroup_(groups[11], 'GC-Background-Sweeping', 550, 5500);
+ checkRuntimeGroup_(groups[12], 'GC-Background-Scavenger', 110, 1100);
+ checkRuntimeGroup_(groups[13], 'GC-Background-MinorMC',
+ 300 + 200 + 30 + 20, 3000 + 2000 + 300 + 200);
+ checkRuntimeGroup_(groups[14], 'GC-Background-MajorMC',
+ 700 + 600 + 70 + 60, 7000 + 6000 + 700 + 600);
+ checkRuntimeGroup_(groups[15], 'GC-Background-Other', 1, 10);
+ checkRuntimeGroup_(groups[16], 'GC', 0, 0);
+ checkRuntimeGroup_(groups[17], 'JavaScript', 0, 0);
+ checkRuntimeGroup_(groups[18], 'V8 C++', 0, 0);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/v8/v8_cpu_profile_node.html b/chromium/third_party/catapult/tracing/tracing/extras/v8/v8_cpu_profile_node.html
new file mode 100644
index 00000000000..b5a1855fdca
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/v8/v8_cpu_profile_node.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/model/profile_node.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Provides the V8CpuProfileNode class.
+ */
+tr.exportTo('tr.e.v8', function() {
+ const ProfileNode = tr.model.ProfileNode;
+ /**
+ * A V8CpuProfileNode represents a node in the v8 cpu profile tree,
+ * it is essentially a frame in the stack when the sample gets recorded.
+ */
+ function V8CpuProfileNode(id, callFrame, parentNode) {
+ ProfileNode.call(this, id, callFrame.functionName, parentNode);
+ this.callFrame_ = tr.b.deepCopy(callFrame);
+ this.deoptReason_ = '';
+ this.colorId_ = tr.b.ColorScheme.getColorIdForGeneralPurposeString(
+ callFrame.functionName);
+ }
+
+ V8CpuProfileNode.prototype = {
+ __proto__: ProfileNode.prototype,
+
+ get functionName() {
+ return this.callFrame_.functionName;
+ },
+
+ get scriptId() {
+ return this.callFrame_.scriptId;
+ },
+
+ get url() {
+ if (!this.callFrame_.url) {
+ return 'unknown';
+ }
+ let url = this.callFrame_.url;
+ if (this.callFrame_.lineNumber === undefined) {
+ return url;
+ }
+ url = url + ':' + this.callFrame_.lineNumber;
+ if (this.callFrame_.columnNumber === undefined) {
+ return url;
+ }
+ url = url + ':' + this.callFrame_.columnNumber;
+ return url;
+ },
+
+ get deoptReason() {
+ return this.deoptReason_;
+ },
+
+ set deoptReason(value) {
+ this.deoptReason_ = value;
+ },
+
+ get userFriendlyName() {
+ const name = this.functionName + ' url: ' + this.url;
+ return !this.deoptReason_ ?
+ name : name + ' Deoptimized reason: ' + this.deoptReason_;
+ },
+
+ get sampleTitle() {
+ return 'V8 Sample';
+ }
+ };
+
+ V8CpuProfileNode.constructFromObject = function(profileTree, node) {
+ const nodeId = node.id;
+ if (nodeId === 1) {
+ // Ignore fake root.
+ // The node with id 1 is a fake root for the profile tree,
+ // since we are using map, we don't really need it.
+ return undefined;
+ }
+ const parentNode = profileTree.getNode(node.parent);
+ const profileNode = new V8CpuProfileNode(nodeId, node.callFrame,
+ parentNode);
+ if (node.deoptReason !== undefined) {
+ profileNode.deoptReason = node.deoptReason;
+ }
+ return profileNode;
+ };
+
+ ProfileNode.subTypes.register(
+ V8CpuProfileNode,
+ {
+ typeName: 'cpuProfile',
+ name: 'v8 cpu profile node',
+ pluralName: 'v8 cpu profile nodes'
+ }
+ );
+
+ ProfileNode.subTypes.register(
+ V8CpuProfileNode,
+ {
+ typeName: 'legacySample',
+ name: 'v8 cpu profile node',
+ pluralName: 'v8 cpu profile nodes'
+ }
+ );
+
+ return {
+ ProfileNode,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/v8/v8_gc_stats_thread_slice.html b/chromium/third_party/catapult/tracing/tracing/extras/v8/v8_gc_stats_thread_slice.html
new file mode 100644
index 00000000000..8e29b5fdf3a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/v8/v8_gc_stats_thread_slice.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/model/thread_slice.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.v8', function() {
+ const ThreadSlice = tr.model.ThreadSlice;
+
+ function V8GCStatsThreadSlice() {
+ ThreadSlice.apply(this, arguments);
+ this.liveObjects_ = JSON.parse(this.args.live);
+ delete this.args.live;
+ this.deadObjects_ = JSON.parse(this.args.dead);
+ delete this.args.dead;
+ }
+
+ V8GCStatsThreadSlice.prototype = {
+ __proto__: ThreadSlice.prototype,
+
+ get liveObjects() {
+ return this.liveObjects_;
+ },
+
+ get deadObjects() {
+ return this.deadObjects_;
+ }
+ };
+
+ ThreadSlice.subTypes.register(
+ V8GCStatsThreadSlice,
+ {
+ categoryParts: ['disabled-by-default-v8.gc_stats'],
+ name: 'v8 gc stats slice',
+ pluralName: 'v8 gc stats slices'
+ }
+ );
+
+ return {
+ V8GCStatsThreadSlice,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/v8/v8_ic_stats_thread_slice.html b/chromium/third_party/catapult/tracing/tracing/extras/v8/v8_ic_stats_thread_slice.html
new file mode 100644
index 00000000000..7ee3f0c527c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/v8/v8_ic_stats_thread_slice.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/model/thread_slice.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.v8', function() {
+ const ThreadSlice = tr.model.ThreadSlice;
+
+ function V8ICStatsThreadSlice() {
+ ThreadSlice.apply(this, arguments);
+ this.icStats_ = undefined;
+ if (this.args['ic-stats']) {
+ this.icStats_ = this.args['ic-stats'].data;
+ delete this.args['ic-stats'];
+ }
+ }
+
+ V8ICStatsThreadSlice.prototype = {
+ __proto__: ThreadSlice.prototype,
+
+ get icStats() {
+ return this.icStats_;
+ }
+ };
+
+ ThreadSlice.subTypes.register(
+ V8ICStatsThreadSlice,
+ {
+ categoryParts: ['disabled-by-default-v8.ic_stats'],
+ name: 'v8 ic stats slice',
+ pluralName: 'v8 ic stats slices'
+ }
+ );
+
+ return {
+ V8ICStatsThreadSlice,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/v8/v8_thread_slice.html b/chromium/third_party/catapult/tracing/tracing/extras/v8/v8_thread_slice.html
new file mode 100644
index 00000000000..4aaee2eeca5
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/v8/v8_thread_slice.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/model/thread_slice.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.v8', function() {
+ const ThreadSlice = tr.model.ThreadSlice;
+
+ function V8ThreadSlice() {
+ ThreadSlice.apply(this, arguments);
+ this.runtimeCallStats_ = undefined;
+ }
+
+ V8ThreadSlice.prototype = {
+ __proto__: ThreadSlice.prototype,
+
+ get runtimeCallStats() {
+ if ('runtime-call-stats' in this.args) {
+ this.runtimeCallStats_ = this.args['runtime-call-stats'];
+ delete this.args['runtime-call-stats'];
+ }
+ return this.runtimeCallStats_;
+ }
+ };
+
+ ThreadSlice.subTypes.register(
+ V8ThreadSlice,
+ {
+ categoryParts: ['v8', 'disabled-by-default-v8.runtime_stats'],
+ name: 'v8 slice',
+ pluralName: 'v8 slices'
+ }
+ );
+
+ return {
+ V8ThreadSlice,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/v8_config.html b/chromium/third_party/catapult/tracing/tracing/extras/v8_config.html
new file mode 100644
index 00000000000..7729e6c1905
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/v8_config.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/v8/v8_cpu_profile_node.html">
+<link rel="import" href="/tracing/extras/v8/v8_gc_stats_thread_slice.html">
+<link rel="import" href="/tracing/extras/v8/v8_ic_stats_thread_slice.html">
+<link rel="import" href="/tracing/extras/v8/v8_thread_slice.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/vsync/vsync_auditor.html b/chromium/third_party/catapult/tracing/tracing/extras/vsync/vsync_auditor.html
new file mode 100644
index 00000000000..df56592cde5
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/vsync/vsync_auditor.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/core/auditor.html">
+
+<script>
+'use strict';
+
+function filterDuplicateTimestamps(timestamps) {
+ const dedupedTimestamps = [];
+ let lastTs = 0;
+ for (const ts of timestamps) {
+ if (ts - lastTs >= 1) {
+ dedupedTimestamps.push(ts);
+ lastTs = ts;
+ }
+ }
+ return dedupedTimestamps;
+}
+
+tr.exportTo('tr.e.audits', function() {
+ const VSYNC_COUNTER_PRECISIONS = {
+ // Android. Some versions have VSYNC split out into VSYNC-app and VSYNC-sf.
+ // Requires "gfx" systrace category to be enabled.
+ 'android.VSYNC-app': 15,
+ 'android.VSYNC': 15
+ };
+
+ const VSYNC_SLICE_PRECISIONS = {
+ // Android.
+ 'RenderWidgetHostViewAndroid::OnVSync': 5,
+ // Android. Very precise. Requires "gfx" systrace category to be enabled.
+ 'VSYNC': 10,
+ // Linux. Very precise. Requires "gpu" tracing category to be enabled.
+ 'vblank': 10,
+ // Mac. Derived from a Mac callback (CVDisplayLinkSetOutputCallback).
+ 'DisplayLinkMac::GetVSyncParameters': 5
+ };
+
+ const BEGIN_FRAME_SLICE_PRECISION = {
+ 'DisplayScheduler::BeginFrame': 10
+ };
+
+ /**
+ * Auditor that analyzes the model and, if possible, adds data to it
+ * indicating when vertical sync events took place.
+ *
+ * @constructor
+ * @extends {tr.c.Auditor}
+ */
+ function VSyncAuditor(model) {
+ tr.c.Auditor.call(this, model);
+ }
+
+ VSyncAuditor.prototype = {
+ __proto__: tr.c.Auditor.prototype,
+
+ runAnnotate() {
+ this.model.device.vSyncTimestamps = this.findVSyncTimestamps(this.model);
+ },
+
+ /**
+ * Returns an array of the most accurate VSync times available in the model.
+ */
+ findVSyncTimestamps(model) {
+ let times = [];
+
+ // Only keep the most precise VSync data.
+ let maxPrecision = Number.NEGATIVE_INFINITY;
+ let maxTitle = undefined;
+
+ function useInstead(title, precisions) {
+ const precision = precisions[title];
+ if (precision === undefined) return false;
+
+ if (title === maxTitle) return true;
+
+ if (precision <= maxPrecision) {
+ if (precision === maxPrecision) {
+ model.importWarning({
+ type: 'VSyncAuditor',
+ message: 'Encountered two different VSync events (' +
+ maxTitle + ', ' + title + ') with the same precision, ' +
+ 'ignoring the newer one (' + title + ')',
+ showToUser: false,
+ });
+ }
+ return false;
+ }
+ maxPrecision = precision;
+ maxTitle = title;
+ times = [];
+
+ return true;
+ }
+
+ for (const pid in model.processes) {
+ const process = model.processes[pid];
+ // Traverse process counters.
+ for (const cid in process.counters) {
+ if (useInstead(cid, VSYNC_COUNTER_PRECISIONS)) {
+ const counter = process.counters[cid];
+ for (let i = 0; i < counter.series.length; i++) {
+ const series = counter.series[i];
+ Array.prototype.push.apply(times, series.timestamps);
+ }
+ }
+ }
+
+ // Traverse thread slices.
+ for (const tid in process.threads) {
+ const thread = process.threads[tid];
+ for (let i = 0; i < thread.sliceGroup.slices.length; i++) {
+ const slice = thread.sliceGroup.slices[i];
+ if (useInstead(slice.title, VSYNC_SLICE_PRECISIONS)) {
+ times.push(slice.start);
+ } else if (useInstead(slice.title, BEGIN_FRAME_SLICE_PRECISION) &&
+ slice.args.args && slice.args.args.frame_time_us) {
+ // We need to check not only that we have a Scheduler::BeginFrame
+ // event, but also that we have one that has a frame time
+ // associated with it.
+ // Older versions of Scheduler::BeginFrame don't have one.
+ times.push(slice.args.args.frame_time_us / 1000.0);
+ }
+ }
+ }
+ }
+ times.sort(function(x, y) { return x - y; });
+ return filterDuplicateTimestamps(times);
+ }
+ };
+
+ tr.c.Auditor.register(VSyncAuditor);
+
+ return {
+ VSyncAuditor,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/extras/vsync/vsync_auditor_test.html b/chromium/third_party/catapult/tracing/tracing/extras/vsync/vsync_auditor_test.html
new file mode 100644
index 00000000000..69aea99fd9e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/extras/vsync/vsync_auditor_test.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/extras/vsync/vsync_auditor.html">
+<link rel="import" href="/tracing/model/counter_series.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/model/thread_slice.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const CounterSeries = tr.model.CounterSeries;
+ const Model = tr.Model;
+ const ThreadSlice = tr.model.ThreadSlice;
+ const VSyncAuditor = tr.e.audits.VSyncAuditor;
+
+ function testFindVSyncTimestamps(slices, counters, expectedTimes) {
+ const model = buildModel(slices, counters);
+ const auditor = new VSyncAuditor(model);
+ assert.deepEqual(auditor.findVSyncTimestamps(model), expectedTimes);
+ }
+
+ function buildModel(slices, counters) {
+ const model = new Model();
+ const process = model.getOrCreateProcess(1);
+ for (let i = 0; i < slices.length; i++) {
+ const thread = process.getOrCreateThread(i);
+ for (let j = 0; j < slices[i].length; j++) {
+ thread.sliceGroup.pushSlice(slices[i][j]);
+ }
+ }
+ for (let i = 0; i < counters.length; i++) {
+ const counter = process.getOrCreateCounter(
+ counters[i].category,
+ counters[i].name);
+ counter.addSeries(counters[i].series);
+ }
+ return model;
+ }
+
+ function buildSlice(title, time) {
+ return new ThreadSlice('', title, 0, time, {});
+ }
+
+ function buildCounterSeries(name, timestamps) {
+ const series = new CounterSeries(name, '');
+ for (let i = 0; i < timestamps.length; i++) {
+ series.addCounterSample(timestamps[i], 1);
+ }
+ return series;
+ }
+
+ test('findEmpty', function() {
+ testFindVSyncTimestamps([], [], []);
+ });
+
+ test('findNoVsync', function() {
+ testFindVSyncTimestamps([
+ [buildSlice('MessageLoop::RunTask', 10),
+ buildSlice('MessageLoop::RunTask', 20)],
+ [buildSlice('MessageLoop::RunTask', 15)]
+ ], [], []);
+ });
+
+ test('findOneVsync', function() {
+ testFindVSyncTimestamps([[buildSlice('vblank', 42)]], [], [42]);
+ });
+
+ test('findMultipleVsyncs', function() {
+ testFindVSyncTimestamps([
+ [buildSlice('VSYNC', 1), buildSlice('MessageLoop::RunTask', 2)],
+ [buildSlice('MessageLoop::RunTask', 3)],
+ [buildSlice('MessageLoop::RunTask', 4), buildSlice('VSYNC', 5)],
+ [buildSlice('VSYNC', 6), buildSlice('VSYNC', 7)]
+ ], [], [1, 5, 6, 7]);
+ });
+
+ test('filterNearDuplicates', function() {
+ testFindVSyncTimestamps([
+ [buildSlice('VSYNC', 1), buildSlice('VSYNC', 1)],
+ [buildSlice('VSYNC', 2), buildSlice('VSYNC', 2.1)],
+ [buildSlice('VSYNC', 3), buildSlice('VSYNC', 4)],
+ ], [], [1, 2, 3, 4]);
+ });
+
+ test('findMultipleAndroidVsyncs', function() {
+ testFindVSyncTimestamps([
+ [buildSlice('MessageLoop::RunTask', 2)],
+ [buildSlice('MessageLoop::RunTask', 3)],
+ [buildSlice('MessageLoop::RunTask', 4)]
+ ],
+ [
+ {
+ category: 'android',
+ name: 'VSYNC-app',
+ series: buildCounterSeries('VSYNC-app', [1, 5, 6, 7])
+ }
+ ], [1, 5, 6, 7]);
+ });
+
+ test('findUnsorted', function() {
+ testFindVSyncTimestamps([
+ [buildSlice('RenderWidgetHostViewAndroid::OnVSync', 4),
+ buildSlice('MessageLoop::RunTask', 2)],
+ [buildSlice('RenderWidgetHostViewAndroid::OnVSync', 1),
+ buildSlice('RenderWidgetHostViewAndroid::OnVSync', 3)]
+ ], [], [1, 3, 4]);
+ });
+
+ test('findDifferentPrecisions', function() {
+ // vblank has higher precision than RenderWidgetHostViewAndroid::OnVSync.
+ testFindVSyncTimestamps([
+ [buildSlice('RenderWidgetHostViewAndroid::OnVSync', 1),
+ buildSlice('vblank', 2),
+ buildSlice('RenderWidgetHostViewAndroid::OnVSync', 3)]
+ ], [], [2]);
+ });
+
+ test('findBeginFrame', function() {
+ const title = 'DisplayScheduler::BeginFrame';
+ testFindVSyncTimestamps([[
+ new ThreadSlice('', title, 0, 2, { args: { frame_time_us: 1000 } }),
+ new ThreadSlice('', title, 0, 4, { args: { frame_time_us: 3000 } })
+ ]], [], [1, 3]);
+ });
+
+ test('findBeginFrame_noFrameTime', function() {
+ const title = 'DisplayScheduler::BeginFrame';
+ testFindVSyncTimestamps([[
+ new ThreadSlice('', title, 0, 2, {}),
+ new ThreadSlice('', title, 0, 4, { args: {} })
+ ]], [], []);
+ });
+});
+</script>