[observatory] Dramatically reduce timeline loading time.
Restore fetching from the Catapult IFrame instead of the main frame to avoid the postMessage bottleneck.
Cf. 68dede011e81de04ffc826f42732b8ce620bc52d
TEST=view timeline
Change-Id: I417ec40393fc149d0bf22657a2d9a46c3125f634
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/263160
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
diff --git a/runtime/observatory/lib/src/elements/timeline_page.dart b/runtime/observatory/lib/src/elements/timeline_page.dart
index d4b1a26..7e30a2d 100644
--- a/runtime/observatory/lib/src/elements/timeline_page.dart
+++ b/runtime/observatory/lib/src/elements/timeline_page.dart
@@ -249,8 +249,9 @@
Future _refresh() async {
_postMessage('loading');
- final traceData = await _repository.getTimeline(vm);
- return _postMessage('refresh', traceData);
+ final params =
+ new Map<String, dynamic>.from(await _repository.getIFrameParams(vm));
+ return _postMessage('refresh', params);
}
Future _clear() async {
diff --git a/runtime/observatory/lib/src/models/repositories/timeline.dart b/runtime/observatory/lib/src/models/repositories/timeline.dart
index 3d528c9..bd8276e 100644
--- a/runtime/observatory/lib/src/models/repositories/timeline.dart
+++ b/runtime/observatory/lib/src/models/repositories/timeline.dart
@@ -8,5 +8,5 @@
Future<TimelineFlags> getFlags(VMRef ref);
Future setRecordedStreams(VMRef ref, Iterable<TimelineStream> streams);
Future clear(VMRef ref);
- Future<Map> getTimeline(VMRef ref);
+ Future<Map<String, dynamic>> getIFrameParams(VMRef ref);
}
diff --git a/runtime/observatory/lib/src/repositories/timeline.dart b/runtime/observatory/lib/src/repositories/timeline.dart
index 834e637..97f0a6d 100644
--- a/runtime/observatory/lib/src/repositories/timeline.dart
+++ b/runtime/observatory/lib/src/repositories/timeline.dart
@@ -29,6 +29,17 @@
return vm.invokeRpc('clearVMTimeline', {});
}
+ Future<Map<String, dynamic>> getIFrameParams(M.VMRef ref) async {
+ S.VM vm = ref as S.VM;
+ assert(vm != null);
+ await vm.reload();
+ await vm.reloadIsolates();
+ return <String, dynamic>{
+ 'vmAddress': vm.target.networkAddress,
+ 'isolateIds': vm.isolates.map((i) => i.id).toList()
+ };
+ }
+
Future<void> _formatSamples(
M.Isolate isolate, Map traceObject, S.ServiceMap cpuSamples) async {
const kRootFrameId = 0;
diff --git a/runtime/observatory/web/timeline.js b/runtime/observatory/web/timeline.js
index 5b431f6..0ff35f0 100644
--- a/runtime/observatory/web/timeline.js
+++ b/runtime/observatory/web/timeline.js
@@ -4,6 +4,7 @@
// See also timeline_message_handler.js.
+var traceObject;
var pendingRequests;
var loadingOverlay;
@@ -16,6 +17,16 @@
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;
@@ -542,6 +553,9 @@
}
function showLoadingOverlay(msg) {
+ if (typeof tr === "undefined") {
+ return;
+ }
if (!loadingOverlay) {
loadingOverlay = new tr.ui.b.Overlay();
}
@@ -558,6 +572,139 @@
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();
@@ -643,7 +790,7 @@
}
function refreshTimeline() {
- updateTimeline(traceObject);
+ fetchTimeline(timeline_vm_address, timeline_isolates);
}
window.addEventListener('DOMContentLoaded', function() {
@@ -658,9 +805,9 @@
timeline_loaded = true;
console.log('DOMContentLoaded');
document.getElementById('trace-viewer').highlightVSync = true;
- if (traceObject != undefined) {
+ if (timeline_vm_address != undefined) {
refreshTimeline();
- traceObject = undefined;
+ timeline_vm_address = undefined;
}
});
diff --git a/runtime/observatory/web/timeline_message_handler.js b/runtime/observatory/web/timeline_message_handler.js
index 4b3f11a..f2da8ba 100644
--- a/runtime/observatory/web/timeline_message_handler.js
+++ b/runtime/observatory/web/timeline_message_handler.js
@@ -5,7 +5,11 @@
// This file is loaded before the about:tracing code is loaded so that we have
// an event listener registered early.
-var traceObject;
+// 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);
@@ -24,11 +28,12 @@
showLoadingOverlay('Fetching timeline...');
break;
case 'refresh':
- traceObject = params;
- if (typeof populateTimeline != 'undefined') {
- populateTimeline();
+ timeline_vm_address = params['vmAddress'];
+ timeline_isolates = params['isolateIds'];
+ if (typeof fetchTimeline === "undefined") {
+ console.log('Delaying timeline refresh until loaded.');
} else {
- console.log('populateTimeline is not yet defined');
+ fetchTimeline(timeline_vm_address, timeline_isolates);
}
break;
case 'clear':