From caffe45c7af08b514dc01bb31dde3edd3480787d Mon Sep 17 00:00:00 2001 From: Ryan Macnak Date: Mon, 10 Oct 2022 19:03:02 +0000 Subject: [PATCH] [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 Commit-Queue: Ryan Macnak --- .../lib/src/elements/timeline_page.dart | 5 +- .../lib/src/models/repositories/timeline.dart | 2 +- .../lib/src/repositories/timeline.dart | 11 ++ runtime/observatory/web/timeline.js | 153 +++++++++++++++++- .../web/timeline_message_handler.js | 15 +- 5 files changed, 175 insertions(+), 11 deletions(-) diff --git a/runtime/observatory/lib/src/elements/timeline_page.dart b/runtime/observatory/lib/src/elements/timeline_page.dart index d4b1a26cf0c..7e30a2da957 100644 --- a/runtime/observatory/lib/src/elements/timeline_page.dart +++ b/runtime/observatory/lib/src/elements/timeline_page.dart @@ -249,8 +249,9 @@ class TimelinePageElement extends CustomElement implements Renderable { Future _refresh() async { _postMessage('loading'); - final traceData = await _repository.getTimeline(vm); - return _postMessage('refresh', traceData); + final params = + new Map.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 3d528c96a34..bd8276ece3e 100644 --- a/runtime/observatory/lib/src/models/repositories/timeline.dart +++ b/runtime/observatory/lib/src/models/repositories/timeline.dart @@ -8,5 +8,5 @@ abstract class TimelineRepository { Future getFlags(VMRef ref); Future setRecordedStreams(VMRef ref, Iterable streams); Future clear(VMRef ref); - Future getTimeline(VMRef ref); + Future> getIFrameParams(VMRef ref); } diff --git a/runtime/observatory/lib/src/repositories/timeline.dart b/runtime/observatory/lib/src/repositories/timeline.dart index 834e6374fd3..97f0a6da40c 100644 --- a/runtime/observatory/lib/src/repositories/timeline.dart +++ b/runtime/observatory/lib/src/repositories/timeline.dart @@ -29,6 +29,17 @@ class TimelineRepository extends TimelineRepositoryBase return vm.invokeRpc('clearVMTimeline', {}); } + Future> getIFrameParams(M.VMRef ref) async { + S.VM vm = ref as S.VM; + assert(vm != null); + await vm.reload(); + await vm.reloadIsolates(); + return { + 'vmAddress': vm.target.networkAddress, + 'isolateIds': vm.isolates.map((i) => i.id).toList() + }; + } + Future _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 5b431f6d1b9..0ff35f0f4bc 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 @@ 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; @@ -542,6 +553,9 @@ function updateTimeline(events) { } function showLoadingOverlay(msg) { + if (typeof tr === "undefined") { + return; + } if (!loadingOverlay) { loadingOverlay = new tr.ui.b.Overlay(); } @@ -558,6 +572,139 @@ function hideLoadingOverlay() { 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 loadTimeline() { } function refreshTimeline() { - updateTimeline(traceObject); + fetchTimeline(timeline_vm_address, timeline_isolates); } window.addEventListener('DOMContentLoaded', function() { @@ -658,9 +805,9 @@ window.addEventListener('DOMContentLoaded', function() { 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 4b3f11ae274..f2da8bab9f6 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 @@ function onMessage(event) { 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':