mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
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:
parent
8f7f747d9f
commit
62045a4590
8 changed files with 683 additions and 57 deletions
|
@ -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';
|
||||
|
|
|
@ -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%;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue