| // 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; |
| |
| class IsolateNotFound implements Exception { |
| String isolateId; |
| IsolateNotFound(this.isolateId); |
| String toString() => "IsolateNotFound: $isolateId"; |
| } |
| |
| /// 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 extends Observable { |
| final ObservatoryApplication app; |
| final ObservableMap<String, String> internalArguments = |
| new ObservableMap<String, String>(); |
| @observable ObservatoryElement 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); |
| Analytics.reportPageView(uri); |
| _visit(uri); |
| } |
| |
| // Overridden by subclasses. |
| void _visit(Uri uri); |
| |
| /// Called to test whether this page can visit [uri]. |
| bool canVisit(Uri uri); |
| } |
| |
| /// A [SimplePage] matches a single uri path and displays a single element. |
| class SimplePage extends Page { |
| final String path; |
| final String elementTagName; |
| SimplePage(this.path, this.elementTagName, app) : super(app); |
| |
| void onInstall() { |
| if (element == null) { |
| element = new Element.tag(elementTagName); |
| } |
| } |
| |
| void _visit(Uri uri) { |
| assert(uri != null); |
| assert(canVisit(uri)); |
| } |
| |
| Future<Isolate> getIsolate(Uri uri) { |
| var isolateId = uri.queryParameters['isolateId']; |
| return app.vm.getIsolate(isolateId).then((isolate) { |
| if (isolate == null) { |
| throw new IsolateNotFound(isolateId); |
| } |
| return isolate; |
| }); |
| } |
| |
| bool canVisit(Uri uri) => uri.path == path; |
| } |
| |
| /// Error page for unrecognized paths. |
| class ErrorPage extends Page { |
| ErrorPage(app) : super(app); |
| |
| void onInstall() { |
| if (element == null) { |
| // Lazily create page. |
| element = new Element.tag('general-error'); |
| } |
| } |
| |
| void _visit(Uri uri) { |
| assert(element != null); |
| assert(canVisit(uri)); |
| |
| /* |
| if (uri.path == '') { |
| // Nothing requested. |
| return; |
| } |
| */ |
| |
| if (element != null) { |
| GeneralErrorElement serviceElement = element; |
| serviceElement.message = "Path '${uri.path}' not found"; |
| } |
| } |
| |
| /// Catch all. |
| bool canVisit(Uri uri) => true; |
| } |
| |
| /// Top-level vm info page. |
| class VMPage extends SimplePage { |
| VMPage(app) : super('vm', 'service-view', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| app.vm.reload().then((vm) { |
| if (element != null) { |
| ServiceObjectViewElement serviceElement = element; |
| serviceElement.object = vm; |
| } |
| }).catchError((e, stack) { |
| Logger.root.severe('VMPage visit error: $e'); |
| // Reroute to vm-connect. |
| app.locationManager.go(app.locationManager.makeLink('/vm-connect')); |
| }); |
| } |
| } |
| |
| class FlagsPage extends SimplePage { |
| FlagsPage(app) : super('flags', 'flag-list', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| app.vm.getFlagList().then((flags) { |
| if (element != null) { |
| FlagListElement serviceElement = element; |
| serviceElement.flagList = flags; |
| } |
| }).catchError((e, stack) { |
| Logger.root.severe('FlagsPage visit error: $e\n$stack'); |
| }); |
| } |
| } |
| |
| class InspectPage extends SimplePage { |
| InspectPage(app) : super('inspect', 'service-view', app); |
| |
| 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 _visitObject(obj) { |
| if (element != null) { |
| ServiceObjectViewElement serviceElement = element; |
| serviceElement.object = obj; |
| } |
| } |
| } |
| |
| |
| /// Class tree page. |
| class ClassTreePage extends SimplePage { |
| ClassTreePage(app) : super('class-tree', 'class-tree', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| getIsolate(uri).then((isolate) { |
| if (element != null) { |
| /// Update the page. |
| ClassTreeElement page = element; |
| page.isolate = isolate; |
| } |
| }); |
| } |
| } |
| |
| class DebuggerPage extends SimplePage { |
| DebuggerPage(app) : super('debugger', 'debugger-page', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| getIsolate(uri).then((isolate) { |
| if (element != null) { |
| /// Update the page. |
| DebuggerPageElement page = element; |
| page.isolate = isolate; |
| } |
| }); |
| } |
| } |
| |
| |
| class ObjectStorePage extends SimplePage { |
| ObjectStorePage(app) : super('object-store', 'objectstore-view', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| getIsolate(uri).then((isolate) { |
| isolate.getObjectStore().then((objectStore) { |
| if (element != null) { |
| /// Update the page. |
| ObjectStoreViewElement page = element; |
| page.objectStore = objectStore; |
| } |
| }); |
| }); |
| } |
| } |
| |
| class CpuProfilerPage extends SimplePage { |
| CpuProfilerPage(app) : super('profiler', 'cpu-profile', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| getIsolate(uri).then((isolate) { |
| if (element != null) { |
| /// Update the page. |
| CpuProfileElement page = element; |
| page.isolate = isolate; |
| } |
| }); |
| } |
| } |
| |
| class TableCpuProfilerPage extends SimplePage { |
| TableCpuProfilerPage(app) |
| : super('profiler-table', 'cpu-profile-table', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| getIsolate(uri).then((isolate) { |
| if (element != null) { |
| /// Update the page. |
| CpuProfileTableElement page = element; |
| page.isolate = isolate; |
| // TODO(johnmccutchan): Provide a more general mechanism to notify |
| // elements of URI parameter changes. Possibly via a stream off of |
| // LocationManager. With a stream individual elements (not just pages) |
| // could be notified. |
| page.checkParameters(); |
| } |
| }); |
| } |
| } |
| |
| class AllocationProfilerPage extends SimplePage { |
| AllocationProfilerPage(app) |
| : super('allocation-profiler', 'heap-profile', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| getIsolate(uri).then((isolate) { |
| if (element != null) { |
| /// Update the page. |
| HeapProfileElement page = element; |
| page.isolate = isolate; |
| } |
| }); |
| } |
| } |
| |
| class PortsPage extends SimplePage { |
| PortsPage(app) |
| : super('ports', 'ports-page', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| getIsolate(uri).then((isolate) { |
| if (element != null) { |
| PortsPageElement page = element; |
| page.isolate = isolate; |
| } |
| }); |
| } |
| } |
| |
| class PersistentHandlesPage extends SimplePage { |
| PersistentHandlesPage(app) |
| : super('persistent-handles', 'persistent-handles-page', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| getIsolate(uri).then((isolate) { |
| if (element != null) { |
| PersistentHandlesPageElement page = element; |
| page.isolate = isolate; |
| } |
| }); |
| } |
| } |
| |
| class HeapMapPage extends SimplePage { |
| HeapMapPage(app) : super('heap-map', 'heap-map', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| getIsolate(uri).then((isolate) { |
| if (element != null) { |
| /// Update the page. |
| HeapMapElement page = element; |
| page.isolate = isolate; |
| } |
| }); |
| } |
| } |
| |
| class HeapSnapshotPage extends SimplePage { |
| HeapSnapshotPage(app) : super('heap-snapshot', 'heap-snapshot', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| getIsolate(uri).then((isolate) { |
| if (element != null) { |
| /// Update the page. |
| HeapSnapshotElement page = element; |
| page.isolate = isolate; |
| } |
| }); |
| } |
| } |
| |
| |
| class LoggingPage extends SimplePage { |
| LoggingPage(app) : super('logging', 'logging-page', app); |
| |
| void _visit(Uri uri) { |
| super._visit(uri); |
| getIsolate(uri).then((isolate) { |
| if (element != null) { |
| /// Update the page. |
| LoggingPageElement page = element; |
| page.isolate = isolate; |
| } |
| }); |
| } |
| } |
| |
| class ErrorViewPage extends Page { |
| ErrorViewPage(app) : super(app); |
| |
| void onInstall() { |
| if (element == null) { |
| /// Lazily create page. |
| element = new Element.tag('service-view'); |
| } |
| } |
| |
| void _visit(Uri uri) { |
| assert(element != null); |
| assert(canVisit(uri)); |
| (element as ServiceObjectViewElement).object = app.lastErrorOrException; |
| } |
| |
| // 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 Element.tag('vm-connect'); |
| } |
| 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); |
| |
| void onInstall() { |
| if (element == null) { |
| element = new Element.tag('isolate-reconnect'); |
| } |
| assert(element != null); |
| } |
| |
| void _visit(Uri uri) { |
| app.vm.reload(); |
| assert(element != null); |
| assert(canVisit(uri)); |
| } |
| |
| bool canVisit(Uri uri) => uri.path == 'isolate-reconnect'; |
| } |
| |
| class MetricsPage extends Page { |
| // Page state, retained as long as ObservatoryApplication. |
| String selectedMetricId; |
| |
| final Map<int, MetricPoller> pollers = new Map<int, MetricPoller>(); |
| |
| // 8 seconds, 4 seconds, 2 seconds, 1 second, and one hundred milliseconds. |
| static final List<int> POLL_PERIODS = [8000, |
| 4000, |
| 2000, |
| 1000, |
| 100]; |
| |
| MetricsPage(app) : super(app) { |
| for (var i = 0; i < POLL_PERIODS.length; i++) { |
| pollers[POLL_PERIODS[i]] = new MetricPoller(POLL_PERIODS[i]); |
| } |
| } |
| |
| void onInstall() { |
| if (element == null) { |
| element = new Element.tag('metrics-page'); |
| (element as MetricsPageElement).page = this; |
| } |
| assert(element != null); |
| } |
| |
| void setRefreshPeriod(int refreshPeriod, ServiceMetric metric) { |
| if (metric.poller != null) { |
| if (metric.poller.pollPeriod.inMilliseconds == refreshPeriod) { |
| return; |
| } |
| // Remove from current poller. |
| metric.poller.metrics.remove(metric); |
| metric.poller = null; |
| } |
| if (refreshPeriod == 0) { |
| return; |
| } |
| var poller = pollers[refreshPeriod]; |
| if (poller != null) { |
| poller.metrics.add(metric); |
| metric.poller = poller; |
| return; |
| } |
| throw new FallThroughError(); |
| } |
| |
| void _visit(Uri uri) { |
| assert(element != null); |
| assert(canVisit(uri)); |
| app.vm.getIsolate(uri.queryParameters['isolateId']).then((i) { |
| (element as MetricsPageElement).isolate = i; |
| }); |
| } |
| |
| bool canVisit(Uri uri) => uri.path == 'metrics'; |
| } |
| |
| class TimelinePage extends Page { |
| TimelinePage(app) : super(app); |
| |
| void onInstall() { |
| if (element == null) { |
| element = new Element.tag('timeline-page'); |
| } |
| assert(element != null); |
| } |
| |
| void _visit(Uri uri) { |
| assert(element != null); |
| assert(canVisit(uri)); |
| } |
| |
| bool canVisit(Uri uri) => uri.path == 'timeline'; |
| } |