| // 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 = <Page>[]; |
| 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 TimelineDashboardPage(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<String, String> 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) { |
| assert(page != null); |
| if (currentPage == page) { |
| // Already installed. |
| return; |
| } |
| if (currentPage != null) { |
| Logger.root.info('Uninstalling page: $currentPage'); |
| currentPage!.onUninstall(); |
| // Clear children. |
| rootElement.children.clear(); |
| } |
| Logger.root.info('Installing page: $page'); |
| try { |
| page.onInstall(); |
| } catch (e) { |
| Logger.root.severe('Failed to install page: $e'); |
| } |
| // Add new page. |
| rootElement.children.add(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<String, bool> expansions = {}; |
| } |