[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:
committed by
Commit Queue
parent
16703cdd84
commit
caffe45c7a
@@ -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;
|
||||
|
||||
@@ -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':
|
||||
|
||||
Reference in New Issue
Block a user