[observatory] Dramatically reduce timeline loading time.

Restore fetching from the Catapult IFrame instead of the main frame to avoid the postMessage bottleneck.

Cf. 68dede011e

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>
This commit is contained in:
Ryan Macnak
2022-10-10 19:03:02 +00:00
committed by Commit Queue
parent 16703cdd84
commit caffe45c7a
5 changed files with 175 additions and 11 deletions
@@ -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<String, dynamic>.from(await _repository.getIFrameParams(vm));
return _postMessage('refresh', params);
}
Future _clear() async {
@@ -8,5 +8,5 @@ abstract class TimelineRepository {
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);
}
@@ -29,6 +29,17 @@ class TimelineRepository extends TimelineRepositoryBase
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;
+150 -3
View File
@@ -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;
}
});
@@ -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':