Add Logical Frame View to Timeline Dashboard

This version of the Timeline allows the developer to see a logical
view over the operations involved in each frame.
Events are grouped by frame and shifted accordingly to avoid to have
overlapped frame.

See runtime/observatory/web/timeline.js for the undestrand the steps
involded in the process.

Change-Id: I3980a3278a32fe69ed70db07cfa7189dc0c9a643
Reviewed-on: https://dart-review.googlesource.com/5603
Commit-Queue: Carlo Bernaschina <cbernaschina@google.com>
Reviewed-by: Todd Turnidge <turnidge@google.com>
This commit is contained in:
Carlo Bernaschina 2017-09-14 23:53:57 +00:00 committed by commit-bot@chromium.org
parent 8f7f747d9f
commit 62045a4590
8 changed files with 683 additions and 57 deletions

View file

@ -986,14 +986,29 @@ class TimelinePage extends Page {
class TimelineDashboardPage extends Page {
TimelineDashboardPage(app) : super(app);
DivElement container = new DivElement();
void onInstall() {
element = new TimelineDashboardElement(
app.vm, _timelineRepository, app.notifications,
queue: app.queue);
if (element == null) {
element = container;
}
}
void _visit(Uri uri) {
assert(canVisit(uri));
app.vm.load().then((_) {
container.children = [
new TimelineDashboardElement(
app.vm, _timelineRepository, app.notifications,
queue: app.queue)
];
});
}
@override
void onUninstall() {
super.onUninstall();
container.children = const [];
}
bool canVisit(Uri uri) => uri.path == 'timeline-dashboard';

View file

@ -1626,6 +1626,12 @@ timeline-dashboard {
margin-top: -30px;
}
memory-profile > div > h1,
timeline-dashboard > div > h1 {
overflow: hidden;
border-bottom: 1px solid #d5d5d5;
}
memory-dashboard button:disabled,
timeline-dashboard button:disabled,
memory-dashboard button:disabled:hover,
@ -1670,6 +1676,31 @@ timeline-dashboard .header_button:first-child {
margin-left: 5px;
}
memory-profile .tab_buttons,
timeline-dashboard .tab_buttons {
position: relative;
top: 1px;
float: right;
}
memory-profile .tab_buttons button,
timeline-dashboard .tab_buttons button {
padding: 10px 5px;
}
memory-profile .tab_buttons button:not(:first-child),
timeline-dashboard .tab_buttons button:not(:first-child) {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
memory-profile .tab_buttons button:not(:last-child),
timeline-dashboard .tab_buttons button:not(:last-child) {
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}
/* memory-dashboard */
memory-dashboard memory-graph {
@ -1820,32 +1851,6 @@ memory-allocations .collection-item .group button:hover {
border-radius: 0px;
}
/* memory-profile */
memory-profile > div > h1 {
overflow: hidden;
border-bottom: 1px solid #d5d5d5;
}
memory-profile .tab_buttons {
position: relative;
top: 1px;
float: right;
}
memory-profile .tab_buttons button {
padding: 10px 5px;
}
memory-profile .tab_buttons button:not(:first-child) {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
memory-profile .tab_buttons button:not(:last-child) {
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}
/* memory-snapshot */
@ -2549,9 +2554,14 @@ stack-trace-tree-config {
/* timeline-dashboard */
timeline-dashboard .iframe {
height: 100%;
width: 100%;
padding-top: 25px;
position: absolute;
top: 90px;
bottom: 0;
left: 0;
right: 0;
}
timeline-dashboard h1 {
margin-bottom: 5px;
}
timeline-dashboard .iframe iframe {
width: 100%;

View file

@ -2,6 +2,16 @@
// 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.
/// This page is not directly reachable from the main Observatory ui.
/// It is mainly mented to be used from editors as an integrated tool.
///
/// This page mainly targeting developers and not VM experts, so concepts like
/// timeline streams are hidden away.
///
/// The page exposes two views over the timeline data.
/// Both of them are filtered based on the optional argument `mode`.
/// See [_TimelineView] for the explanation of the two possible values.
import 'dart:async';
import 'dart:html';
import 'dart:convert';
@ -11,6 +21,18 @@ import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
import 'package:observatory/src/elements/helpers/tag.dart';
import 'package:observatory/src/elements/nav/notify.dart';
/// The two possible views are available.
/// * `string`
/// The events are just filtered by `mode` and maintain their original
/// timestamp.
/// * `frame`
/// The events are organized by frame.
/// The events are shifted in order to give a high level view of the
/// computation involved in a frame.
/// The frame are concatenated one after the other taking care of not
/// overlapping the related events.
enum _TimelineView { strict, frame }
class TimelineDashboardElement extends HtmlElement implements Renderable {
static const tag = const Tag<TimelineDashboardElement>('timeline-dashboard',
dependencies: const [NavNotifyElement.tag]);
@ -24,6 +46,7 @@ class TimelineDashboardElement extends HtmlElement implements Renderable {
M.TimelineRepository _repository;
M.NotificationRepository _notifications;
M.TimelineFlags _flags;
_TimelineView _view = _TimelineView.strict;
M.VM get vm => _vm;
M.NotificationRepository get notifications => _notifications;
@ -39,6 +62,9 @@ class TimelineDashboardElement extends HtmlElement implements Renderable {
e._vm = vm;
e._repository = repository;
e._notifications = notifications;
if (vm.embedder == 'Flutter') {
e._view = _TimelineView.frame;
}
return e;
}
@ -63,14 +89,22 @@ class TimelineDashboardElement extends HtmlElement implements Renderable {
void render() {
if (_frame == null) {
_frame = new IFrameElement()..src = 'timeline.html#basic';
_frame = new IFrameElement();
}
if (_content == null) {
_content = new DivElement()..classes = ['content-centered-big'];
}
_frame.src = _makeFrameUrl();
_content.children = [
new HeadingElement.h1()
..children = ([new Text("Timeline")]..addAll(_createButtons()))
..children = ([new Text("Timeline")]
..addAll(_createButtons())
..addAll(_createTabs())),
new Text(_view == _TimelineView.frame
? 'Logical view of the computation involved in each frame. '
'(Timestamps may not be preserved)'
: 'Sequence of events generated during the execution. '
'(Timestamps are preserved)')
];
if (children.isEmpty) {
children = [
@ -87,7 +121,7 @@ class TimelineDashboardElement extends HtmlElement implements Renderable {
if (_flags == null) {
return [new Text('Loading')];
}
if (_flags.streams.any((s) => !s.isRecorded)) {
if (_suggestedProfile(_flags.profiles).streams.any((s) => !s.isRecorded)) {
return [
new ButtonElement()
..classes = ['header_button']
@ -125,8 +159,51 @@ class TimelineDashboardElement extends HtmlElement implements Renderable {
];
}
List<Element> _createTabs() {
if (_vm.embedder != 'Flutter') {
return const [];
}
return [
new SpanElement()
..classes = ['tab_buttons']
..children = [
new ButtonElement()
..text = 'Frame View'
..title = 'Logical view of the computation involved in each frame\n'
'Timestamps may not be preserved'
..disabled = _view == _TimelineView.frame
..onClick.listen((_) {
_view = _TimelineView.frame;
_r.dirty();
}),
new ButtonElement()
..text = 'Time View'
..title = 'Sequence of events generated during the execution\n'
'Timestamps are preserved'
..disabled = _view == _TimelineView.strict
..onClick.listen((_) {
_view = _TimelineView.strict;
_r.dirty();
}),
]
];
}
String _makeFrameUrl() {
final String mode = 'basic';
final String view = _view == _TimelineView.frame ? 'frame' : 'strict';
return 'timeline.html#mode=$mode&view=$view';
}
M.TimelineProfile _suggestedProfile(Iterable<M.TimelineProfile> profiles) {
return profiles
.where((profile) => profile.name == 'Flutter Developer')
.single;
}
Future _enable() async {
await _repository.setRecordedStreams(vm, _flags.streams);
await _repository.setRecordedStreams(
vm, _suggestedProfile(_flags.profiles).streams);
_refresh();
}

View file

@ -4742,6 +4742,10 @@ class TimelineFlags implements M.TimelineFlags {
static final Set<String> _dart =
new Set<String>.from(const <String>['GC', 'Compiler', 'Dart']);
// Dart developers care about the following streams:
static final Set<String> _flutter =
new Set<String>.from(const <String>['GC', 'Dart', 'Embedder']);
// VM developers care about the following streams:
static final Set<String> _vm = new Set<String>.from(const <String>[
'GC',
@ -4777,6 +4781,8 @@ class TimelineFlags implements M.TimelineFlags {
const TimelineProfile('None', const []),
new TimelineProfile('Dart Developer',
streams.where((s) => _dart.contains(s.name)).toList()),
new TimelineProfile('Flutter Developer',
streams.where((s) => _flutter.contains(s.name)).toList()),
new TimelineProfile(
'VM Developer', streams.where((s) => _vm.contains(s.name)).toList()),
new TimelineProfile('All', streams),

View file

@ -25,15 +25,515 @@ function onImportFail(err) {
console.log('import failed');
}
function basicModelEventsFilter(event) {
return event.args && event.args.mode === 'basic'; // white-listed
function compareTimestamp(a, b) { return a.ts - b.ts; }
function compareBeginTimestamp(a, b) { return a.begin.ts - b.begin.ts; }
function compareEndTimestamp(a, b) { return a.end.ts - b.end.ts; }
var basicModelEventsWaterfall = [
// Sort events and remove orphan async ends.
function filterUnwantedEvents(events) {
events = events.slice();
events.sort(compareTimestamp);
var threads = {};
return events.filter(function (event) {
if (event.ph === 'E') {
return threads[event.tid] && threads[event.tid].pop();
}
var result = event.args && event.args.mode === 'basic';
if (event.ph === 'B') {
threads[event.tid] = threads[event.tid] || [];
threads[event.tid].push(result);
}
return result;
});
}
];
var frameModelEventsWaterfall = [
// Sort events and remove orphan async ends.
function filterUnwantedEvents(events) {
events = events.slice();
events.sort(compareTimestamp);
var threads = {};
return events.filter(function (event) {
if (event.ph === 'E') {
if (!threads[event.tid]) {
return false
}
threads[event.tid] -= 1;
} else if (event.ph === 'B') {
threads[event.tid] = (threads[event.tid] || 0) + 1;
}
return true;
});
},
// Clone the events (we want to preserve them for dumps).
function cloneDeep(input) {
if (typeof input === 'object') {
if (Array.isArray(input)) {
return input.map(cloneDeep);
} else {
var clone = {};
Object.keys(input).forEach(function (key) {
clone[key] = cloneDeep(input[key]);
});
return clone;
}
}
return input;
},
// Group nested sync begin end sequences on every thread.
//
// Example:
// Input = [B,B,E,B,E,E,B,E,B,B,E,E]
// Output = [[B,B,E,B,E,E],[B,E],[B,B,E,E]]
function groupIsolatedPerThreadSequences(events) {
var sequences = [],
timeless = [],
threadOpen = {};
threadSequences = {};
events.forEach(function (event) {
if (event.ph === 'M') {
timeless.push(event);
} else if (event.ph === 'B') {
threadOpen[event.tid] = Math.max(threadOpen[event.tid] || 0) + 1;
threadSequences[event.tid] = threadSequences[event.tid] || [];
threadSequences[event.tid].push(event);
} else if (event.ph === 'E') {
threadSequences[event.tid].push(event);
threadOpen[event.tid] -= 1;
if (threadOpen[event.tid] == 0) {
threadSequences[event.tid].sort()
sequences.push(threadSequences[event.tid]);
threadSequences[event.tid] = [];
}
} else if (threadSequences[event.tid]){
threadSequences[event.tid] = threadSequences[event.tid] || [];
threadSequences[event.tid].push(event);
}
})
return {
timeless: timeless,
sequences: sequences
};
},
// Transform every sequence into an object for rapid begin end analysis and
// block types lookup.
//
// Example:
// Input = [B1,B2,E2,B3,E3,E1]
// Output = {
// begin: B1,
// end: E1,
// events: [B1,B2,E2,B3,E3,E1],
// isGPU: ...,
// isVSync: ...,
// isFramework: ...,
// isShiftable: ...,
// }
function sequenceToBlockDescriptor(input) {
return {
timeless: input.timeless,
blocks: input.sequences.map(function (events) {
var begin,
end,
isGPU,
isVSync,
isFramework;
events.forEach(function (event) {
if (event.ph === 'B') {
begin = begin || event;
} else if (event.ph === 'E') {
end = event;
}
});
isGPU = begin.name === 'GPU Workload';
isVSync = begin.name === 'VSYNC';
isFramework = begin.name === 'Framework Workload';
return {
begin: begin,
end: end,
events: events,
isGPU: isGPU,
isVSync: isVSync,
isFramework: isFramework,
isShiftable: !(isGPU || isVSync || isFramework)
};
})
};
},
// Remove all the blocks that ended before the first VSYNC.
// These events do not give any information to the analysis.
function removePreVSyncBlocks(input) {
input.blocks.sort(compareEndTimestamp);
var sawVSyncBlock = false;
return {
timeless: input.timeless,
blocks: input.blocks.filter(function (block) {
sawVSyncBlock = sawVSyncBlock || block.isVSync;
return sawVSyncBlock;
})
};
},
// Remove all the GPU blocks that started before the first Framework block.
// They are orphans of other frames.
function removePreFrameworkGPUBlocks(input) {
input.blocks.sort(compareBeginTimestamp);
var firstFrameworkBlockBeginTimestamp = 0;
return {
timeless: input.timeless,
blocks: input.blocks.filter(function (block) {
if (block.isFramework) {
firstFrameworkBlockBeginTimestamp =
firstFrameworkBlockBeginTimestamp || block.begin.ts;
} else if (block.isGPU) {
if (!firstFrameworkBlockBeginTimestamp) {
return false;
} else if (block.begin.ts < firstFrameworkBlockBeginTimestamp) {
return false;
}
}
return true;
})
};
},
// Merge all shiftable blocks that are between two Framework blocks.
// By merging them we preserve their relative timing.
function mergeShiftableBlocks(input) {
input.blocks.sort(compareEndTimestamp);
var begin,
end,
events = [],
shiftableBlocks = [],
blocks;
blocks = input.blocks.filter(function (block) {
if (block.isShiftable) {
begin = begin || block.begin;
end = block.end;
events = events.concat(block.events);
return false;
} else if (block.isFramework) {
if (events.length) {
shiftableBlocks.push({
begin: begin,
end: end,
events: events
});
}
}
return true;
});
if (events.length) {
shiftableBlocks.push({
begin: begin,
end: end,
events: events
});
}
return {
timeless: input.timeless,
blocks: blocks.concat(shiftableBlocks)
};
},
// Remove all VSyncs that didn't started an actual frame.
function filterFramelessVSyncs(input) {
input.blocks.sort(compareBeginTimestamp);
var lastVSyncBlock,
blocks,
vSyncBlocks = [];
blocks = input.blocks.filter(function (block) {
if (block.isVSync) {
lastVSyncBlock = block;
return false;
} else if (block.isFramework) {
vSyncBlocks.push(lastVSyncBlock);
}
return true;
});
return {
timeless: input.timeless,
blocks: blocks.concat(vSyncBlocks)
};
},
// Group blocks by type.
//
// Example:
// Input = [S1, V1, F1, V2, G1, F2, V3, G2, F3]
// Output = {
// gpu: [G1, G2],
// vsync: [V1, V2, V3],
// framework: [F1, F2, F3],
// shiftable: [S1]
// }
function groupBlocksByFrames(input) {
return {
timeless: input.timeless,
gpu: input.blocks.filter(function (b) { return b.isGPU; }),
vsync: input.blocks.filter(function (b) { return b.isVSync; }),
framework: input.blocks.filter(function (b) { return b.isFramework; }),
shiftable: input.blocks.filter(function (b) { return b.isShiftable; })
};
},
// Remove possible out of sync GPU Blocks.
// If the buffer has already delete the VSync and the Framework, but not the
// GPU it can potentially be still alive.
function groupBlocksByFrames(input) {
var gpu = input.gpu,
framework = input.framework;
while (gpu.length &&
gpu[0].begin.args.frame !== framework[0].begin.args.frame) {
gpu.shift();
}
return input;
},
// Group blocks related to the same frame.
// Input = {
// gpu: [G1, G2],
// vsync: [V1, V2],
// framework: [F1, F2],
// shiftable: [S1]
// }
// Output = [{V1, F1, G1, S1}, {V2, F2, G2}, {V3, F3, G3}]
function groupBlocksByFrames(input) {
var shiftable = input.shiftable.slice();
return {
timeless: input.timeless,
frames: input.vsync.map(function (vsync, i) {
var frame = {
begin: vsync.begin,
vsync: vsync,
framework: input.framework[i],
deadline: parseInt(vsync.begin.args.deadline) + 1000
};
if (i < input.gpu.length) {
frame.gpu = input.gpu[i];
}
if (shiftable.length && shiftable[0].begin.ts < framework.begin.ts ) {
frame.shiftable = shiftable.shift();
}
return frame;
})
};
},
// Move Framework and GPU as back in time as possible
//
// Example:
// Before
// [GPU]
// [VSYNC]
// [SHIFTABLE] [FRAMEWORK]
// After
// |[GPU]
// [VSYNC] |
// [SHIFTABLE]|[FRAMEWORK]
function shiftEvents(input) {
input.frames.forEach(function (frame) {
var earlierTimestamp = frame.vsync.end.ts,
shift;
if (frame.shiftable) {
frame.shiftable.events.forEach(function (event) {
if (event.tid === frame.framework.begin.tid) {
earlierTimestamp = Math.max(earlierTimestamp, event.ts);
}
});
}
if (frame.gpu) {
if (frame.shiftable) {
frame.shiftable.events.forEach(function (event) {
if (event.tid === frame.gpu.begin.tid) {
earlierTimestamp = Math.max(earlierTimestamp, event.ts);
}
});
}
shift = earlierTimestamp - frame.gpu.begin.ts;
frame.gpu.events.forEach(function (event) {
event.ts += shift;
});
}
shift = earlierTimestamp - frame.framework.begin.ts;
frame.framework.events.forEach(function (event) {
event.ts += shift;
});
frame.end = frame.framework.end;
if (frame.gpu && frame.framework.end.ts < frame.gpu.end.ts) {
frame.end = frame.gpu.end;
}
});
return input;
},
// Group events in frame (precomputation for next stage).
function groupEventsInFrame(input) {
input.frames.forEach(function (frame) {
var events = frame.vsync.events;
events = events.concat(frame.framework.events);
if (frame.gpu) {
events = events.concat(frame.gpu.events);
}
if (frame.shiftable) {
events = events.concat(frame.shiftable.events);
}
events.sort(compareTimestamp);
frame.events = events;
});
return input;
},
// Move frames in order to do not overlap.
//
// Example:
// Before
// |[GPU1--------------------]
// |[GPU2----]
// [VSYNC1] | [VSYNC2] |
// [SHIFTABLE1]|[FRAMEWORK1] |[FRAMEWORK2]
// After
// |[GPU1--------------------]| |[GPU2-----]
// [VSYNC1] | |[VSYNC2] |
// [SHIFTABLE1]|[FRAMEWORK1] | |[FRAMEWORK2]
// OtherExample:
// Before
// {FRAME BUDGET1-------------------------}
// {FRAME BUDGET2-------------------------}
// |[GPU1]
// |[GPU2]
// [VSYNC1] | [VSYNC2] |
// [SHIFTABLE1]|[FRAMEWORK1] |[FRAMEWORK2]
// After
// {FRAME BUDGET1-------------------------}|{FRAME BUDGET2----------------
// |[GPU1] | |[GPU2-----]
// [VSYNC1] | |[VSYNC2] |
// [SHIFTABLE1]|[FRAMEWORK1] | |[FRAMEWORK2]
function shiftBlocks(input) {
function minThreadtimestamps(frame) {
var timestamps = {};
frame.events.forEach(function (event) {
if (event.tid != undefined) {
timestamps[event.tid] = timestamps[event.tid] || event.ts;
}
});
return timestamps;
}
function maxThreadTimestamps(frame) {
var timestamps = {};
frame.events.forEach(function (event) {
if (event.tid != undefined) {
timestamps[event.tid] = event.ts;
}
});
return timestamps;
}
input.frames.slice(1).forEach(function (current, index) {
var previous = input.frames[index],
shift = Math.max(previous.end.ts, previous.deadline) - current.begin.ts,
maxThreadTimestamp = maxThreadTimestamps(previous),
minThreadTimestamp = minThreadtimestamps(current);
Object.keys(maxThreadTimestamp).forEach(function (tid) {
if (minThreadTimestamp[tid]) {
var delta = maxThreadTimestamp[tid] - minThreadTimestamp[tid];
shift = Math.max(shift, delta);
}
});
current.events.forEach(function (event) {
event.ts += shift;
});
current.deadline += shift;
});
return input;
},
// Add auxilary events to frame (Frame Budget and Frame Length).
// Example:
// Before
// |[GPU1--------------------]| |[GPU2-----]
// [VSYNC1] | |[VSYNC2] |
// [SHIFTABLE1]|[FRAMEWORK1] | |[FRAMEWORK2]
// After
// [Budget1---------------------------]|[Budget2--------------------------
// [Length1---------------------------]|[Length2------------]
// |[GPU1--------------------]| |[GPU2-----]
// [VSYNC1] | |[VSYNC2] |
// [SHIFTABLE1]|[FRAMEWORK1] | |[FRAMEWORK2]
function addAuxilaryEvents(input) {
input.frames.forEach(function (frame) {
frame.events.unshift({
args: {name: "Frame Budgets"},
name: "thread_name",
ph: "M",
pid: frame.begin.pid,
tid: "budgets",
});
frame.events.unshift({
args: {name: "Frames"},
name: "thread_name",
ph: "M",
pid: frame.begin.pid,
tid: "frames",
});
var duration = Math.floor((frame.end.ts - frame.begin.ts) / 1000),
frameName = "Frame " + duration + "ms";
frame.events = frame.events.concat({
ph: "B",
name: "Frame Budget",
cat: "budgets",
pid: frame.begin.pid,
tid: "budgets",
ts: frame.begin.ts
}, {
ph: "E",
name: "Frame Budget",
cat: "budgets",
pid: frame.begin.pid,
tid: "budgets",
ts: frame.deadline,
cname: 'rail_response'
}, {
ph: "B",
name: frameName,
cat: "frames",
pid: frame.begin.pid,
tid: "frames",
ts: frame.begin.ts
}, {
ph: "E",
name: frameName,
cat: "frames",
pid: frame.begin.pid,
tid: "frames",
ts: frame.end.ts,
cname: frame.end.ts > frame.deadline ? 'terrible' : 'good'
});
});
return input;
},
// Restore the events array used by catapult.
function linearizeBlocks(input) {
return input.frames.reduce(function (events, frame) {
return events.concat(frame.events);
}, input.timeless);
}
];
function basicModelEventsMap(events) {
return basicModelEventsWaterfall.reduce(function (input, step) {
return step(input);
}, events);
}
function frameModelEventsMap(events) {
return frameModelEventsWaterfall.reduce(function (input, step) {
return step(input);
}, events);
}
function updateTimeline(events) {
if (window.location.hash === '#basic') {
if (window.location.hash.indexOf('mode=basic') > -1) {
events = {
'stackFrames': events['stackFrames'],
'traceEvents': events['traceEvents'].filter(basicModelEventsFilter)
'traceEvents': basicModelEventsMap(events['traceEvents'])
};
}
if (window.location.hash.indexOf('view=frame') > -1) {
events = {
'stackFrames': events['stackFrames'],
'traceEvents': frameModelEventsMap(events['traceEvents'])
};
}
var model = new tr.Model();
@ -244,6 +744,10 @@ function loadTimeline() {
inputElement.click();
}
function refreshTimeline() {
updateTimeline(traceObject);
}
window.addEventListener('DOMContentLoaded', function() {
var container = document.createElement('track-view-container');
container.id = 'track_view_container';
@ -255,6 +759,7 @@ window.addEventListener('DOMContentLoaded', function() {
document.body.appendChild(viewer);
timeline_loaded = true;
console.log('DOMContentLoaded');
document.getElementById('trace-viewer').highlightVSync = true;
if (timeline_vm_address != undefined) {
console.log('Triggering delayed timeline refresh.');
fetchTimeline(timeline_vm_address, timeline_isolates);

View file

@ -13,6 +13,7 @@ timeline_isolates = undefined;
function registerForMessages() {
window.addEventListener("message", onMessage, false);
window.addEventListener("hashchange", onHashChange, false);
}
registerForMessages();
@ -46,4 +47,8 @@ function onMessage(event) {
}
}
function onHashChange() {
refreshTimeline();
}
console.log('message handler registered');

View file

@ -391,7 +391,7 @@ void Heap::CollectNewSpaceGarbage(Thread* thread,
RecordBeforeGC(kNew, reason);
{
VMTagScope tagScope(thread, VMTag::kGCNewSpaceTagId);
TIMELINE_FUNCTION_GC_DURATION(thread, "CollectNewGeneration");
TIMELINE_FUNCTION_GC_DURATION_BASIC(thread, "CollectNewGeneration");
NOT_IN_PRODUCT(UpdateClassHeapStatsBeforeGC(kNew));
new_space_.Scavenge();
NOT_IN_PRODUCT(isolate()->class_table()->UpdatePromoted());
@ -413,7 +413,7 @@ void Heap::CollectOldSpaceGarbage(Thread* thread,
if (BeginOldSpaceGC(thread)) {
RecordBeforeGC(kOld, reason);
VMTagScope tagScope(thread, VMTag::kGCOldSpaceTagId);
TIMELINE_FUNCTION_GC_DURATION(thread, "CollectOldGeneration");
TIMELINE_FUNCTION_GC_DURATION_BASIC(thread, "CollectOldGeneration");
NOT_IN_PRODUCT(UpdateClassHeapStatsBeforeGC(kOld));
old_space_.MarkSweep();
RecordAfterGC(kOld);
@ -804,32 +804,33 @@ void Heap::PrintStatsToTimeline(TimelineEventScope* event) {
if ((event == NULL) || !event->enabled()) {
return;
}
event->SetNumArguments(12);
event->FormatArgument(0, "Before.New.Used (kB)", "%" Pd "",
intptr_t arguments = event->GetNumArguments();
event->SetNumArguments(arguments + 12);
event->FormatArgument(arguments + 0, "Before.New.Used (kB)", "%" Pd "",
RoundWordsToKB(stats_.before_.new_.used_in_words));
event->FormatArgument(1, "After.New.Used (kB)", "%" Pd "",
event->FormatArgument(arguments + 1, "After.New.Used (kB)", "%" Pd "",
RoundWordsToKB(stats_.after_.new_.used_in_words));
event->FormatArgument(2, "Before.Old.Used (kB)", "%" Pd "",
event->FormatArgument(arguments + 2, "Before.Old.Used (kB)", "%" Pd "",
RoundWordsToKB(stats_.before_.old_.used_in_words));
event->FormatArgument(3, "After.Old.Used (kB)", "%" Pd "",
event->FormatArgument(arguments + 3, "After.Old.Used (kB)", "%" Pd "",
RoundWordsToKB(stats_.after_.old_.used_in_words));
event->FormatArgument(4, "Before.New.Capacity (kB)", "%" Pd "",
event->FormatArgument(arguments + 4, "Before.New.Capacity (kB)", "%" Pd "",
RoundWordsToKB(stats_.before_.new_.capacity_in_words));
event->FormatArgument(5, "After.New.Capacity (kB)", "%" Pd "",
event->FormatArgument(arguments + 5, "After.New.Capacity (kB)", "%" Pd "",
RoundWordsToKB(stats_.after_.new_.capacity_in_words));
event->FormatArgument(6, "Before.Old.Capacity (kB)", "%" Pd "",
event->FormatArgument(arguments + 6, "Before.Old.Capacity (kB)", "%" Pd "",
RoundWordsToKB(stats_.before_.old_.capacity_in_words));
event->FormatArgument(7, "After.Old.Capacity (kB)", "%" Pd "",
event->FormatArgument(arguments + 7, "After.Old.Capacity (kB)", "%" Pd "",
RoundWordsToKB(stats_.after_.old_.capacity_in_words));
event->FormatArgument(8, "Before.New.External (kB)", "%" Pd "",
event->FormatArgument(arguments + 8, "Before.New.External (kB)", "%" Pd "",
RoundWordsToKB(stats_.before_.new_.external_in_words));
event->FormatArgument(9, "After.New.External (kB)", "%" Pd "",
event->FormatArgument(arguments + 9, "After.New.External (kB)", "%" Pd "",
RoundWordsToKB(stats_.after_.new_.external_in_words));
event->FormatArgument(10, "Before.Old.External (kB)", "%" Pd "",
event->FormatArgument(arguments + 10, "Before.Old.External (kB)", "%" Pd "",
RoundWordsToKB(stats_.before_.old_.external_in_words));
event->FormatArgument(11, "After.Old.External (kB)", "%" Pd "",
event->FormatArgument(arguments + 11, "After.Old.External (kB)", "%" Pd "",
RoundWordsToKB(stats_.after_.old_.external_in_words));
#endif // !defined(PRODUCT)
}

View file

@ -149,7 +149,7 @@ class TimelineEventArguments {
public:
TimelineEventArguments() : buffer_(NULL), length_(0) {}
~TimelineEventArguments() { Free(); }
// Set the number of arguments in the event.
// Get/Set the number of arguments in the event.
void SetNumArguments(intptr_t length);
// |name| must be a compile time constant. Takes ownership of |argument|.
void SetArgument(intptr_t i, const char* name, char* argument);
@ -268,7 +268,8 @@ class TimelineEvent {
// Completes this event with pre-serialized JSON. Copies |json|.
void CompleteWithPreSerializedJSON(const char* json);
// Set the number of arguments in the event.
// Get/Set the number of arguments in the event.
intptr_t GetNumArguments() { return arguments_.length(); }
void SetNumArguments(intptr_t length) { arguments_.SetNumArguments(length); }
// |name| must be a compile time constant. Takes ownership of |argument|.
void SetArgument(intptr_t i, const char* name, char* argument) {
@ -457,9 +458,14 @@ class TimelineEvent {
#define TIMELINE_FUNCTION_GC_DURATION(thread, name) \
TimelineDurationScope tds(thread, Timeline::GetGCStream(), name);
#define TIMELINE_FUNCTION_GC_DURATION_BASIC(thread, name) \
TIMELINE_FUNCTION_GC_DURATION(thread, name) \
tds.SetNumArguments(1); \
tds.CopyArgument(0, "mode", "basic");
#else
#define TIMELINE_FUNCTION_COMPILATION_DURATION(thread, name, function)
#define TIMELINE_FUNCTION_GC_DURATION(thread, name)
#define TIMELINE_FUNCTION_GC_DURATION_BASIC(thread, name)
#endif // !PRODUCT
// See |TimelineDurationScope| and |TimelineBeginEndScope|.
@ -467,6 +473,7 @@ class TimelineEventScope : public StackResource {
public:
bool enabled() const { return enabled_; }
intptr_t GetNumArguments() { return arguments_.length(); }
void SetNumArguments(intptr_t length);
void SetArgument(intptr_t i, const char* name, char* argument);