From d7cef28beeb5d70a8ae7aec9e559982f4ed1fbc6 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Thu, 22 May 2025 07:19:03 -0700 Subject: [PATCH] [ Observatory ] Create fork of `runtime/observatory` at `pkg/observatory` This change further prepares for the removal of Observatory from the shipped Dart SDK by creating a fork to be used by the remaining Observatory users. This is basically a straight copy of the contents from runtime/observatory with the exception of two new scripts: - `bin/observatory.dart`, a utility to launch Observatory - `bin/activate.dart`, which globally activates `bin/observatory.dart` as `observatory` This change also updates the presubmits to ensure that `runtime/observatory` is effectively placed in read-only mode to prevent any divergences with the fork. Work towards https://github.com/dart-lang/sdk/issues/50233 Change-Id: Iff3a7512058f36afa2a96d45d94a1dff424401d6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/429800 Reviewed-by: Siva Annamalai Commit-Queue: Ben Konyi --- PRESUBMIT.py | 14 + pkg/observatory/.gitignore | 6 + pkg/observatory/README.md | 36 + pkg/observatory/analysis_options.yaml | 12 + pkg/observatory/bin/activate.dart | 35 + pkg/observatory/bin/observatory.dart | 122 + pkg/observatory/lib/allocation_profile.dart | 10 + pkg/observatory/lib/app.dart | 31 + pkg/observatory/lib/cli.dart | 9 + pkg/observatory/lib/debugger.dart | 13 + pkg/observatory/lib/elements.dart | 97 + pkg/observatory/lib/event.dart | 337 + pkg/observatory/lib/models.dart | 97 + pkg/observatory/lib/object_graph.dart | 1714 +++ pkg/observatory/lib/repositories.dart | 58 + pkg/observatory/lib/sample_profile.dart | 15 + pkg/observatory/lib/service.dart | 21 + pkg/observatory/lib/service_common.dart | 364 + pkg/observatory/lib/service_html.dart | 87 + pkg/observatory/lib/service_io.dart | 69 + .../allocation_profile.dart | 97 + pkg/observatory/lib/src/app/application.dart | 293 + .../lib/src/app/location_manager.dart | 178 + pkg/observatory/lib/src/app/notification.dart | 18 + pkg/observatory/lib/src/app/page.dart | 1070 ++ pkg/observatory/lib/src/app/settings.dart | 45 + pkg/observatory/lib/src/app/view_model.dart | 104 + pkg/observatory/lib/src/cli/command.dart | 282 + .../lib/src/debugger/debugger.dart | 14 + .../lib/src/debugger/debugger_location.dart | 494 + .../lib/src/elements/allocation_profile.dart | 673 + .../elements/class_allocation_profile.dart | 137 + .../lib/src/elements/class_instances.dart | 361 + .../lib/src/elements/class_ref.dart | 60 + .../lib/src/elements/class_tree.dart | 206 + .../lib/src/elements/class_view.dart | 574 + .../lib/src/elements/code_ref.dart | 64 + .../lib/src/elements/code_view.dart | 642 + .../src/elements/containers/search_bar.dart | 212 + .../containers/virtual_collection.dart | 274 + .../src/elements/containers/virtual_tree.dart | 201 + .../lib/src/elements/context_ref.dart | 154 + .../lib/src/elements/context_view.dart | 201 + .../lib/src/elements/cpu_profile.dart | 183 + .../elements/cpu_profile/virtual_tree.dart | 366 + .../lib/src/elements/cpu_profile_table.dart | 547 + .../lib/src/elements/css/shared.css | 2941 +++++ .../lib/src/elements/curly_block.dart | 107 + .../lib/src/elements/debugger.dart | 3400 +++++ .../lib/src/elements/error_ref.dart | 51 + .../lib/src/elements/error_view.dart | 91 + .../lib/src/elements/eval_box.dart | 222 + .../lib/src/elements/field_ref.dart | 99 + .../lib/src/elements/field_view.dart | 315 + .../lib/src/elements/flag_list.dart | 151 + .../lib/src/elements/function_ref.dart | 100 + .../lib/src/elements/function_view.dart | 470 + .../lib/src/elements/general_error.dart | 75 + .../lib/src/elements/heap_map.dart | 346 + .../lib/src/elements/heap_snapshot.dart | 1575 +++ .../lib/src/elements/helpers/any_ref.dart | 135 + .../src/elements/helpers/custom_element.dart | 126 + .../src/elements/helpers/element_utils.dart | 40 + .../lib/src/elements/helpers/nav_bar.dart | 14 + .../lib/src/elements/helpers/nav_menu.dart | 24 + .../src/elements/helpers/rendering_queue.dart | 91 + .../elements/helpers/rendering_scheduler.dart | 139 + .../lib/src/elements/helpers/uris.dart | 55 + .../lib/src/elements/icdata_ref.dart | 65 + .../lib/src/elements/icdata_view.dart | 196 + .../lib/src/elements/img/chromium_icon.png | Bin 0 -> 23518 bytes .../lib/src/elements/img/dart_icon.png | Bin 0 -> 9409 bytes .../lib/src/elements/img/isolate_icon.png | Bin 0 -> 375 bytes .../lib/src/elements/inbound_references.dart | 134 + .../lib/src/elements/instance_ref.dart | 490 + .../lib/src/elements/instance_view.dart | 615 + .../src/elements/isolate/counter_chart.dart | 69 + .../lib/src/elements/isolate/location.dart | 138 + .../lib/src/elements/isolate/run_state.dart | 88 + .../src/elements/isolate/shared_summary.dart | 190 + .../lib/src/elements/isolate/summary.dart | 200 + .../lib/src/elements/isolate_reconnect.dart | 120 + .../lib/src/elements/isolate_ref.dart | 70 + .../lib/src/elements/isolate_view.dart | 352 + .../lib/src/elements/json_view.dart | 149 + .../lib/src/elements/library_ref.dart | 62 + .../lib/src/elements/library_view.dart | 361 + .../elements/local_var_descriptors_ref.dart | 69 + pkg/observatory/lib/src/elements/logging.dart | 120 + .../lib/src/elements/logging_list.dart | 90 + .../src/elements/megamorphiccache_ref.dart | 67 + .../src/elements/megamorphiccache_view.dart | 187 + .../lib/src/elements/metric/details.dart | 181 + .../lib/src/elements/metric/graph.dart | 111 + pkg/observatory/lib/src/elements/metrics.dart | 173 + .../src/elements/native_memory_profiler.dart | 168 + .../lib/src/elements/nav/class_menu.dart | 69 + .../lib/src/elements/nav/isolate_menu.dart | 132 + .../lib/src/elements/nav/library_menu.dart | 69 + .../lib/src/elements/nav/menu_item.dart | 72 + .../lib/src/elements/nav/notify.dart | 98 + .../lib/src/elements/nav/notify_event.dart | 256 + .../src/elements/nav/notify_exception.dart | 137 + .../lib/src/elements/nav/refresh.dart | 87 + .../lib/src/elements/nav/reload.dart | 117 + .../lib/src/elements/nav/top_menu.dart | 58 + .../lib/src/elements/nav/vm_menu.dart | 78 + .../lib/src/elements/object_common.dart | 251 + .../lib/src/elements/object_view.dart | 119 + .../lib/src/elements/objectpool_ref.dart | 65 + .../lib/src/elements/objectpool_view.dart | 164 + .../lib/src/elements/objectstore_view.dart | 132 + .../src/elements/observatory_application.dart | 24 + .../lib/src/elements/pc_descriptors_ref.dart | 63 + .../lib/src/elements/persistent_handles.dart | 308 + pkg/observatory/lib/src/elements/ports.dart | 137 + .../lib/src/elements/process_snapshot.dart | 659 + .../lib/src/elements/retaining_path.dart | 135 + .../src/elements/sample_buffer_control.dart | 285 + .../lib/src/elements/script_inset.dart | 1462 +++ .../lib/src/elements/script_ref.dart | 67 + .../lib/src/elements/script_view.dart | 160 + .../lib/src/elements/sentinel_value.dart | 65 + .../lib/src/elements/sentinel_view.dart | 105 + .../src/elements/singletargetcache_ref.dart | 69 + .../src/elements/singletargetcache_view.dart | 181 + .../lib/src/elements/source_inset.dart | 89 + .../lib/src/elements/source_link.dart | 102 + .../src/elements/stack_trace_tree_config.dart | 266 + .../strongly_reachable_instances.dart | 129 + .../src/elements/subtypetestcache_ref.dart | 65 + .../src/elements/subtypetestcache_view.dart | 150 + .../lib/src/elements/timeline_page.dart | 339 + .../lib/src/elements/tree_map.dart | 250 + .../lib/src/elements/type_arguments_ref.dart | 63 + .../lib/src/elements/unknown_ref.dart | 61 + .../lib/src/elements/unlinkedcall_ref.dart | 65 + .../lib/src/elements/unlinkedcall_view.dart | 161 + .../lib/src/elements/vm_connect.dart | 170 + .../lib/src/elements/vm_connect_target.dart | 103 + pkg/observatory/lib/src/elements/vm_view.dart | 343 + .../lib/src/models/exceptions.dart | 49 + .../models/objects/allocation_profile.dart | 31 + .../lib/src/models/objects/breakpoint.dart | 21 + .../lib/src/models/objects/class.dart | 73 + .../lib/src/models/objects/code.dart | 44 + .../lib/src/models/objects/context.dart | 22 + .../lib/src/models/objects/error.dart | 27 + .../lib/src/models/objects/event.dart | 210 + .../src/models/objects/extension_data.dart | 7 + .../lib/src/models/objects/field.dart | 43 + .../lib/src/models/objects/flag.dart | 21 + .../lib/src/models/objects/frame.dart | 14 + .../lib/src/models/objects/function.dart | 99 + .../lib/src/models/objects/guarded.dart | 12 + .../lib/src/models/objects/heap_space.dart | 15 + .../lib/src/models/objects/icdata.dart | 15 + .../models/objects/inbound_references.dart | 22 + .../lib/src/models/objects/instance.dart | 503 + .../lib/src/models/objects/isolate.dart | 75 + .../lib/src/models/objects/isolate_group.dart | 24 + .../lib/src/models/objects/library.dart | 45 + .../models/objects/local_var_descriptors.dart | 10 + .../src/models/objects/map_association.dart | 10 + .../src/models/objects/megamorphiccache.dart | 16 + .../lib/src/models/objects/metric.dart | 16 + .../lib/src/models/objects/notification.dart | 18 + .../lib/src/models/objects/object.dart | 38 + .../lib/src/models/objects/objectpool.dart | 22 + .../lib/src/models/objects/objectstore.dart | 14 + .../src/models/objects/pc_descriptors.dart | 10 + .../models/objects/persistent_handles.dart | 21 + .../lib/src/models/objects/ports.dart | 14 + .../src/models/objects/retaining_path.dart | 24 + .../src/models/objects/sample_profile.dart | 76 + .../lib/src/models/objects/script.dart | 28 + .../lib/src/models/objects/sentinel.dart | 30 + .../lib/src/models/objects/service.dart | 9 + .../models/objects/single_target_cache.dart | 15 + .../src/models/objects/source_location.dart | 35 + .../models/objects/subtype_test_cache.dart | 11 + .../lib/src/models/objects/target.dart | 9 + .../lib/src/models/objects/timeline.dart | 25 + .../src/models/objects/timeline_event.dart | 7 + .../src/models/objects/type_arguments.dart | 18 + .../lib/src/models/objects/unknown.dart | 9 + .../lib/src/models/objects/unlinked_call.dart | 13 + .../lib/src/models/objects/vm.dart | 56 + .../repositories/allocation_profile.dart | 14 + .../src/models/repositories/breakpoint.dart | 10 + .../lib/src/models/repositories/class.dart | 10 + .../lib/src/models/repositories/context.dart | 9 + .../lib/src/models/repositories/editor.dart | 15 + .../lib/src/models/repositories/eval.dart | 14 + .../lib/src/models/repositories/event.dart | 37 + .../lib/src/models/repositories/field.dart | 9 + .../lib/src/models/repositories/flag.dart | 9 + .../lib/src/models/repositories/function.dart | 9 + .../models/repositories/heap_snapshot.dart | 9 + .../lib/src/models/repositories/icdata.dart | 9 + .../repositories/inbound_references.dart | 9 + .../lib/src/models/repositories/instance.dart | 9 + .../lib/src/models/repositories/isolate.dart | 11 + .../models/repositories/isolate_group.dart | 9 + .../lib/src/models/repositories/library.dart | 9 + .../models/repositories/megamorphiccache.dart | 9 + .../lib/src/models/repositories/metric.dart | 20 + .../src/models/repositories/notification.dart | 16 + .../lib/src/models/repositories/object.dart | 9 + .../src/models/repositories/objectpool.dart | 9 + .../src/models/repositories/objectstore.dart | 9 + .../repositories/persistent_handles.dart | 9 + .../lib/src/models/repositories/ports.dart | 9 + .../models/repositories/reachable_size.dart | 9 + .../models/repositories/retained_size.dart | 9 + .../models/repositories/retaining_path.dart | 9 + .../models/repositories/sample_profile.dart | 63 + .../lib/src/models/repositories/script.dart | 9 + .../repositories/single_target_cache.dart | 9 + .../strongly_reachable_instances.dart | 15 + .../repositories/subtype_test_cache.dart | 9 + .../lib/src/models/repositories/target.dart | 21 + .../lib/src/models/repositories/timeline.dart | 12 + .../models/repositories/type_arguments.dart | 9 + .../models/repositories/unlinked_call.dart | 9 + .../lib/src/models/repositories/vm.dart | 9 + .../src/repositories/allocation_profile.dart | 39 + .../lib/src/repositories/breakpoint.dart | 17 + .../lib/src/repositories/class.dart | 17 + .../lib/src/repositories/context.dart | 12 + .../lib/src/repositories/editor.dart | 91 + .../lib/src/repositories/eval.dart | 22 + .../lib/src/repositories/event.dart | 180 + .../lib/src/repositories/field.dart | 12 + .../lib/src/repositories/flag.dart | 36 + .../lib/src/repositories/function.dart | 12 + .../lib/src/repositories/heap_snapshot.dart | 12 + .../lib/src/repositories/icdata.dart | 12 + .../src/repositories/inbound_references.dart | 16 + .../lib/src/repositories/instance.dart | 16 + .../lib/src/repositories/isolate.dart | 34 + .../lib/src/repositories/isolate_group.dart | 19 + .../lib/src/repositories/library.dart | 14 + .../src/repositories/megamorphiccache.dart | 12 + .../lib/src/repositories/metric.dart | 210 + .../lib/src/repositories/notification.dart | 74 + .../lib/src/repositories/object.dart | 17 + .../lib/src/repositories/objectpool.dart | 12 + .../lib/src/repositories/objectstore.dart | 12 + .../src/repositories/persistent_handles.dart | 13 + .../lib/src/repositories/ports.dart | 13 + .../lib/src/repositories/reachable_size.dart | 15 + .../lib/src/repositories/retained_size.dart | 15 + .../lib/src/repositories/retaining_path.dart | 16 + .../lib/src/repositories/sample_profile.dart | 189 + .../lib/src/repositories/script.dart | 12 + .../lib/src/repositories/settings.dart | 45 + .../src/repositories/single_target_cache.dart | 12 + .../strongly_reachable_instances.dart | 34 + .../src/repositories/subtype_test_cache.dart | 12 + .../lib/src/repositories/target.dart | 129 + .../lib/src/repositories/timeline.dart | 37 + .../lib/src/repositories/timeline_base.dart | 121 + .../lib/src/repositories/type_arguments.dart | 12 + .../lib/src/repositories/unlinked_call.dart | 12 + pkg/observatory/lib/src/repositories/vm.dart | 13 + .../src/sample_profile/sample_profile.dart | 1370 +++ pkg/observatory/lib/src/service/object.dart | 5095 ++++++++ pkg/observatory/lib/tracer.dart | 97 + pkg/observatory/lib/utils.dart | 306 + pkg/observatory/observatory_sources.gni | 281 + pkg/observatory/pubspec.yaml | 19 + pkg/observatory/tools/heap_snapshot.dart | 429 + pkg/observatory/update_sources.py | 49 + pkg/observatory/web/favicon.ico | Bin 0 -> 1589 bytes pkg/observatory/web/index.html | 11 + pkg/observatory/web/main.dart | 19 + pkg/observatory/web/third_party/README.md | 4 + .../web/third_party/trace_viewer_full.html | 10249 ++++++++++++++++ .../web/third_party/webcomponents.min.js | 14 + pkg/observatory/web/timeline.html | 30 + pkg/observatory/web/timeline.js | 814 ++ .../web/timeline_message_handler.js | 57 + tools/package_deps/bin/package_deps.dart | 10 +- 284 files changed, 56469 insertions(+), 1 deletion(-) create mode 100644 pkg/observatory/.gitignore create mode 100644 pkg/observatory/README.md create mode 100644 pkg/observatory/analysis_options.yaml create mode 100644 pkg/observatory/bin/activate.dart create mode 100644 pkg/observatory/bin/observatory.dart create mode 100644 pkg/observatory/lib/allocation_profile.dart create mode 100644 pkg/observatory/lib/app.dart create mode 100644 pkg/observatory/lib/cli.dart create mode 100644 pkg/observatory/lib/debugger.dart create mode 100644 pkg/observatory/lib/elements.dart create mode 100644 pkg/observatory/lib/event.dart create mode 100644 pkg/observatory/lib/models.dart create mode 100644 pkg/observatory/lib/object_graph.dart create mode 100644 pkg/observatory/lib/repositories.dart create mode 100644 pkg/observatory/lib/sample_profile.dart create mode 100644 pkg/observatory/lib/service.dart create mode 100644 pkg/observatory/lib/service_common.dart create mode 100644 pkg/observatory/lib/service_html.dart create mode 100644 pkg/observatory/lib/service_io.dart create mode 100644 pkg/observatory/lib/src/allocation_profile/allocation_profile.dart create mode 100644 pkg/observatory/lib/src/app/application.dart create mode 100644 pkg/observatory/lib/src/app/location_manager.dart create mode 100644 pkg/observatory/lib/src/app/notification.dart create mode 100644 pkg/observatory/lib/src/app/page.dart create mode 100644 pkg/observatory/lib/src/app/settings.dart create mode 100644 pkg/observatory/lib/src/app/view_model.dart create mode 100644 pkg/observatory/lib/src/cli/command.dart create mode 100644 pkg/observatory/lib/src/debugger/debugger.dart create mode 100644 pkg/observatory/lib/src/debugger/debugger_location.dart create mode 100644 pkg/observatory/lib/src/elements/allocation_profile.dart create mode 100644 pkg/observatory/lib/src/elements/class_allocation_profile.dart create mode 100644 pkg/observatory/lib/src/elements/class_instances.dart create mode 100644 pkg/observatory/lib/src/elements/class_ref.dart create mode 100644 pkg/observatory/lib/src/elements/class_tree.dart create mode 100644 pkg/observatory/lib/src/elements/class_view.dart create mode 100644 pkg/observatory/lib/src/elements/code_ref.dart create mode 100644 pkg/observatory/lib/src/elements/code_view.dart create mode 100644 pkg/observatory/lib/src/elements/containers/search_bar.dart create mode 100644 pkg/observatory/lib/src/elements/containers/virtual_collection.dart create mode 100644 pkg/observatory/lib/src/elements/containers/virtual_tree.dart create mode 100644 pkg/observatory/lib/src/elements/context_ref.dart create mode 100644 pkg/observatory/lib/src/elements/context_view.dart create mode 100644 pkg/observatory/lib/src/elements/cpu_profile.dart create mode 100644 pkg/observatory/lib/src/elements/cpu_profile/virtual_tree.dart create mode 100644 pkg/observatory/lib/src/elements/cpu_profile_table.dart create mode 100644 pkg/observatory/lib/src/elements/css/shared.css create mode 100644 pkg/observatory/lib/src/elements/curly_block.dart create mode 100644 pkg/observatory/lib/src/elements/debugger.dart create mode 100644 pkg/observatory/lib/src/elements/error_ref.dart create mode 100644 pkg/observatory/lib/src/elements/error_view.dart create mode 100644 pkg/observatory/lib/src/elements/eval_box.dart create mode 100644 pkg/observatory/lib/src/elements/field_ref.dart create mode 100644 pkg/observatory/lib/src/elements/field_view.dart create mode 100644 pkg/observatory/lib/src/elements/flag_list.dart create mode 100644 pkg/observatory/lib/src/elements/function_ref.dart create mode 100644 pkg/observatory/lib/src/elements/function_view.dart create mode 100644 pkg/observatory/lib/src/elements/general_error.dart create mode 100644 pkg/observatory/lib/src/elements/heap_map.dart create mode 100644 pkg/observatory/lib/src/elements/heap_snapshot.dart create mode 100644 pkg/observatory/lib/src/elements/helpers/any_ref.dart create mode 100644 pkg/observatory/lib/src/elements/helpers/custom_element.dart create mode 100644 pkg/observatory/lib/src/elements/helpers/element_utils.dart create mode 100644 pkg/observatory/lib/src/elements/helpers/nav_bar.dart create mode 100644 pkg/observatory/lib/src/elements/helpers/nav_menu.dart create mode 100644 pkg/observatory/lib/src/elements/helpers/rendering_queue.dart create mode 100644 pkg/observatory/lib/src/elements/helpers/rendering_scheduler.dart create mode 100644 pkg/observatory/lib/src/elements/helpers/uris.dart create mode 100644 pkg/observatory/lib/src/elements/icdata_ref.dart create mode 100644 pkg/observatory/lib/src/elements/icdata_view.dart create mode 100644 pkg/observatory/lib/src/elements/img/chromium_icon.png create mode 100644 pkg/observatory/lib/src/elements/img/dart_icon.png create mode 100644 pkg/observatory/lib/src/elements/img/isolate_icon.png create mode 100644 pkg/observatory/lib/src/elements/inbound_references.dart create mode 100644 pkg/observatory/lib/src/elements/instance_ref.dart create mode 100644 pkg/observatory/lib/src/elements/instance_view.dart create mode 100644 pkg/observatory/lib/src/elements/isolate/counter_chart.dart create mode 100644 pkg/observatory/lib/src/elements/isolate/location.dart create mode 100644 pkg/observatory/lib/src/elements/isolate/run_state.dart create mode 100644 pkg/observatory/lib/src/elements/isolate/shared_summary.dart create mode 100644 pkg/observatory/lib/src/elements/isolate/summary.dart create mode 100644 pkg/observatory/lib/src/elements/isolate_reconnect.dart create mode 100644 pkg/observatory/lib/src/elements/isolate_ref.dart create mode 100644 pkg/observatory/lib/src/elements/isolate_view.dart create mode 100644 pkg/observatory/lib/src/elements/json_view.dart create mode 100644 pkg/observatory/lib/src/elements/library_ref.dart create mode 100644 pkg/observatory/lib/src/elements/library_view.dart create mode 100644 pkg/observatory/lib/src/elements/local_var_descriptors_ref.dart create mode 100644 pkg/observatory/lib/src/elements/logging.dart create mode 100644 pkg/observatory/lib/src/elements/logging_list.dart create mode 100644 pkg/observatory/lib/src/elements/megamorphiccache_ref.dart create mode 100644 pkg/observatory/lib/src/elements/megamorphiccache_view.dart create mode 100644 pkg/observatory/lib/src/elements/metric/details.dart create mode 100644 pkg/observatory/lib/src/elements/metric/graph.dart create mode 100644 pkg/observatory/lib/src/elements/metrics.dart create mode 100644 pkg/observatory/lib/src/elements/native_memory_profiler.dart create mode 100644 pkg/observatory/lib/src/elements/nav/class_menu.dart create mode 100644 pkg/observatory/lib/src/elements/nav/isolate_menu.dart create mode 100644 pkg/observatory/lib/src/elements/nav/library_menu.dart create mode 100644 pkg/observatory/lib/src/elements/nav/menu_item.dart create mode 100644 pkg/observatory/lib/src/elements/nav/notify.dart create mode 100644 pkg/observatory/lib/src/elements/nav/notify_event.dart create mode 100644 pkg/observatory/lib/src/elements/nav/notify_exception.dart create mode 100644 pkg/observatory/lib/src/elements/nav/refresh.dart create mode 100644 pkg/observatory/lib/src/elements/nav/reload.dart create mode 100644 pkg/observatory/lib/src/elements/nav/top_menu.dart create mode 100644 pkg/observatory/lib/src/elements/nav/vm_menu.dart create mode 100644 pkg/observatory/lib/src/elements/object_common.dart create mode 100644 pkg/observatory/lib/src/elements/object_view.dart create mode 100644 pkg/observatory/lib/src/elements/objectpool_ref.dart create mode 100644 pkg/observatory/lib/src/elements/objectpool_view.dart create mode 100644 pkg/observatory/lib/src/elements/objectstore_view.dart create mode 100644 pkg/observatory/lib/src/elements/observatory_application.dart create mode 100644 pkg/observatory/lib/src/elements/pc_descriptors_ref.dart create mode 100644 pkg/observatory/lib/src/elements/persistent_handles.dart create mode 100644 pkg/observatory/lib/src/elements/ports.dart create mode 100644 pkg/observatory/lib/src/elements/process_snapshot.dart create mode 100644 pkg/observatory/lib/src/elements/retaining_path.dart create mode 100644 pkg/observatory/lib/src/elements/sample_buffer_control.dart create mode 100644 pkg/observatory/lib/src/elements/script_inset.dart create mode 100644 pkg/observatory/lib/src/elements/script_ref.dart create mode 100644 pkg/observatory/lib/src/elements/script_view.dart create mode 100644 pkg/observatory/lib/src/elements/sentinel_value.dart create mode 100644 pkg/observatory/lib/src/elements/sentinel_view.dart create mode 100644 pkg/observatory/lib/src/elements/singletargetcache_ref.dart create mode 100644 pkg/observatory/lib/src/elements/singletargetcache_view.dart create mode 100644 pkg/observatory/lib/src/elements/source_inset.dart create mode 100644 pkg/observatory/lib/src/elements/source_link.dart create mode 100644 pkg/observatory/lib/src/elements/stack_trace_tree_config.dart create mode 100644 pkg/observatory/lib/src/elements/strongly_reachable_instances.dart create mode 100644 pkg/observatory/lib/src/elements/subtypetestcache_ref.dart create mode 100644 pkg/observatory/lib/src/elements/subtypetestcache_view.dart create mode 100644 pkg/observatory/lib/src/elements/timeline_page.dart create mode 100644 pkg/observatory/lib/src/elements/tree_map.dart create mode 100644 pkg/observatory/lib/src/elements/type_arguments_ref.dart create mode 100644 pkg/observatory/lib/src/elements/unknown_ref.dart create mode 100644 pkg/observatory/lib/src/elements/unlinkedcall_ref.dart create mode 100644 pkg/observatory/lib/src/elements/unlinkedcall_view.dart create mode 100644 pkg/observatory/lib/src/elements/vm_connect.dart create mode 100644 pkg/observatory/lib/src/elements/vm_connect_target.dart create mode 100644 pkg/observatory/lib/src/elements/vm_view.dart create mode 100644 pkg/observatory/lib/src/models/exceptions.dart create mode 100644 pkg/observatory/lib/src/models/objects/allocation_profile.dart create mode 100644 pkg/observatory/lib/src/models/objects/breakpoint.dart create mode 100644 pkg/observatory/lib/src/models/objects/class.dart create mode 100644 pkg/observatory/lib/src/models/objects/code.dart create mode 100644 pkg/observatory/lib/src/models/objects/context.dart create mode 100644 pkg/observatory/lib/src/models/objects/error.dart create mode 100644 pkg/observatory/lib/src/models/objects/event.dart create mode 100644 pkg/observatory/lib/src/models/objects/extension_data.dart create mode 100644 pkg/observatory/lib/src/models/objects/field.dart create mode 100644 pkg/observatory/lib/src/models/objects/flag.dart create mode 100644 pkg/observatory/lib/src/models/objects/frame.dart create mode 100644 pkg/observatory/lib/src/models/objects/function.dart create mode 100644 pkg/observatory/lib/src/models/objects/guarded.dart create mode 100644 pkg/observatory/lib/src/models/objects/heap_space.dart create mode 100644 pkg/observatory/lib/src/models/objects/icdata.dart create mode 100644 pkg/observatory/lib/src/models/objects/inbound_references.dart create mode 100644 pkg/observatory/lib/src/models/objects/instance.dart create mode 100644 pkg/observatory/lib/src/models/objects/isolate.dart create mode 100644 pkg/observatory/lib/src/models/objects/isolate_group.dart create mode 100644 pkg/observatory/lib/src/models/objects/library.dart create mode 100644 pkg/observatory/lib/src/models/objects/local_var_descriptors.dart create mode 100644 pkg/observatory/lib/src/models/objects/map_association.dart create mode 100644 pkg/observatory/lib/src/models/objects/megamorphiccache.dart create mode 100644 pkg/observatory/lib/src/models/objects/metric.dart create mode 100644 pkg/observatory/lib/src/models/objects/notification.dart create mode 100644 pkg/observatory/lib/src/models/objects/object.dart create mode 100644 pkg/observatory/lib/src/models/objects/objectpool.dart create mode 100644 pkg/observatory/lib/src/models/objects/objectstore.dart create mode 100644 pkg/observatory/lib/src/models/objects/pc_descriptors.dart create mode 100644 pkg/observatory/lib/src/models/objects/persistent_handles.dart create mode 100644 pkg/observatory/lib/src/models/objects/ports.dart create mode 100644 pkg/observatory/lib/src/models/objects/retaining_path.dart create mode 100644 pkg/observatory/lib/src/models/objects/sample_profile.dart create mode 100644 pkg/observatory/lib/src/models/objects/script.dart create mode 100644 pkg/observatory/lib/src/models/objects/sentinel.dart create mode 100644 pkg/observatory/lib/src/models/objects/service.dart create mode 100644 pkg/observatory/lib/src/models/objects/single_target_cache.dart create mode 100644 pkg/observatory/lib/src/models/objects/source_location.dart create mode 100644 pkg/observatory/lib/src/models/objects/subtype_test_cache.dart create mode 100644 pkg/observatory/lib/src/models/objects/target.dart create mode 100644 pkg/observatory/lib/src/models/objects/timeline.dart create mode 100644 pkg/observatory/lib/src/models/objects/timeline_event.dart create mode 100644 pkg/observatory/lib/src/models/objects/type_arguments.dart create mode 100644 pkg/observatory/lib/src/models/objects/unknown.dart create mode 100644 pkg/observatory/lib/src/models/objects/unlinked_call.dart create mode 100644 pkg/observatory/lib/src/models/objects/vm.dart create mode 100644 pkg/observatory/lib/src/models/repositories/allocation_profile.dart create mode 100644 pkg/observatory/lib/src/models/repositories/breakpoint.dart create mode 100644 pkg/observatory/lib/src/models/repositories/class.dart create mode 100644 pkg/observatory/lib/src/models/repositories/context.dart create mode 100644 pkg/observatory/lib/src/models/repositories/editor.dart create mode 100644 pkg/observatory/lib/src/models/repositories/eval.dart create mode 100644 pkg/observatory/lib/src/models/repositories/event.dart create mode 100644 pkg/observatory/lib/src/models/repositories/field.dart create mode 100644 pkg/observatory/lib/src/models/repositories/flag.dart create mode 100644 pkg/observatory/lib/src/models/repositories/function.dart create mode 100644 pkg/observatory/lib/src/models/repositories/heap_snapshot.dart create mode 100644 pkg/observatory/lib/src/models/repositories/icdata.dart create mode 100644 pkg/observatory/lib/src/models/repositories/inbound_references.dart create mode 100644 pkg/observatory/lib/src/models/repositories/instance.dart create mode 100644 pkg/observatory/lib/src/models/repositories/isolate.dart create mode 100644 pkg/observatory/lib/src/models/repositories/isolate_group.dart create mode 100644 pkg/observatory/lib/src/models/repositories/library.dart create mode 100644 pkg/observatory/lib/src/models/repositories/megamorphiccache.dart create mode 100644 pkg/observatory/lib/src/models/repositories/metric.dart create mode 100644 pkg/observatory/lib/src/models/repositories/notification.dart create mode 100644 pkg/observatory/lib/src/models/repositories/object.dart create mode 100644 pkg/observatory/lib/src/models/repositories/objectpool.dart create mode 100644 pkg/observatory/lib/src/models/repositories/objectstore.dart create mode 100644 pkg/observatory/lib/src/models/repositories/persistent_handles.dart create mode 100644 pkg/observatory/lib/src/models/repositories/ports.dart create mode 100644 pkg/observatory/lib/src/models/repositories/reachable_size.dart create mode 100644 pkg/observatory/lib/src/models/repositories/retained_size.dart create mode 100644 pkg/observatory/lib/src/models/repositories/retaining_path.dart create mode 100644 pkg/observatory/lib/src/models/repositories/sample_profile.dart create mode 100644 pkg/observatory/lib/src/models/repositories/script.dart create mode 100644 pkg/observatory/lib/src/models/repositories/single_target_cache.dart create mode 100644 pkg/observatory/lib/src/models/repositories/strongly_reachable_instances.dart create mode 100644 pkg/observatory/lib/src/models/repositories/subtype_test_cache.dart create mode 100644 pkg/observatory/lib/src/models/repositories/target.dart create mode 100644 pkg/observatory/lib/src/models/repositories/timeline.dart create mode 100644 pkg/observatory/lib/src/models/repositories/type_arguments.dart create mode 100644 pkg/observatory/lib/src/models/repositories/unlinked_call.dart create mode 100644 pkg/observatory/lib/src/models/repositories/vm.dart create mode 100644 pkg/observatory/lib/src/repositories/allocation_profile.dart create mode 100644 pkg/observatory/lib/src/repositories/breakpoint.dart create mode 100644 pkg/observatory/lib/src/repositories/class.dart create mode 100644 pkg/observatory/lib/src/repositories/context.dart create mode 100644 pkg/observatory/lib/src/repositories/editor.dart create mode 100644 pkg/observatory/lib/src/repositories/eval.dart create mode 100644 pkg/observatory/lib/src/repositories/event.dart create mode 100644 pkg/observatory/lib/src/repositories/field.dart create mode 100644 pkg/observatory/lib/src/repositories/flag.dart create mode 100644 pkg/observatory/lib/src/repositories/function.dart create mode 100644 pkg/observatory/lib/src/repositories/heap_snapshot.dart create mode 100644 pkg/observatory/lib/src/repositories/icdata.dart create mode 100644 pkg/observatory/lib/src/repositories/inbound_references.dart create mode 100644 pkg/observatory/lib/src/repositories/instance.dart create mode 100644 pkg/observatory/lib/src/repositories/isolate.dart create mode 100644 pkg/observatory/lib/src/repositories/isolate_group.dart create mode 100644 pkg/observatory/lib/src/repositories/library.dart create mode 100644 pkg/observatory/lib/src/repositories/megamorphiccache.dart create mode 100644 pkg/observatory/lib/src/repositories/metric.dart create mode 100644 pkg/observatory/lib/src/repositories/notification.dart create mode 100644 pkg/observatory/lib/src/repositories/object.dart create mode 100644 pkg/observatory/lib/src/repositories/objectpool.dart create mode 100644 pkg/observatory/lib/src/repositories/objectstore.dart create mode 100644 pkg/observatory/lib/src/repositories/persistent_handles.dart create mode 100644 pkg/observatory/lib/src/repositories/ports.dart create mode 100644 pkg/observatory/lib/src/repositories/reachable_size.dart create mode 100644 pkg/observatory/lib/src/repositories/retained_size.dart create mode 100644 pkg/observatory/lib/src/repositories/retaining_path.dart create mode 100644 pkg/observatory/lib/src/repositories/sample_profile.dart create mode 100644 pkg/observatory/lib/src/repositories/script.dart create mode 100644 pkg/observatory/lib/src/repositories/settings.dart create mode 100644 pkg/observatory/lib/src/repositories/single_target_cache.dart create mode 100644 pkg/observatory/lib/src/repositories/strongly_reachable_instances.dart create mode 100644 pkg/observatory/lib/src/repositories/subtype_test_cache.dart create mode 100644 pkg/observatory/lib/src/repositories/target.dart create mode 100644 pkg/observatory/lib/src/repositories/timeline.dart create mode 100644 pkg/observatory/lib/src/repositories/timeline_base.dart create mode 100644 pkg/observatory/lib/src/repositories/type_arguments.dart create mode 100644 pkg/observatory/lib/src/repositories/unlinked_call.dart create mode 100644 pkg/observatory/lib/src/repositories/vm.dart create mode 100644 pkg/observatory/lib/src/sample_profile/sample_profile.dart create mode 100644 pkg/observatory/lib/src/service/object.dart create mode 100644 pkg/observatory/lib/tracer.dart create mode 100644 pkg/observatory/lib/utils.dart create mode 100644 pkg/observatory/observatory_sources.gni create mode 100644 pkg/observatory/pubspec.yaml create mode 100644 pkg/observatory/tools/heap_snapshot.dart create mode 100755 pkg/observatory/update_sources.py create mode 100644 pkg/observatory/web/favicon.ico create mode 100644 pkg/observatory/web/index.html create mode 100644 pkg/observatory/web/main.dart create mode 100644 pkg/observatory/web/third_party/README.md create mode 100644 pkg/observatory/web/third_party/trace_viewer_full.html create mode 100644 pkg/observatory/web/third_party/webcomponents.min.js create mode 100644 pkg/observatory/web/timeline.html create mode 100644 pkg/observatory/web/timeline.js create mode 100644 pkg/observatory/web/timeline_message_handler.js diff --git a/PRESUBMIT.py b/PRESUBMIT.py index d289ab26f9c..016bbc0f9c4 100644 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py @@ -537,6 +537,19 @@ def _CheckDartApiWinCSync(input_api, output_api): return [] +def _CheckNoRuntimeObservatoryChanges(input_api, output_api): + """Ensures that no further changes are made to runtime/observatory.""" + for f in input_api.AffectedFiles(include_deletes=False): + path = f.LocalPath() + if path.startswith("runtime/observatory/"): + return [ + output_api.PresubmitError( + 'Observatory is being moved to pkg/observatory. Files under ' + 'runtime/observatory should no longer be modified.') + ] + return [] + + def _CommonChecks(input_api, output_api): results = [] results.extend(_CheckValidHostsInDEPS(input_api, output_api)) @@ -552,6 +565,7 @@ def _CommonChecks(input_api, output_api): results.extend(_CheckAnalyzerFiles(input_api, output_api)) results.extend(_CheckDevCompilerSync(input_api, output_api)) results.extend(_CheckDartApiWinCSync(input_api, output_api)) + results.extend(_CheckNoRuntimeObservatoryChanges(input_api, output_api)) return results diff --git a/pkg/observatory/.gitignore b/pkg/observatory/.gitignore new file mode 100644 index 00000000000..d5e9f3e48a3 --- /dev/null +++ b/pkg/observatory/.gitignore @@ -0,0 +1,6 @@ +bootstrap_css +out +build +.pub +.idea +.dart_tool diff --git a/pkg/observatory/README.md b/pkg/observatory/README.md new file mode 100644 index 00000000000..150e8d98de9 --- /dev/null +++ b/pkg/observatory/README.md @@ -0,0 +1,36 @@ +# Observatory Developer Tooling + +_**WARNING: This tooling is deprecated and maintained on a best-effort basis by members of the Dart VM team.**_ + +## Activating Observatory + +To easily serve Observatory without having to manually run `webdev serve` in this project, Observatory can be activated as a +global `pub` package and run via the `observatory` tool. + +To do this, run `dart bin/activate.dart`, which will create the `observatory` script at `$PUB_CACHE/bin`. + +**Note:** If any changes are made to `bin/observatory.dart`, `dart bin/activate.dart` must be re-run to pick up the changes. + +## Serving Observatory + +To serve Observatory, simply run the `observatory` command. To automatically launch Observatory in Chrome, provide the `--launch` +flag. + +## Developing Observatory + +When making changes to Observatory, run `observatory --debug` to run Observatory with DDC. This will allow for a more typical +web development workflow as changes to the Observatory sources will be picked up automatically on a page refresh. + +## Code Reviews + +The development workflow of Dart (and Observatory) is based on code reviews. + +Follow the code review [instructions][code_review] to be able to successfully +submit your code. + +The main reviewers for Observatory related CLs are: + +- aam +- rmacnak + +[code_review]: https://github.com/dart-lang/sdk/blob/main/docs/Code-review-workflow-with-GitHub-and-Gerrit.md "Code Review" diff --git a/pkg/observatory/analysis_options.yaml b/pkg/observatory/analysis_options.yaml new file mode 100644 index 00000000000..bbfc22ce68c --- /dev/null +++ b/pkg/observatory/analysis_options.yaml @@ -0,0 +1,12 @@ +analyzer: + errors: + dead_code: info + getter_not_subtype_setter_types: info + missing_enum_constant_in_switch: info + unused_element: info + unused_field: info + unused_import: info + unused_local_variable: info +linter: + rules: + - prefer_relative_imports diff --git a/pkg/observatory/bin/activate.dart b/pkg/observatory/bin/activate.dart new file mode 100644 index 00000000000..265f21abc77 --- /dev/null +++ b/pkg/observatory/bin/activate.dart @@ -0,0 +1,35 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +void main() { + final snapshotDir = Directory( + Platform.script.resolve('../.dart_tool/pub/bin/observatory').toFilePath(), + ); + if (snapshotDir.existsSync()) { + print('Deleting previous observatory script snapshot at $snapshotDir'); + snapshotDir.deleteSync(recursive: true); + } + + print('Globally activating observatory...'); + Process.runSync(Platform.resolvedExecutable, [ + 'pub', + 'global', + 'activate', + '--source', + 'path', + Platform.script.resolve('..').toFilePath(), + ]); + print('observatory has been globally activated.'); + + try { + Process.runSync('observatory', const ['--help']); + } on ProcessException { + stderr.writeln(''' +WARNING: observatory is globally activated but not available on your path. +Be sure to add \$PUB_CACHE/bin/ to your path. + '''); + } +} diff --git a/pkg/observatory/bin/observatory.dart b/pkg/observatory/bin/observatory.dart new file mode 100644 index 00000000000..63ca6a1e5d1 --- /dev/null +++ b/pkg/observatory/bin/observatory.dart @@ -0,0 +1,122 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// IMPORTANT: to pick up changes made in this file, activate.dart must be +// re-run to delete the old snapshot. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:args/command_runner.dart'; +import 'package:browser_launcher/browser_launcher.dart'; + +class ObservatoryCommand extends CommandRunner { + ObservatoryCommand() + : super('Observatory', 'Serves the Observatory developer tool.'); + + static const String kHelp = 'help'; + static const String kDebug = 'debug'; + static const String kLaunch = 'launch'; + + @override + final ArgParser argParser = ArgParser() + ..addFlag( + kDebug, + help: + 'Run Observatory in debug mode. Useful when making changes to ' + 'Observatory to automatically pick up code changes.', + ) + ..addFlag(kLaunch, help: 'Launch Observatory in Chrome.'); + + @override + Future runCommand(ArgResults results) async { + if (results.flag(kHelp)) { + printUsage(); + return; + } + if (!_checkForWebDev()) { + print('Warning: webdev is not installed. Installing it now...'); + _activateWebDev(); + print('webdev installed successfully.'); + } + + await _startWebDev( + debug: results.flag(kDebug), + launch: results.flag(kLaunch), + ); + } + + bool _checkForWebDev() { + final result = Process.runSync(Platform.resolvedExecutable, [ + 'pub', + 'global', + 'list', + ]); + return result.stdout.contains('webdev'); + } + + void _activateWebDev() { + final result = Process.runSync(Platform.resolvedExecutable, [ + 'pub', + 'global', + 'activate', + 'webdev', + ]); + if (result.exitCode != 0) { + throw StateError(''' +Unexpected issue encountered while activating webdev.' + +STDOUT: +${result.stdout} + +STDERR: +${result.stderr} +'''); + } + } + + Future _startWebDev({required bool debug, required bool launch}) async { + Directory.current = _findObservatoryProjectRoot(); + final process = await Process.start(Platform.resolvedExecutable, [ + 'pub', + 'global', + 'run', + 'webdev', + 'serve', + if (!debug) '--release', + ]); + final uriCompleter = Completer(); + final uriRegExp = RegExp('Serving `web` on (http://.*)'); + late StreamSubscription sub; + sub = process.stdout.transform(utf8.decoder).listen((e) { + if (uriRegExp.hasMatch(e)) { + uriCompleter.complete(uriRegExp.firstMatch(e)!.group(1)); + sub.cancel(); + } + }); + + final observatoryUri = await uriCompleter.future; + print('Observatory is available at: $observatoryUri'); + + if (launch) { + Chrome.start([observatoryUri]); + } + await process.exitCode; + } + + String _findObservatoryProjectRoot() { + final uri = Platform.script; + String relativePath = '..'; + if (uri.path.endsWith('.snapshot')) { + relativePath = '../../../..'; + } + return uri.resolve(relativePath).toFilePath(); + } +} + +Future main(List args) async { + await ObservatoryCommand().run(args); +} diff --git a/pkg/observatory/lib/allocation_profile.dart b/pkg/observatory/lib/allocation_profile.dart new file mode 100644 index 00000000000..b3e3773d0f7 --- /dev/null +++ b/pkg/observatory/lib/allocation_profile.dart @@ -0,0 +1,10 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library allocation_profiler; + +import 'models.dart' as M; +import 'service.dart' as S; + +part 'src/allocation_profile/allocation_profile.dart'; diff --git a/pkg/observatory/lib/app.dart b/pkg/observatory/lib/app.dart new file mode 100644 index 00000000000..0ede83159f0 --- /dev/null +++ b/pkg/observatory/lib/app.dart @@ -0,0 +1,31 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library app; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:js_interop'; + +import 'package:logging/logging.dart'; +import 'package:stack_trace/stack_trace.dart'; +import 'package:web/web.dart' as web; + +import 'elements.dart'; +import 'event.dart'; +import 'models.dart' as M; +import 'repositories.dart'; +import 'service_html.dart'; +import 'src/elements/helpers/element_utils.dart'; +import 'src/elements/helpers/uris.dart'; +import 'tracer.dart'; + +export 'utils.dart'; + +part 'src/app/application.dart'; +part 'src/app/location_manager.dart'; +part 'src/app/notification.dart'; +part 'src/app/page.dart'; +part 'src/app/settings.dart'; +part 'src/app/view_model.dart'; diff --git a/pkg/observatory/lib/cli.dart b/pkg/observatory/lib/cli.dart new file mode 100644 index 00000000000..607c58a9196 --- /dev/null +++ b/pkg/observatory/lib/cli.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library cli; + +import 'dart:async'; + +part 'src/cli/command.dart'; diff --git a/pkg/observatory/lib/debugger.dart b/pkg/observatory/lib/debugger.dart new file mode 100644 index 00000000000..d78663bc427 --- /dev/null +++ b/pkg/observatory/lib/debugger.dart @@ -0,0 +1,13 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library debugger; + +import 'dart:async'; + +import 'models.dart' as M; +import 'service.dart'; + +part 'src/debugger/debugger.dart'; +part 'src/debugger/debugger_location.dart'; diff --git a/pkg/observatory/lib/elements.dart b/pkg/observatory/lib/elements.dart new file mode 100644 index 00000000000..25a600e6ecc --- /dev/null +++ b/pkg/observatory/lib/elements.dart @@ -0,0 +1,97 @@ +library observatory_elements; + +export 'src/elements/allocation_profile.dart'; +export 'src/elements/class_allocation_profile.dart'; +export 'src/elements/class_instances.dart'; +export 'src/elements/class_ref.dart'; +export 'src/elements/class_tree.dart'; +export 'src/elements/class_view.dart'; +export 'src/elements/code_ref.dart'; +export 'src/elements/code_view.dart'; +export 'src/elements/containers/search_bar.dart'; +export 'src/elements/containers/virtual_collection.dart'; +export 'src/elements/containers/virtual_tree.dart'; +export 'src/elements/context_ref.dart'; +export 'src/elements/context_view.dart'; +export 'src/elements/cpu_profile.dart'; +export 'src/elements/cpu_profile/virtual_tree.dart'; +export 'src/elements/cpu_profile_table.dart'; +export 'src/elements/curly_block.dart'; +export 'src/elements/debugger.dart'; +export 'src/elements/error_ref.dart'; +export 'src/elements/error_view.dart'; +export 'src/elements/eval_box.dart'; +export 'src/elements/field_ref.dart'; +export 'src/elements/field_view.dart'; +export 'src/elements/flag_list.dart'; +export 'src/elements/function_ref.dart'; +export 'src/elements/function_view.dart'; +export 'src/elements/general_error.dart'; +export 'src/elements/heap_map.dart'; +export 'src/elements/heap_snapshot.dart'; +export 'src/elements/process_snapshot.dart'; +export 'src/elements/helpers/rendering_queue.dart'; +export 'src/elements/icdata_ref.dart'; +export 'src/elements/icdata_view.dart'; +export 'src/elements/instance_ref.dart'; +export 'src/elements/instance_view.dart'; +export 'src/elements/isolate/counter_chart.dart'; +export 'src/elements/isolate/location.dart'; +export 'src/elements/isolate/run_state.dart'; +export 'src/elements/isolate/shared_summary.dart'; +export 'src/elements/isolate/summary.dart'; +export 'src/elements/isolate_reconnect.dart'; +export 'src/elements/isolate_ref.dart'; +export 'src/elements/isolate_view.dart'; +export 'src/elements/json_view.dart'; +export 'src/elements/library_ref.dart'; +export 'src/elements/library_view.dart'; +export 'src/elements/local_var_descriptors_ref.dart'; +export 'src/elements/logging.dart'; +export 'src/elements/megamorphiccache_ref.dart'; +export 'src/elements/megamorphiccache_view.dart'; +export 'src/elements/metric/details.dart'; +export 'src/elements/metric/graph.dart'; +export 'src/elements/metrics.dart'; +export 'src/elements/native_memory_profiler.dart'; +export 'src/elements/nav/class_menu.dart'; +export 'src/elements/nav/isolate_menu.dart'; +export 'src/elements/nav/library_menu.dart'; +export 'src/elements/nav/menu_item.dart'; +export 'src/elements/nav/notify.dart'; +export 'src/elements/nav/notify_event.dart'; +export 'src/elements/nav/notify_exception.dart'; +export 'src/elements/nav/refresh.dart'; +export 'src/elements/nav/top_menu.dart'; +export 'src/elements/nav/vm_menu.dart'; +export 'src/elements/object_common.dart'; +export 'src/elements/object_view.dart'; +export 'src/elements/objectpool_ref.dart'; +export 'src/elements/objectpool_view.dart'; +export 'src/elements/objectstore_view.dart'; +export 'src/elements/observatory_application.dart'; +export 'src/elements/pc_descriptors_ref.dart'; +export 'src/elements/persistent_handles.dart'; +export 'src/elements/ports.dart'; +export 'src/elements/sample_buffer_control.dart'; +export 'src/elements/script_inset.dart'; +export 'src/elements/script_ref.dart'; +export 'src/elements/script_view.dart'; +export 'src/elements/sentinel_value.dart'; +export 'src/elements/sentinel_view.dart'; +export 'src/elements/singletargetcache_ref.dart'; +export 'src/elements/singletargetcache_view.dart'; +export 'src/elements/source_inset.dart'; +export 'src/elements/source_link.dart'; +export 'src/elements/stack_trace_tree_config.dart'; +export 'src/elements/strongly_reachable_instances.dart'; +export 'src/elements/subtypetestcache_ref.dart'; +export 'src/elements/subtypetestcache_view.dart'; +export 'src/elements/timeline_page.dart'; +export 'src/elements/type_arguments_ref.dart'; +export 'src/elements/unknown_ref.dart'; +export 'src/elements/unlinkedcall_ref.dart'; +export 'src/elements/unlinkedcall_view.dart'; +export 'src/elements/vm_connect.dart'; +export 'src/elements/vm_connect_target.dart'; +export 'src/elements/vm_view.dart'; diff --git a/pkg/observatory/lib/event.dart b/pkg/observatory/lib/event.dart new file mode 100644 index 00000000000..bd8006b7eb9 --- /dev/null +++ b/pkg/observatory/lib/event.dart @@ -0,0 +1,337 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:logging/logging.dart'; + +import 'models.dart' as M; +import 'service.dart' as S; + +class VMUpdateEvent implements M.VMUpdateEvent { + final DateTime timestamp; + final M.VMRef vm; + VMUpdateEvent(this.timestamp, this.vm); +} + +class IsolateStartEvent implements M.IsolateStartEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + IsolateStartEvent(this.timestamp, this.isolate); +} + +class IsolateRunnableEvent implements M.IsolateRunnableEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + IsolateRunnableEvent(this.timestamp, this.isolate); +} + +class IsolateExitEvent implements M.IsolateExitEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + IsolateExitEvent(this.timestamp, this.isolate); +} + +class IsolateUpdateEvent implements M.IsolateUpdateEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + IsolateUpdateEvent(this.timestamp, this.isolate); +} + +class IsolateReloadEvent implements M.IsolateReloadEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final M.ErrorRef error; + IsolateReloadEvent(this.timestamp, this.isolate, this.error); +} + +class ServiceExtensionAddedEvent implements M.ServiceExtensionAddedEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final String extensionRPC; + ServiceExtensionAddedEvent(this.timestamp, this.isolate, this.extensionRPC); +} + +class DebuggerSettingsUpdateEvent implements M.DebuggerSettingsUpdateEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + DebuggerSettingsUpdateEvent(this.timestamp, this.isolate); +} + +class PauseStartEvent implements M.PauseStartEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + PauseStartEvent(this.timestamp, this.isolate); +} + +class PauseExitEvent implements M.PauseExitEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + PauseExitEvent(this.timestamp, this.isolate); +} + +class PauseBreakpointEvent implements M.PauseBreakpointEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final Iterable pauseBreakpoints; + final M.Frame topFrame; + final bool atAsyncSuspension; + + /// [optional] + final M.Breakpoint? breakpoint; + PauseBreakpointEvent( + this.timestamp, + this.isolate, + Iterable pauseBreakpoints, + this.topFrame, + this.atAsyncSuspension, [ + this.breakpoint, + ]) : pauseBreakpoints = new List.unmodifiable(pauseBreakpoints) {} +} + +class PauseInterruptedEvent implements M.PauseInterruptedEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final M.Frame? topFrame; + final bool atAsyncSuspension; + PauseInterruptedEvent( + this.timestamp, + this.isolate, + this.topFrame, + this.atAsyncSuspension, + ); +} + +class PausePostRequestEvent implements M.PausePostRequestEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final M.Frame? topFrame; + final bool atAsyncSuspension; + PausePostRequestEvent( + this.timestamp, + this.isolate, + this.topFrame, + this.atAsyncSuspension, + ); +} + +class PauseExceptionEvent implements M.PauseExceptionEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final M.Frame topFrame; + final M.InstanceRef exception; + PauseExceptionEvent( + this.timestamp, + this.isolate, + this.topFrame, + this.exception, + ); +} + +class ResumeEvent implements M.ResumeEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final M.Frame? topFrame; + ResumeEvent(this.timestamp, this.isolate, this.topFrame); +} + +class BreakpointAddedEvent implements M.BreakpointAddedEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final M.Breakpoint breakpoint; + BreakpointAddedEvent(this.timestamp, this.isolate, this.breakpoint); +} + +class BreakpointResolvedEvent implements M.BreakpointResolvedEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final M.Breakpoint breakpoint; + BreakpointResolvedEvent(this.timestamp, this.isolate, this.breakpoint); +} + +class BreakpointRemovedEvent implements M.BreakpointRemovedEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final M.Breakpoint breakpoint; + BreakpointRemovedEvent(this.timestamp, this.isolate, this.breakpoint); +} + +class InspectEvent implements M.InspectEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final M.InstanceRef inspectee; + InspectEvent(this.timestamp, this.isolate, this.inspectee); +} + +class NoneEvent implements M.NoneEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + NoneEvent(this.timestamp, this.isolate); +} + +class GCEvent implements M.GCEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + GCEvent(this.timestamp, this.isolate); +} + +class LoggingEvent implements M.LoggingEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final Map logRecord; + LoggingEvent(this.timestamp, this.isolate, this.logRecord); +} + +class ExtensionEvent implements M.ExtensionEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final String extensionKind; + final M.ExtensionData extensionData; + ExtensionEvent( + this.timestamp, + this.isolate, + this.extensionKind, + this.extensionData, + ); +} + +class TimelineEventsEvent implements M.TimelineEventsEvent { + final DateTime timestamp; + final M.IsolateRef isolate; + final Iterable timelineEvents; + TimelineEventsEvent( + this.timestamp, + this.isolate, + Iterable timelineEvents, + ) : timelineEvents = new List.unmodifiable(timelineEvents) {} +} + +class ConnectionClosedEvent implements M.ConnectionClosedEvent { + final DateTime timestamp; + final String reason; + ConnectionClosedEvent(this.timestamp, this.reason); +} + +class ServiceRegisteredEvent implements M.ServiceRegisteredEvent { + final DateTime timestamp; + final String service; + final String method; + final String alias; + ServiceRegisteredEvent(this.timestamp, this.service, this.method, this.alias); +} + +class ServiceUnregisteredEvent implements M.ServiceUnregisteredEvent { + final DateTime timestamp; + final String service; + final String method; + ServiceUnregisteredEvent(this.timestamp, this.service, this.method); +} + +M.Event? createEventFromServiceEvent(S.ServiceEvent event) { + switch (event.kind) { + case S.ServiceEvent.kVMUpdate: + return new VMUpdateEvent(event.timestamp!, event.vm); + case S.ServiceEvent.kIsolateStart: + return new IsolateStartEvent(event.timestamp!, event.isolate!); + case S.ServiceEvent.kIsolateRunnable: + return new IsolateRunnableEvent(event.timestamp!, event.isolate!); + case S.ServiceEvent.kIsolateUpdate: + return new IsolateUpdateEvent(event.timestamp!, event.isolate!); + case S.ServiceEvent.kIsolateReload: + return new IsolateReloadEvent( + event.timestamp!, + event.isolate!, + event.error!, + ); + case S.ServiceEvent.kIsolateExit: + return new IsolateExitEvent(event.timestamp!, event.isolate!); + case S.ServiceEvent.kBreakpointAdded: + return new BreakpointAddedEvent( + event.timestamp!, + event.isolate!, + event.breakpoint!, + ); + case S.ServiceEvent.kBreakpointResolved: + return new BreakpointResolvedEvent( + event.timestamp!, + event.isolate!, + event.breakpoint!, + ); + case S.ServiceEvent.kBreakpointRemoved: + return new BreakpointRemovedEvent( + event.timestamp!, + event.isolate!, + event.breakpoint!, + ); + case S.ServiceEvent.kDebuggerSettingsUpdate: + return new DebuggerSettingsUpdateEvent(event.timestamp!, event.isolate!); + case S.ServiceEvent.kResume: + return new ResumeEvent(event.timestamp!, event.isolate!, event.topFrame); + case S.ServiceEvent.kPauseStart: + return new PauseStartEvent(event.timestamp!, event.isolate!); + case S.ServiceEvent.kPauseExit: + return new PauseExitEvent(event.timestamp!, event.isolate!); + case S.ServiceEvent.kPausePostRequest: + return new PausePostRequestEvent( + event.timestamp!, + event.isolate!, + event.topFrame, + event.atAsyncSuspension!, + ); + case S.ServiceEvent.kPauseBreakpoint: + return new PauseBreakpointEvent( + event.timestamp!, + event.isolate!, + event.pauseBreakpoints!, + event.topFrame!, + event.atAsyncSuspension!, + event.breakpoint, + ); + case S.Isolate.kLoggingStream: + return new LoggingEvent( + event.timestamp!, + event.isolate!, + event.logRecord!, + ); + case S.ServiceEvent.kPauseInterrupted: + return new PauseInterruptedEvent( + event.timestamp!, + event.isolate!, + event.topFrame, + event.atAsyncSuspension!, + ); + case S.ServiceEvent.kPauseException: + return new PauseExceptionEvent( + event.timestamp!, + event.isolate!, + event.topFrame!, + event.exception!, + ); + case S.ServiceEvent.kInspect: + return new InspectEvent( + event.timestamp!, + event.isolate!, + event.inspectee!, + ); + case S.ServiceEvent.kGC: + return new GCEvent(event.timestamp!, event.isolate!); + case S.ServiceEvent.kServiceRegistered: + return new ServiceRegisteredEvent( + event.timestamp!, + event.service!, + event.method!, + event.alias!, + ); + case S.ServiceEvent.kServiceUnregistered: + return new ServiceUnregisteredEvent( + event.timestamp!, + event.service!, + event.method!, + ); + case S.ServiceEvent.kNone: + return new NoneEvent(event.timestamp!, event.isolate!); + default: + // Ignore unrecognized events. + Logger.root.severe('Unrecognized event: $event'); + return null; + } +} diff --git a/pkg/observatory/lib/models.dart b/pkg/observatory/lib/models.dart new file mode 100644 index 00000000000..b6dc5fda1c7 --- /dev/null +++ b/pkg/observatory/lib/models.dart @@ -0,0 +1,97 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library models; + +import 'dart:async'; + +import 'object_graph.dart'; + +part 'src/models/exceptions.dart'; + +part 'src/models/objects/allocation_profile.dart'; +part 'src/models/objects/breakpoint.dart'; +part 'src/models/objects/class.dart'; +part 'src/models/objects/code.dart'; +part 'src/models/objects/context.dart'; +part 'src/models/objects/error.dart'; +part 'src/models/objects/event.dart'; +part 'src/models/objects/extension_data.dart'; +part 'src/models/objects/field.dart'; +part 'src/models/objects/flag.dart'; +part 'src/models/objects/frame.dart'; +part 'src/models/objects/function.dart'; +part 'src/models/objects/guarded.dart'; +part 'src/models/objects/heap_space.dart'; +part 'src/models/objects/icdata.dart'; +part 'src/models/objects/inbound_references.dart'; +part 'src/models/objects/instance.dart'; +part 'src/models/objects/isolate.dart'; +part 'src/models/objects/isolate_group.dart'; +part 'src/models/objects/library.dart'; +part 'src/models/objects/local_var_descriptors.dart'; +part 'src/models/objects/map_association.dart'; +part 'src/models/objects/megamorphiccache.dart'; +part 'src/models/objects/metric.dart'; +part 'src/models/objects/notification.dart'; +part 'src/models/objects/object.dart'; +part 'src/models/objects/objectpool.dart'; +part 'src/models/objects/objectstore.dart'; +part 'src/models/objects/pc_descriptors.dart'; +part 'src/models/objects/persistent_handles.dart'; +part 'src/models/objects/ports.dart'; +part 'src/models/objects/retaining_path.dart'; +part 'src/models/objects/sample_profile.dart'; +part 'src/models/objects/script.dart'; +part 'src/models/objects/sentinel.dart'; +part 'src/models/objects/service.dart'; +part 'src/models/objects/single_target_cache.dart'; +part 'src/models/objects/source_location.dart'; +part 'src/models/objects/subtype_test_cache.dart'; +part 'src/models/objects/target.dart'; +part 'src/models/objects/timeline.dart'; +part 'src/models/objects/timeline_event.dart'; +part 'src/models/objects/type_arguments.dart'; +part 'src/models/objects/unknown.dart'; +part 'src/models/objects/unlinked_call.dart'; +part 'src/models/objects/vm.dart'; + +part 'src/models/repositories/allocation_profile.dart'; +part 'src/models/repositories/breakpoint.dart'; +part 'src/models/repositories/class.dart'; +part 'src/models/repositories/context.dart'; +part 'src/models/repositories/editor.dart'; +part 'src/models/repositories/eval.dart'; +part 'src/models/repositories/event.dart'; +part 'src/models/repositories/field.dart'; +part 'src/models/repositories/flag.dart'; +part 'src/models/repositories/function.dart'; +part 'src/models/repositories/heap_snapshot.dart'; +part 'src/models/repositories/icdata.dart'; +part 'src/models/repositories/inbound_references.dart'; +part 'src/models/repositories/instance.dart'; +part 'src/models/repositories/isolate.dart'; +part 'src/models/repositories/isolate_group.dart'; +part 'src/models/repositories/library.dart'; +part 'src/models/repositories/megamorphiccache.dart'; +part 'src/models/repositories/metric.dart'; +part 'src/models/repositories/notification.dart'; +part 'src/models/repositories/object.dart'; +part 'src/models/repositories/objectpool.dart'; +part 'src/models/repositories/objectstore.dart'; +part 'src/models/repositories/persistent_handles.dart'; +part 'src/models/repositories/ports.dart'; +part 'src/models/repositories/reachable_size.dart'; +part 'src/models/repositories/retained_size.dart'; +part 'src/models/repositories/retaining_path.dart'; +part 'src/models/repositories/sample_profile.dart'; +part 'src/models/repositories/script.dart'; +part 'src/models/repositories/single_target_cache.dart'; +part 'src/models/repositories/strongly_reachable_instances.dart'; +part 'src/models/repositories/subtype_test_cache.dart'; +part 'src/models/repositories/target.dart'; +part 'src/models/repositories/timeline.dart'; +part 'src/models/repositories/type_arguments.dart'; +part 'src/models/repositories/unlinked_call.dart'; +part 'src/models/repositories/vm.dart'; diff --git a/pkg/observatory/lib/object_graph.dart b/pkg/observatory/lib/object_graph.dart new file mode 100644 index 00000000000..d8a21448598 --- /dev/null +++ b/pkg/observatory/lib/object_graph.dart @@ -0,0 +1,1714 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library object_graph; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +/// Decodes and analyzes heap snapshots produced by the Dart VM. +abstract class SnapshotReader { + factory SnapshotReader() => _SnapshotReader._new(); + + void add(Uint8List chunk); + Future close(); + + Future get done; + Stream get onProgress; +} + +class _SnapshotReader implements SnapshotReader { + bool _closed = false; + List? _chunks = []; + final _onProgress = new StreamController.broadcast(); + final _done = new Completer(); + + _SnapshotReader._new(); + + void add(Uint8List chunk) { + if (_closed) { + throw new StateError("Stream is closed"); + } + _chunks!.add(chunk); + _onProgress.add("Receiving snapshot chunk ${_chunks!.length}..."); + + // TODO(rmacnak): Incremental loading. + } + + Future close() { + if (_closed) { + throw new StateError("Stream is closed"); + } + _closed = true; + + var graph = new _SnapshotGraph._new(); + var chunks = _chunks!; + _chunks = null; // Let the binary chunks be GCable. + _done.complete(graph._load(chunks, _onProgress)); + return _done.future; + } + + Future get done => _done.future; + Stream get onProgress => _onProgress.stream; +} + +Uint8List _newUint8Array(int size) { + try { + return new Uint8List(size); + } on ArgumentError { + // JS throws a misleading invalid argument error. Convert to a more user-friendly message. + throw new Exception( + "OutOfMemoryError: Not enough memory available to analyze the snapshot.", + ); + } +} + +Uint16List _newUint16Array(int size) { + try { + return new Uint16List(size); + } on ArgumentError { + // JS throws a misleading invalid argument error. Convert to a more user-friendly message. + throw new Exception( + "OutOfMemoryError: Not enough memory available to analyze the snapshot.", + ); + } +} + +Uint32List _newUint32Array(int size) { + try { + return new Uint32List(size); + } on ArgumentError { + // JS throws a misleading invalid argument error. Convert to a more user-friendly message. + throw new Exception( + "OutOfMemoryError: Not enough memory available to analyze the snapshot.", + ); + } +} + +class _ReadStream { + final List _buffers; + Uint8List _currentBuffer = Uint8List(0); + int _bufferIndex = 0; + int _byteIndex = 0; + + _ReadStream._new(this._buffers); + + bool atEnd() { + return _bufferIndex >= _buffers.length && + _byteIndex >= _currentBuffer.length; + } + + int readByte() { + int i = _byteIndex; + Uint8List b = _currentBuffer; + if (i < b.length) { + int r = b[i]; + _byteIndex = i + 1; + return r; + } + + return _readByteSlowPath(); + } + + int _readByteSlowPath() { + int i = _byteIndex; + Uint8List b = _currentBuffer; + while (i >= b.length) { + if (_bufferIndex >= _buffers.length) { + throw new StateError("Attempt to read past the end of a stream"); + } + b = _currentBuffer = _buffers[_bufferIndex++]; + i = 0; + } + int r = b[i]; + _byteIndex = i + 1; + return r; + } + + /// Read one ULEB128 number. + int readUnsigned() { + int result = 0; + int shift = 0; + for (;;) { + int part = readByte(); + result |= (part & 0x7F) << shift; + if ((part & 0x80) == 0) { + break; + } + shift += 7; + } + return result; + } + + /// Read one SLEB128 number. + int readSigned() { + int result = 0; + int shift = 0; + for (;;) { + int part = readByte(); + result |= (part & 0x7F) << shift; + shift += 7; + if ((part & 0x80) == 0) { + if ((part & 0x40) != 0) { + result |= (-1 << shift); + } + break; + } + } + return result; + } + + double readFloat64() { + final bytes = _newUint8Array(8); + for (var i = 0; i < 8; i++) { + bytes[i] = readByte(); + } + return new Float64List.view(bytes.buffer)[0]; + } + + String readUtf8() { + final len = readUnsigned(); + final bytes = _newUint8Array(len); + for (var i = 0; i < len; i++) { + bytes[i] = readByte(); + } + return new Utf8Codec(allowMalformed: true).decode(bytes); + } + + String readLatin1() { + final len = readUnsigned(); + final codeUnits = _newUint8Array(len); + for (var i = 0; i < len; i++) { + codeUnits[i] = readByte(); + } + return new String.fromCharCodes(codeUnits); + } + + String readUtf16() { + final len = readUnsigned(); + final codeUnits = _newUint16Array(len); + for (var i = 0; i < len; i++) { + codeUnits[i] = readByte() | (readByte() << 8); + } + return new String.fromCharCodes(codeUnits); + } +} + +// Node indices for the root and sentinel nodes. Note that using 0 as the +// sentinel means a newly allocated typed array comes initialized with all +// elements as the sentinel. +const _ROOT = 1; +const _SENTINEL = 0; + +/// An object in a heap snapshot. +abstract class SnapshotObject { + /// The identity hash code of this object, used to compare objects across + /// snapshots. If [identityHashCode] is 0, this object cannot be compared to + /// other objects. + int get identityHashCode; + + /// If this object has been obtained from [successors] or [predecessors], the + /// name of slot. Otherwise, the empty string. + String get label; + + /// The value for primitives. Otherwise, the class name. + String get description; + + /// [internalSize] + [externalSize]. + int get shallowSize; + + /// The number of bytes in the Dart heap occupied by this object. May be 0 + /// for objects that are in another heap but referenced from the heap of + /// interest. May also be 0 for synthetic objects such as the root. + int get internalSize; + + /// The sum of all external allocations associated with this object. + /// See Dart_NewFinalizableHandle and Dart_NewWeakPersistentHandle. + int get externalSize; + + /// The [shallowSize] of this object, plus the retainedSize of all its + /// children in the dominator tree. This is the amount of memory that would + /// be freed if the last reference to this object was erased. + int get retainedSize; + + SnapshotClass get klass; + + /// The objects directly referenced by this object. The [SnapshotObject]s + /// returned by this iterable have their [label] set to name of the slot + /// if it is available. + Iterable get successors; + + /// The objects directly referencing this object. The [SnapshotObject]s + /// returned by this iterable have their [label] set to name of the slot + /// if it is available. + Iterable get predecessors; + + /// The immediate dominator of this object. For the root object, returns self. + /// + /// See https://en.wikipedia.org/wiki/Dominator_(graph_theory). + SnapshotObject get parent; + + /// The objects for which this object is the immediate dominator. + /// + /// See https://en.wikipedia.org/wiki/Dominator_(graph_theory). + Iterable get children; + + /// An iterable containing only this object. For polymorphism with + /// SnapshotMergedDominators. + Iterable get objects; +} + +class _SnapshotObject implements SnapshotObject { + final int _id; + final _SnapshotGraph _graph; + final String label; + + _SnapshotObject._new(this._id, this._graph, this.label); + + bool operator ==(Object other) { + if (other is _SnapshotObject) { + return _id == other._id && _graph == other._graph; + } + return false; + } + + int get hashCode => _id ^ _graph.hashCode; + int get identityHashCode => _graph._identityHashes![_id]; + + int get shallowSize => internalSize + externalSize; + int get internalSize => _graph._internalSizes![_id]; + int get externalSize => _graph._externalSizes![_id]; + int get retainedSize => _graph._retainedSizes![_id]; + + String get description => _graph._describeObject(_id); + SnapshotClass get klass => _graph._classes![_graph._cids![_id]]!; + + Iterable get successors sync* { + final id = _id; + final cid = _graph._cids![id]; + final startSuccIndex = _graph._firstSuccs![id]; + final limitSuccIndex = _graph._firstSuccs![id + 1]; + for ( + var nextSuccIndex = startSuccIndex; + nextSuccIndex < limitSuccIndex; + nextSuccIndex++ + ) { + final index = nextSuccIndex - startSuccIndex; + final succId = _graph._succs![nextSuccIndex]; + final name = _graph._edgeName(cid, index); + yield _SnapshotObject._new(succId, _graph, name); + } + } + + Iterable get predecessors sync* { + var firstSuccs = _graph._firstSuccs!; + var succs = _graph._succs!; + var id = _id; + var N = _graph._N!; + for (var predId = 1; predId <= N; predId++) { + var base = firstSuccs[predId]; + var limit = firstSuccs[predId + 1]; + for (var i = base; i < limit; i++) { + if (succs[i] == id) { + var cid = _graph._cids![predId]; + var name = _graph._edgeName(cid, i - base); + yield _SnapshotObject._new(predId, _graph, name); + } + } + } + } + + SnapshotObject get parent { + if (_id == _ROOT) { + return this; + } + return _SnapshotObject._new(_graph._doms![_id], _graph, ""); + } + + Iterable get children sync* { + var N = _graph._N!; + var doms = _graph._doms!; + var parentId = _id; + for (var childId = _ROOT; childId <= N; childId++) { + if (doms[childId] == parentId) { + yield _SnapshotObject._new(childId, _graph, ""); + } + } + } + + Iterable get objects sync* { + yield this; + } +} + +class _SyntheticSnapshotObject implements SnapshotObject { + late String _description; + late SnapshotClass _klass; + late int _internalSize; + late int _externalSize; + late int _retainedSize; + late List _successors; + late List _predecessors; + late SnapshotObject _parent; + late List _children; + + int get identityHashCode => 0; + String get label => ""; + String get description => _description; + SnapshotClass get klass => _klass; + + int get shallowSize => internalSize + externalSize; + int get internalSize => _internalSize; + int get externalSize => _externalSize; + int get retainedSize => _retainedSize; + + Iterable get successors => _successors; + Iterable get predecessors => _predecessors; + SnapshotObject get parent => _parent; + Iterable get children => _children; + + Iterable get objects sync* { + yield this; + } +} + +/// A set of sibling objects in the graph's dominator tree that have the same +/// class. +abstract class SnapshotMergedDominator { + SnapshotClass get klass; + + /// "n instances of Class". + String get description; + + /// [internalSize] + [externalSize]. + int get shallowSize; + + /// The sum of [internalSize] for all objects in this set. + int get internalSize; + + /// The sum of [externalSize] for all objects in this set. + int get externalSize; + + /// The sum of [externalSize] for all objects in this set. + /// This is the amount of memory that would be freed if all references to + /// objects in this set were erased. + int get retainedSize; + + /// The number of objects in this set. Polymorphic with + /// [SnapshotClass.instanceCount]. + int get instanceCount; + + SnapshotMergedDominator get parent; + Iterable get children; + + Iterable get objects; +} + +// A node in the dominator tree where siblings with the same class are merged. +// That is, a set of objects with the same cid whose parent chains in the +// dominator tree have the same cids at each level. [id_] is the representative +// object of this set. The other members of the set are found by walking the +// mergedDomNext links until finding the sentinel node or a node with a +// different class. +class _SnapshotMergedDominator implements SnapshotMergedDominator { + final int _id; + final _SnapshotGraph _graph; + final _SnapshotMergedDominator? _parent; + + _SnapshotMergedDominator._new(this._id, this._graph, this._parent); + + bool operator ==(Object other) { + if (other is _SnapshotMergedDominator) { + return _id == other._id && _graph == other._graph; + } + return false; + } + + int get hashCode => _id ^ _graph.hashCode; + + String get description { + return _id == _ROOT + ? "Live Objects + External" + : "$instanceCount instances of ${klass.name}"; + } + + SnapshotClass get klass => _graph._classes![_graph._cids![_id]]!; + + int get shallowSize => internalSize + externalSize; + + int get internalSize { + var cids = _graph._cids!; + var internalSizes = _graph._internalSizes!; + var mergedDomNext = _graph._mergedDomNext!; + var size = 0; + var sibling = _id; + while (sibling != _SENTINEL && cids[sibling] == cids[_id]) { + size += internalSizes[sibling]; + sibling = mergedDomNext[sibling]; + } + return size; + } + + int get externalSize { + var cids = _graph._cids!; + var externalSizes = _graph._externalSizes!; + var mergedDomNext = _graph._mergedDomNext!; + var size = 0; + var sibling = _id; + while (sibling != _SENTINEL && cids[sibling] == cids[_id]) { + size += externalSizes[sibling]; + sibling = mergedDomNext[sibling]; + } + return size; + } + + int get retainedSize { + var cids = _graph._cids!; + var retainedSizes = _graph._retainedSizes!; + var mergedDomNext = _graph._mergedDomNext!; + var size = 0; + var sibling = _id; + while (sibling != _SENTINEL && cids[sibling] == cids[_id]) { + size += retainedSizes[sibling]; + sibling = mergedDomNext[sibling]; + } + return size; + } + + int get instanceCount { + var cids = _graph._cids!; + var mergedDomNext = _graph._mergedDomNext!; + var count = 0; + var sibling = _id; + while (sibling != _SENTINEL && cids[sibling] == cids[_id]) { + count++; + sibling = mergedDomNext[sibling]; + } + return count; + } + + Iterable get objects sync* { + var cids = _graph._cids!; + var mergedDomNext = _graph._mergedDomNext!; + var sibling = _id; + while (sibling != _SENTINEL && cids[sibling] == cids[_id]) { + yield _SnapshotObject._new(sibling, _graph, ""); + sibling = mergedDomNext[sibling]; + } + } + + SnapshotMergedDominator get parent => _parent ?? this; + + Iterable get children sync* { + var next = _graph._mergedDomNext!; + var cids = _graph._cids!; + var prev = _SENTINEL; + var child = _graph._mergedDomHead![_id]; + // Walk the list of children and look for the representative objects, i.e. + // the first sibling of each cid. + while (child != _SENTINEL) { + if (prev == _SENTINEL || cids[prev] != cids[child]) { + yield _SnapshotMergedDominator._new(child, _graph, this); + } + prev = child; + child = next[child]; + } + } +} + +class _SyntheticSnapshotMergedDominator implements SnapshotMergedDominator { + late String _description; + late SnapshotClass _klass; + late int _internalSize; + late int _externalSize; + late int _retainedSize; + late List _objects; + late SnapshotMergedDominator _parent; + late List _children; + + SnapshotClass get klass => _klass; + String get description => _description; + int get shallowSize => internalSize + externalSize; + int get internalSize => _internalSize; + int get externalSize => _externalSize; + int get retainedSize => _retainedSize; + int get instanceCount => _objects.length; + SnapshotMergedDominator get parent => _parent; + Iterable get children => _children; + Iterable get objects => _objects; +} + +/// A class in a heap snapshot. +abstract class SnapshotClass { + String get name; + String get qualifiedName; + + int get shallowSize; + int get externalSize; + int get internalSize; + int get ownedSize; + + int get instanceCount; + Iterable get instances; +} + +class _SnapshotClass implements SnapshotClass { + final _SnapshotGraph _graph; + final int _cid; + final String name; + String get qualifiedName => "$libUri $name"; + final String libName; + final String libUri; + final Map fields = new Map(); + + int totalExternalSize = 0; + int totalInternalSize = 0; + int totalInstanceCount = 0; + + int ownedSize = 0; + + int liveExternalSize = 0; + int liveInternalSize = 0; + int liveInstanceCount = 0; + + int get shallowSize => internalSize + externalSize; + int get internalSize => liveInternalSize; + int get externalSize => liveExternalSize; + int get instanceCount => liveInstanceCount; + + Iterable get instances sync* { + final N = _graph._N!; + final cids = _graph._cids!; + final retainedSizes = _graph._retainedSizes!; + for (var id = 1; id <= N; id++) { + if (cids[id] == _cid && retainedSizes[id] > 0) { + yield _SnapshotObject._new(id, _graph, ""); + } + } + } + + _SnapshotClass._new( + this._graph, + this._cid, + this.name, + this.libName, + this.libUri, + ); +} + +class _SyntheticSnapshotClass implements SnapshotClass { + final _SyntheticSnapshotObject _node; + _SyntheticSnapshotClass(this._node); + + String get name => _node.description; + String get qualifiedName => _node.description; + + int get shallowSize => _node.shallowSize; + int get externalSize => _node.externalSize; + int get internalSize => _node.internalSize; + int get ownedSize => 0; + + int get instanceCount => 1; + Iterable get instances => [_node]; +} + +/// The analyzed graph from a heap snapshot. +abstract class SnapshotGraph { + String get description; + + int get internalSize; + int get externalSize; + // [internalSize] + [externalSize] + int get size; + + // The amount of memory reserved for the heap. [internalSize] will always be + // less than or equal to [capacity]. + int get capacity; + + Iterable get classes; + Iterable get objects; + + SnapshotObject get root; + SnapshotObject get extendedRoot; + SnapshotMergedDominator get mergedRoot; + SnapshotMergedDominator get extendedMergedRoot; + + // TODO: Insist that the client remember the chunks if needed? Always keeping + // this increasing the peak memory usage during analysis. + List get chunks; +} + +const _tagNone = 0; +const _tagNull = 1; +const _tagBool = 2; +const _tagInt = 3; +const _tagDouble = 4; +const _tagLatin1 = 5; +const _tagUtf16 = 6; +const _tagLength = 7; +const _tagName = 8; + +const _kSentinelName = ""; +const _kRootName = "Live Objects + External"; +const _kUnknownFieldName = ""; + +class _SnapshotGraph implements SnapshotGraph { + List? _chunks; + List get chunks => _chunks!; + + _SnapshotGraph._new(); + + String get description => _description!; + + int get size => _liveInternalSize! + _liveExternalSize!; + int get internalSize => _liveInternalSize!; + int get externalSize => _liveExternalSize!; + int get capacity => _capacity!; + + SnapshotObject get root => _SnapshotObject._new(_ROOT, this, "Root"); + SnapshotMergedDominator get mergedRoot => + _SnapshotMergedDominator._new(_ROOT, this, null); + + SnapshotObject? _extendedRoot; + SnapshotObject get extendedRoot { + if (_extendedRoot == null) { + _createExtended(); + } + return _extendedRoot!; + } + + SnapshotMergedDominator? _extendedMergedRoot; + SnapshotMergedDominator get extendedMergedRoot { + if (_extendedMergedRoot == null) { + _createExtended(); + } + return _extendedMergedRoot!; + } + + void _createExtended() { + var capacity = new _SyntheticSnapshotObject(); + var uncollected = new _SyntheticSnapshotObject(); + var fragmentation = new _SyntheticSnapshotObject(); + var live = root; + var mcapacity = new _SyntheticSnapshotMergedDominator(); + var muncollected = new _SyntheticSnapshotMergedDominator(); + var mfragmentation = new _SyntheticSnapshotMergedDominator(); + var mlive = mergedRoot; + + capacity._description = "Capacity + External"; + capacity._klass = new _SyntheticSnapshotClass(capacity); + capacity._internalSize = 0; // No shallow size. + capacity._externalSize = 0; // No shallow size. + capacity._retainedSize = _capacity! + _totalExternalSize!; + capacity._successors = [live, uncollected, fragmentation]; + capacity._predecessors = []; + capacity._children = [live, uncollected, fragmentation]; + + mcapacity._description = "Capacity + External"; + mcapacity._klass = capacity._klass; + mcapacity._internalSize = 0; // No shallow size. + mcapacity._externalSize = 0; // No shallow size. + mcapacity._retainedSize = _capacity! + _totalExternalSize!; + mcapacity._children = [ + mlive, + muncollected, + mfragmentation, + ]; + mcapacity._objects = [capacity]; + + uncollected._description = "Uncollected Garbage"; + uncollected._klass = new _SyntheticSnapshotClass(uncollected); + uncollected._internalSize = _totalInternalSize! - _liveInternalSize!; + uncollected._externalSize = _totalExternalSize! - _liveExternalSize!; + uncollected._retainedSize = + uncollected._internalSize + uncollected._externalSize; + uncollected._successors = []; + uncollected._predecessors = [capacity]; + uncollected._parent = capacity; + uncollected._children = []; + + muncollected._description = "Uncollected Garbage"; + muncollected._klass = uncollected._klass; + muncollected._internalSize = _totalInternalSize! - _liveInternalSize!; + muncollected._externalSize = _totalExternalSize! - _liveExternalSize!; + muncollected._retainedSize = + muncollected._internalSize + muncollected._externalSize; + muncollected._parent = mcapacity; + muncollected._children = []; + muncollected._objects = [uncollected]; + + fragmentation._description = "Free"; + fragmentation._klass = new _SyntheticSnapshotClass(fragmentation); + fragmentation._internalSize = _capacity! - _totalInternalSize!; + fragmentation._externalSize = 0; + fragmentation._retainedSize = fragmentation._internalSize; + fragmentation._successors = []; + fragmentation._predecessors = [capacity]; + fragmentation._parent = capacity; + fragmentation._children = []; + + mfragmentation._description = "Free"; + mfragmentation._klass = fragmentation._klass; + mfragmentation._internalSize = _capacity! - _totalInternalSize!; + mfragmentation._externalSize = 0; + mfragmentation._retainedSize = mfragmentation._internalSize; + mfragmentation._parent = mcapacity; + mfragmentation._children = []; + mfragmentation._objects = [fragmentation]; + + _extendedRoot = capacity; + _extendedMergedRoot = mcapacity; + } + + Iterable get objects sync* { + final N = _N!; + final retainedSizes = _retainedSizes!; + for (var id = 1; id <= N; id++) { + if (retainedSizes[id] > 0) { + yield _SnapshotObject._new(id, this, ""); + } + } + } + + String _describeObject(int oid) { + if (oid == _SENTINEL) { + return _kSentinelName; + } + if (oid == _ROOT) { + return _kRootName; + } + var cls = _className(oid); + var data = _nonReferenceData![oid]; + if (data == null) { + return cls; + } else { + return "$cls($data)"; + } + } + + String _className(int oid) { + var cid = _cids![oid]; + var cls = _classes![cid]; + if (cls == null) { + return "Class$cid"; + } + return cls.name; + } + + String _edgeName(int cid, int index) { + var c = _classes![cid]; + if (c == null) { + return _kUnknownFieldName; + } + var n = c.fields[index]; + if (n == null) { + return _kUnknownFieldName; + } + return n; + } + + Iterable get classes sync* { + for (final c in _classes!) { + // Not all CIDs are occupied. + if (c != null) { + yield c; + } + } + } + + Future _load( + List? chunks, + StreamController onProgress, + ) async { + _chunks = chunks; + _ReadStream? stream = _ReadStream._new(chunks!); + chunks = null; + + // The phases of loading are placed in explicit `new Future(computation)` so + // they will be deferred to the message loop. Ordinary async-await will only + // defer to the microtask loop. + + onProgress.add("Loading classes..."); + await new Future(() => _readClasses(stream!)); + + onProgress.add("Loading objects..."); + await new Future(() => _readObjects(stream!)); + + onProgress.add("Loading external properties..."); + await new Future(() => _readExternalProperties(stream!)); + + onProgress.add("Loading object identity hash codes..."); + await new Future(() => _readObjectIdentityHashes(stream!)); + + stream = null; + + onProgress.add("Compute class table..."); + await new Future(() => _computeClassTable()); + + onProgress.add("Finding depth-first order..."); + await new Future(() => _dfs()); + + onProgress.add("Finding predecessors..."); + await new Future(() => _buildPredecessors()); + + onProgress.add("Finding dominators..."); + await new Future(() => _buildDominators()); + + _semi = null; + _parent = null; + + onProgress.add("Finding in-degree(1) groups..."); + await new Future(() => _buildOwnedSizes()); + + _firstPreds = null; + _preds = null; + + onProgress.add("Finding retained sizes..."); + await new Future(() => _calculateRetainedSizes()); + + _vertex = null; + + onProgress.add("Linking dominator tree children..."); + await new Future(() => _linkDominatorChildren()); + + onProgress.add("Sorting dominator tree children..."); + await new Future(() => _sortDominatorChildren()); + + onProgress.add("Merging dominator tree siblings..."); + await new Future(() => _mergeDominatorSiblings()); + + onProgress.add("Loaded"); + // We await here so SnapshotReader clients see all progress events before + // seeing the done future as completed. + await onProgress.close(); + + return this; + } + + String? _description; + + int? _kStackCid; + int? _kFieldCid; + int? _N; // Objects in the snapshot. + int? _Nconnected; // Objects reachable from root. + int? _E; // References in the snapshot. + + int? _capacity; + int? _liveInternalSize; + int? _liveExternalSize; + int? _totalInternalSize; + int? _totalExternalSize; + + List<_SnapshotClass?>? _classes; + + // Indexed by node id, with id 0 representing invalid/uninitialized. + // From snapshot. + List? _nonReferenceData; + Uint16List? _cids; + Uint32List? _internalSizes; + Uint32List? _externalSizes; + Uint32List? _firstSuccs; + Uint32List? _succs; + Uint32List? _identityHashes; + + // Intermediates. + Uint32List? _vertex; + Uint32List? _parent; + Uint32List? _semi; + Uint32List? _firstPreds; // Offset into preds. + Uint32List? _preds; + + // Outputs. + Uint32List? _doms; + Uint32List? _retainedSizes; + Uint32List? _mergedDomHead; + Uint32List? _mergedDomNext; + + void _readClasses(_ReadStream stream) { + for (var i = 0; i < 8; i++) { + stream.readByte(); // Magic value. + } + stream.readUnsigned(); // Flags + _description = stream.readUtf8(); + + _totalInternalSize = stream.readUnsigned(); + _capacity = stream.readUnsigned(); + _totalExternalSize = stream.readUnsigned(); + + var K = stream.readUnsigned(); + var classes = new List<_SnapshotClass?>.filled(K + 1, null); + classes[0] = _SnapshotClass._new(this, 0, "Root", "", ""); + + for (var cid = 1; cid <= K; cid++) { + // ignore: unused_local_variable + int flags = stream.readUnsigned(); + String name = stream.readUtf8(); + String libName = stream.readUtf8(); + String libUri = stream.readUtf8(); + // ignore: unused_local_variable + String reserved = stream.readUtf8(); + final cls = _SnapshotClass._new(this, cid, name, libName, libUri); + int edgeCount = stream.readUnsigned(); + for (int i = 0; i < edgeCount; i++) { + // ignore: unused_local_variable + int flags = stream.readUnsigned(); + int index = stream.readUnsigned(); + String fieldName = stream.readUtf8(); + // ignore: unused_local_variable + String reserved = stream.readUtf8(); + cls.fields[index] = fieldName; + } + classes[cid] = cls; + } + + _classes = classes; + } + + void _readObjects(_ReadStream stream) { + final E = stream.readUnsigned(); + final N = stream.readUnsigned(); + + // The negative check accounts for int64 overflow in readUnsigned. + const maxUint32 = 0xFFFFFFFF; + if (N < 0 || N + 2 >= maxUint32) { + throw new Exception("Snapshot contains too many objects: $N"); + } + if (E < 0 || E + 2 >= maxUint32) { + throw new Exception("Snapshot contains too many references: $E"); + } + + _N = N; + _E = E; + + var internalSizes = _newUint32Array(N + 1); + var cids = _newUint16Array(N + 1); + var nonReferenceData = new List.filled(N + 1, null); + var firstSuccs = _newUint32Array(N + 2); + var succs = _newUint32Array(E); + var eid = 0; + for (var oid = 1; oid <= N; oid++) { + var cid = stream.readUnsigned(); + cids[oid] = cid; + + var internalSize = stream.readUnsigned(); + internalSizes[oid] = internalSize; + + var nonReferenceDataTag = stream.readUnsigned(); + switch (nonReferenceDataTag) { + case _tagNone: + break; + case _tagNull: + nonReferenceData[oid] = "null"; + break; + case _tagBool: + nonReferenceData[oid] = stream.readByte() != 0; + break; + case _tagInt: + nonReferenceData[oid] = stream.readSigned(); + break; + case _tagDouble: + nonReferenceData[oid] = stream.readFloat64(); + break; + case _tagLatin1: + var len = stream.readUnsigned(); + var str = stream.readLatin1(); + if (str.length < len) { + nonReferenceData[oid] = '$str...'; + } else { + nonReferenceData[oid] = str; + } + break; + case _tagUtf16: + int len = stream.readUnsigned(); + var str = stream.readUtf16(); + if (str.length < len) { + nonReferenceData[oid] = '$str...'; + } else { + nonReferenceData[oid] = str; + } + break; + case _tagLength: + nonReferenceData[oid] = stream.readUnsigned(); // Length + break; + case _tagName: + nonReferenceData[oid] = stream.readUtf8(); // Name + break; + default: + throw "Unknown tag $nonReferenceDataTag"; + } + + firstSuccs[oid] = eid; + var referenceCount = stream.readUnsigned(); + while (referenceCount > 0) { + var childOid = stream.readUnsigned(); + succs[eid] = childOid; + eid++; + referenceCount--; + } + } + firstSuccs[N + 1] = eid; + + assert(eid <= E); + _E = eid; + _internalSizes = internalSizes; + _cids = cids; + _nonReferenceData = nonReferenceData; + _firstSuccs = firstSuccs; + _succs = succs; + } + + void _readExternalProperties(_ReadStream stream) { + final N = _N!; + final externalPropertyCount = stream.readUnsigned(); + + final externalSizes = _newUint32Array(N + 1); + for (var i = 0; i < externalPropertyCount; i++) { + final oid = stream.readUnsigned(); + final externalSize = stream.readUnsigned(); + // ignore: unused_local_variable + final name = stream.readUtf8(); + externalSizes[oid] += externalSize; + } + + _externalSizes = externalSizes; + } + + void _readObjectIdentityHashes(_ReadStream stream) { + final N = _N!; + final identityHashes = _newUint32Array(N + 1); + for (int oid = 1; oid <= N; ++oid) { + identityHashes[oid] = stream.readUnsigned(); + } + _identityHashes = identityHashes; + } + + void _computeClassTable() { + final N = _N!; + final classes = _classes!; + final cids = _cids!; + final internalSizes = _internalSizes!; + final externalSizes = _externalSizes!; + var totalInternalSize = 0; + var totalExternalSize = 0; + + for (var oid = 1; oid <= N; oid++) { + var internalSize = internalSizes[oid]; + totalInternalSize += internalSize; + + var externalSize = externalSizes[oid]; + totalExternalSize += externalSize; + + var cls = classes[cids[oid]]!; + cls.totalInternalSize += internalSize; + cls.totalExternalSize += externalSize; + cls.totalInstanceCount++; + } + + _totalInternalSize = totalInternalSize; + _totalExternalSize = totalExternalSize; + } + + void _dfs() { + final N = _N!; + final firstSuccs = _firstSuccs!; + final succs = _succs!; + + final stackNodes = _newUint32Array(N); + final stackCurrentEdgePos = _newUint32Array(N); + + final vertex = _newUint32Array(N + 1); + final semi = _newUint32Array(N + 1); + final parent = _newUint32Array(N + 1); + var dfsNumber = 0; + + var stackTop = 0; + + // Push root. + stackNodes[0] = _ROOT; + stackCurrentEdgePos[0] = firstSuccs[_ROOT]; + + while (stackTop >= 0) { + var v = stackNodes[stackTop]; + var edgePos = stackCurrentEdgePos[stackTop]; + + if (semi[v] == 0) { + // First visit. + dfsNumber++; + semi[v] = dfsNumber; + vertex[dfsNumber] = v; + } + + if (edgePos < firstSuccs[v + 1]) { + var childId = succs[edgePos]; + edgePos++; + stackCurrentEdgePos[stackTop] = edgePos; + + if (childId == _SENTINEL) { + // Omitted target. + } else if (semi[childId] == 0) { + parent[childId] = v; + + // Push child. + stackTop++; + stackNodes[stackTop] = childId; + stackCurrentEdgePos[stackTop] = firstSuccs[childId]; + } + } else { + // Done with all children. + stackTop--; + } + } + + if (dfsNumber != N) { + // This may happen in filtered snapshots. + print('Heap snapshot contains ${N - dfsNumber} unreachable nodes.'); + } + + assert(() { + for (var i = 1; i <= dfsNumber; i++) { + var v = vertex[i]; + assert(semi[v] != _SENTINEL); + } + assert(parent[1] == _SENTINEL); + for (var i = 2; i <= dfsNumber; i++) { + var v = vertex[i]; + assert(parent[v] != _SENTINEL); + } + return true; + }()); + + if (dfsNumber != N) { + // Remove successors of unconnected nodes + for (var i = _ROOT + 1; i <= N; i++) { + if (parent[i] == _SENTINEL) { + var startSuccIndex = firstSuccs[i]; + var limitSuccIndex = firstSuccs[i + 1]; + for ( + var succIndex = startSuccIndex; + succIndex < limitSuccIndex; + succIndex++ + ) { + succs[succIndex] = _SENTINEL; + } + } + } + } + + _Nconnected = dfsNumber; + _vertex = vertex; + _semi = semi; + _parent = parent; + } + + void _buildPredecessors() { + final N = _N!; + final Nconnected = _Nconnected!; + final E = _E!; + final firstSuccs = _firstSuccs!; + final succs = _succs!; + + // This is first filled with the predecessor counts, then reused to hold the + // offset to the first predecessor (see alias below). + // + 1 because 0 is a sentinel + // + 1 so the number of predecessors can be found from the difference with + // the next node's offset. + final numPreds = _newUint32Array(N + 2); + final preds = _newUint32Array(E); + + // Count predecessors of each node. + for (var succIndex = 0; succIndex < E; succIndex++) { + final succId = succs[succIndex]; + if (succId != _SENTINEL) { + numPreds[succId]++; + } + } + + // Assign indices into predecessors array. + final firstPreds = numPreds; // Alias. + final nextPreds = _newUint32Array(N + 1); + var predIndex = 0; + for (var i = 1; i <= N; i++) { + var thisPredIndex = predIndex; + predIndex += numPreds[i]; + firstPreds[i] = thisPredIndex; + nextPreds[i] = thisPredIndex; + } + if (N == Nconnected) { + assert(predIndex == E); + } + firstPreds[N + 1] = predIndex; // Extra entry for cheap boundary detection. + + // Fill predecessors array. + for (var i = 1; i <= N; i++) { + var startSuccIndex = firstSuccs[i]; + var limitSuccIndex = firstSuccs[i + 1]; + for ( + var succIndex = startSuccIndex; + succIndex < limitSuccIndex; + succIndex++ + ) { + var succId = succs[succIndex]; + if (succId != _SENTINEL) { + var predIndex = nextPreds[succId]++; + preds[predIndex] = i; + } + } + } + + _firstPreds = firstPreds; + _preds = preds; + } + + // Fold the size of any object with in-degree(1) into its parent. + // Requires the DFS numbering and predecessor lists. + void _buildOwnedSizes() { + final N = _N!; + final Nconnected = _Nconnected!; + final kStackCid = _kStackCid; + final kFieldCid = _kFieldCid; + + final cids = _cids!; + final internalSizes = _internalSizes!; + final externalSizes = _externalSizes!; + final vertex = _vertex!; + final firstPreds = _firstPreds!; + final preds = _preds!; + + final ownedSizes = _newUint32Array(N + 1); + for (var i = 1; i <= Nconnected; i++) { + final v = vertex[i]; + ownedSizes[v] = internalSizes[v] + externalSizes[v]; + } + + for (var i = Nconnected; i > 1; i--) { + var w = vertex[i]; + assert(w != _ROOT); + + var onlyPred = _SENTINEL; + + var startPred = firstPreds[w]; + var limitPred = firstPreds[w + 1]; + for (var predIndex = startPred; predIndex < limitPred; predIndex++) { + var v = preds[predIndex]; + if (v == w) { + // Ignore self-predecessor. + } else if (onlyPred == _SENTINEL) { + onlyPred = v; + } else if (onlyPred == v) { + // Repeated predecessor. + } else { + // Multiple-predecessors. + onlyPred = _SENTINEL; + break; + } + } + + // If this object has a single precessor which is not a Field, Stack or + // the root, blame its size against the precessor. + if ((onlyPred != _SENTINEL) && + (onlyPred != _ROOT) && + (cids[onlyPred] != kStackCid) && + (cids[onlyPred] != kFieldCid)) { + assert(onlyPred != w); + ownedSizes[onlyPred] += ownedSizes[w]; + ownedSizes[w] = 0; + } + } + + // TODO(rmacnak): Maybe keep the per-objects sizes to be able to provide + // examples of large owners for each class. + final classes = _classes!; + for (var i = 1; i <= Nconnected; i++) { + final v = vertex[i]; + final cid = cids[v]; + final cls = classes[cid]!; + cls.ownedSize += ownedSizes[v]; + } + } + + static int _eval( + int v, + Uint32List ancestor, + Uint32List semi, + Uint32List label, + Uint32List stackNode, + Uint8List stackState, + ) { + if (ancestor[v] == _SENTINEL) { + return label[v]; + } else { + { + // Inlined 'compress' with an explicit stack to prevent JS stack + // overflow. + var top = 0; + stackNode[top] = v; + stackState[top] = 0; + while (top >= 0) { + var v = stackNode[top]; + var state = stackState[top]; + if (state == 0) { + assert(ancestor[v] != 0); + if (ancestor[ancestor[v]] != 0) { + stackState[top] = 1; + // Recurse with ancestor[v] + top++; + stackNode[top] = ancestor[v]; + stackState[top] = 0; + } else { + top--; + } + } else { + assert(state == 1); + if (semi[label[ancestor[v]]] < semi[label[v]]) { + label[v] = label[ancestor[v]]; + } + ancestor[v] = ancestor[ancestor[v]]; + top--; + } + } + } + + if (semi[label[ancestor[v]]] >= semi[label[v]]) { + return label[v]; + } else { + return label[ancestor[v]]; + } + } + } + + // Note the version in the main text of Lengauer & Tarjan incorrectly + // uses parent instead of ancestor. The correct version is in Appendix B. + static void _link( + int v, + int w, + Uint32List size, + Uint32List label, + Uint32List semi, + Uint32List child, + Uint32List ancestor, + ) { + assert(size[0] == 0); + assert(label[0] == 0); + assert(semi[0] == 0); + var s = w; + while (semi[label[w]] < semi[label[child[s]]]) { + if (size[s] + size[child[child[s]]] >= 2 * size[child[s]]) { + ancestor[child[s]] = s; + child[s] = child[child[s]]; + } else { + size[child[s]] = size[s]; + s = ancestor[s] = child[s]; + } + } + label[s] = label[w]; + size[v] = size[v] + size[w]; + if (size[v] < 2 * size[w]) { + var tmp = s; + s = child[v]; + child[v] = tmp; + } + while (s != 0) { + ancestor[s] = v; + s = child[s]; + } + } + + // T. Lengauer and R. E. Tarjan. "A Fast Algorithm for Finding Dominators + // in a Flowgraph." + void _buildDominators() { + final N = _N!; + final Nconnected = _Nconnected!; + + final vertex = _vertex!; + final semi = _semi!; + final parent = _parent!; + final firstPreds = _firstPreds!; + final preds = _preds!; + + final dom = _newUint32Array(N + 1); + + final ancestor = _newUint32Array(N + 1); + final label = _newUint32Array(N + 1); + for (var i = 1; i <= N; i++) { + label[i] = i; + } + final buckets = new List.filled(N + 1, null); + final child = _newUint32Array(N + 1); + final size = _newUint32Array(N + 1); + for (var i = 1; i <= N; i++) { + size[i] = 1; + } + final stackNode = _newUint32Array(N + 1); + final stackState = _newUint8Array(N + 1); + + for (var i = Nconnected; i > 1; i--) { + var w = vertex[i]; + assert(w != _ROOT); + + // Lengauer & Tarjan Step 2. + var startPred = firstPreds[w]; + var limitPred = firstPreds[w + 1]; + for (var predIndex = startPred; predIndex < limitPred; predIndex++) { + var v = preds[predIndex]; + var u = _eval(v, ancestor, semi, label, stackNode, stackState); + if (semi[u] < semi[w]) { + semi[w] = semi[u]; + } + } + + // w.semi.bucket.add(w); + var tmp = vertex[semi[w]]; + if (buckets[tmp] == null) { + buckets[tmp] = []; + } + buckets[tmp].add(w); + + _link(parent[w], w, size, label, semi, child, ancestor); + + // Lengauer & Tarjan Step 3. + tmp = parent[w]; + var bucket = buckets[tmp]; + buckets[tmp] = null; + if (bucket != null) { + for (var v in bucket) { + var u = _eval(v, ancestor, semi, label, stackNode, stackState); + dom[v] = semi[u] < semi[v] ? u : parent[w]; + } + } + } + for (var i = _ROOT; i <= N; i++) { + assert(buckets[i] == null); + } + // Lengauer & Tarjan Step 4. + for (var i = 2; i <= Nconnected; i++) { + var w = vertex[i]; + if (dom[w] != vertex[semi[w]]) { + dom[w] = dom[dom[w]]; + } + } + + _doms = dom; + } + + void _calculateRetainedSizes() { + final N = _N!; + final Nconnected = _Nconnected!; + + var liveInternalSize = 0; + var liveExternalSize = 0; + final classes = _classes!; + final cids = _cids!; + final internalSizes = _internalSizes!; + final externalSizes = _externalSizes!; + final vertex = _vertex!; + final doms = _doms!; + + // Sum internal and external sizes. + for (var i = 1; i <= Nconnected; i++) { + var v = vertex[i]; + var internalSize = internalSizes[v]; + var externalSize = externalSizes[v]; + liveInternalSize += internalSize; + liveExternalSize += externalSize; + + var cls = classes[cids[v]]!; + cls.liveInternalSize += internalSize; + cls.liveExternalSize += externalSize; + cls.liveInstanceCount++; + } + + // Start with retained size as shallow size + external size. For reachable + // objects only; leave unreachable objects with a retained size of 0 so + // they can be filtered during graph iterations. + var retainedSizes = new Uint32List(N + 1); + assert(Nconnected <= N); + for (var i = 0; i <= Nconnected; i++) { + var v = vertex[i]; + retainedSizes[v] = internalSizes[v] + externalSizes[v]; + } + + // In post order (bottom up), add retained size to dominator's retained + // size, skipping root. + for (var i = Nconnected; i > 1; i--) { + var v = vertex[i]; + assert(v != _ROOT); + retainedSizes[doms[v]] += retainedSizes[v]; + } + + // Root retains everything. + assert(retainedSizes[_ROOT] == (liveInternalSize + liveExternalSize)); + + _retainedSizes = retainedSizes; + _liveInternalSize = liveInternalSize; + _liveExternalSize = liveExternalSize; + + print("internal-garbage: ${_totalInternalSize! - _liveInternalSize!}"); + print("external-garbage: ${_totalExternalSize! - _liveExternalSize!}"); + print("fragmentation: ${_capacity! - _totalInternalSize!}"); + assert(_liveInternalSize! <= _totalInternalSize!); + assert(_liveExternalSize! <= _totalExternalSize!); + assert(_totalInternalSize! <= _capacity!); + } + + // Build linked lists of the children for each node in the dominator tree. + void _linkDominatorChildren() { + final N = _N!; + final doms = _doms!; + final head = _newUint32Array(N + 1); + final next = _newUint32Array(N + 1); + + for (var child = _ROOT; child <= N; child++) { + var parent = doms[child]; + next[child] = head[parent]; + head[parent] = child; + } + + _mergedDomHead = head; + _mergedDomNext = next; + } + + // Merge the given lists according to the given key in ascending order. + // Returns the head of the merged list. + static int _mergeSorted( + int head1, + int head2, + Uint32List next, + Uint16List key, + ) { + var head = head1; + var beforeInsert = _SENTINEL; + var afterInsert = head1; + var startInsert = head2; + + while (startInsert != _SENTINEL) { + while ((afterInsert != _SENTINEL) && + (key[afterInsert] <= key[startInsert])) { + beforeInsert = afterInsert; + afterInsert = next[beforeInsert]; + } + + var endInsert = startInsert; + var peek = next[endInsert]; + + while ((peek != _SENTINEL) && (key[peek] < key[afterInsert])) { + endInsert = peek; + peek = next[endInsert]; + } + assert(endInsert != _SENTINEL); + + if (beforeInsert == _SENTINEL) { + head = startInsert; + } else { + next[beforeInsert] = startInsert; + } + next[endInsert] = afterInsert; + + startInsert = peek; + beforeInsert = endInsert; + } + + return head; + } + + void _sortDominatorChildren() { + final N = _N!; + final cids = _cids!; + final head = _mergedDomHead!; + final next = _mergedDomNext!; + + // Returns the new head of the sorted list. + int sort(int head) { + if (head == _SENTINEL) return _SENTINEL; + if (next[head] == _SENTINEL) return head; + + // Find the middle of the list. + int head1 = head; + int slow = head; + int fast = head; + while (next[fast] != _SENTINEL && next[next[fast]] != _SENTINEL) { + slow = next[slow]; + fast = next[next[fast]]; + } + + // Split the list in half. + int head2 = next[slow]; + next[slow] = _SENTINEL; + + // Recursively sort the sublists and merge. + assert(head1 != head2); + int newHead1 = sort(head1); + int newHead2 = sort(head2); + return _mergeSorted(newHead1, newHead2, next, cids); + } + + // Sort all list of dominator tree children by cid. + for (var parent = _ROOT; parent <= N; parent++) { + head[parent] = sort(head[parent]); + } + } + + void _mergeDominatorSiblings() { + var N = _N!; + var cids = _cids!; + var head = _mergedDomHead!; + var next = _mergedDomNext!; + var workStack = _newUint32Array(N); + var workStackTop = 0; + + mergeChildrenAndSort(var parent1, var end) { + assert(parent1 != _SENTINEL); + if (next[parent1] == end) return; + + // Find the middle of the list. + int slow = parent1; + int fast = parent1; + while (next[fast] != end && next[next[fast]] != end) { + slow = next[slow]; + fast = next[next[fast]]; + } + + int parent2 = next[slow]; + + assert(parent2 != _SENTINEL); + assert(parent1 != parent2); + assert(cids[parent1] == cids[parent2]); + + // Recursively sort the sublists. + mergeChildrenAndSort(parent1, parent2); + mergeChildrenAndSort(parent2, end); + + // Merge sorted sublists. + head[parent1] = _mergeSorted(head[parent1], head[parent2], next, cids); + + // Children moved to parent1. + head[parent2] = _SENTINEL; + } + + // Push root. + workStack[workStackTop++] = _ROOT; + + while (workStackTop > 0) { + var parent = workStack[--workStackTop]; + + var child = head[parent]; + while (child != _SENTINEL) { + // Push child. + workStack[workStackTop++] = child; + + // Find next sibling with a different cid. + var after = child; + while (after != _SENTINEL && cids[after] == cids[child]) { + after = next[after]; + } + + // From all the siblings between child and after, take their children, + // merge them and given to child. + mergeChildrenAndSort(child, after); + + child = after; + } + } + } +} diff --git a/pkg/observatory/lib/repositories.dart b/pkg/observatory/lib/repositories.dart new file mode 100644 index 00000000000..dc035f25f71 --- /dev/null +++ b/pkg/observatory/lib/repositories.dart @@ -0,0 +1,58 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library repositories; + +import 'dart:async'; +import 'dart:convert'; + +import 'package:web/web.dart'; + +import 'allocation_profile.dart'; +import 'models.dart' as M; +import 'object_graph.dart'; +import 'sample_profile.dart'; +import 'service.dart' as S; +import 'service_common.dart' as SC; +import 'src/repositories/timeline_base.dart'; + +part 'src/repositories/allocation_profile.dart'; +part 'src/repositories/breakpoint.dart'; +part 'src/repositories/class.dart'; +part 'src/repositories/context.dart'; +part 'src/repositories/editor.dart'; +part 'src/repositories/eval.dart'; +part 'src/repositories/event.dart'; +part 'src/repositories/field.dart'; +part 'src/repositories/flag.dart'; +part 'src/repositories/function.dart'; +part 'src/repositories/heap_snapshot.dart'; +part 'src/repositories/icdata.dart'; +part 'src/repositories/inbound_references.dart'; +part 'src/repositories/instance.dart'; +part 'src/repositories/isolate.dart'; +part 'src/repositories/isolate_group.dart'; +part 'src/repositories/library.dart'; +part 'src/repositories/megamorphiccache.dart'; +part 'src/repositories/metric.dart'; +part 'src/repositories/notification.dart'; +part 'src/repositories/object.dart'; +part 'src/repositories/objectpool.dart'; +part 'src/repositories/objectstore.dart'; +part 'src/repositories/persistent_handles.dart'; +part 'src/repositories/ports.dart'; +part 'src/repositories/reachable_size.dart'; +part 'src/repositories/retained_size.dart'; +part 'src/repositories/retaining_path.dart'; +part 'src/repositories/sample_profile.dart'; +part 'src/repositories/script.dart'; +part 'src/repositories/settings.dart'; +part 'src/repositories/single_target_cache.dart'; +part 'src/repositories/strongly_reachable_instances.dart'; +part 'src/repositories/subtype_test_cache.dart'; +part 'src/repositories/target.dart'; +part 'src/repositories/timeline.dart'; +part 'src/repositories/type_arguments.dart'; +part 'src/repositories/unlinked_call.dart'; +part 'src/repositories/vm.dart'; diff --git a/pkg/observatory/lib/sample_profile.dart b/pkg/observatory/lib/sample_profile.dart new file mode 100644 index 00000000000..4f68229c930 --- /dev/null +++ b/pkg/observatory/lib/sample_profile.dart @@ -0,0 +1,15 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library sample_profiler; + +import 'dart:async'; + +import 'package:logging/logging.dart'; + +import 'models.dart' as M; +import 'service.dart'; +import 'utils.dart'; + +part 'src/sample_profile/sample_profile.dart'; diff --git a/pkg/observatory/lib/service.dart b/pkg/observatory/lib/service.dart new file mode 100644 index 00000000000..06e05d37017 --- /dev/null +++ b/pkg/observatory/lib/service.dart @@ -0,0 +1,21 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library service; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:math' as math; +import 'dart:typed_data'; + +import 'package:logging/logging.dart'; + +import 'event.dart' show createEventFromServiceEvent; +import 'models.dart' as M; +import 'object_graph.dart'; +import 'sample_profile.dart'; +import 'service_common.dart'; +import 'tracer.dart'; + +part 'src/service/object.dart'; diff --git a/pkg/observatory/lib/service_common.dart b/pkg/observatory/lib/service_common.dart new file mode 100644 index 00000000000..83e4962f367 --- /dev/null +++ b/pkg/observatory/lib/service_common.dart @@ -0,0 +1,364 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library service_common; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:logging/logging.dart'; + +import 'models.dart' as M; +import 'service.dart'; + +// Export the service library. +export 'service.dart'; + +/// Description of a VM target. +class WebSocketVMTarget implements M.Target { + // Last time this VM has been connected to. + int lastConnectionTime = 0; + bool get hasEverConnected => lastConnectionTime > 0; + + // Chrome VM or standalone; + bool chrome = false; + bool get standalone => !chrome; + + // User defined name. + late String name; + // Network address of VM. + late String networkAddress; + + WebSocketVMTarget(this.networkAddress) { + name = networkAddress; + } + + WebSocketVMTarget.fromMap(Map json) { + lastConnectionTime = json['lastConnectionTime']; + chrome = json['chrome']; + networkAddress = json['networkAddress']; + name = json['name'] ?? networkAddress; + } + + Map toJson() { + return { + 'lastConnectionTime': lastConnectionTime, + 'chrome': chrome, + 'name': name, + 'networkAddress': networkAddress, + }; + } +} + +class _WebSocketRequest { + final String method; + final Map params; + final Completer completer; + + _WebSocketRequest(this.method, this.params) + : completer = new Completer(); +} + +/// Minimal common interface for 'WebSocket' in [dart:io] and [package:web/web.dart]. +abstract class CommonWebSocket { + Future connect( + WebSocketVMTarget target, + void onOpen(), + void onMessage(dynamic data), + void onError(), + void onClose(), + ); + bool get isOpen; + void send(dynamic data); + void close(); + Future nonStringToByteData(dynamic data); +} + +/// A [CommonWebSocketVM] communicates with a Dart VM over a CommonWebSocket. +/// The Dart VM can be embedded in Chromium or standalone. +abstract class CommonWebSocketVM extends VM { + final Completer _connected = new Completer(); + final Completer _disconnected = new Completer(); + final WebSocketVMTarget target; + final Map _delayedRequests = + new Map(); + final Map _pendingRequests = + new Map(); + int _requestSerial = 0; + bool _hasInitiatedConnect = false; + Utf8Decoder _utf8Decoder = const Utf8Decoder(); + + String get displayName => '${name}@${target.name}'; + + CommonWebSocket? _webSocket; + + CommonWebSocketVM(this.target, this._webSocket); + + void _notifyConnect() { + if (!_connected.isCompleted) { + Logger.root.info( + 'WebSocketVM connection opened: ${target.networkAddress}', + ); + _connected.complete(this); + } + } + + Future get onConnect => _connected.future; + bool get wasOrIsConnected => _connected.isCompleted; + bool get isConnected => wasOrIsConnected && !isDisconnected; + void _notifyDisconnect(String reason) { + if (!_disconnected.isCompleted) { + Logger.root.info( + 'WebSocketVM connection error: ${target.networkAddress}', + ); + _disconnected.complete(reason); + } + } + + Future get onDisconnect => _disconnected.future; + bool get isDisconnected => _disconnected.isCompleted; + + void disconnect({String reason = 'WebSocket closed'}) { + if (_hasInitiatedConnect) { + if (_webSocket != null) { + _webSocket!.close(); + } + } + // We don't need to cancel requests and notify here. These + // functions will be called again when the onClose callback + // fires. However, we may have a better 'reason' string now, so + // let's take care of business. + _cancelAllRequests(reason); + _notifyDisconnect(reason); + } + + Future invokeRpcRaw(String method, Map params) async { + if (!_hasInitiatedConnect) { + _hasInitiatedConnect = true; + try { + await _webSocket!.connect( + target, + _onOpen, + _onMessage, + _onError, + _onClose, + ); + } catch (_, stack) { + _webSocket = null; + var exception = new NetworkRpcException('WebSocket closed'); + return new Future.error(exception, stack); + } + } + if (_disconnected.isCompleted) { + // This connection was closed already. + var exception = new NetworkRpcException(await onDisconnect); + return new Future.error(exception); + } + String serial = (_requestSerial++).toString(); + var request = new _WebSocketRequest(method, { + ...params, + // Include internal response data. + '_includePrivateMembers': true, + }); + if ((_webSocket != null) && _webSocket!.isOpen) { + // Already connected, send request immediately. + _sendRequest(serial, request); + } else { + // Not connected yet, add to delayed requests. + _delayedRequests[serial] = request; + } + return request.completer.future; + } + + void _onClose() { + _cancelAllRequests('WebSocket closed'); + _notifyDisconnect('WebSocket closed'); + } + + // WebSocket error event handler. + void _onError() { + // TODO(turnidge): The implementors of CommonWebSocket have more + // error information available. Consider providing that here. + _cancelAllRequests('WebSocket closed due to error'); + _notifyDisconnect('WebSocket closed due to error'); + } + + // WebSocket open event handler. + void _onOpen() { + target.lastConnectionTime = new DateTime.now().millisecondsSinceEpoch; + _sendAllDelayedRequests(); + _notifyConnect(); + } + + Map? _parseJSON(String message) { + var map; + try { + map = json.decode(message); + } catch (e, st) { + Logger.root.severe('Disconnecting: Error decoding message: $e\n$st'); + disconnect(reason: 'Connection saw corrupt JSON message: $e'); + return null; + } + if (map == null) { + Logger.root.severe("Disconnecting: Unable to decode 'null' message"); + disconnect(reason: "Connection saw 'null' message"); + return null; + } + return map; + } + + void _onBinaryMessage(dynamic data) { + _webSocket!.nonStringToByteData(data).then((ByteData bytes) { + var metadataOffset = 4; + var dataOffset = bytes.getUint32(0, Endian.little); + var metadataLength = dataOffset - metadataOffset; + var dataLength = bytes.lengthInBytes - dataOffset; + var metadata = _utf8Decoder.convert( + new Uint8List.view( + bytes.buffer, + bytes.offsetInBytes + metadataOffset, + metadataLength, + ), + ); + var data = new Uint8List.view( + bytes.buffer, + bytes.offsetInBytes + dataOffset, + dataLength, + ); + var map = _parseJSON(metadata); + if (map == null || map['method'] != 'streamNotify') { + return; + } + var event = map['params']['event']; + var streamId = map['params']['streamId']; + scheduleMicrotask(() { + postServiceEvent(streamId, event, data); + }); + }); + } + + void _onStringMessage(String data) { + var map = _parseJSON(data); + if (map == null) { + return; + } + + if (map['method'] == 'streamNotify') { + var event = map['params']['event']; + var streamId = map['params']['streamId']; + scheduleMicrotask(() { + postServiceEvent(streamId, event, null); + }); + return; + } + + // Extract serial and result. + var serial = map['id']; + + // Complete request. + var request = _pendingRequests.remove(serial); + if (request == null) { + Logger.root.severe('Received unexpected message: ${map}'); + return; + } + if (request.method != 'getTagProfile' && + request.method != 'getIsolateMetric' && + request.method != 'getVMMetric') { + Logger.root.info('RESPONSE [${serial}] ${request.method}'); + } + + var result = map['result']; + if (result != null) { + request.completer.complete(result); + } else { + var exception = new ServerRpcException.fromMap(map['error']); + request.completer.completeError(exception); + } + } + + // WebSocket message event handler. + void _onMessage(dynamic data) { + if (data is! String) { + _onBinaryMessage(data); + } else { + _onStringMessage(data); + } + } + + void _cancelRequests( + Map requests, + String message, + ) { + requests.forEach((String serial, _WebSocketRequest request) { + var exception = new NetworkRpcException( + message + + '(id: $serial method: ${request.method} params: ${request.params})', + ); + request.completer.completeError(exception); + }); + requests.clear(); + } + + /// Cancel all pending and delayed requests by completing them with an error. + void _cancelAllRequests(String reason) { + String message = 'Canceling request: $reason'; + if (_pendingRequests.length > 0) { + Logger.root.info('Canceling all pending requests.'); + _cancelRequests(_pendingRequests, message); + } + if (_delayedRequests.length > 0) { + Logger.root.info('Canceling all delayed requests.'); + _cancelRequests(_delayedRequests, message); + } + } + + /// Send all delayed requests. + void _sendAllDelayedRequests() { + assert(_webSocket!.isOpen); + if (_delayedRequests.length == 0) { + return; + } + Logger.root.info('Sending all delayed requests.'); + // Send all delayed requests. + _delayedRequests.forEach(_sendRequest); + // Clear all delayed requests. + _delayedRequests.clear(); + } + + /// Send the request over WebSocket. + void _sendRequest(String serial, _WebSocketRequest request) { + assert(_webSocket!.isOpen); + // Mark request as pending. + assert(_pendingRequests.containsKey(serial) == false); + _pendingRequests[serial] = request; + var message; + // Encode message. + if (target.chrome) { + message = json.encode({ + 'id': int.parse(serial), + 'method': 'Dart.observatoryQuery', + 'params': {'id': serial, 'query': request.method}, + }); + } else { + message = json.encode({ + 'jsonrpc': '2.0', + 'id': serial, + 'method': request.method, + 'params': request.params, + }); + } + if (request.method != 'getTagProfile' && + request.method != 'getIsolateMetric' && + request.method != 'getVMMetric') { + Logger.root.info( + 'GET [${serial}] ${request.method}(${request.params}) from ${target.networkAddress}', + ); + } + // Send message. + _webSocket!.send(message); + } + + String toString() => displayName; +} diff --git a/pkg/observatory/lib/service_html.dart b/pkg/observatory/lib/service_html.dart new file mode 100644 index 00000000000..82050cf5dbd --- /dev/null +++ b/pkg/observatory/lib/service_html.dart @@ -0,0 +1,87 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library service_html; + +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:web/web.dart'; + +import 'service_common.dart'; + +// Export the service library. +export 'service_common.dart'; + +class _HtmlWebSocket implements CommonWebSocket { + WebSocket? _webSocket; + + Future connect( + WebSocketVMTarget target, + void onOpen(), + void onMessage(dynamic data), + void onError(), + void onClose(), + ) async { + // The VM service will attempt to redirect our websocket connection request + // to DDS, but the package:web/web.dart WebSocket doesn't follow redirects. Instead of + // relying on a redirect, we'll request the websocket URI from the service. + + // TODO(bkonyi): re-enable when DDS is enabled. Currently causing Observatory + // failures when running with Flutter (see + // https://github.com/flutter/flutter/issues/64333) + /*Uri getWebSocketUriRequest = Uri.parse(target.networkAddress); + getWebSocketUriRequest = + getWebSocketUriRequest.replace(scheme: 'http', pathSegments: [ + ...getWebSocketUriRequest.pathSegments.where((e) => e != 'ws'), + 'getWebSocketTarget', + ]); + final response = json.decode(await HttpRequest.getString( + getWebSocketUriRequest.toString(), + )); + if (!response.containsKey('result') || + !response['result'].containsKey('uri')) { + onError(); + return; + } + target.networkAddress = response['result']['uri']; + */ + _webSocket = new WebSocket(target.networkAddress); + _webSocket!.onClose.listen((CloseEvent) => onClose()); + _webSocket!.onError.listen((Event) => onError()); + _webSocket!.onOpen.listen((Event) => onOpen()); + _webSocket!.onMessage.listen((MessageEvent event) => onMessage(event.data)); + } + + bool get isOpen => _webSocket!.readyState == WebSocket.OPEN; + + void send(dynamic data) { + _webSocket!.send(data); + } + + void close() { + _webSocket!.close(); + } + + Future nonStringToByteData(dynamic data) { + assert(data is Blob); + FileReader fileReader = new FileReader(); + fileReader.readAsArrayBuffer(data); + return fileReader.onLoadEnd.first.then((e) { + final Uint8List result = (fileReader.result as ByteBuffer).asUint8List(); + return new ByteData.view( + result.buffer, + result.offsetInBytes, + result.length, + ); + }); + } +} + +/// The [WebSocketVM] communicates with a Dart VM over WebSocket. The Dart VM +/// can be embedded in Chromium or standalone. In the case of Chromium, we +/// make the service requests via the Chrome Remote Debugging Protocol. +class WebSocketVM extends CommonWebSocketVM { + WebSocketVM(WebSocketVMTarget target) : super(target, new _HtmlWebSocket()); +} diff --git a/pkg/observatory/lib/service_io.dart b/pkg/observatory/lib/service_io.dart new file mode 100644 index 00000000000..7e058d11178 --- /dev/null +++ b/pkg/observatory/lib/service_io.dart @@ -0,0 +1,69 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library service_io; + +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:logging/logging.dart'; + +import 'service_common.dart'; + +// Export the service library. +export 'service_common.dart'; + +class _IOWebSocket implements CommonWebSocket { + WebSocket? _webSocket; + + Future connect( + WebSocketVMTarget target, + void onOpen(), + void onMessage(dynamic data), + void onError(), + void onClose(), + ) async { + try { + _webSocket = await WebSocket.connect(target.networkAddress); + _webSocket!.listen( + onMessage, + onError: (dynamic) => onError(), + onDone: onClose, + cancelOnError: true, + ); + onOpen(); + } catch (_) { + onError(); + } + } + + bool get isOpen => + (_webSocket != null) && (_webSocket!.readyState == WebSocket.open); + + void send(dynamic data) { + _webSocket!.add(data); + } + + void close() { + _webSocket?.close(); + } + + Future nonStringToByteData(dynamic data) { + assert(data is Uint8List); + Logger.root.info('Binary data size in bytes: ${data.lengthInBytes}'); + return new Future.sync( + () => new ByteData.view( + data.buffer, + data.offsetInBytes, + data.lengthInBytes, + ), + ); + } +} + +/// The [WebSocketVM] communicates with a Dart VM over WebSocket. +class WebSocketVM extends CommonWebSocketVM { + WebSocketVM(WebSocketVMTarget target) : super(target, new _IOWebSocket()); +} diff --git a/pkg/observatory/lib/src/allocation_profile/allocation_profile.dart b/pkg/observatory/lib/src/allocation_profile/allocation_profile.dart new file mode 100644 index 00000000000..f14efbc4eba --- /dev/null +++ b/pkg/observatory/lib/src/allocation_profile/allocation_profile.dart @@ -0,0 +1,97 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of allocation_profiler; + +class AllocationProfile implements M.AllocationProfile { + static const _lastServiceGC = 'dateLastServiceGC'; + final DateTime? lastServiceGC; + static const _lastAccumulatorReset = 'dateLastAccumulatorReset'; + final DateTime? lastAccumulatorReset; + final S.HeapSpace newSpace; + final S.HeapSpace oldSpace; + final S.HeapSpace totalSpace; + final Iterable members; + + AllocationProfile( + S.ServiceMap map, { + Map /*>*/ ? defaults, + }) : lastAccumulatorReset = _intString2DateTime(map[_lastAccumulatorReset]), + lastServiceGC = _intString2DateTime(map[_lastServiceGC]), + oldSpace = new S.HeapSpace()..update(map['_heaps']['old']), + newSpace = new S.HeapSpace()..update(map['_heaps']['new']), + totalSpace = new S.HeapSpace(), + members = _convertMembers(map['members'], defaults: defaults) { + totalSpace.add(oldSpace); + totalSpace.add(newSpace); + } + + static DateTime? _intString2DateTime(String? milliseconds) { + if ((milliseconds == null) || milliseconds == '') { + return null; + } + return new DateTime.fromMillisecondsSinceEpoch(int.parse(milliseconds)); + } + + static ClassHeapStats _convertMember(/*S.ServiceMap*/ map) { + assert(map['type'] == 'ClassHeapStats'); + return new ClassHeapStats(map); + } + + static List _convertMembers( + Iterable /**/ raw, { + Map /*>*/ ? defaults, + }) { + final List members = raw + .map(_convertMember) + .toList(); + if (defaults == null) { + return members; + } + final Map> aliases = new Map.fromIterable( + defaults.keys, + value: (_) => [], + ); + final Map> accumulators = + >{}; + defaults.forEach((/*String*/ key, /*List*/ values) { + final classes = aliases[key]!; + accumulators.addAll(new Map.fromIterable(values, value: (_) => classes)); + }); + final List result = []; + members.forEach((M.ClassHeapStats member) { + if (accumulators.containsKey(member.clazz!.id)) { + accumulators[member.clazz!.id]!.add(member as ClassHeapStats); + } else { + result.add(member as ClassHeapStats); + } + }); + return result..addAll( + aliases.keys.map((key) => new ClassesHeapStats(key, aliases[key]!)), + ); + } +} + +class ClassHeapStats implements M.ClassHeapStats { + final S.Class? clazz; + final String? displayName = null; + final S.Allocations newSpace; + final S.Allocations oldSpace; + + ClassHeapStats(Map map) + : clazz = map['class'], + oldSpace = new S.Allocations()..update(map['_old']), + newSpace = new S.Allocations()..update(map['_new']); +} + +class ClassesHeapStats implements M.ClassHeapStats { + final S.Class? clazz = null; + final String? displayName; + final S.Allocations newSpace; + final S.Allocations oldSpace; + + ClassesHeapStats(this.displayName, Iterable classes) + : oldSpace = new S.Allocations()..combine(classes.map((m) => m.oldSpace)), + newSpace = new S.Allocations()..combine(classes.map((m) => m.newSpace)); +} diff --git a/pkg/observatory/lib/src/app/application.dart b/pkg/observatory/lib/src/app/application.dart new file mode 100644 index 00000000000..78275d7a664 --- /dev/null +++ b/pkg/observatory/lib/src/app/application.dart @@ -0,0 +1,293 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of app; + +/// The observatory application. Instances of this are created and owned +/// by the observatory_application custom element. +class ObservatoryApplication { + static late ObservatoryApplication app; + final RenderingQueue queue = new RenderingQueue(); + final TargetRepository targets = new TargetRepository(isConnectedVMTarget); + final EventRepository events = new EventRepository(); + final NotificationRepository notifications = new NotificationRepository(); + final _pageRegistry = []; + late LocationManager _locationManager; + LocationManager get locationManager => _locationManager; + Page? currentPage; + bool _vmConnected = false; + VM? _vm; + VM get vm => _vm!; + + static bool isConnectedVMTarget(M.Target target) { + if (app._vm is CommonWebSocketVM) { + if ((app._vm as CommonWebSocketVM).target == target) { + return app._vm!.isConnected; + } + } + return false; + } + + _switchVM(VM? newVM) { + final VM? oldVM = _vm; + + Logger.root.info('_switchVM from:${oldVM} to:${newVM}'); + + if (oldVM == newVM) { + // Do nothing. + return; + } + + if (oldVM != null) { + print('disconnecting from the old VM ${oldVM}--'); + // Disconnect from current VM. + stopGCEventListener(); + notifications.deleteAll(); + oldVM.disconnect(); + } + + if (newVM != null) { + // Mark that we haven't connected yet. + _vmConnected = false; + // On connect: + newVM.onConnect.then((_) async { + // We connected. + _vmConnected = true; + notifications.deleteDisconnectEvents(); + await newVM.load(); + // TODO(cbernaschina) smart connection of streams in the events object. + newVM.listenEventStream(VM.kVMStream, _onEvent); + newVM.listenEventStream(VM.kIsolateStream, _onEvent); + newVM.listenEventStream(VM.kDebugStream, _onEvent); + newVM.listenEventStream(VM.kServiceStream, _onEvent); + }); + // On disconnect: + newVM.onDisconnect.then((String reason) { + if (this.vm != newVM) { + // This disconnect event occurred *after* a new VM was installed. + return; + } + // Let anyone looking at the targets know that we have disconnected + // from one. + targets.emitDisconnectEvent(); + if (!_vmConnected) { + // Connection error. Navigate back to the connect page. + Logger.root.info('Connection failed, navigating to VM connect page.'); + // Clear the vm. + _vm = null; + app.locationManager.go(Uris.vmConnect()); + } else { + // Disconnect. Stay at the current page and push an a connection + // closed event. + Logger.root.info('Lost an existing connection to a VM'); + events.add(new ConnectionClosedEvent(new DateTime.now(), reason)); + } + }); + } + + _vm = newVM; + } + + StreamSubscription? _gcSubscription; + StreamSubscription? _loggingSubscription; + + Future startGCEventListener() async { + if (_gcSubscription != null || _vm == null) { + return; + } + _gcSubscription = await _vm!.listenEventStream(VM.kGCStream, _onEvent); + } + + Future startLoggingEventListener() async { + if (_loggingSubscription != null || _vm == null) { + return; + } + _loggingSubscription = await _vm!.listenEventStream( + Isolate.kLoggingStream, + _onEvent, + ); + } + + Future stopGCEventListener() async { + if (_gcSubscription == null) { + return; + } + _gcSubscription!.cancel(); + _gcSubscription = null; + } + + Future stopLoggingEventListener() async { + if (_loggingSubscription == null) { + return; + } + _loggingSubscription!.cancel(); + _loggingSubscription = null; + } + + final ObservatoryApplicationElement rootElement; + + ServiceObject? lastErrorOrException; + + void _initOnce() { + app = this; + _registerPages(); + // Visit the current page. + locationManager._visit(); + } + + void _deletePauseEvents(e) { + notifications.deletePauseEvents(isolate: e.isolate); + } + + void _addNotification(M.Event e) { + notifications.add(new EventNotification(e)); + } + + void _onEvent(ServiceEvent event) { + assert(event.kind != ServiceEvent.kNone); + M.Event? e = createEventFromServiceEvent(event); + if (e != null) { + events.add(e); + } + } + + void _registerPages() { + _pageRegistry.add(new VMPage(this)); + _pageRegistry.add(new FlagsPage(this)); + _pageRegistry.add(new NativeMemoryProfilerPage(this)); + _pageRegistry.add(new InspectPage(this)); + _pageRegistry.add(new ClassTreePage(this)); + _pageRegistry.add(new DebuggerPage(this)); + _pageRegistry.add(new ObjectStorePage(this)); + _pageRegistry.add(new CpuProfilerPage(this)); + _pageRegistry.add(new TableCpuProfilerPage(this)); + _pageRegistry.add(new AllocationProfilerPage(this)); + _pageRegistry.add(new HeapMapPage(this)); + _pageRegistry.add(new HeapSnapshotPage(this)); + _pageRegistry.add(new VMConnectPage(this)); + _pageRegistry.add(new IsolateReconnectPage(this)); + _pageRegistry.add(new ErrorViewPage(this)); + _pageRegistry.add(new MetricsPage(this)); + _pageRegistry.add(new PersistentHandlesPage(this)); + _pageRegistry.add(new PortsPage(this)); + _pageRegistry.add(new LoggingPage(this)); + _pageRegistry.add(new TimelinePage(this)); + _pageRegistry.add(new ProcessSnapshotPage(this)); + // Note that ErrorPage must be the last entry in the list as it is + // the catch all. + _pageRegistry.add(new ErrorPage(this)); + } + + void _visit(Uri uri, Map internalArguments) { + if (internalArguments['trace'] != null) { + var traceArg = internalArguments['trace']; + if (traceArg == 'on') { + Tracer.start(); + } else if (traceArg == 'off') { + Tracer.stop(); + } + } + if (Tracer.current != null) { + Tracer.current!.reset(); + } + for (var i = 0; i < _pageRegistry.length; i++) { + var page = _pageRegistry[i]; + if (page.canVisit(uri)) { + _installPage(page); + page.visit(uri, internalArguments); + return; + } + } + throw new ArgumentError.value(uri, 'uri'); + } + + /// Set the Observatory application page. + void _installPage(Page page) { + if (currentPage == page) { + // Already installed. + return; + } + if (currentPage != null) { + Logger.root.info('Uninstalling page: $currentPage'); + currentPage!.onUninstall(); + // Clear children. + rootElement.removeChildren(); + } + Logger.root.info('Installing page: $page'); + try { + page.onInstall(); + } catch (e) { + Logger.root.severe('Failed to install page: $e'); + } + // Add new page. + rootElement.appendChild(page.element!); + + // Remember page. + currentPage = page; + } + + ObservatoryApplication(this.rootElement) { + _locationManager = new LocationManager(this); + targets.onChange.listen((TargetChangeEvent e) { + if (e.disconnected) { + // We don't care about disconnected events. + return; + } + if (targets.current == null) { + _switchVM(null); + } else { + final bool currentTarget = + (_vm as WebSocketVM?)?.target == targets.current; + final bool currentTargetConnected = (_vm != null) && _vm!.isConnected; + if (!currentTarget || !currentTargetConnected) { + _switchVM(new WebSocketVM(targets.current!)); + _vm!.onConnect.then((_) { + app.locationManager.go(Uris.vm()); + }); + _vm!.load(); + } else if (currentTargetConnected) { + app.locationManager.go(Uris.vm()); + } + } + }); + + Logger.root.info('Setting initial target to ${targets.current!.name}'); + _switchVM(new WebSocketVM(targets.current!)); + _initOnce(); + + // delete pause events. + events.onIsolateExit.listen(_deletePauseEvents); + events.onResume.listen(_deletePauseEvents); + events.onPauseStart.listen(_deletePauseEvents); + events.onPauseExit.listen(_deletePauseEvents); + events.onPauseBreakpoint.listen(_deletePauseEvents); + events.onPauseInterrupted.listen(_deletePauseEvents); + events.onPauseException.listen(_deletePauseEvents); + + // show notification for an event. + events.onIsolateReload.listen(_addNotification); + events.onPauseExit.listen(_addNotification); + events.onPauseBreakpoint.listen(_addNotification); + events.onPauseInterrupted.listen(_addNotification); + events.onPauseException.listen(_addNotification); + events.onInspect.listen(_addNotification); + events.onConnectionClosed.listen(_addNotification); + } + + void handleException(e, st) { + if (e is ServerRpcException) { + if (e.code == ServerRpcException.kFeatureDisabled) return; + if (e.code == ServerRpcException.kIsolateMustBePaused) return; + if (e.code == ServerRpcException.kCannotAddBreakpoint) return; + Logger.root.fine('Dropping exception: ${e}\n${st}'); + } + + // TODO(turnidge): Report this failure via analytics. + Logger.root.warning('Caught exception: ${e}\n${st}'); + notifications.add(new ExceptionNotification(e, stacktrace: st)); + } + + // This map keeps track of which curly-blocks have been expanded by the user. + Map expansions = {}; +} diff --git a/pkg/observatory/lib/src/app/location_manager.dart b/pkg/observatory/lib/src/app/location_manager.dart new file mode 100644 index 00000000000..e088616905a --- /dev/null +++ b/pkg/observatory/lib/src/app/location_manager.dart @@ -0,0 +1,178 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of app; + +class LocationManager { + final ObservatoryApplication _app; + + /// [internalArguments] are parameters specified after a '---' in the + /// application URL. + final Map internalArguments = new Map(); + + Uri? _uri; + + /// [uri] is the application uri. Application uris consist of a path and + /// the queryParameters map. + Uri get uri => _uri!; + + LocationManager(this._app) { + web.window.onPopState.listen(_onBrowserNavigation); + // Determine initial application path. + var applicationPath = '${web.window.location.hash}'; + if ((web.window.location.hash == '') || (web.window.location.hash == '#')) { + // Observatory has loaded but no application path has been specified, + // use the default. + // By default we navigate to the VM page. + applicationPath = Uris.vm(); + } + // Update current application path. + web.window.history.replaceState( + applicationPath.toJS, + web.document.title, + applicationPath, + ); + _updateApplicationLocation(applicationPath); + } + + bool getBoolParameter(String name, bool defaultValue) { + var value = uri.queryParameters[name]; + if ("true" == value) return true; + if ("false" == value) return false; + return defaultValue; + } + + /// Called whenever the browser changes the location bar (e.g. forward or + /// back button press). + void _onBrowserNavigation(web.PopStateEvent event) { + _updateApplicationLocation(web.window.location.hash); + _visit(); + } + + /// Given an application url, generate an href link. + String makeLink(String url) => '#$url'; + + /// Update the application location. After this function returns, + /// [uri] and [debugArguments] will be updated. + _updateApplicationLocation(String url) { + if (url == Uris.vmConnect()) { + // When we go to the vm-connect page, drop all notifications. + _app.notifications.deleteAll(); + } + + // Chop off leading '#'. + if (url.startsWith('#')) { + url = url.substring(1); + } + // Fall through handles '#/' + // Chop off leading '/'. + if (url.startsWith('/')) { + url = url.substring(1); + } + // Parse out debug arguments. + if (url.contains('---')) { + var chunks = url.split('---'); + url = chunks[0]; + if ((chunks.length > 1) && (chunks[1] != '')) { + internalArguments.clear(); + try { + internalArguments.addAll(Uri.splitQueryString(chunks[1])); + } catch (e) { + Logger.root.warning('Could not parse debug arguments ${e}'); + } + } + } + _uri = Uri.parse(url); + } + + /// Add [url] to the browser history. + _addToBrowserHistory(String url) { + web.window.history.pushState(url.toJS, web.document.title, url); + } + + /// Notify the current page that something has changed. + _visit() { + Chain.capture( + () => _app._visit(_uri!, internalArguments), + onError: (e, st) { + if (e is IsolateNotFound) { + var newPath = _app.vm.isDisconnected + ? '/vm-connect' + : '/isolate-reconnect'; + var parameters = {}; + parameters.addAll(_uri!.queryParameters); + parameters['originalUri'] = _uri.toString(); + parameters['isolateId'] = parameters['isolateId']; + var generatedUri = new Uri( + path: newPath, + queryParameters: parameters, + ); + go(makeLink(generatedUri.toString()), true); + return; + } + // Surface any uncaught exceptions. + _app.handleException(e, st); + }, + ); + } + + /// Navigate to [url]. + void go(String url, [bool addToBrowserHistory = true]) { + if (addToBrowserHistory) { + _addToBrowserHistory(url); + } + _updateApplicationLocation(url); + _visit(); + } + + /// Starting with the current uri path and queryParameters, update + /// queryParameters present in [updateParameters], then generate a new uri + /// and navigate to that. + goReplacingParameters( + Map updatedParameters, [ + bool addToBrowserHistory = true, + ]) { + go(makeLinkReplacingParameters(updatedParameters), addToBrowserHistory); + } + + makeLinkReplacingParameters(Map updatedParameters) { + var parameters = new Map.from(_uri!.queryParameters); + updatedParameters.forEach((k, v) { + parameters[k] = v; + }); + // Ensure path starts with a slash. + var path = uri.path.startsWith('/') ? uri.path : '/${uri.path}'; + var generatedUri = new Uri(path: path, queryParameters: parameters); + return makeLink(generatedUri.toString()); + } + + goForwardingParameters(String newPath, [bool addToBrowserHistory = true]) { + go(makeLinkForwardingParameters(newPath), addToBrowserHistory); + } + + makeLinkForwardingParameters(String newPath) { + var parameters = _uri!.queryParameters; + var generatedUri = new Uri(path: newPath, queryParameters: parameters); + return makeLink(generatedUri.toString()); + } + + /// Utility event handler when clicking on application url link. + void onGoto(web.MouseEvent event) { + if ((event.button > 0) || + event.metaKey || + event.ctrlKey || + event.shiftKey || + event.altKey) { + // Mouse event is not a left-click OR + // mouse event is a left-click with a modifier key: + // let browser handle. + return; + } + event.preventDefault(); + // 'currentTarget' is the dom element that would process the event. + // If we use 'target' we might get an element or somesuch. + web.HTMLElement target = event.currentTarget as web.HTMLElement; + go(target.attributes.getNamedItem('href')!.value); + } +} diff --git a/pkg/observatory/lib/src/app/notification.dart b/pkg/observatory/lib/src/app/notification.dart new file mode 100644 index 00000000000..933cdb9fadc --- /dev/null +++ b/pkg/observatory/lib/src/app/notification.dart @@ -0,0 +1,18 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of app; + +class ExceptionNotification implements M.ExceptionNotification { + final exception; + + /// [optional] + final StackTrace? stacktrace; + ExceptionNotification(this.exception, {this.stacktrace}); +} + +class EventNotification implements M.EventNotification { + final M.Event event; + EventNotification(this.event); +} diff --git a/pkg/observatory/lib/src/app/page.dart b/pkg/observatory/lib/src/app/page.dart new file mode 100644 index 00000000000..942c8121d78 --- /dev/null +++ b/pkg/observatory/lib/src/app/page.dart @@ -0,0 +1,1070 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of app; + +final _allocationProfileRepository = new AllocationProfileRepository(); +final _breakpointRepository = new BreakpointRepository(); +final _classRepository = new ClassRepository(); +final _classSampleProfileRepository = new ClassSampleProfileRepository(); +final _contextRepository = new ContextRepository(); +final _evalRepository = new EvalRepository(); +final _fieldRepository = new FieldRepository(); +final _functionRepository = new FunctionRepository(); +final _heapSnapshotRepository = new HeapSnapshotRepository(); +final _icdataRepository = new ICDataRepository(); +final _inboundReferencesRepository = new InboundReferencesRepository(); +final _isolateSampleProfileRepository = new IsolateSampleProfileRepository(); +final _libraryRepository = new LibraryRepository(); +final _megamorphicCacheRepository = new MegamorphicCacheRepository(); +final _metricRepository = new MetricRepository(); +final _nativeMemorySampleProfileRepository = + new NativeMemorySampleProfileRepository(); +final _objectPoolRepository = new ObjectPoolRepository(); +final _objectRepository = new ObjectRepository(); +final _objectstoreRepository = new ObjectStoreRepository(); +final _persistentHandlesRepository = new PersistentHandlesRepository(); +final _portsRepository = new PortsRepository(); +final _scriptRepository = new ScriptRepository(); +final _singleTargetCacheRepository = new SingleTargetCacheRepository(); +final _stronglyReachangleInstancesRepository = + new StronglyReachableInstancesRepository(); +final _subtypeTestCacheRepository = new SubtypeTestCacheRepository(); +final _timelineRepository = new TimelineRepository(); +final _typeArgumentsRepository = new TypeArgumentsRepository(); +final _unlinkedCallRepository = new UnlinkedCallRepository(); +final _vmrepository = new VMRepository(); + +class IsolateNotFound implements Exception { + String isolateId; + IsolateNotFound(this.isolateId); + String toString() => "IsolateNotFound: $isolateId"; +} + +RetainedSizeRepository _retainedSizeRepository = new RetainedSizeRepository(); +ReachableSizeRepository _reachableSizeRepository = + new ReachableSizeRepository(); +RetainingPathRepository _retainingPathRepository = + new RetainingPathRepository(); + +/// A [Page] controls the user interface of Observatory. At any given time +/// one page will be the current page. Pages are registered at startup. +/// When the user navigates within the application, each page is asked if it +/// can handle the current location, the first page to say yes, wins. +abstract class Page { + final ObservatoryApplication app; + final Map internalArguments = {}; + web.HTMLElement? element; + + Page(this.app); + + /// Called when the page is installed, this callback must initialize + /// [element]. + void onInstall(); + + /// Called when the page is uninstalled, this callback must clear + /// [element]. + void onUninstall() { + element = null; + } + + /// Called when the page should update its state based on [uri]. + void visit(Uri uri, Map internalArguments) { + this.internalArguments.clear(); + this.internalArguments.addAll(internalArguments); + _visit(uri); + } + + // Overridden by subclasses. + void _visit(Uri uri); + + /// Called to test whether this page can visit [uri]. + bool canVisit(Uri uri); +} + +/// A [MatchingPage] matches a single uri path. +abstract class MatchingPage extends Page { + final String path; + MatchingPage(this.path, app) : super(app); + + void _visit(Uri uri) { + assert(canVisit(uri)); + } + + Future getIsolate(Uri uri) { + var isolateId = uri.queryParameters['isolateId']; + return app.vm.getIsolate(isolateId!).then((isolate) { + return isolate; + }); + } + + EditorRepository getEditor(Uri uri) { + final editor = uri.queryParameters['editor']; + return new EditorRepository(app.vm, editor: editor); + } + + bool canVisit(Uri uri) => uri.path == path; +} + +/// A [SimplePage] matches a single uri path and displays a single element. +abstract class SimplePage extends MatchingPage { + final String elementTagName; + SimplePage(String path, this.elementTagName, app) : super(path, app); + + void onInstall() { + if (element == null) { + element = createElement(); + } + } + + web.HTMLElement createElement(); +} + +/// Error page for unrecognized paths. +class ErrorPage extends Page { + ErrorPage(app) : super(app); + + void onInstall() { + if (element == null) { + // Lazily create page. + element = new GeneralErrorElement( + app.notifications, + queue: app.queue, + ).element; + } + } + + void _visit(Uri uri) { + assert(element != null); + assert(canVisit(uri)); + + (element as GeneralErrorElement).message = "Path '${uri.path}' not found"; + } + + /// Catch all. + bool canVisit(Uri uri) => true; +} + +/// Top-level vm info page. +class VMPage extends MatchingPage { + VMPage(app) : super('vm', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + void onInstall() { + if (element == null) { + element = container; + } + assert(element != null); + } + + void _visit(Uri uri) { + super._visit(uri); + app.vm + .reload() + .then((serviceObject) { + VM vm = serviceObject as VM; + container.removeChildren(); + final vmview = new VMViewElement( + vm, + _vmrepository, + app.events, + app.notifications, + new IsolateRepository(app.vm), + new IsolateGroupRepository(), + _scriptRepository, + queue: app.queue, + ).element; + container.appendChild(vmview); + }) + .catchError((e, stack) { + Logger.root.severe('VMPage visit error: $e'); + Logger.root.severe('Stack: $stack'); + // Reroute to vm-connect. + app.locationManager.go(Uris.vmConnect()); + }); + } +} + +class FlagsPage extends SimplePage { + FlagsPage(app) : super('flags', 'flag-list', app); + + @override + web.HTMLElement createElement() => new FlagListElement( + app.vm, + app.events, + new FlagsRepository(app.vm), + app.notifications, + queue: app.queue, + ).element; + + void _visit(Uri uri) { + super._visit(uri); + } +} + +class NativeMemoryProfilerPage extends SimplePage { + NativeMemoryProfilerPage(app) + : super('native-memory-profile', 'native-memory-profile', app); + + @override + web.HTMLElement createElement() => NativeMemoryProfileElement( + app.vm, + app.events, + app.notifications, + _nativeMemorySampleProfileRepository, + queue: app.queue, + ).element; + + void _visit(Uri uri) { + super._visit(uri); + } +} + +class InspectPage extends MatchingPage { + InspectPage(app) : super('inspect', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + void _visit(Uri uri) { + super._visit(uri); + getIsolate(uri).then((isolate) { + var objectId = uri.queryParameters['objectId']; + if (objectId == null) { + isolate.reload().then(_visitObject); + } else { + isolate.getObject(objectId).then(_visitObject); + } + }); + } + + void onInstall() { + if (element == null) { + element = container; + } + assert(element != null); + } + + Future _visitObject(obj) async { + container.removeChildren(); + await obj.reload(); + if (obj is Class) { + container.removeChildren(); + container.appendChild( + new ClassViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _classRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _fieldRepository, + _scriptRepository, + _objectRepository, + _evalRepository, + _stronglyReachangleInstancesRepository, + _classSampleProfileRepository, + queue: app.queue, + ).element, + ); + } else if (obj is Code) { + await obj.loadScript(); + container.removeChildren(); + container.appendChild( + new CodeViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + } else if (obj is Context) { + container.removeChildren(); + container.appendChild( + new ContextViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _contextRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + } else if (obj is DartError) { + container.removeChildren(); + container.appendChild( + new ErrorViewElement(app.notifications, obj, queue: app.queue).element, + ); + } else if (obj is Field) { + container.removeChildren(); + container.appendChild( + new FieldViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _fieldRepository, + _classRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _scriptRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + } else if (obj is Instance) { + container.removeChildren(); + container.appendChild( + new InstanceViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _objectRepository, + _classRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _scriptRepository, + _evalRepository, + _typeArgumentsRepository, + _breakpointRepository, + _functionRepository, + queue: app.queue, + ).element, + ); + } else if (obj is Isolate) { + container.removeChildren(); + container.appendChild( + new IsolateViewElement( + app.vm, + obj, + app.events, + app.notifications, + new IsolateRepository(app.vm), + _scriptRepository, + _functionRepository, + _libraryRepository, + _objectRepository, + _evalRepository, + queue: app.queue, + ).element, + ); + } else if (obj is ServiceFunction) { + container.removeChildren(); + container.appendChild( + new FunctionViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _functionRepository, + _classRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _scriptRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + } else if (obj is ICData) { + container.removeChildren(); + container.appendChild( + new ICDataViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _icdataRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + } else if (obj is SingleTargetCache) { + container.removeChildren(); + container.appendChild( + new SingleTargetCacheViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _singleTargetCacheRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + } else if (obj is SubtypeTestCache) { + container.removeChildren(); + container.appendChild( + new SubtypeTestCacheViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _subtypeTestCacheRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + } else if (obj is UnlinkedCall) { + container.removeChildren(); + container.appendChild( + new UnlinkedCallViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _unlinkedCallRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + } else if (obj is Library) { + container.removeChildren(); + container.appendChild( + new LibraryViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _libraryRepository, + _fieldRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _scriptRepository, + _objectRepository, + _evalRepository, + queue: app.queue, + ).element, + ); + } else if (obj is MegamorphicCache) { + container.removeChildren(); + container.appendChild( + new MegamorphicCacheViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _megamorphicCacheRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + } else if (obj is ObjectPool) { + container.removeChildren(); + container.appendChild( + new ObjectPoolViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _objectPoolRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + } else if (obj is Script) { + var pos; + if (app.locationManager.internalArguments['pos'] != null) { + try { + pos = int.parse(app.locationManager.internalArguments['pos']!); + } catch (_) {} + } + container.removeChildren(); + container.appendChild( + new ScriptViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _scriptRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + _objectRepository, + pos: pos, + queue: app.queue, + ).element, + ); + } else if (obj is HeapObject) { + container.removeChildren(); + container.appendChild( + new ObjectViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + _objectRepository, + _retainedSizeRepository, + _reachableSizeRepository, + _inboundReferencesRepository, + _retainingPathRepository, + queue: app.queue, + ).element, + ); + } else if (obj is Sentinel) { + container.removeChildren(); + container.appendChild( + new SentinelViewElement( + app.vm, + obj.isolate as Isolate, + obj, + app.events, + app.notifications, + queue: app.queue, + ).element, + ); + } else { + container.removeChildren(); + container.appendChild( + new JSONViewElement(obj, app.notifications, queue: app.queue).element, + ); + } + } +} + +/// Class tree page. +class ClassTreePage extends SimplePage { + ClassTreePage(app) : super('class-tree', 'class-tree', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + @override + web.HTMLElement createElement() => container; + + void _visit(Uri uri) { + super._visit(uri); + getIsolate(uri).then((isolate) { + container.removeChildren(); + container.appendChild( + new ClassTreeElement( + app.vm, + isolate, + app.events, + app.notifications, + _classRepository, + ).element, + ); + }); + } +} + +class DebuggerPage extends MatchingPage { + DebuggerPage(app) : super('debugger', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + void _visit(Uri uri) { + super._visit(uri); + getIsolate(uri).then((isolate) async { + container.removeChildren(); + container.appendChild( + new DebuggerPageElement( + isolate, + _objectRepository, + _scriptRepository, + app.events, + ).element, + ); + }); + } + + void onInstall() { + if (element == null) { + element = container; + } + assert(element != null); + } + + @override + void onUninstall() { + super.onUninstall(); + } +} + +class ObjectStorePage extends MatchingPage { + ObjectStorePage(app) : super('object-store', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + void _visit(Uri uri) { + super._visit(uri); + getIsolate(uri).then((isolate) async { + container.removeChildren(); + container.appendChild( + new ObjectStoreViewElement( + isolate.vm, + isolate, + app.events, + app.notifications, + _objectstoreRepository, + _objectRepository, + ).element, + ); + }); + } + + void onInstall() { + if (element == null) { + element = container; + } + assert(element != null); + } +} + +class CpuProfilerPage extends MatchingPage { + CpuProfilerPage(app) : super('profiler', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + void _visit(Uri uri) { + super._visit(uri); + getIsolate(uri).then((isolate) { + container.removeChildren(); + container.appendChild( + new CpuProfileElement( + isolate.vm, + isolate, + app.events, + app.notifications, + _isolateSampleProfileRepository, + ).element, + ); + }); + } + + void onInstall() { + if (element == null) { + element = container; + } + assert(element != null); + } +} + +class TableCpuProfilerPage extends MatchingPage { + TableCpuProfilerPage(app) : super('profiler-table', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + void _visit(Uri uri) { + super._visit(uri); + getIsolate(uri).then((isolate) { + container.removeChildren(); + container.appendChild( + new CpuProfileTableElement( + isolate.vm, + isolate, + app.events, + app.notifications, + _isolateSampleProfileRepository, + ).element, + ); + }); + } + + void onInstall() { + if (element == null) { + element = container; + } + assert(element != null); + } +} + +class AllocationProfilerPage extends MatchingPage { + AllocationProfilerPage(app) : super('allocation-profiler', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + void _visit(Uri uri) { + super._visit(uri); + getIsolate(uri).then((isolate) { + container.removeChildren(); + container.appendChild( + new AllocationProfileElement( + isolate.vm, + isolate, + app.events, + app.notifications, + _allocationProfileRepository, + queue: app.queue, + ).element, + ); + }); + } + + void onInstall() { + if (element == null) { + element = container; + } + app.startGCEventListener(); + } + + @override + void onUninstall() { + super.onUninstall(); + app.stopGCEventListener(); + } +} + +class PortsPage extends MatchingPage { + PortsPage(app) : super('ports', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + void _visit(Uri uri) { + super._visit(uri); + getIsolate(uri).then((isolate) { + container.removeChildren(); + container.appendChild( + new PortsElement( + isolate.vm, + isolate, + app.events, + app.notifications, + _portsRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + }); + } + + void onInstall() { + if (element == null) { + element = container; + } + } +} + +class PersistentHandlesPage extends MatchingPage { + PersistentHandlesPage(app) : super('persistent-handles', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + void _visit(Uri uri) { + super._visit(uri); + getIsolate(uri).then((isolate) { + container.removeChildren(); + container.appendChild( + new PersistentHandlesPageElement( + isolate.vm, + isolate, + app.events, + app.notifications, + _persistentHandlesRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + }); + } + + void onInstall() { + if (element == null) { + element = container; + } + } +} + +class HeapMapPage extends MatchingPage { + HeapMapPage(app) : super('heap-map', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + void _visit(Uri uri) { + super._visit(uri); + getIsolate(uri).then((isolate) { + container.removeChildren(); + container.appendChild( + new HeapMapElement( + isolate.vm, + isolate, + app.events, + app.notifications, + queue: app.queue, + ).element, + ); + }); + } + + void onInstall() { + if (element == null) { + element = container; + } + } +} + +class HeapSnapshotPage extends MatchingPage { + HeapSnapshotPage(app) : super('heap-snapshot', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + void _visit(Uri uri) { + super._visit(uri); + getIsolate(uri).then((isolate) { + container.removeChildren(); + container.appendChild( + new HeapSnapshotElement( + isolate.vm, + isolate, + app.events, + app.notifications, + _heapSnapshotRepository, + _objectRepository, + queue: app.queue, + ).element, + ); + }); + } + + void onInstall() { + if (element == null) { + element = container; + } + } +} + +class LoggingPage extends MatchingPage { + LoggingPage(app) : super('logging', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + @override + void onInstall() { + element = container; + app.startLoggingEventListener(); + } + + @override + void onUninstall() { + super.onUninstall(); + app.stopLoggingEventListener(); + } + + void _visit(Uri uri) { + assert(element != null); + assert(canVisit(uri)); + getIsolate(uri).then((isolate) { + container.removeChildren(); + container.appendChild( + new LoggingPageElement( + app.vm, + isolate, + app.events, + app.notifications, + queue: app.queue, + ).element, + ); + }); + } +} + +class ErrorViewPage extends Page { + ErrorViewPage(app) : super(app); + + void onInstall() { + element = new ErrorViewElement( + app.notifications, + app.lastErrorOrException as DartError, + queue: app.queue, + ).element; + } + + void _visit(Uri uri) { + assert(element != null); + assert(canVisit(uri)); + } + + // TODO(turnidge): How to test this page? + bool canVisit(Uri uri) => uri.path == 'error'; +} + +class VMConnectPage extends Page { + VMConnectPage(app) : super(app); + + void onInstall() { + if (element == null) { + element = new VMConnectElement( + ObservatoryApplication.app.targets, + ObservatoryApplication.app.notifications, + queue: ObservatoryApplication.app.queue, + ).element; + } + assert(element != null); + } + + void _visit(Uri uri) { + assert(element != null); + assert(canVisit(uri)); + } + + bool canVisit(Uri uri) => uri.path == 'vm-connect'; +} + +class IsolateReconnectPage extends Page { + IsolateReconnectPage(app) : super(app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + void onInstall() { + element = container; + } + + void _visit(Uri uri) { + app.vm.reload(); + container.removeChildren(); + container.appendChild( + new IsolateReconnectElement( + app.vm, + app.events, + app.notifications, + uri.queryParameters['isolateId']!, + Uri.parse(uri.queryParameters['originalUri']!), + ).element, + ); + assert(element != null); + assert(canVisit(uri)); + } + + bool canVisit(Uri uri) => uri.path == 'isolate-reconnect'; +} + +class MetricsPage extends MatchingPage { + MetricsPage(app) : super('metrics', app); + + final web.HTMLDivElement container = web.HTMLDivElement(); + + Isolate? lastIsolate; + + void _visit(Uri uri) { + super._visit(uri); + getIsolate(uri).then((isolate) async { + lastIsolate = isolate; + await _metricRepository.startSampling(isolate); + container.removeChildren(); + container.appendChild( + new MetricsPageElement( + isolate.vm, + isolate, + app.events, + app.notifications, + _metricRepository, + queue: app.queue, + ).element, + ); + }); + } + + void onInstall() { + if (element == null) { + element = container; + } + } + + @override + void onUninstall() { + super.onUninstall(); + _metricRepository.stopSampling(lastIsolate!); + } +} + +class TimelinePage extends Page { + TimelinePage(app) : super(app); + + void onInstall() { + element = new TimelinePageElement( + app.vm, + _timelineRepository, + app.events, + app.notifications, + queue: app.queue, + ).element; + } + + void _visit(Uri uri) { + assert(canVisit(uri)); + } + + bool canVisit(Uri uri) => uri.path == 'timeline'; +} + +class ProcessSnapshotPage extends Page { + ProcessSnapshotPage(app) : super(app); + + void onInstall() { + element = new ProcessSnapshotElement( + app.vm, + app.events, + app.notifications, + queue: app.queue, + ).element; + } + + void _visit(Uri uri) { + assert(canVisit(uri)); + } + + bool canVisit(Uri uri) => uri.path == 'process-snapshot'; +} diff --git a/pkg/observatory/lib/src/app/settings.dart b/pkg/observatory/lib/src/app/settings.dart new file mode 100644 index 00000000000..267205f090c --- /dev/null +++ b/pkg/observatory/lib/src/app/settings.dart @@ -0,0 +1,45 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of app; + +/// Static settings database. +class _Settings { + static web.Storage _storage = web.window.localStorage; + + /// Associated [value] with [key]. [value] must be JSON encodable. + static void set(String key, dynamic value) { + _storage.setItem(key, json.encode(value)); + } + + /// Get value associated with [key]. Return value will be a JSON encodable + /// object. + static dynamic get(String key) { + var value = _storage.getItem(key); + if (value == null) { + return null; + } + return json.decode(value); + } +} + +/// A group of settings each prefixed with group name and a dot. +class SettingsGroup { + /// Group name + final String group; + + SettingsGroup(this.group); + + String _fullKey(String key) => '$group.$key'; + + void set(String key, dynamic value) { + var fullKey = _fullKey(key); + _Settings.set(fullKey, value); + } + + dynamic get(String key) { + var fullKey = _fullKey(key); + return _Settings.get(fullKey); + } +} diff --git a/pkg/observatory/lib/src/app/view_model.dart b/pkg/observatory/lib/src/app/view_model.dart new file mode 100644 index 00000000000..f210e137d72 --- /dev/null +++ b/pkg/observatory/lib/src/app/view_model.dart @@ -0,0 +1,104 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of app; + +typedef String ValueFormatter(dynamic value); + +class SortedTableColumn { + static String toStringFormatter(dynamic v) { + return v != null ? v.toString() : ''; + } + + final String label; + final ValueFormatter formatter; + SortedTableColumn.withFormatter(this.label, this.formatter); + SortedTableColumn(this.label) : formatter = toStringFormatter; +} + +class SortedTableRow { + final List values; + SortedTableRow(this.values); +} + +class SortedTable { + final List columns; + final List rows = []; + final List sortedRows = []; + + SortedTable(this.columns); + + int _sortColumnIndex = 0; + set sortColumnIndex(var index) { + assert(index >= 0); + assert(index < columns.length); + _sortColumnIndex = index; + } + + int get sortColumnIndex => _sortColumnIndex; + bool _sortDescending = true; + bool get sortDescending => _sortDescending; + set sortDescending(var descending) { + _sortDescending = descending; + } + + dynamic getSortKeyFor(int row, int col) { + return rows[row].values[col]; + } + + int _sortFuncDescending(int i, int j) { + var a = getSortKeyFor(i, _sortColumnIndex); + var b = getSortKeyFor(j, _sortColumnIndex); + return b.compareTo(a); + } + + int _sortFuncAscending(int i, int j) { + var a = getSortKeyFor(i, _sortColumnIndex); + var b = getSortKeyFor(j, _sortColumnIndex); + return a.compareTo(b); + } + + void sort() { + assert(_sortColumnIndex >= 0); + assert(_sortColumnIndex < columns.length); + if (_sortDescending) { + sortedRows.sort(_sortFuncDescending); + } else { + sortedRows.sort(_sortFuncAscending); + } + } + + void clearRows() { + rows.clear(); + sortedRows.clear(); + } + + void addRow(SortedTableRow row) { + sortedRows.add(rows.length); + rows.add(row); + } + + String getFormattedValue(int row, int column) { + var value = getValue(row, column); + var formatter = columns[column].formatter; + return formatter(value); + } + + String getColumnLabel(int column) { + assert(column >= 0); + assert(column < columns.length); + // TODO(johnmccutchan): Move expander display decisions into html once + // tables and templates are better supported. + const arrowUp = '\u25BC'; + const arrowDown = '\u25B2'; + if (column != _sortColumnIndex) { + return columns[column].label + '\u2003'; + } + return columns[column].label + (_sortDescending ? arrowUp : arrowDown); + } + + dynamic getValue(int row, int column) { + return rows[row].values[column]; + } +} diff --git a/pkg/observatory/lib/src/cli/command.dart b/pkg/observatory/lib/src/cli/command.dart new file mode 100644 index 00000000000..5094ec3df56 --- /dev/null +++ b/pkg/observatory/lib/src/cli/command.dart @@ -0,0 +1,282 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cli; + +// Splits a line into a list of string args. Each arg retains any +// trailing whitespace so that we can reconstruct the original command +// line from the pieces. +List _splitLine(String line) { + line = line.trimLeft(); + var args = []; + int pos = 0; + while (pos < line.length) { + int startPos = pos; + + // Advance to end of word. + for (; pos < line.length && line[pos] != ' '; pos++) ; + + // Advance to end of spaces. + for (; pos < line.length && line[pos] == ' '; pos++) ; + + args.add(line.substring(startPos, pos)); + } + return args; +} + +// Concatenates the first 'count' args. +String _concatArgs(List args, int count) { + if (count == 0) { + return ''; + } + return '${args.sublist(0, count).join('')}'; +} + +// Shared functionality for RootCommand and Command. +abstract class _CommandBase { + _CommandBase(List children) { + _children.addAll(children); + for (var child in _children) { + child._parent = this; + } + } + + // A command may optionally have sub-commands. + List _children = []; + + _CommandBase? _parent; + int get _depth => (_parent == null ? 0 : _parent!._depth + 1); + + // Override in subclasses to provide command-specific argument completion. + // + // Given a list of arguments to this command, provide a list of + // possible completions for those arguments. + Future> complete(List args) => + new Future.value([]); + + // Override in subclasses to provide command-specific execution. + Future run(List args); + + // Returns a list of local subcommands which match the args. + List _matchLocal(String argWithSpace, bool preferExact) { + var matches = []; + var arg = argWithSpace.trimRight(); + for (var child in _children) { + if (child.name.startsWith(arg)) { + if (preferExact && ((child.name == arg) || (child.alias == arg))) { + return [child]; + } + matches.add(child); + } + } + return matches; + } + + // Returns the set of commands could be triggered by a list of + // arguments. + List _match(List args, bool preferExact) { + if (args.isEmpty) { + return []; + } + bool lastArg = (args.length == 1); + var matches = _matchLocal(args[0], !lastArg || preferExact); + if (matches.isEmpty) { + return []; + } else if (matches.length == 1) { + var childMatches = matches[0]._match(args.sublist(1), preferExact); + if (childMatches.isEmpty) { + return matches; + } else { + return childMatches; + } + } else { + return matches; + } + } + + // Builds a list of completions for this command. + Future> _buildCompletions( + List args, + bool addEmptyString, + ) { + return complete(args.sublist(_depth, args.length)).then((completions) { + if (addEmptyString && + completions.isEmpty && + args[args.length - 1] == '') { + // Special case allowance for an empty particle at the end of + // the command. + completions = ['']; + } + var prefix = _concatArgs(args, _depth); + return completions.map((str) => '${prefix}${str}').toList(); + }); + } +} + +// The root of a tree of commands. +class RootCommand extends _CommandBase { + RootCommand(List children, [List? history]) + : this._(children, history ?? ['']); + + RootCommand._(List children, List history) + : history = history, + historyPos = history.length - 1, + super(children); + + // Provides a list of possible completions for a line of text. + Future> completeCommand(String line) { + var args = _splitLine(line); + bool showAll = line.endsWith(' ') || args.isEmpty; + if (showAll) { + // Adding an empty string to the end causes us to match all + // subcommands of the last command. + args.add(''); + } + var commands = _match(args, false); + if (commands.isEmpty) { + // No matching commands. + return new Future.value([]); + } + int matchLen = commands[0]._depth; + if (matchLen < args.length) { + // We were able to find a command which matches a prefix of the + // args, but not the full list. + if (commands.length == 1) { + // The matching command is unique. Attempt to provide local + // argument completion from the command. + return commands[0]._buildCompletions(args, true); + } else { + // An ambiguous prefix match leaves us nowhere. The user is + // typing a bunch of stuff that we don't know how to complete. + return new Future.value([]); + } + } + + // We have found a set of commands which match all of the args. + // Return the completion strings. + var prefix = _concatArgs(args, args.length - 1); + var completions = commands + .map((command) => '${prefix}${command.name} ') + .toList(); + if (matchLen == args.length) { + // If we are showing all possibilities, also include local + // completions for the parent command. + return commands[0]._parent!._buildCompletions(args, false).then(( + localCompletions, + ) { + completions.addAll(localCompletions); + return completions; + }); + } + return new Future.value(completions); + } + + // Runs a command. + Future runCommand(String line) { + _historyAdvance(line); + var args = _splitLine(line); + var commands = _match(args, true); + if (commands.isEmpty) { + return new Future.error(new NoSuchCommandException(line)); + } else if (commands.length == 1) { + return commands[0].run(args.sublist(commands[0]._depth)); + } else { + return new Future.error(new AmbiguousCommandException(line, commands)); + } + } + + // Find all matching commands. Useful for implementing help systems. + List matchCommand(List args, bool preferExact) { + if (args.isEmpty) { + // Adding an empty string to the end causes us to match all + // subcommands of the last command. + args.add(''); + } + return _match(args, preferExact); + } + + // Command line history always contains one slot to hold the current + // line, so we start off with one entry. + List history; + int historyPos; + + String historyPrev(String line) { + if (historyPos == 0) { + return line; + } + history[historyPos] = line; + historyPos--; + return history[historyPos]; + } + + String historyNext(String line) { + if (historyPos == history.length - 1) { + return line; + } + history[historyPos] = line; + historyPos++; + return history[historyPos]; + } + + void _historyAdvance(String line) { + // Replace the last history line. + historyPos = history.length - 1; + history[historyPos] = line; + + // Create an empty spot for the next line. + history.add(''); + historyPos++; + } + + Future run(List args) { + throw 'should-not-execute-the-root-command'; + } + + String toString() => 'RootCommand'; +} + +// A node in the command tree. +abstract class Command extends _CommandBase { + Command(this.name, List children) : super(children); + + final String name; + String? alias; + + String get fullName { + if (_parent is RootCommand) { + return name; + } else { + Command parent = _parent as Command; + return '${parent.fullName} $name'; + } + } + + String toString() => 'Command(${name})'; +} + +abstract class CommandException implements Exception {} + +class AmbiguousCommandException extends CommandException { + AmbiguousCommandException(this.command, this.matches); + + final String command; + final List matches; + + @override + String toString() { + List matchNames = matches + .map((Command command) => '${command.fullName}') + .toList(); + return "Command '$command' is ambiguous: $matchNames"; + } +} + +class NoSuchCommandException extends CommandException { + NoSuchCommandException(this.command); + + final String command; + + @override + String toString() => "No such command: '$command'"; +} diff --git a/pkg/observatory/lib/src/debugger/debugger.dart b/pkg/observatory/lib/src/debugger/debugger.dart new file mode 100644 index 00000000000..f243e021112 --- /dev/null +++ b/pkg/observatory/lib/src/debugger/debugger.dart @@ -0,0 +1,14 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of debugger; + +// TODO(turnidge): Move more of ObservatoryDebugger to this class. +abstract class Debugger { + VM get vm; + Isolate get isolate; + M.ObjectRepository? objects; + ServiceMap? get stack; + int? get currentFrame; +} diff --git a/pkg/observatory/lib/src/debugger/debugger_location.dart b/pkg/observatory/lib/src/debugger/debugger_location.dart new file mode 100644 index 00000000000..7c7da887a67 --- /dev/null +++ b/pkg/observatory/lib/src/debugger/debugger_location.dart @@ -0,0 +1,494 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of debugger; + +class DebuggerLocation { + DebuggerLocation.file(this.script, this.line, this.col); + DebuggerLocation.func(this.function); + DebuggerLocation.error(this.errorMessage); + + static RegExp sourceLocMatcher = new RegExp(r'^([^\d:][^:]+:)?(\d+)(:\d+)?'); + static RegExp packageLocMatcher = new RegExp( + r'^package:([^\d:][^:]+:)?(\d+)(:\d+)?', + ); + static RegExp functionMatcher = new RegExp(r'^([^.]+)([.][^.]+)?'); + + /// Parses a source location description. + /// + /// Formats: + /// '' - current position + /// 13 - line 13, current script + /// 13:20 - line 13, col 20, current script + /// script.dart:13 - line 13, script.dart + /// script.dart:13:20 - line 13, col 20, script.dart + /// package:a/b.dart:13 - line 13, "b.dart" in package "a". + /// package:a/b.dart:13:20 - line 13, col 20, "b.dart" in package "a". + /// main - function + /// FormatException - constructor + /// _SHA1._updateHash - method + static Future parse(Debugger debugger, String locDesc) { + if (locDesc == '') { + // Special case: '' means return current location. + return _currentLocation(debugger); + } + + // Parse the location description. + var match = sourceLocMatcher.firstMatch(locDesc); + if (match != null) { + return _parseScriptLine(debugger, match); + } + match = packageLocMatcher.firstMatch(locDesc); + if (match != null) { + return _parseScriptLine(debugger, match, package: true); + } + match = functionMatcher.firstMatch(locDesc); + if (match != null) { + return _parseFunction(debugger, match); + } + return new Future.value( + new DebuggerLocation.error("Invalid source location '${locDesc}'"), + ); + } + + static Future _currentFrame(Debugger debugger) async { + ServiceMap? stack = debugger.stack; + if (stack == null || stack['frames'].length == 0) { + return null; + } + return stack['frames'][debugger.currentFrame] as Frame?; + } + + static Future _currentLocation(Debugger debugger) async { + var frame = await _currentFrame(debugger); + if (frame == null) { + return new DebuggerLocation.error( + 'A script must be provided when the stack is empty', + ); + } + Script script = frame.location!.script; + await script.load(); + var line = script.tokenToLine(frame.location!.tokenPos); + var col = script.tokenToCol(frame.location!.tokenPos); + return new DebuggerLocation.file(script, line, col); + } + + static Future _parseScriptLine( + Debugger debugger, + Match match, { + bool package = false, + }) async { + var scriptName = match.group(1); + if (package) { + scriptName = "package:$scriptName"; + } + if (scriptName != null) { + scriptName = scriptName.substring(0, scriptName.length - 1); + } + var lineStr = match.group(2); + assert(lineStr != null); + var colStr = match.group(3); + if (colStr != null) { + colStr = colStr.substring(1); + } + var line = int.tryParse(lineStr!) ?? -1; + var col = (colStr != null ? int.tryParse(colStr) ?? -1 : null); + if (line == -1) { + return new Future.value( + new DebuggerLocation.error("Line '${lineStr}' must be an integer"), + ); + } + if (col == -1) { + return new Future.value( + new DebuggerLocation.error("Column '${colStr}' must be an integer"), + ); + } + + if (scriptName != null) { + // Resolve the script. + Set + + +'''; + +main(List args) async { + if (args.length < 1) { + print('Usage: heap_snapshot.dart '); + exitCode = 1; + return; + } + + var uri = Uri.parse(args[0]); + if (uri.isScheme('http')) { + uri = uri.replace(scheme: 'ws'); + } else if (uri.isScheme('https')) { + uri = uri.replace(scheme: 'wss'); + } + if (!uri.path.endsWith('/ws')) { + uri = uri.resolve('ws'); + } + + final snapshot = await load(uri.toString()); + + final dir = await Directory.systemTemp.createTemp('heap-snapshot'); + final path = dir.path + '/merged-dominator.html'; + final file = await File(path).create(); + final tree = makeData(snapshot.mergedRoot); + await file.writeAsString(html.replaceAll('__DATA__', tree)); + print('Wrote file://' + path); +} diff --git a/pkg/observatory/update_sources.py b/pkg/observatory/update_sources.py new file mode 100755 index 00000000000..a9eaef73131 --- /dev/null +++ b/pkg/observatory/update_sources.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +# for details. All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +# Updates the list of Observatory source files. + +import os +import sys +from datetime import date + + +def getDir(rootdir, target): + sources = [] + for root, subdirs, files in os.walk(rootdir): + subdirs.sort() + files.sort() + for f in files: + sources.append(root + '/' + f) + return sources + + +HEADER = """# Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +# for details. All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +# DO NOT EDIT. This file is generated by update_sources.py in this directory. + +# This file contains all dart, css, and html sources for Observatory. +""" + + +def main(): + with open('observatory_sources.gni', 'w') as target: + target.write(HEADER) + target.write('observatory_sources = [\n') + sources = [] + for rootdir in ['lib', 'web']: + sources.extend(getDir(rootdir, target)) + sources.sort() + for s in sources: + if (s[-9:] != 'README.md'): + target.write(' "' + s + '",\n') + target.write(']\n') + + +if __name__ == "__main__": + main() diff --git a/pkg/observatory/web/favicon.ico b/pkg/observatory/web/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c8613958ca5bf40007df498991b18cb8c691d981 GIT binary patch literal 1589 zcmV-52Fm$~P)f)&|8< zTMxwOZW7cgs2pO2R)!6=GiWKDo_JyvkCJHN?M}wT88kye7D%%1^X2{c-|X{yCmWbq z9*?7mpF$r7;H?!lXng@9HKKa$cL2(Q#F(0C0QC`x7i*#cZpuVYwKRarTbQlw2QaId zhjGxtV`()2|BnQr_5*ghL+vIPJkumZuY^EcmDl1;YE zNN?|gItl6$mb@0r0;m5t4sT9c1YR zumu1i*bLzu0@SjcS$h=XtwjR)301$$W0D=a}(p26D;v5|SLa-8m|0trKRCv9` z)&cec03lciAaIXt9QA1DU1tS=5X=DZYa)utO`hA>KEPf9AcQsm0>Y#-spO;hk znoku7fLjnu0Q^IVMsoj%-w^<}AiVf@fDG9%>dua92!I<9UI5^?gIGt#_8Jjf$^0tN z(zyu0xgnVSU%>c+)Z1h)eoJteJWRERRv`fAgkTncF}^vgGLHxIZv>r@HK@e&P6Xgw z5ZVP`%$plie2y*RUkMV(vQ+{)9RWB81d9O}Ggi9dvT{4WBrs1FMCs@_1mM^ZtOj6= zcgk|f(M$Yefy1S>R5|@V0&q+S9R^^`C`t0z{1*PMfE?*s>N9%0SpbTPzjTke3&36> z*bKm!5gR_P%Maye2D~C2OGVKGO#q^6iuJz%M3+^5qxV1n_6C790LHwrd+DwtX?#ME zC8?suR5vFMIO}$uIVk?l59joAuy}iez&Ze9yv`;?=bh&#yhnYRQaP`_JSA5-qgMy$=>{FAzE%fH9M&=HzRW2h0O|f41fv3V)_IF zp_jPH0O1@K&oKZd2utac3CI~C(Zy5+Z z#SaY-GzfsBiBF|ZGZ0*!L7)%-S0PC0GYo_t;ztGuN(8`F2x)X71A)gt7{)-zLjYWb nu!b%I;Dh+F0m4B9U|s$PGdu)MQ!d^R00000NkvXXu0mjfkQBR3 literal 0 HcmV?d00001 diff --git a/pkg/observatory/web/index.html b/pkg/observatory/web/index.html new file mode 100644 index 00000000000..161e8664981 --- /dev/null +++ b/pkg/observatory/web/index.html @@ -0,0 +1,11 @@ + + + + + Dart VM Observatory + + + + + + diff --git a/pkg/observatory/web/main.dart b/pkg/observatory/web/main.dart new file mode 100644 index 00000000000..f719b0714fd --- /dev/null +++ b/pkg/observatory/web/main.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:web/web.dart'; +import 'package:logging/logging.dart'; +import '../lib/elements.dart'; +import 'package:stack_trace/stack_trace.dart'; + +main() async { + Chain.capture(() async { + Logger.root.level = Level.INFO; + Logger.root.onRecord.listen((LogRecord rec) { + print('${rec.level.name}: ${rec.time}: ${rec.message}'); + }); + Logger.root.info('Starting Observatory'); + document.body!.append(ObservatoryApplicationElement.created().element); + }); +} diff --git a/pkg/observatory/web/third_party/README.md b/pkg/observatory/web/third_party/README.md new file mode 100644 index 00000000000..ba945747dd5 --- /dev/null +++ b/pkg/observatory/web/third_party/README.md @@ -0,0 +1,4 @@ +This directory contains a vulcanized version of trace-viewer: + +https://github.com/catapult-project/catapult/wiki/Embedding-Trace-Viewer + diff --git a/pkg/observatory/web/third_party/trace_viewer_full.html b/pkg/observatory/web/third_party/trace_viewer_full.html new file mode 100644 index 00000000000..daa87d0692e --- /dev/null +++ b/pkg/observatory/web/third_party/trace_viewer_full.html @@ -0,0 +1,10249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pkg/observatory/web/third_party/webcomponents.min.js b/pkg/observatory/web/third_party/webcomponents.min.js new file mode 100644 index 00000000000..ad8196bc301 --- /dev/null +++ b/pkg/observatory/web/third_party/webcomponents.min.js @@ -0,0 +1,14 @@ +/** + * @license + * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt + */ +// @version 0.7.24 +!function(){window.WebComponents=window.WebComponents||{flags:{}};var e="webcomponents.js",t=document.querySelector('script[src*="'+e+'"]'),n={};if(!n.noOpts){if(location.search.slice(1).split("&").forEach(function(e){var t,r=e.split("=");r[0]&&(t=r[0].match(/wc-(.+)/))&&(n[t[1]]=r[1]||!0)}),t)for(var r,o=0;r=t.attributes[o];o++)"src"!==r.name&&(n[r.name]=r.value||!0);if(n.log&&n.log.split){var i=n.log.split(",");n.log={},i.forEach(function(e){n.log[e]=!0})}else n.log={}}n.shadow=n.shadow||n.shadowdom||n.polyfill,"native"===n.shadow?n.shadow=!1:n.shadow=n.shadow||!HTMLElement.prototype.createShadowRoot,n.register&&(window.CustomElements=window.CustomElements||{flags:{}},window.CustomElements.flags.register=n.register),WebComponents.flags=n}(),WebComponents.flags.shadow&&("undefined"==typeof WeakMap&&!function(){var e=Object.defineProperty,t=Date.now()%1e9,n=function(){this.name="__st"+(1e9*Math.random()>>>0)+(t++ +"__")};n.prototype={set:function(t,n){var r=t[this.name];return r&&r[0]===t?r[1]=n:e(t,this.name,{value:[t,n],writable:!0}),this},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0},"delete":function(e){var t=e[this.name];return!(!t||t[0]!==e)&&(t[0]=t[1]=void 0,!0)},has:function(e){var t=e[this.name];return!!t&&t[0]===e}},window.WeakMap=n}(),window.ShadowDOMPolyfill={},function(e){"use strict";function t(){if("undefined"!=typeof chrome&&chrome.app&&chrome.app.runtime)return!1;if(navigator.getDeviceStorage)return!1;try{var e=new Function("return true;");return e()}catch(t){return!1}}function n(e){if(!e)throw new Error("Assertion failed")}function r(e,t){for(var n=W(t),r=0;r0||n>0;)if(0!=t)if(0!=n){var l,u=e[t-1][n-1],d=e[t-1][n],p=e[t][n-1];l=d0){for(var u=0;u0&&r.length>0;){var i=n.pop(),a=r.pop();if(i!==a)break;o=i}return o}function u(e,t,n){t instanceof G.Window&&(t=t.document);var o,i=A(t),a=A(n),s=r(n,e),o=l(i,a);o||(o=a.root);for(var c=o;c;c=c.parent)for(var u=0;u0;i--)if(!g(t[i],e,o,t,r))return!1;return!0}function w(e,t,n,r){var o=ie,i=t[0]||n;return g(i,e,o,t,r)}function v(e,t,n,r){for(var o=ae,i=1;i0&&g(n,e,o,t,r)}function g(e,t,n,r,o){var i=z.get(e);if(!i)return!0;var a=o||s(r,e);if(a===e){if(n===oe)return!0;n===ae&&(n=ie)}else if(n===ae&&!t.bubbles)return!0;if("relatedTarget"in t){var c=B(t),l=c.relatedTarget;if(l){if(l instanceof Object&&l.addEventListener){var d=V(l),p=u(t,e,d);if(p===a)return!0}else p=null;Z.set(t,p)}}J.set(t,n);var h=t.type,f=!1;X.set(t,a),Y.set(t,e),i.depth++;for(var m=0,w=i.length;m=0;a--)e.removeChild(i[a]),i[a].parentNode_=t;U=!1;for(var a=0;a>>/g," ")}function r(e){return String(e).replace(/:host\(([^\s]+)\)/g,"$1").replace(/([^\s]):host/g,"$1").replace(":host","*").replace(/\^|\/shadow\/|\/shadow-deep\/|::shadow|\/deep\/|::content|>>>/g," ")}function o(e,t){for(var n,r=e.firstElementChild;r;){if(r.matches(t))return r;if(n=o(r,t))return n;r=r.nextElementSibling}return null}function i(e,t){return e.matches(t)}function a(e,t,n){var r=e.localName;return r===t||r===n&&e.namespaceURI===j}function s(){return!0}function c(e,t,n){return e.localName===n}function l(e,t){return e.namespaceURI===t}function u(e,t,n){return e.namespaceURI===t&&e.localName===n}function d(e,t,n,r,o,i){for(var a=e.firstElementChild;a;)r(a,o,i)&&(n[t++]=a),t=d(a,t,n,r,o,i),a=a.nextElementSibling;return t}function p(n,r,o,i,a){var s,c=g(this),l=v(this).root;if(l instanceof e.wrappers.ShadowRoot)return d(this,r,o,n,i,null);if(c instanceof N)s=S.call(c,i);else{if(!(c instanceof C))return d(this,r,o,n,i,null);s=_.call(c,i)}return t(s,r,o,a)}function h(n,r,o,i,a){var s,c=g(this),l=v(this).root;if(l instanceof e.wrappers.ShadowRoot)return d(this,r,o,n,i,a);if(c instanceof N)s=M.call(c,i,a);else{if(!(c instanceof C))return d(this,r,o,n,i,a);s=T.call(c,i,a)}return t(s,r,o,!1)}function f(n,r,o,i,a){var s,c=g(this),l=v(this).root;if(l instanceof e.wrappers.ShadowRoot)return d(this,r,o,n,i,a);if(c instanceof N)s=L.call(c,i,a);else{if(!(c instanceof C))return d(this,r,o,n,i,a);s=O.call(c,i,a)}return t(s,r,o,!1)}var m=e.wrappers.HTMLCollection,w=e.wrappers.NodeList,v=e.getTreeScope,g=e.unsafeUnwrap,b=e.wrap,y=document.querySelector,E=document.documentElement.querySelector,_=document.querySelectorAll,S=document.documentElement.querySelectorAll,T=document.getElementsByTagName,M=document.documentElement.getElementsByTagName,O=document.getElementsByTagNameNS,L=document.documentElement.getElementsByTagNameNS,N=window.Element,C=window.HTMLDocument||window.Document,j="http://www.w3.org/1999/xhtml",D={ +querySelector:function(t){var r=n(t),i=r!==t;t=r;var a,s=g(this),c=v(this).root;if(c instanceof e.wrappers.ShadowRoot)return o(this,t);if(s instanceof N)a=b(E.call(s,t));else{if(!(s instanceof C))return o(this,t);a=b(y.call(s,t))}return a&&!i&&(c=v(a).root)&&c instanceof e.wrappers.ShadowRoot?o(this,t):a},querySelectorAll:function(e){var t=n(e),r=t!==e;e=t;var o=new w;return o.length=p.call(this,i,0,o,e,r),o}},H={matches:function(t){return t=r(t),e.originalMatches.call(g(this),t)}},x={getElementsByTagName:function(e){var t=new m,n="*"===e?s:a;return t.length=h.call(this,n,0,t,e,e.toLowerCase()),t},getElementsByClassName:function(e){return this.querySelectorAll("."+e)},getElementsByTagNameNS:function(e,t){var n=new m,r=null;return r="*"===e?"*"===t?s:c:"*"===t?l:u,n.length=f.call(this,r,0,n,e||null,t),n}};e.GetElementsByInterface=x,e.SelectorsInterface=D,e.MatchesInterface=H}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){for(;e&&e.nodeType!==Node.ELEMENT_NODE;)e=e.nextSibling;return e}function n(e){for(;e&&e.nodeType!==Node.ELEMENT_NODE;)e=e.previousSibling;return e}var r=e.wrappers.NodeList,o={get firstElementChild(){return t(this.firstChild)},get lastElementChild(){return n(this.lastChild)},get childElementCount(){for(var e=0,t=this.firstElementChild;t;t=t.nextElementSibling)e++;return e},get children(){for(var e=new r,t=0,n=this.firstElementChild;n;n=n.nextElementSibling)e[t++]=n;return e.length=t,e},remove:function(){var e=this.parentNode;e&&e.removeChild(this)}},i={get nextElementSibling(){return t(this.nextSibling)},get previousElementSibling(){return n(this.previousSibling)}},a={getElementById:function(e){return/[ \t\n\r\f]/.test(e)?null:this.querySelector('[id="'+e+'"]')}};e.ChildNodeInterface=i,e.NonElementParentNodeInterface=a,e.ParentNodeInterface=o}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}var n=e.ChildNodeInterface,r=e.wrappers.Node,o=e.enqueueMutation,i=e.mixin,a=e.registerWrapper,s=e.unsafeUnwrap,c=window.CharacterData;t.prototype=Object.create(r.prototype),i(t.prototype,{get nodeValue(){return this.data},set nodeValue(e){this.data=e},get textContent(){return this.data},set textContent(e){this.data=e},get data(){return s(this).data},set data(e){var t=s(this).data;o(this,"characterData",{oldValue:t}),s(this).data=e}}),i(t.prototype,n),a(c,t,document.createTextNode("")),e.wrappers.CharacterData=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return e>>>0}function n(e){r.call(this,e)}var r=e.wrappers.CharacterData,o=(e.enqueueMutation,e.mixin),i=e.registerWrapper,a=window.Text;n.prototype=Object.create(r.prototype),o(n.prototype,{splitText:function(e){e=t(e);var n=this.data;if(e>n.length)throw new Error("IndexSizeError");var r=n.slice(0,e),o=n.slice(e);this.data=r;var i=this.ownerDocument.createTextNode(o);return this.parentNode&&this.parentNode.insertBefore(i,this.nextSibling),i}}),i(a,n,document.createTextNode("")),e.wrappers.Text=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return i(e).getAttribute("class")}function n(e,t){a(e,"attributes",{name:"class",namespace:null,oldValue:t})}function r(t){e.invalidateRendererBasedOnAttribute(t,"class")}function o(e,o,i){var a=e.ownerElement_;if(null==a)return o.apply(e,i);var s=t(a),c=o.apply(e,i);return t(a)!==s&&(n(a,s),r(a)),c}if(!window.DOMTokenList)return void console.warn("Missing DOMTokenList prototype, please include a compatible classList polyfill such as http://goo.gl/uTcepH.");var i=e.unsafeUnwrap,a=e.enqueueMutation,s=DOMTokenList.prototype.add;DOMTokenList.prototype.add=function(){o(this,s,arguments)};var c=DOMTokenList.prototype.remove;DOMTokenList.prototype.remove=function(){o(this,c,arguments)};var l=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(){return o(this,l,arguments)}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(t,n){var r=t.parentNode;if(r&&r.shadowRoot){var o=e.getRendererForHost(r);o.dependsOnAttribute(n)&&o.invalidate()}}function n(e,t,n){u(e,"attributes",{name:t,namespace:null,oldValue:n})}function r(e){a.call(this,e)}var o=e.ChildNodeInterface,i=e.GetElementsByInterface,a=e.wrappers.Node,s=e.ParentNodeInterface,c=e.SelectorsInterface,l=e.MatchesInterface,u=(e.addWrapNodeListMethod,e.enqueueMutation),d=e.mixin,p=(e.oneOf,e.registerWrapper),h=e.unsafeUnwrap,f=e.wrappers,m=window.Element,w=["matches","mozMatchesSelector","msMatchesSelector","webkitMatchesSelector"].filter(function(e){return m.prototype[e]}),v=w[0],g=m.prototype[v],b=new WeakMap;r.prototype=Object.create(a.prototype),d(r.prototype,{createShadowRoot:function(){var t=new f.ShadowRoot(this);h(this).polymerShadowRoot_=t;var n=e.getRendererForHost(this);return n.invalidate(),t},get shadowRoot(){return h(this).polymerShadowRoot_||null},setAttribute:function(e,r){var o=h(this).getAttribute(e);h(this).setAttribute(e,r),n(this,e,o),t(this,e)},removeAttribute:function(e){var r=h(this).getAttribute(e);h(this).removeAttribute(e),n(this,e,r),t(this,e)},get classList(){var e=b.get(this);if(!e){if(e=h(this).classList,!e)return;e.ownerElement_=this,b.set(this,e)}return e},get className(){return h(this).className},set className(e){this.setAttribute("class",e)},get id(){return h(this).id},set id(e){this.setAttribute("id",e)}}),w.forEach(function(e){"matches"!==e&&(r.prototype[e]=function(e){return this.matches(e)})}),m.prototype.webkitCreateShadowRoot&&(r.prototype.webkitCreateShadowRoot=r.prototype.createShadowRoot),d(r.prototype,o),d(r.prototype,i),d(r.prototype,s),d(r.prototype,c),d(r.prototype,l),p(m,r,document.createElementNS(null,"x")),e.invalidateRendererBasedOnAttribute=t,e.matchesNames=w,e.originalMatches=g,e.wrappers.Element=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){switch(e){case"&":return"&";case"<":return"<";case">":return">";case'"':return""";case" ":return" "}}function n(e){return e.replace(L,t)}function r(e){return e.replace(N,t)}function o(e){for(var t={},n=0;n"):c+">"+s(e)+"";case Node.TEXT_NODE:var d=e.data;return t&&j[t.localName]?d:r(d);case Node.COMMENT_NODE:return"";default:throw console.error(e),new Error("not implemented")}}function s(e){e instanceof O.HTMLTemplateElement&&(e=e.content);for(var t="",n=e.firstChild;n;n=n.nextSibling)t+=a(n,e);return t}function c(e,t,n){var r=n||"div";e.textContent="";var o=T(e.ownerDocument.createElement(r));o.innerHTML=t;for(var i;i=o.firstChild;)e.appendChild(M(i))}function l(e){m.call(this,e)}function u(e,t){var n=T(e.cloneNode(!1));n.innerHTML=t;for(var r,o=T(document.createDocumentFragment());r=n.firstChild;)o.appendChild(r);return M(o)}function d(t){return function(){return e.renderAllPending(),S(this)[t]}}function p(e){w(l,e,d(e))}function h(t){Object.defineProperty(l.prototype,t,{get:d(t),set:function(n){e.renderAllPending(),S(this)[t]=n},configurable:!0,enumerable:!0})}function f(t){Object.defineProperty(l.prototype,t,{value:function(){return e.renderAllPending(),S(this)[t].apply(S(this),arguments)},configurable:!0,enumerable:!0})}var m=e.wrappers.Element,w=e.defineGetter,v=e.enqueueMutation,g=e.mixin,b=e.nodesWereAdded,y=e.nodesWereRemoved,E=e.registerWrapper,_=e.snapshotNodeList,S=e.unsafeUnwrap,T=e.unwrap,M=e.wrap,O=e.wrappers,L=/[&\u00A0"]/g,N=/[&\u00A0<>]/g,C=o(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"]),j=o(["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"]),D="http://www.w3.org/1999/xhtml",H=/MSIE/.test(navigator.userAgent),x=window.HTMLElement,R=window.HTMLTemplateElement;l.prototype=Object.create(m.prototype),g(l.prototype,{get innerHTML(){return s(this)},set innerHTML(e){if(H&&j[this.localName])return void(this.textContent=e);var t=_(this.childNodes);this.invalidateShadowRenderer()?this instanceof O.HTMLTemplateElement?c(this.content,e):c(this,e,this.tagName):!R&&this instanceof O.HTMLTemplateElement?c(this.content,e):S(this).innerHTML=e;var n=_(this.childNodes);v(this,"childList",{addedNodes:n,removedNodes:t}),y(t),b(n,this)},get outerHTML(){return a(this,this.parentNode)},set outerHTML(e){var t=this.parentNode;if(t){t.invalidateShadowRenderer();var n=u(t,e);t.replaceChild(n,this)}},insertAdjacentHTML:function(e,t){var n,r;switch(String(e).toLowerCase()){case"beforebegin":n=this.parentNode,r=this;break;case"afterend":n=this.parentNode,r=this.nextSibling;break;case"afterbegin":n=this,r=this.firstChild;break;case"beforeend":n=this,r=null;break;default:return}var o=u(n,t);n.insertBefore(o,r)},get hidden(){return this.hasAttribute("hidden")},set hidden(e){e?this.setAttribute("hidden",""):this.removeAttribute("hidden")}}),["clientHeight","clientLeft","clientTop","clientWidth","offsetHeight","offsetLeft","offsetTop","offsetWidth","scrollHeight","scrollWidth"].forEach(p),["scrollLeft","scrollTop"].forEach(h),["focus","getBoundingClientRect","getClientRects","scrollIntoView"].forEach(f),E(x,l,document.createElement("b")),e.wrappers.HTMLElement=l,e.getInnerHTML=s,e.setInnerHTML=c}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.unsafeUnwrap,a=e.wrap,s=window.HTMLCanvasElement;t.prototype=Object.create(n.prototype),r(t.prototype,{getContext:function(){var e=i(this).getContext.apply(i(this),arguments);return e&&a(e)}}),o(s,t,document.createElement("canvas")),e.wrappers.HTMLCanvasElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=window.HTMLContentElement;t.prototype=Object.create(n.prototype),r(t.prototype,{constructor:t,get select(){return this.getAttribute("select")},set select(e){this.setAttribute("select",e)},setAttribute:function(e,t){n.prototype.setAttribute.call(this,e,t),"select"===String(e).toLowerCase()&&this.invalidateShadowRenderer(!0)}}),i&&o(i,t),e.wrappers.HTMLContentElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.wrapHTMLCollection,a=e.unwrap,s=window.HTMLFormElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get elements(){return i(a(this).elements)}}),o(s,t,document.createElement("form")),e.wrappers.HTMLFormElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}function n(e,t){if(!(this instanceof n))throw new TypeError("DOM object constructor cannot be called as a function.");var o=i(document.createElement("img"));r.call(this,o),a(o,this),void 0!==e&&(o.width=e),void 0!==t&&(o.height=t)}var r=e.wrappers.HTMLElement,o=e.registerWrapper,i=e.unwrap,a=e.rewrap,s=window.HTMLImageElement;t.prototype=Object.create(r.prototype),o(s,t,document.createElement("img")),n.prototype=t.prototype,e.wrappers.HTMLImageElement=t,e.wrappers.Image=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=(e.mixin,e.wrappers.NodeList,e.registerWrapper),o=window.HTMLShadowElement;t.prototype=Object.create(n.prototype),t.prototype.constructor=t,o&&r(o,t),e.wrappers.HTMLShadowElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){if(!e.defaultView)return e;var t=d.get(e);if(!t){for(t=e.implementation.createHTMLDocument("");t.lastChild;)t.removeChild(t.lastChild);d.set(e,t)}return t}function n(e){for(var n,r=t(e.ownerDocument),o=c(r.createDocumentFragment());n=e.firstChild;)o.appendChild(n);return o}function r(e){if(o.call(this,e),!p){var t=n(e);u.set(this,l(t))}}var o=e.wrappers.HTMLElement,i=e.mixin,a=e.registerWrapper,s=e.unsafeUnwrap,c=e.unwrap,l=e.wrap,u=new WeakMap,d=new WeakMap,p=window.HTMLTemplateElement;r.prototype=Object.create(o.prototype),i(r.prototype,{constructor:r,get content(){return p?l(s(this).content):u.get(this)}}),p&&a(p,r),e.wrappers.HTMLTemplateElement=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.registerWrapper,o=window.HTMLMediaElement;o&&(t.prototype=Object.create(n.prototype),r(o,t,document.createElement("audio")),e.wrappers.HTMLMediaElement=t)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}function n(e){if(!(this instanceof n))throw new TypeError("DOM object constructor cannot be called as a function.");var t=i(document.createElement("audio"));r.call(this,t),a(t,this),t.setAttribute("preload","auto"),void 0!==e&&t.setAttribute("src",e)}var r=e.wrappers.HTMLMediaElement,o=e.registerWrapper,i=e.unwrap,a=e.rewrap,s=window.HTMLAudioElement;s&&(t.prototype=Object.create(r.prototype),o(s,t,document.createElement("audio")),n.prototype=t.prototype,e.wrappers.HTMLAudioElement=t,e.wrappers.Audio=n)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return e.replace(/\s+/g," ").trim()}function n(e){o.call(this,e)}function r(e,t,n,i){if(!(this instanceof r))throw new TypeError("DOM object constructor cannot be called as a function.");var a=c(document.createElement("option"));o.call(this,a),s(a,this),void 0!==e&&(a.text=e),void 0!==t&&a.setAttribute("value",t),n===!0&&a.setAttribute("selected",""),a.selected=i===!0}var o=e.wrappers.HTMLElement,i=e.mixin,a=e.registerWrapper,s=e.rewrap,c=e.unwrap,l=e.wrap,u=window.HTMLOptionElement;n.prototype=Object.create(o.prototype),i(n.prototype,{get text(){return t(this.textContent)},set text(e){this.textContent=t(String(e))},get form(){return l(c(this).form)}}),a(u,n,document.createElement("option")),r.prototype=n.prototype,e.wrappers.HTMLOptionElement=n,e.wrappers.Option=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.unwrap,a=e.wrap,s=window.HTMLSelectElement;t.prototype=Object.create(n.prototype),r(t.prototype,{add:function(e,t){"object"==typeof t&&(t=i(t)),i(this).add(i(e),t)},remove:function(e){return void 0===e?void n.prototype.remove.call(this):("object"==typeof e&&(e=i(e)),void i(this).remove(e))},get form(){return a(i(this).form)}}),o(s,t,document.createElement("select")),e.wrappers.HTMLSelectElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.unwrap,a=e.wrap,s=e.wrapHTMLCollection,c=window.HTMLTableElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get caption(){return a(i(this).caption)},createCaption:function(){return a(i(this).createCaption())},get tHead(){return a(i(this).tHead)},createTHead:function(){return a(i(this).createTHead())},createTFoot:function(){return a(i(this).createTFoot())},get tFoot(){return a(i(this).tFoot)},get tBodies(){return s(i(this).tBodies)},createTBody:function(){return a(i(this).createTBody())},get rows(){return s(i(this).rows)},insertRow:function(e){return a(i(this).insertRow(e))}}),o(c,t,document.createElement("table")),e.wrappers.HTMLTableElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.wrapHTMLCollection,a=e.unwrap,s=e.wrap,c=window.HTMLTableSectionElement;t.prototype=Object.create(n.prototype),r(t.prototype,{constructor:t,get rows(){return i(a(this).rows)},insertRow:function(e){return s(a(this).insertRow(e))}}),o(c,t,document.createElement("thead")),e.wrappers.HTMLTableSectionElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.wrapHTMLCollection,a=e.unwrap,s=e.wrap,c=window.HTMLTableRowElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get cells(){return i(a(this).cells)},insertCell:function(e){return s(a(this).insertCell(e))}}),o(c,t,document.createElement("tr")),e.wrappers.HTMLTableRowElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){switch(e.localName){case"content":return new n(e);case"shadow":return new o(e);case"template":return new i(e)}r.call(this,e)}var n=e.wrappers.HTMLContentElement,r=e.wrappers.HTMLElement,o=e.wrappers.HTMLShadowElement,i=e.wrappers.HTMLTemplateElement,a=(e.mixin,e.registerWrapper),s=window.HTMLUnknownElement;t.prototype=Object.create(r.prototype),a(s,t),e.wrappers.HTMLUnknownElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.Element,r=e.wrappers.HTMLElement,o=e.registerWrapper,i=(e.defineWrapGetter,e.unsafeUnwrap),a=e.wrap,s=e.mixin,c="http://www.w3.org/2000/svg",l=window.SVGElement,u=document.createElementNS(c,"title");if(!("classList"in u)){var d=Object.getOwnPropertyDescriptor(n.prototype,"classList");Object.defineProperty(r.prototype,"classList",d),delete n.prototype.classList}t.prototype=Object.create(n.prototype),s(t.prototype,{get ownerSVGElement(){return a(i(this).ownerSVGElement)}}),o(l,t,document.createElementNS(c,"title")),e.wrappers.SVGElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){p.call(this,e)}var n=e.mixin,r=e.registerWrapper,o=e.unwrap,i=e.wrap,a=window.SVGUseElement,s="http://www.w3.org/2000/svg",c=i(document.createElementNS(s,"g")),l=document.createElementNS(s,"use"),u=c.constructor,d=Object.getPrototypeOf(u.prototype),p=d.constructor;t.prototype=Object.create(d),"instanceRoot"in l&&n(t.prototype,{get instanceRoot(){return i(o(this).instanceRoot)},get animatedInstanceRoot(){return i(o(this).animatedInstanceRoot)}}),r(a,t,l),e.wrappers.SVGUseElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.EventTarget,r=e.mixin,o=e.registerWrapper,i=e.unsafeUnwrap,a=e.wrap,s=window.SVGElementInstance;s&&(t.prototype=Object.create(n.prototype),r(t.prototype,{get correspondingElement(){return a(i(this).correspondingElement)},get correspondingUseElement(){return a(i(this).correspondingUseElement)},get parentNode(){return a(i(this).parentNode)},get childNodes(){throw new Error("Not implemented")},get firstChild(){return a(i(this).firstChild)},get lastChild(){return a(i(this).lastChild)},get previousSibling(){return a(i(this).previousSibling)},get nextSibling(){return a(i(this).nextSibling)}}),o(s,t),e.wrappers.SVGElementInstance=t)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){o(e,this)}var n=e.mixin,r=e.registerWrapper,o=e.setWrapper,i=e.unsafeUnwrap,a=e.unwrap,s=e.unwrapIfNeeded,c=e.wrap,l=window.CanvasRenderingContext2D;n(t.prototype,{get canvas(){return c(i(this).canvas)},drawImage:function(){arguments[0]=s(arguments[0]),i(this).drawImage.apply(i(this),arguments)},createPattern:function(){return arguments[0]=a(arguments[0]),i(this).createPattern.apply(i(this),arguments)}}),r(l,t,document.createElement("canvas").getContext("2d")),e.wrappers.CanvasRenderingContext2D=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){i(e,this)}var n=e.addForwardingProperties,r=e.mixin,o=e.registerWrapper,i=e.setWrapper,a=e.unsafeUnwrap,s=e.unwrapIfNeeded,c=e.wrap,l=window.WebGLRenderingContext;if(l){r(t.prototype,{get canvas(){return c(a(this).canvas)},texImage2D:function(){arguments[5]=s(arguments[5]),a(this).texImage2D.apply(a(this),arguments)},texSubImage2D:function(){arguments[6]=s(arguments[6]),a(this).texSubImage2D.apply(a(this),arguments)}});var u=Object.getPrototypeOf(l.prototype);u!==Object.prototype&&n(u,t.prototype);var d=/WebKit/.test(navigator.userAgent)?{drawingBufferHeight:null,drawingBufferWidth:null}:{};o(l,t,d),e.wrappers.WebGLRenderingContext=t}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.Node,r=e.GetElementsByInterface,o=e.NonElementParentNodeInterface,i=e.ParentNodeInterface,a=e.SelectorsInterface,s=e.mixin,c=e.registerObject,l=e.registerWrapper,u=window.DocumentFragment;t.prototype=Object.create(n.prototype),s(t.prototype,i),s(t.prototype,a),s(t.prototype,r),s(t.prototype,o),l(u,t,document.createDocumentFragment()),e.wrappers.DocumentFragment=t;var d=c(document.createComment(""));e.wrappers.Comment=d}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t=d(u(e).ownerDocument.createDocumentFragment());n.call(this,t),c(t,this);var o=e.shadowRoot;f.set(this,o),this.treeScope_=new r(this,a(o||e)),h.set(this,e)}var n=e.wrappers.DocumentFragment,r=e.TreeScope,o=e.elementFromPoint,i=e.getInnerHTML,a=e.getTreeScope,s=e.mixin,c=e.rewrap,l=e.setInnerHTML,u=e.unsafeUnwrap,d=e.unwrap,p=e.wrap,h=new WeakMap,f=new WeakMap;t.prototype=Object.create(n.prototype),s(t.prototype,{constructor:t,get innerHTML(){return i(this)},set innerHTML(e){l(this,e),this.invalidateShadowRenderer()},get olderShadowRoot(){return f.get(this)||null},get host(){return h.get(this)||null},invalidateShadowRenderer:function(){return h.get(this).invalidateShadowRenderer()},elementFromPoint:function(e,t){return o(this,this.ownerDocument,e,t)},getSelection:function(){return document.getSelection()},get activeElement(){var e=d(this).ownerDocument.activeElement;if(!e||!e.nodeType)return null;for(var t=p(e);!this.contains(t);){for(;t.parentNode;)t=t.parentNode;if(!t.host)return null;t=t.host}return t}}),e.wrappers.ShadowRoot=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t=d(e).root;return t instanceof h?t.host:null}function n(t,n){if(t.shadowRoot){n=Math.min(t.childNodes.length-1,n);var r=t.childNodes[n];if(r){var o=e.getDestinationInsertionPoints(r);if(o.length>0){var i=o[0].parentNode;i.nodeType==Node.ELEMENT_NODE&&(t=i)}}}return t}function r(e){return e=u(e),t(e)||e}function o(e){a(e,this)}var i=e.registerWrapper,a=e.setWrapper,s=e.unsafeUnwrap,c=e.unwrap,l=e.unwrapIfNeeded,u=e.wrap,d=e.getTreeScope,p=window.Range,h=e.wrappers.ShadowRoot;o.prototype={get startContainer(){return r(s(this).startContainer)},get endContainer(){return r(s(this).endContainer)},get commonAncestorContainer(){return r(s(this).commonAncestorContainer)},setStart:function(e,t){e=n(e,t),s(this).setStart(l(e),t)},setEnd:function(e,t){e=n(e,t),s(this).setEnd(l(e),t)},setStartBefore:function(e){s(this).setStartBefore(l(e))},setStartAfter:function(e){s(this).setStartAfter(l(e))},setEndBefore:function(e){s(this).setEndBefore(l(e))},setEndAfter:function(e){s(this).setEndAfter(l(e))},selectNode:function(e){s(this).selectNode(l(e))},selectNodeContents:function(e){s(this).selectNodeContents(l(e))},compareBoundaryPoints:function(e,t){return s(this).compareBoundaryPoints(e,c(t))},extractContents:function(){return u(s(this).extractContents())},cloneContents:function(){return u(s(this).cloneContents())},insertNode:function(e){s(this).insertNode(l(e))},surroundContents:function(e){s(this).surroundContents(l(e))},cloneRange:function(){return u(s(this).cloneRange())},isPointInRange:function(e,t){return s(this).isPointInRange(l(e),t)},comparePoint:function(e,t){return s(this).comparePoint(l(e),t)},intersectsNode:function(e){return s(this).intersectsNode(l(e))},toString:function(){return s(this).toString()}},p.prototype.createContextualFragment&&(o.prototype.createContextualFragment=function(e){return u(s(this).createContextualFragment(e))}),i(window.Range,o,document.createRange()),e.wrappers.Range=o}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){e.previousSibling_=e.previousSibling,e.nextSibling_=e.nextSibling,e.parentNode_=e.parentNode}function n(n,o,i){var a=x(n),s=x(o),c=i?x(i):null;if(r(o),t(o),i)n.firstChild===i&&(n.firstChild_=i),i.previousSibling_=i.previousSibling;else{n.lastChild_=n.lastChild,n.lastChild===n.firstChild&&(n.firstChild_=n.firstChild);var l=R(a.lastChild);l&&(l.nextSibling_=l.nextSibling)}e.originalInsertBefore.call(a,s,c)}function r(n){var r=x(n),o=r.parentNode;if(o){var i=R(o);t(n),n.previousSibling&&(n.previousSibling.nextSibling_=n),n.nextSibling&&(n.nextSibling.previousSibling_=n),i.lastChild===n&&(i.lastChild_=n),i.firstChild===n&&(i.firstChild_=n),e.originalRemoveChild.call(o,r)}}function o(e){P.set(e,[])}function i(e){var t=P.get(e);return t||P.set(e,t=[]),t}function a(e){for(var t=[],n=0,r=e.firstChild;r;r=r.nextSibling)t[n++]=r;return t}function s(){for(var e=0;e=0;o--){var i=r[o],a=m(i);if(a){var s=i.olderShadowRoot;s&&(n=f(s));for(var c=0;c=0;u--)l=Object.create(l);["createdCallback","attachedCallback","detachedCallback","attributeChangedCallback"].forEach(function(e){var t=o[e];t&&(l[e]=function(){j(this)instanceof r||O(this),t.apply(j(this),arguments)})});var d={prototype:l};i&&(d["extends"]=i),r.prototype=o,r.prototype.constructor=r,e.constructorTable.set(l,r),e.nativePrototypeTable.set(o,l);k.call(C(this),t,d);return r},E([window.HTMLDocument||window.Document],["registerElement"])}E([window.HTMLBodyElement,window.HTMLDocument||window.Document,window.HTMLHeadElement,window.HTMLHtmlElement],["appendChild","compareDocumentPosition","contains","getElementsByClassName","getElementsByTagName","getElementsByTagNameNS","insertBefore","querySelector","querySelectorAll","removeChild","replaceChild"]),E([window.HTMLBodyElement,window.HTMLHeadElement,window.HTMLHtmlElement],_),E([window.HTMLDocument||window.Document],["adoptNode","importNode","contains","createComment","createDocumentFragment","createElement","createElementNS","createEvent","createEventNS","createRange","createTextNode","createTreeWalker","elementFromPoint","getElementById","getElementsByName","getSelection"]),S(t.prototype,l),S(t.prototype,d),S(t.prototype,f),S(t.prototype,p),S(t.prototype,{get implementation(){var e=H.get(this);return e?e:(e=new a(C(this).implementation),H.set(this,e),e)},get defaultView(){return j(C(this).defaultView)}}),T(window.Document,t,document.implementation.createHTMLDocument("")),window.HTMLDocument&&T(window.HTMLDocument,t),D([window.HTMLBodyElement,window.HTMLDocument||window.Document,window.HTMLHeadElement]);var A=document.implementation.createDocument;a.prototype.createDocument=function(){return arguments[2]=C(arguments[2]),j(A.apply(N(this),arguments))},s(a,"createDocumentType"),s(a,"createHTMLDocument"),c(a,"hasFeature"),T(window.DOMImplementation,a),E([window.DOMImplementation],["createDocument","createDocumentType","createHTMLDocument","hasFeature"]),e.adoptNodeNoRemove=r,e.wrappers.DOMImplementation=a,e.wrappers.Document=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.EventTarget,r=e.wrappers.Selection,o=e.mixin,i=e.registerWrapper,a=e.renderAllPending,s=e.unwrap,c=e.unwrapIfNeeded,l=e.wrap,u=window.Window,d=window.getComputedStyle,p=window.getDefaultComputedStyle,h=window.getSelection;t.prototype=Object.create(n.prototype),u.prototype.getComputedStyle=function(e,t){return l(this||window).getComputedStyle(c(e),t)},p&&(u.prototype.getDefaultComputedStyle=function(e,t){return l(this||window).getDefaultComputedStyle(c(e),t)}),u.prototype.getSelection=function(){return l(this||window).getSelection()},delete window.getComputedStyle,delete window.getDefaultComputedStyle,delete window.getSelection,["addEventListener","removeEventListener","dispatchEvent"].forEach(function(e){u.prototype[e]=function(){var t=l(this||window);return t[e].apply(t,arguments)},delete window[e]}),o(t.prototype,{getComputedStyle:function(e,t){return a(),d.call(s(this),c(e),t)},getSelection:function(){return a(),new r(h.call(s(this)))},get document(){return l(s(this).document)}}),p&&(t.prototype.getDefaultComputedStyle=function(e,t){return a(),p.call(s(this),c(e),t)}),i(u,t,window),e.wrappers.Window=t}(window.ShadowDOMPolyfill),function(e){"use strict";var t=e.unwrap,n=window.DataTransfer||window.Clipboard,r=n.prototype.setDragImage;r&&(n.prototype.setDragImage=function(e,n,o){r.call(this,t(e),n,o)})}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t;t=e instanceof i?e:new i(e&&o(e)),r(t,this)}var n=e.registerWrapper,r=e.setWrapper,o=e.unwrap,i=window.FormData;i&&(n(i,t,new i),e.wrappers.FormData=t)}(window.ShadowDOMPolyfill),function(e){"use strict";var t=e.unwrapIfNeeded,n=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.send=function(e){return n.call(this,t(e))}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t=n[e],r=window[t];if(r){var o=document.createElement(e),i=o.constructor;window[t]=i}}var n=(e.isWrapperFor,{a:"HTMLAnchorElement",area:"HTMLAreaElement",audio:"HTMLAudioElement",base:"HTMLBaseElement",body:"HTMLBodyElement",br:"HTMLBRElement",button:"HTMLButtonElement",canvas:"HTMLCanvasElement",caption:"HTMLTableCaptionElement",col:"HTMLTableColElement",content:"HTMLContentElement",data:"HTMLDataElement",datalist:"HTMLDataListElement",del:"HTMLModElement",dir:"HTMLDirectoryElement",div:"HTMLDivElement",dl:"HTMLDListElement",embed:"HTMLEmbedElement",fieldset:"HTMLFieldSetElement",font:"HTMLFontElement",form:"HTMLFormElement",frame:"HTMLFrameElement",frameset:"HTMLFrameSetElement",h1:"HTMLHeadingElement",head:"HTMLHeadElement",hr:"HTMLHRElement",html:"HTMLHtmlElement",iframe:"HTMLIFrameElement",img:"HTMLImageElement",input:"HTMLInputElement",keygen:"HTMLKeygenElement",label:"HTMLLabelElement",legend:"HTMLLegendElement",li:"HTMLLIElement",link:"HTMLLinkElement",map:"HTMLMapElement",marquee:"HTMLMarqueeElement",menu:"HTMLMenuElement",menuitem:"HTMLMenuItemElement",meta:"HTMLMetaElement",meter:"HTMLMeterElement",object:"HTMLObjectElement",ol:"HTMLOListElement",optgroup:"HTMLOptGroupElement",option:"HTMLOptionElement",output:"HTMLOutputElement",p:"HTMLParagraphElement",param:"HTMLParamElement",pre:"HTMLPreElement",progress:"HTMLProgressElement",q:"HTMLQuoteElement",script:"HTMLScriptElement",select:"HTMLSelectElement",shadow:"HTMLShadowElement",source:"HTMLSourceElement",span:"HTMLSpanElement",style:"HTMLStyleElement",table:"HTMLTableElement",tbody:"HTMLTableSectionElement",template:"HTMLTemplateElement",textarea:"HTMLTextAreaElement",thead:"HTMLTableSectionElement",time:"HTMLTimeElement",title:"HTMLTitleElement",tr:"HTMLTableRowElement",track:"HTMLTrackElement",ul:"HTMLUListElement",video:"HTMLVideoElement"});Object.keys(n).forEach(t),Object.getOwnPropertyNames(e.wrappers).forEach(function(t){window[t]=e.wrappers[t]})}(window.ShadowDOMPolyfill),function(e){function t(e,t){var n="";return Array.prototype.forEach.call(e,function(e){n+=e.textContent+"\n\n"}),t||(n=n.replace(d,"")),n}function n(e){var t=document.createElement("style");return t.textContent=e,t}function r(e){var t=n(e);document.head.appendChild(t);var r=[];if(t.sheet)try{r=t.sheet.cssRules}catch(o){}else console.warn("sheet not found",t);return t.parentNode.removeChild(t),r}function o(){C.initialized=!0,document.body.appendChild(C);var e=C.contentDocument,t=e.createElement("base");t.href=document.baseURI,e.head.appendChild(t)}function i(e){C.initialized||o(),document.body.appendChild(C),e(C.contentDocument),document.body.removeChild(C)}function a(e,t){if(t){var o;if(e.match("@import")&&D){var a=n(e);i(function(e){e.head.appendChild(a.impl),o=Array.prototype.slice.call(a.sheet.cssRules,0),t(o)})}else o=r(e),t(o)}}function s(e){e&&l().appendChild(document.createTextNode(e))}function c(e,t){var r=n(e);r.setAttribute(t,""),r.setAttribute(x,""),document.head.appendChild(r)}function l(){return j||(j=document.createElement("style"),j.setAttribute(x,""),j[x]=!0),j}var u={strictStyling:!1,registry:{},shimStyling:function(e,n,r){var o=this.prepareRoot(e,n,r),i=this.isTypeExtension(r),a=this.makeScopeSelector(n,i),s=t(o,!0);s=this.scopeCssText(s,a),e&&(e.shimmedStyle=s),this.addCssToDocument(s,n)},shimStyle:function(e,t){return this.shimCssText(e.textContent,t)},shimCssText:function(e,t){return e=this.insertDirectives(e),this.scopeCssText(e,t)},makeScopeSelector:function(e,t){return e?t?"[is="+e+"]":e:""},isTypeExtension:function(e){return e&&e.indexOf("-")<0},prepareRoot:function(e,t,n){var r=this.registerRoot(e,t,n);return this.replaceTextInStyles(r.rootStyles,this.insertDirectives),this.removeStyles(e,r.rootStyles),this.strictStyling&&this.applyScopeToContent(e,t),r.scopeStyles},removeStyles:function(e,t){for(var n,r=0,o=t.length;r","+","~"],r=e,o="["+t+"]";return n.forEach(function(e){var t=r.split(e);r=t.map(function(e){var t=e.trim().replace(L,"");return t&&n.indexOf(t)<0&&t.indexOf(o)<0&&(e=t.replace(/([^:]*)(:*)(.*)/,"$1"+o+"$2$3")),e}).join(e)}),r},insertPolyfillHostInCssText:function(e){return e.replace(M,b).replace(T,g)},propertiesFromRule:function(e){var t=e.style.cssText;e.style.content&&!e.style.content.match(/['"]+|attr/)&&(t=t.replace(/content:[^;]*;/g,"content: '"+e.style.content+"';"));var n=e.style;for(var r in n)"initial"===n[r]&&(t+=r+": initial; ");return t},replaceTextInStyles:function(e,t){e&&t&&(e instanceof Array||(e=[e]),Array.prototype.forEach.call(e,function(e){e.textContent=t.call(this,e.textContent)},this))},addCssToDocument:function(e,t){e.match("@import")?c(e,t):s(e)}},d=/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//gim,p=/\/\*\s*@polyfill ([^*]*\*+([^\/*][^*]*\*+)*\/)([^{]*?){/gim,h=/polyfill-next-selector[^}]*content\:[\s]*?['"](.*?)['"][;\s]*}([^{]*?){/gim,f=/\/\*\s@polyfill-rule([^*]*\*+([^\/*][^*]*\*+)*)\//gim,m=/(polyfill-rule)[^}]*(content\:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim,w=/\/\*\s@polyfill-unscoped-rule([^*]*\*+([^\/*][^*]*\*+)*)\//gim,v=/(polyfill-unscoped-rule)[^}]*(content\:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim,g="-shadowcsshost",b="-shadowcsscontext",y=")(?:\\(((?:\\([^)(]*\\)|[^)(]*)+?)\\))?([^,{]*)",E=new RegExp("("+g+y,"gim"),_=new RegExp("("+b+y,"gim"),S="([>\\s~+[.,{:][\\s\\S]*)?$",T=/\:host/gim,M=/\:host-context/gim,O=g+"-no-combinator",L=new RegExp(g,"gim"),N=(new RegExp(b,"gim"),[/>>>/g,/::shadow/g,/::content/g,/\/deep\//g,/\/shadow\//g,/\/shadow-deep\//g,/\^\^/g,/\^(?!=)/g]),C=document.createElement("iframe");C.style.display="none";var j,D=navigator.userAgent.match("Chrome"),H="shim-shadowdom",x="shim-shadowdom-css",R="no-shim";if(window.ShadowDOMPolyfill){s("style { display: none !important; }\n");var I=ShadowDOMPolyfill.wrap(document),P=I.querySelector("head");P.insertBefore(l(),P.childNodes[0]),document.addEventListener("DOMContentLoaded",function(){e.urlResolver;if(window.HTMLImports&&!HTMLImports.useNative){var t="link[rel=stylesheet]["+H+"]",n="style["+H+"]";HTMLImports.importer.documentPreloadSelectors+=","+t,HTMLImports.importer.importsPreloadSelectors+=","+t,HTMLImports.parser.documentSelectors=[HTMLImports.parser.documentSelectors,t,n].join(",");var r=HTMLImports.parser.parseGeneric;HTMLImports.parser.parseGeneric=function(e){if(!e[x]){var t=e.__importElement||e;if(!t.hasAttribute(H))return void r.call(this,e);e.__resource&&(t=e.ownerDocument.createElement("style"),t.textContent=e.__resource),HTMLImports.path.resolveUrlsInStyle(t,e.href),t.textContent=u.shimStyle(t),t.removeAttribute(H,""),t.setAttribute(x,""),t[x]=!0,t.parentNode!==P&&(e.parentNode===P?P.replaceChild(t,e):this.addElementToDocument(t)),t.__importParsed=!0,this.markParsingComplete(e),this.parseNext()}};var o=HTMLImports.parser.hasResource;HTMLImports.parser.hasResource=function(e){return"link"===e.localName&&"stylesheet"===e.rel&&e.hasAttribute(H)?e.__resource:o.call(this,e)}}})}e.ShadowCSS=u}(window.WebComponents)),function(e){window.ShadowDOMPolyfill?(window.wrap=ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}}(window.WebComponents),function(e){"use strict";function t(e){return void 0!==p[e]}function n(){s.call(this),this._isInvalid=!0}function r(e){return""==e&&n.call(this),e.toLowerCase()}function o(e){var t=e.charCodeAt(0);return t>32&&t<127&&[34,35,60,62,63,96].indexOf(t)==-1?e:encodeURIComponent(e)}function i(e){var t=e.charCodeAt(0);return t>32&&t<127&&[34,35,60,62,96].indexOf(t)==-1?e:encodeURIComponent(e)}function a(e,a,s){function c(e){b.push(e)}var l=a||"scheme start",u=0,d="",v=!1,g=!1,b=[];e:for(;(e[u-1]!=f||0==u)&&!this._isInvalid;){var y=e[u];switch(l){case"scheme start":if(!y||!m.test(y)){if(a){c("Invalid scheme.");break e}d="",l="no scheme";continue}d+=y.toLowerCase(),l="scheme";break;case"scheme":if(y&&w.test(y))d+=y.toLowerCase();else{if(":"!=y){if(a){if(f==y)break e;c("Code point not allowed in scheme: "+y);break e}d="",u=0,l="no scheme";continue}if(this._scheme=d,d="",a)break e;t(this._scheme)&&(this._isRelative=!0),l="file"==this._scheme?"relative":this._isRelative&&s&&s._scheme==this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":"?"==y?(this._query="?",l="query"):"#"==y?(this._fragment="#",l="fragment"):f!=y&&"\t"!=y&&"\n"!=y&&"\r"!=y&&(this._schemeData+=o(y));break;case"no scheme":if(s&&t(s._scheme)){l="relative";continue}c("Missing scheme."),n.call(this);break;case"relative or authority":if("/"!=y||"/"!=e[u+1]){c("Expected /, got: "+y),l="relative";continue}l="authority ignore slashes";break;case"relative":if(this._isRelative=!0,"file"!=this._scheme&&(this._scheme=s._scheme),f==y){this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._username=s._username,this._password=s._password;break e}if("/"==y||"\\"==y)"\\"==y&&c("\\ is an invalid code point."),l="relative slash";else if("?"==y)this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query="?",this._username=s._username,this._password=s._password,l="query";else{if("#"!=y){var E=e[u+1],_=e[u+2];("file"!=this._scheme||!m.test(y)||":"!=E&&"|"!=E||f!=_&&"/"!=_&&"\\"!=_&&"?"!=_&&"#"!=_)&&(this._host=s._host,this._port=s._port,this._username=s._username,this._password=s._password,this._path=s._path.slice(),this._path.pop()),l="relative path";continue}this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._fragment="#",this._username=s._username,this._password=s._password,l="fragment"}break;case"relative slash":if("/"!=y&&"\\"!=y){"file"!=this._scheme&&(this._host=s._host,this._port=s._port,this._username=s._username,this._password=s._password),l="relative path";continue}"\\"==y&&c("\\ is an invalid code point."),l="file"==this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!=y){c("Expected '/', got: "+y),l="authority ignore slashes";continue}l="authority second slash";break;case"authority second slash":if(l="authority ignore slashes","/"!=y){c("Expected '/', got: "+y);continue}break;case"authority ignore slashes":if("/"!=y&&"\\"!=y){l="authority";continue}c("Expected authority, got: "+y);break;case"authority":if("@"==y){v&&(c("@ already seen."),d+="%40"),v=!0;for(var S=0;S0){var o=n[r-1],i=h(o,e);if(i)return void(n[r-1]=i)}else t(this.observer);n[r]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.addEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=w.get(e);t||w.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=w.get(e),n=0;n=200&&e.status<300||304===e.status||0===e.status},load:function(n,r,o){var i=new XMLHttpRequest;return(e.flags.debug||e.flags.bust)&&(n+="?"+Math.random()),i.open("GET",n,t.async),i.addEventListener("readystatechange",function(e){if(4===i.readyState){var n=null;try{var a=i.getResponseHeader("Location");a&&(n="/"===a.substr(0,1)?location.origin+a:a)}catch(e){console.error(e.message)}r.call(o,!t.ok(i)&&i,i.response||i.responseText,n)}}),i.send(),i},loadDocument:function(e,t,n){this.load(e,t,n).responseType="document"}};e.xhr=t}),window.HTMLImports.addModule(function(e){var t=e.xhr,n=e.flags,r=function(e,t){this.cache={},this.onload=e,this.oncomplete=t,this.inflight=0,this.pending={}};r.prototype={addNodes:function(e){this.inflight+=e.length;for(var t,n=0,r=e.length;n-1?atob(a):decodeURIComponent(a),setTimeout(function(){this.receive(e,r,null,a)}.bind(this),0)}else{var s=function(t,n,o){this.receive(e,r,t,n,o)}.bind(this);t.load(e,s)}else setTimeout(function(){this.receive(e,r,{error:"href must be specified"},null)}.bind(this),0)},receive:function(e,t,n,r,o){this.cache[e]=r;for(var i,a=this.pending[e],s=0,c=a.length;s=0&&this.dynamicElements.splice(t,1)},parseImport:function(e){if(e["import"]=e.__doc,window.HTMLImports.__importsParsingHook&&window.HTMLImports.__importsParsingHook(e),e["import"]&&(e["import"].__importParsed=!0),this.markParsingComplete(e),e.__resource&&!e.__error?e.dispatchEvent(new CustomEvent("load",{bubbles:!1})):e.dispatchEvent(new CustomEvent("error",{bubbles:!1})),e.__pending)for(var t;e.__pending.length;)t=e.__pending.shift(),t&&t({target:e});this.parseNext()},parseLink:function(e){t(e)?this.parseImport(e):(e.href=e.href,this.parseGeneric(e))},parseStyle:function(e){var t=e;e=i(e),t.__appliedElement=e,e.__importElement=t,this.parseGeneric(e)},parseGeneric:function(e){this.trackElement(e),this.addElementToDocument(e)},rootImportForElement:function(e){for(var t=e;t.ownerDocument.__importLink;)t=t.ownerDocument.__importLink;return t},addElementToDocument:function(e){var t=this.rootImportForElement(e.__importElement||e);t.parentNode.insertBefore(e,t)},trackElement:function(e,t){var n=this,r=function(o){e.removeEventListener("load",r),e.removeEventListener("error",r),t&&t(o),n.markParsingComplete(e),n.parseNext()};if(e.addEventListener("load",r),e.addEventListener("error",r),l&&"style"===e.localName){var o=!1;if(e.textContent.indexOf("@import")==-1)o=!0;else if(e.sheet){o=!0;for(var i,a=e.sheet.cssRules,s=a?a.length:0,c=0;c=0},hasResource:function(e){return!t(e)||void 0!==e.__doc}};e.parser=p,e.IMPORT_SELECTOR=d}),window.HTMLImports.addModule(function(e){function t(e){return n(e,a)}function n(e,t){return"link"===e.localName&&e.getAttribute("rel")===t}function r(e){return!!Object.getOwnPropertyDescriptor(e,"baseURI")}function o(e,t){var n=document.implementation.createHTMLDocument(a);n._URL=t;var o=n.createElement("base");o.setAttribute("href",t),n.baseURI||r(n)||Object.defineProperty(n,"baseURI",{value:t});var i=n.createElement("meta");return i.setAttribute("charset","utf-8"),n.head.appendChild(i),n.head.appendChild(o),n.body.innerHTML=e,window.HTMLTemplateElement&&HTMLTemplateElement.bootstrap&&HTMLTemplateElement.bootstrap(n),n}var i=e.flags,a=e.IMPORT_LINK_TYPE,s=e.IMPORT_SELECTOR,c=e.rootDocument,l=e.Loader,u=e.Observer,d=e.parser,p={documents:{},documentPreloadSelectors:s,importsPreloadSelectors:[s].join(","),loadNode:function(e){h.addNode(e)},loadSubtree:function(e){var t=this.marshalNodes(e);h.addNodes(t)},marshalNodes:function(e){return e.querySelectorAll(this.loadSelectorsForNode(e))},loadSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===c?this.documentPreloadSelectors:this.importsPreloadSelectors},loaded:function(e,n,r,a,s){if(i.load&&console.log("loaded",e,n),n.__resource=r,n.__error=a,t(n)){var c=this.documents[e];void 0===c&&(c=a?null:o(r,s||e),c&&(c.__importLink=n,this.bootDocument(c)),this.documents[e]=c),n.__doc=c}d.parseNext()},bootDocument:function(e){this.loadSubtree(e),this.observer.observe(e),d.parseNext()},loadedAll:function(){d.parseNext()}},h=new l(p.loaded.bind(p),p.loadedAll.bind(p));if(p.observer=new u,!document.baseURI){var f={get:function(){var e=document.querySelector("base");return e?e.href:window.location.href},configurable:!0};Object.defineProperty(document,"baseURI",f),Object.defineProperty(c,"baseURI",f)}e.importer=p,e.importLoader=h}),window.HTMLImports.addModule(function(e){var t=e.parser,n=e.importer,r={added:function(e){for(var r,o,i,a,s=0,c=e.length;s=0)){n.push(e);for(var r,o=e.querySelectorAll("link[rel="+a+"]"),s=0,c=o.length;s=0&&b(r,HTMLElement),r)}function f(e,t){var n=e[t];e[t]=function(){var e=n.apply(this,arguments);return v(e),e}}var m,w=(e.isIE,e.upgradeDocumentTree),v=e.upgradeAll,g=e.upgradeWithDefinition,b=e.implementPrototype,y=e.useNative,E=["annotation-xml","color-profile","font-face","font-face-src","font-face-uri","font-face-format","font-face-name","missing-glyph"],_={},S="http://www.w3.org/1999/xhtml",T=document.createElement.bind(document),M=document.createElementNS.bind(document);m=Object.__proto__||y?function(e,t){return e instanceof t}:function(e,t){if(e instanceof t)return!0;for(var n=e;n;){if(n===t.prototype)return!0;n=n.__proto__}return!1},f(Node.prototype,"cloneNode"),f(document,"importNode"),document.registerElement=t,document.createElement=h,document.createElementNS=p,e.registry=_,e["instanceof"]=m,e.reservedTagList=E,e.getRegisteredDefinition=l,document.register=document.registerElement}),function(e){function t(){i(window.wrap(document)),window.CustomElements.ready=!0;var e=window.requestAnimationFrame||function(e){setTimeout(e,16)};e(function(){setTimeout(function(){window.CustomElements.readyTime=Date.now(),window.HTMLImports&&(window.CustomElements.elapsed=window.CustomElements.readyTime-window.HTMLImports.readyTime),document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})})}var n=e.useNative,r=e.initializeModules;e.isIE;if(n){var o=function(){};e.watchShadow=o,e.upgrade=o,e.upgradeAll=o,e.upgradeDocumentTree=o,e.upgradeSubtree=o,e.takeRecords=o,e["instanceof"]=function(e,t){return e instanceof t}}else r();var i=e.upgradeDocumentTree,a=e.upgradeDocument;if(window.wrap||(window.ShadowDOMPolyfill?(window.wrap=window.ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=window.ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}),window.HTMLImports&&(window.HTMLImports.__importsParsingHook=function(e){e["import"]&&a(wrap(e["import"]))}),"complete"===document.readyState||e.flags.eager)t();else if("interactive"!==document.readyState||window.attachEvent||window.HTMLImports&&!window.HTMLImports.ready){var s=window.HTMLImports&&!window.HTMLImports.ready?"HTMLImportsLoaded":"DOMContentLoaded";window.addEventListener(s,t)}else t()}(window.CustomElements),function(e){Function.prototype.bind||(Function.prototype.bind=function(e){var t=this,n=Array.prototype.slice.call(arguments,1);return function(){var r=n.slice();return r.push.apply(r,arguments),t.apply(e,r)}})}(window.WebComponents),function(e){var t=document.createElement("style");t.textContent="body {transition: opacity ease-in 0.2s; } \nbody[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } \n";var n=document.querySelector("head");n.insertBefore(t,n.firstChild)}(window.WebComponents),function(e){window.Platform=e}(window.WebComponents); \ No newline at end of file diff --git a/pkg/observatory/web/timeline.html b/pkg/observatory/web/timeline.html new file mode 100644 index 00000000000..4bbc396773c --- /dev/null +++ b/pkg/observatory/web/timeline.html @@ -0,0 +1,30 @@ + + + + + Dart VM Observatory Timeline + + + + + + + + + diff --git a/pkg/observatory/web/timeline.js b/pkg/observatory/web/timeline.js new file mode 100644 index 00000000000..26cb2633206 --- /dev/null +++ b/pkg/observatory/web/timeline.js @@ -0,0 +1,814 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// See also timeline_message_handler.js. + +var traceObject; +var pendingRequests; +var loadingOverlay; + +function onModelLoaded(model) { + viewer.globalMode = true; + viewer.model = model; +} + +function clearTimeline() { + viewer.model = undefined; +} + +function fetchUri(uri, onLoad, onError) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', uri, true); + xhr.responseType = 'text'; + xhr.addEventListener('load', onLoad); + xhr.addEventListener('error', onError); + xhr.send(); + console.log('GET ' + uri); +} + +function onImportFail(err) { + var overlay = new tr.ui.b.Overlay(); + overlay.textContent = tr.b.normalizeException(err).message; + overlay.title = 'Import error'; + overlay.visible = true; + console.log('import failed'); +} + +function compareTimestamp(a, b) { return a.ts - b.ts; } +function compareBeginTimestamp(a, b) { return a.begin.ts - b.begin.ts; } +function compareEndTimestamp(a, b) { return a.end.ts - b.end.ts; } + +var basicModelEventsWaterfall = [ + // Sort events and remove orphan async ends. + function filterUnwantedEvents(events) { + events = events.slice(); + events.sort(compareTimestamp); + var threads = {}; + return events.filter(function (event) { + if (event.ph === 'E') { + return threads[event.tid] && threads[event.tid].pop(); + } + var result = event.args && event.args.mode === 'basic'; + if (event.ph === 'B') { + threads[event.tid] = threads[event.tid] || []; + threads[event.tid].push(result); + } + return result; + }); + } +]; + +var frameModelEventsWaterfall = [ + // Sort events and remove orphan async ends. + function filterUnwantedEvents(events) { + events = events.slice(); + events.sort(compareTimestamp); + var threads = {}; + return events.filter(function (event) { + if (event.ph === 'E') { + if (!threads[event.tid]) { + return false + } + threads[event.tid] -= 1; + } else if (event.ph === 'B') { + threads[event.tid] = (threads[event.tid] || 0) + 1; + } + return true; + }); + }, + // Clone the events (we want to preserve them for dumps). + function cloneDeep(input) { + if (typeof input === 'object') { + if (Array.isArray(input)) { + return input.map(cloneDeep); + } else { + var clone = {}; + Object.keys(input).forEach(function (key) { + clone[key] = cloneDeep(input[key]); + }); + return clone; + } + } + return input; + }, + // Group nested sync begin end sequences on every thread. + // + // Example: + // Input = [B,B,E,B,E,E,B,E,B,B,E,E] + // Output = [[B,B,E,B,E,E],[B,E],[B,B,E,E]] + function groupIsolatedPerThreadSequences(events) { + var sequences = [], + timeless = [], + threadOpen = {}; + threadSequences = {}; + events.forEach(function (event) { + if (event.ph === 'M') { + timeless.push(event); + } else if (event.ph === 'B') { + threadOpen[event.tid] = Math.max(threadOpen[event.tid] || 0) + 1; + threadSequences[event.tid] = threadSequences[event.tid] || []; + threadSequences[event.tid].push(event); + } else if (event.ph === 'E') { + threadSequences[event.tid].push(event); + threadOpen[event.tid] -= 1; + if (threadOpen[event.tid] == 0) { + threadSequences[event.tid].sort() + sequences.push(threadSequences[event.tid]); + threadSequences[event.tid] = []; + } + } else if (threadSequences[event.tid]){ + threadSequences[event.tid] = threadSequences[event.tid] || []; + threadSequences[event.tid].push(event); + } + }) + return { + timeless: timeless, + sequences: sequences + }; + }, + // Transform every sequence into an object for rapid begin end analysis and + // block types lookup. + // + // Example: + // Input = [B1,B2,E2,B3,E3,E1] + // Output = { + // begin: B1, + // end: E1, + // events: [B1,B2,E2,B3,E3,E1], + // isGPU: ..., + // isVSync: ..., + // isFramework: ..., + // isShiftable: ..., + // } + function sequenceToBlockDescriptor(input) { + return { + timeless: input.timeless, + blocks: input.sequences.map(function (events) { + var begin, + end, + isGPU, + isVSync, + isFramework; + events.forEach(function (event) { + if (event.ph === 'B') { + begin = begin || event; + } else if (event.ph === 'E') { + end = event; + } + }); + isGPU = begin.name === 'GPU Workload'; + isVSync = begin.name === 'VSYNC'; + isFramework = begin.name === 'Framework Workload'; + return { + begin: begin, + end: end, + events: events, + isGPU: isGPU, + isVSync: isVSync, + isFramework: isFramework, + isShiftable: !(isGPU || isVSync || isFramework) + }; + }) + }; + }, + // Remove all the blocks that ended before the first VSYNC. + // These events do not give any information to the analysis. + function removePreVSyncBlocks(input) { + input.blocks.sort(compareEndTimestamp); + var sawVSyncBlock = false; + return { + timeless: input.timeless, + blocks: input.blocks.filter(function (block) { + sawVSyncBlock = sawVSyncBlock || block.isVSync; + return sawVSyncBlock; + }) + }; + }, + // Remove all the GPU blocks that started before the first Framework block. + // They are orphans of other frames. + function removePreFrameworkGPUBlocks(input) { + input.blocks.sort(compareBeginTimestamp); + var firstFrameworkBlockBeginTimestamp = 0; + return { + timeless: input.timeless, + blocks: input.blocks.filter(function (block) { + if (block.isFramework) { + firstFrameworkBlockBeginTimestamp = + firstFrameworkBlockBeginTimestamp || block.begin.ts; + } else if (block.isGPU) { + if (!firstFrameworkBlockBeginTimestamp) { + return false; + } else if (block.begin.ts < firstFrameworkBlockBeginTimestamp) { + return false; + } + } + return true; + }) + }; + }, + // Merge all shiftable blocks that are between two Framework blocks. + // By merging them we preserve their relative timing. + function mergeShiftableBlocks(input) { + input.blocks.sort(compareEndTimestamp); + var begin, + end, + events = [], + shiftableBlocks = [], + blocks; + blocks = input.blocks.filter(function (block) { + if (block.isShiftable) { + begin = begin || block.begin; + end = block.end; + events = events.concat(block.events); + return false; + } else if (block.isFramework) { + if (events.length) { + shiftableBlocks.push({ + begin: begin, + end: end, + events: events + }); + } + } + return true; + }); + if (events.length) { + shiftableBlocks.push({ + begin: begin, + end: end, + events: events + }); + } + return { + timeless: input.timeless, + blocks: blocks.concat(shiftableBlocks) + }; + }, + // Remove all VSyncs that didn't started an actual frame. + function filterFramelessVSyncs(input) { + input.blocks.sort(compareBeginTimestamp); + var lastVSyncBlock, + blocks, + vSyncBlocks = []; + blocks = input.blocks.filter(function (block) { + if (block.isVSync) { + lastVSyncBlock = block; + return false; + } else if (block.isFramework) { + vSyncBlocks.push(lastVSyncBlock); + } + return true; + }); + return { + timeless: input.timeless, + blocks: blocks.concat(vSyncBlocks) + }; + }, + // Group blocks by type. + // + // Example: + // Input = [S1, V1, F1, V2, G1, F2, V3, G2, F3] + // Output = { + // gpu: [G1, G2], + // vsync: [V1, V2, V3], + // framework: [F1, F2, F3], + // shiftable: [S1] + // } + function groupBlocksByFrames(input) { + return { + timeless: input.timeless, + gpu: input.blocks.filter(function (b) { return b.isGPU; }), + vsync: input.blocks.filter(function (b) { return b.isVSync; }), + framework: input.blocks.filter(function (b) { return b.isFramework; }), + shiftable: input.blocks.filter(function (b) { return b.isShiftable; }) + }; + }, + // Remove possible out of sync GPU Blocks. + // If the buffer has already delete the VSync and the Framework, but not the + // GPU it can potentially be still alive. + function groupBlocksByFrames(input) { + var gpu = input.gpu, + framework = input.framework; + while (gpu.length && + gpu[0].begin.args.frame !== framework[0].begin.args.frame) { + gpu.shift(); + } + return input; + }, + // Group blocks related to the same frame. + // Input = { + // gpu: [G1, G2], + // vsync: [V1, V2], + // framework: [F1, F2], + // shiftable: [S1] + // } + // Output = [{V1, F1, G1, S1}, {V2, F2, G2}, {V3, F3, G3}] + function groupBlocksByFrames(input) { + var shiftable = input.shiftable.slice(); + return { + timeless: input.timeless, + frames: input.vsync.map(function (vsync, i) { + var frame = { + begin: vsync.begin, + vsync: vsync, + framework: input.framework[i], + deadline: parseInt(vsync.begin.args.deadline) + 1000 + }; + if (i < input.gpu.length) { + frame.gpu = input.gpu[i]; + } + if (shiftable.length && shiftable[0].begin.ts < framework.begin.ts ) { + frame.shiftable = shiftable.shift(); + } + return frame; + }) + }; + }, + // Move Framework and GPU as back in time as possible + // + // Example: + // Before + // [GPU] + // [VSYNC] + // [SHIFTABLE] [FRAMEWORK] + // After + // |[GPU] + // [VSYNC] | + // [SHIFTABLE]|[FRAMEWORK] + function shiftEvents(input) { + input.frames.forEach(function (frame) { + var earlierTimestamp = frame.vsync.end.ts, + shift; + if (frame.shiftable) { + frame.shiftable.events.forEach(function (event) { + if (event.tid === frame.framework.begin.tid) { + earlierTimestamp = Math.max(earlierTimestamp, event.ts); + } + }); + } + if (frame.gpu) { + if (frame.shiftable) { + frame.shiftable.events.forEach(function (event) { + if (event.tid === frame.gpu.begin.tid) { + earlierTimestamp = Math.max(earlierTimestamp, event.ts); + } + }); + } + shift = earlierTimestamp - frame.gpu.begin.ts; + frame.gpu.events.forEach(function (event) { + event.ts += shift; + }); + } + shift = earlierTimestamp - frame.framework.begin.ts; + frame.framework.events.forEach(function (event) { + event.ts += shift; + }); + frame.end = frame.framework.end; + if (frame.gpu && frame.framework.end.ts < frame.gpu.end.ts) { + frame.end = frame.gpu.end; + } + }); + return input; + }, + // Group events in frame (precomputation for next stage). + function groupEventsInFrame(input) { + input.frames.forEach(function (frame) { + var events = frame.vsync.events; + events = events.concat(frame.framework.events); + if (frame.gpu) { + events = events.concat(frame.gpu.events); + } + if (frame.shiftable) { + events = events.concat(frame.shiftable.events); + } + events.sort(compareTimestamp); + frame.events = events; + }); + return input; + }, + // Move frames in order to do not overlap. + // + // Example: + // Before + // |[GPU1--------------------] + // |[GPU2----] + // [VSYNC1] | [VSYNC2] | + // [SHIFTABLE1]|[FRAMEWORK1] |[FRAMEWORK2] + // After + // |[GPU1--------------------]| |[GPU2-----] + // [VSYNC1] | |[VSYNC2] | + // [SHIFTABLE1]|[FRAMEWORK1] | |[FRAMEWORK2] + // OtherExample: + // Before + // {FRAME BUDGET1-------------------------} + // {FRAME BUDGET2-------------------------} + // |[GPU1] + // |[GPU2] + // [VSYNC1] | [VSYNC2] | + // [SHIFTABLE1]|[FRAMEWORK1] |[FRAMEWORK2] + // After + // {FRAME BUDGET1-------------------------}|{FRAME BUDGET2---------------- + // |[GPU1] | |[GPU2-----] + // [VSYNC1] | |[VSYNC2] | + // [SHIFTABLE1]|[FRAMEWORK1] | |[FRAMEWORK2] + function shiftBlocks(input) { + function minThreadtimestamps(frame) { + var timestamps = {}; + frame.events.forEach(function (event) { + if (event.tid != undefined) { + timestamps[event.tid] = timestamps[event.tid] || event.ts; + } + }); + return timestamps; + } + function maxThreadTimestamps(frame) { + var timestamps = {}; + frame.events.forEach(function (event) { + if (event.tid != undefined) { + timestamps[event.tid] = event.ts; + } + }); + return timestamps; + } + input.frames.slice(1).forEach(function (current, index) { + var previous = input.frames[index], + shift = Math.max(previous.end.ts, previous.deadline) - current.begin.ts, + maxThreadTimestamp = maxThreadTimestamps(previous), + minThreadTimestamp = minThreadtimestamps(current); + Object.keys(maxThreadTimestamp).forEach(function (tid) { + if (minThreadTimestamp[tid]) { + var delta = maxThreadTimestamp[tid] - minThreadTimestamp[tid]; + shift = Math.max(shift, delta); + } + }); + current.events.forEach(function (event) { + event.ts += shift; + }); + current.deadline += shift; + }); + return input; + }, + // Add auxiliary events to frame (Frame Budget and Frame Length). + // Example: + // Before + // |[GPU1--------------------]| |[GPU2-----] + // [VSYNC1] | |[VSYNC2] | + // [SHIFTABLE1]|[FRAMEWORK1] | |[FRAMEWORK2] + // After + // [Budget1---------------------------]|[Budget2-------------------------- + // [Length1---------------------------]|[Length2------------] + // |[GPU1--------------------]| |[GPU2-----] + // [VSYNC1] | |[VSYNC2] | + // [SHIFTABLE1]|[FRAMEWORK1] | |[FRAMEWORK2] + function addAuxiliaryEvents(input) { + input.frames.forEach(function (frame) { + frame.events.unshift({ + args: {name: "Frame Budgets"}, + name: "thread_name", + ph: "M", + pid: frame.begin.pid, + tid: "budgets", + }); + frame.events.unshift({ + args: {name: "Frames"}, + name: "thread_name", + ph: "M", + pid: frame.begin.pid, + tid: "frames", + }); + var duration = Math.floor((frame.end.ts - frame.begin.ts) / 1000), + frameName = "Frame " + duration + "ms"; + frame.events = frame.events.concat({ + ph: "B", + name: "Frame Budget", + cat: "budgets", + pid: frame.begin.pid, + tid: "budgets", + ts: frame.begin.ts + }, { + ph: "E", + name: "Frame Budget", + cat: "budgets", + pid: frame.begin.pid, + tid: "budgets", + ts: frame.deadline, + cname: 'rail_response' + }, { + ph: "B", + name: frameName, + cat: "frames", + pid: frame.begin.pid, + tid: "frames", + ts: frame.begin.ts + }, { + ph: "E", + name: frameName, + cat: "frames", + pid: frame.begin.pid, + tid: "frames", + ts: frame.end.ts, + cname: frame.end.ts > frame.deadline ? 'terrible' : 'good' + }); + }); + return input; + }, + // Restore the events array used by catapult. + function linearizeBlocks(input) { + return input.frames.reduce(function (events, frame) { + return events.concat(frame.events); + }, input.timeless); + } +]; + +function basicModelEventsMap(events) { + return basicModelEventsWaterfall.reduce(function (input, step) { + return step(input); + }, events); +} + +function frameModelEventsMap(events) { + return frameModelEventsWaterfall.reduce(function (input, step) { + return step(input); + }, events); +} + +function updateTimeline(events) { + if (window.location.hash.indexOf('mode=basic') > -1) { + events = { + 'stackFrames': events['stackFrames'], + 'traceEvents': basicModelEventsMap(events['traceEvents']) + }; + } + if (window.location.hash.indexOf('view=frame') > -1) { + events = { + 'stackFrames': events['stackFrames'], + 'traceEvents': frameModelEventsMap(events['traceEvents']) + }; + } + var model = new tr.Model(); + var importer = new tr.importer.Import(model); + var p = importer.importTracesWithProgressDialog([events]); + p.then(onModelLoaded.bind(undefined, model), onImportFail); +} + +function showLoadingOverlay(msg) { + if (typeof tr === "undefined") { + return; + } + if (!loadingOverlay) { + loadingOverlay = new tr.ui.b.Overlay(); + } + loadingOverlay.textContent = msg; + loadingOverlay.title = 'Loading...'; + loadingOverlay.visible = true; +} + +function hideLoadingOverlay() { + if (!loadingOverlay) { + return; + } + loadingOverlay.visible = false; + loadingOverlay = undefined; +} + +function gotReponse() { + pendingRequests--; + if (pendingRequests === 0) { + console.log('Got all timeline parts'); + updateTimeline(traceObject); + hideLoadingOverlay(); + } +} + +function processTimelineResponse(response) { + if (response.error) { + // Maybe profiling is disabled. + console.log('ERROR ' + response.error.message); + } else { + var result = response['result']; + console.log(result); + + if (result["type"] == "Timeline") { + traceObject.traceEvents = traceObject.traceEvents.concat(result['traceEvents']); + } else if (result["type"] == "CpuSamples") { + var pid = result["pid"]; + var functions = result["functions"]; + var samples = result["samples"]; + var frames = traceObject.stackFrames; + var id = 0; + for (var i = 0; i < samples.length; i++) { + var sample = samples[i]; + var stack = sample["stack"]; + for (var j = 0; j < stack.length; j++) { + var profileFunc = functions[stack[j]]; + var func = profileFunc["function"]; + var name = func["name"]; + while (func["owner"] != undefined) { + func = func["owner"]; + name = func["name"] + "." + name; + } + var frame = { + 'category': 'Dart', + 'name': name, + 'resolvedUrl': profileFunc["resolvedUrl"], + }; + if (j != 0) { + frame['parent'] = "" + pendingRequests + "-" + id; + } + id++; + frames["" + pendingRequests + "-" + id] = frame; + } + traceObject.traceEvents.push({ + 'ph': 'P', // kind = sample event + 'name': '', // Blank to keep about:tracing happy + 'pid': pid, + 'tid': sample['tid'], + 'ts': sample['timestamp'], + 'cat': 'Dart', + 'sf': "" + pendingRequests + "-" + id, + }); + } + } else { + console.log("Unknown timeline content type"); + } + } + + gotReponse(); +} + +function fetchTimelineOnLoad(event) { + var xhr = event.target; + var response = JSON.parse(xhr.responseText); + processTimelineResponse(response); +} + +function fetchTimelineOnError(event) { + var xhr = event.target; + console.log(xhr.statusText); + gotReponse(); +} + +function fetchCPUProfile(vmAddress, isolateIds, timeOrigin, timeExtent) { + showLoadingOverlay('Fetching CPU profile(s) from Dart VM'); + var parser = document.createElement('a'); + parser.href = vmAddress; + pendingRequests += isolateIds.length; + for (var i = 0; i < isolateIds.length; i++) { + var isolateId = isolateIds[i]; + var requestUri = 'http://' + + parser.hostname + + ':' + + parser.port + + parser.pathname.replace(/\/ws$/, "") + + '/getCpuSamples?tags=None&isolateId=' + + isolateId + + '&timeOriginMicros=' + timeOrigin + + '&timeExtentMicros=' + timeExtent; + fetchUri(requestUri, fetchTimelineOnLoad, fetchTimelineOnError); + } +} + +function fetchTimeline(vmAddress, isolateIds, mode) { + // Reset combined timeline. + traceObject = { + 'stackFrames': {}, + 'traceEvents': [] + }; + timelineMode = mode; + pendingRequests = 1; + + showLoadingOverlay('Fetching timeline from Dart VM'); + var parser = document.createElement('a'); + parser.href = vmAddress; + var requestUri = 'http://' + + parser.hostname + + ':' + + parser.port + + parser.pathname.replace(/\/ws$/, "") + + '/getVMTimeline'; + fetchUri(requestUri, function(event) { + // Grab the response. + var xhr = event.target; + var response = JSON.parse(xhr.responseText); + // Extract the time origin and extent. + var timeOrigin = response['result']['timeOriginMicros']; + var timeExtent = response['result']['timeExtentMicros']; + console.assert(Number.isInteger(timeOrigin), timeOrigin); + console.assert(Number.isInteger(timeExtent), timeExtent); + console.log(timeOrigin); + console.log(timeExtent); + // fetchCPUProfile. + fetchCPUProfile(vmAddress, isolateIds, timeOrigin, timeExtent); + // This must happen after 'fetchCPUProfile'; + processTimelineResponse(response, mode); + }, fetchTimelineOnError); +} + +function populateTimeline() { + updateTimeline(traceObject); + hideLoadingOverlay(); +} + +function saveTimeline() { + if (pendingRequests > 0) { + var overlay = new tr.ui.b.Overlay(); + overlay.textContent = 'Cannot save timeline while fetching one.'; + overlay.title = 'Save error'; + overlay.visible = true; + console.log('cannot save timeline while fetching one.'); + return; + } + if (!traceObject || + !traceObject.traceEvents || + (traceObject.traceEvents.length === 0)) { + var overlay = new tr.ui.b.Overlay(); + overlay.textContent = 'Cannot save an empty timeline.'; + overlay.title = 'Save error'; + overlay.visible = true; + console.log('Cannot save an empty timeline.'); + return; + } + var blob = new Blob([JSON.stringify(traceObject)], + {type: 'application/json'}); + var blobUrl = URL.createObjectURL(blob); + var link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); + link.href = blobUrl; + var now = new Date(); + var defaultFilename = 'dart-timeline-' + + now.getFullYear() + + '-' + + (now.getMonth() + 1) + + '-' + + now.getDate() + + '.json'; + var filename = window.prompt('Save as', defaultFilename); + if (filename) { + link.download = filename; + link.click(); + } +} + +function loadTimeline() { + if (pendingRequests > 0) { + var overlay = new tr.ui.b.Overlay(); + overlay.textContent = 'Cannot load timeline while fetching one.'; + overlay.title = 'Save error'; + overlay.visible = true; + console.log('Cannot load timeline while fetching one.'); + return; + } + var inputElement = document.createElement('input'); + inputElement.type = 'file'; + inputElement.multiple = false; + + var changeFired = false; + inputElement.addEventListener('change', function(e) { + if (changeFired) + return; + changeFired = true; + + var file = inputElement.files[0]; + var reader = new FileReader(); + reader.onload = function(event) { + try { + traceObject = JSON.parse(event.target.result); + updateTimeline(traceObject); + } catch (error) { + tr.ui.b.Overlay.showError('Error while loading file: ' + error); + } + }; + reader.onerror = function(event) { + tr.ui.b.Overlay.showError('Error while loading file: ' + event); + }; + reader.onabort = function(event) { + tr.ui.b.Overlay.showError('Error while loading file: ' + event); + } + reader.readAsText(file); + }); + inputElement.click(); +} + +function refreshTimeline() { + fetchTimeline(timeline_vm_address, timeline_isolates); +} + +window.addEventListener('DOMContentLoaded', function() { + var container = document.createElement('track-view-container'); + container.id = 'track_view_container'; + viewer = document.createElement('tr-ui-timeline-view'); + viewer.track_view_container = container; + viewer.appendChild(container); + viewer.id = 'trace-viewer'; + viewer.globalMode = true; + document.body.appendChild(viewer); + timeline_loaded = true; + console.log('DOMContentLoaded'); + document.getElementById('trace-viewer').highlightVSync = true; + if (timeline_vm_address != undefined) { + refreshTimeline(); + timeline_vm_address = undefined; + } +}); + +console.log('timeline.js loaded'); diff --git a/pkg/observatory/web/timeline_message_handler.js b/pkg/observatory/web/timeline_message_handler.js new file mode 100644 index 00000000000..f2da8bab9f6 --- /dev/null +++ b/pkg/observatory/web/timeline_message_handler.js @@ -0,0 +1,57 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// This file is loaded before the about:tracing code is loaded so that we have +// an event listener registered early. + +// Used to delay the initial timeline load until the timeline has finished +// loading. +timeline_loaded = false; +timeline_vm_address = undefined; +timeline_isolates = undefined; + +function registerForMessages() { + window.addEventListener("message", onMessage, false); + window.addEventListener("hashchange", onHashChange, false); +} + +registerForMessages(); + +function onMessage(event) { + var request = JSON.parse(event.data); + var method = request['method']; + var params = request['params']; + console.log('method: ' + method) + switch (method) { + case 'loading': + showLoadingOverlay('Fetching timeline...'); + break; + case 'refresh': + timeline_vm_address = params['vmAddress']; + timeline_isolates = params['isolateIds']; + if (typeof fetchTimeline === "undefined") { + console.log('Delaying timeline refresh until loaded.'); + } else { + fetchTimeline(timeline_vm_address, timeline_isolates); + } + break; + case 'clear': + clearTimeline(); + break; + case 'save': + saveTimeline(); + break; + case 'load': + loadTimeline(); + break; + default: + console.log('Unknown method:' + method + '.'); + } +} + +function onHashChange() { + refreshTimeline(); +} + +console.log('message handler registered'); diff --git a/tools/package_deps/bin/package_deps.dart b/tools/package_deps/bin/package_deps.dart index c996d8982e5..61860cd1b99 100644 --- a/tools/package_deps/bin/package_deps.dart +++ b/tools/package_deps/bin/package_deps.dart @@ -227,7 +227,15 @@ class Package implements Comparable { // Remove package:lints and package:dart_flutter_team_lints - // They are often declared as dev dependencies in order // to bring in analysis_options configuration files. - extraDevDeclarations.removeAll(const ['lints', 'dart_flutter_team_lints']); + // + // Also remove package:build_runner and package:build_web_compilers as they + // are required by projects that are run with webdev. + extraDevDeclarations.removeAll(const [ + 'lints', + 'dart_flutter_team_lints', + 'build_runner', + 'build_web_compilers' + ]); if (extraDevDeclarations.isNotEmpty) { out(' ${_printSet(extraDevDeclarations)} declared in ' "'dev_dependencies:' but not used in dev dirs.");