From c576e700cbe3926997af6ffa1bfa43505113d7bc Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Fri, 7 Sep 2018 12:35:24 -0700 Subject: [PATCH 1/6] improvements to the timeline protocol parsing --- lib/timeline/timeline.dart | 347 +-------------------------- lib/timeline/timeline_protocol.dart | 351 ++++++++++++++++++++++++++++ 2 files changed, 353 insertions(+), 345 deletions(-) create mode 100644 lib/timeline/timeline_protocol.dart diff --git a/lib/timeline/timeline.dart b/lib/timeline/timeline.dart index a8ad7c5b736..9520a374f02 100644 --- a/lib/timeline/timeline.dart +++ b/lib/timeline/timeline.dart @@ -5,13 +5,14 @@ import 'dart:async'; import 'dart:math' as math; -import 'package:vm_service_lib/vm_service_lib.dart'; +import 'package:vm_service_lib/vm_service_lib.dart' hide TimelineEvent; import '../framework/framework.dart'; import '../globals.dart'; import '../ui/elements.dart'; import '../ui/primer.dart'; import 'fps.dart'; +import 'timeline_protocol.dart'; // TODO(devoncarew): expose perf debugging toggles (ext.flutter.repaintRainbow, // ext.flutter.showPerformanceOverlay, others) @@ -222,297 +223,6 @@ class TimelineScreen extends Screen { new HelpInfo(title: 'timeline docs', url: 'http://www.cheese.com'); } -class TimelineData { - final List threads; - final Map threadMap = {}; - - final Map threadData = {}; - - TimelineData(this.threads) { - for (TimelineThread thread in threads) { - threadMap[thread.threadId] = thread; - threadData[thread.threadId] = new TimelineThreadData(this); - } - } - - void processTimelineEvent(TimelineEvent event) { - final TimelineThread thread = threadMap[event.threadId]; - if (thread == null) { - return; - } - - final TimelineThreadData data = threadData[event.threadId]; - - switch (event.phase) { - case 'B': - data.handleDurationBeginEvent(event); - break; - case 'E': - data.handleDurationEndEvent(event); - break; - case 'X': - data.handleCompleteEvent(event); - break; - - default: - // TODO: - print('unhandled phase: ${event.phase}'); - break; - } - } - - TimelineFrameData getFrameData(TimelineFrame frame) { - if (frame == null) { - return null; - } - - final List events = []; - - for (TimelineThreadData data in threadData.values) { - for (TEvent event in data.events) { - if (!event.wellFormed) { - continue; - } - - if (event.endMicros >= frame.start && event.startMicros < frame.end) { - events.add(event); - } - } - } - - return TimelineFrameData(frame, threads, events); - } - - void printData() { - for (TimelineThread thread in threads) { - print('${thread.name}:'); - final StringBuffer buf = new StringBuffer(); - final TimelineThreadData data = threadData[thread.threadId]; - - for (TEvent event in data.events) { - event.format(buf, ' '); - print(buf.toString().trimRight()); - buf.clear(); - } - - print(''); - } - } -} - -class TimelineFrameData { - final TimelineFrame frame; - final List threads; - final List events; - - TimelineFrameData(this.frame, this.threads, this.events); - - void printData() { - for (TimelineThread thread in threads) { - print('${thread.name}:'); - final StringBuffer buf = new StringBuffer(); - - for (TEvent event in events) { - if (event.threadId == thread.threadId) { - event.format(buf, ' '); - print(buf.toString().trimRight()); - buf.clear(); - } - } - } - } - - Iterable eventsForThread(TimelineThread thread) { - return events.where((TEvent event) => event.threadId == thread.threadId); - } -} - -class TimelineThreadData { - final TimelineData parent; - final List events = []; - - TimelineThreadData(this.parent); - - List durationStack = []; - - void handleDurationBeginEvent(TimelineEvent event) { - final TEvent e = new TEvent(event.threadId, event.name); - e.setStart(event.timestampMicros); - - if (durationStack.isEmpty) { - events.add(e); - } else { - durationStack.last.children.add(e); - } - - durationStack.add(e); - } - - void handleDurationEndEvent(TimelineEvent event) { - if (durationStack.isNotEmpty) { - final TEvent e = durationStack.removeLast(); - e.setEnd(event.timestampMicros); - } - } - - void handleCompleteEvent(TimelineEvent event) { - final TEvent e = new TEvent(event.threadId, event.name); - e.setStart(event.timestampMicros); - e.durationMicros = event.duration; - - if (durationStack.isEmpty) { - events.add(e); - } else { - durationStack.last.children.add(e); - } - } -} - -class TEvent { - final int threadId; - final String name; - - List children = []; - - int startMicros; - int durationMicros; - - TEvent(this.threadId, this.name); - - void setStart(int micros) { - startMicros = micros; - } - - void setEnd(int micros) { - durationMicros = micros - startMicros; - } - - int get endMicros => startMicros + (durationMicros ?? 0); - - bool get wellFormed => startMicros != null && durationMicros != null; - - void format(StringBuffer buf, String indent) { - buf.writeln('$indent$name'); - for (TEvent child in children) { - child.format(buf, ' $indent'); - } - } - - @override - String toString() => '$name, start=$startMicros duration=$durationMicros'; -} - -class TimelineThread implements Comparable { - final int threadId; - - String _name; - - TimelineThread(String name, this.threadId) { - _name = name; - - // "name":"io.flutter.1.ui (42499)", - if (name.contains(' (') && name.endsWith(')')) { - _name = name.substring(0, _name.lastIndexOf(' (')); - } - } - - bool get isVisible => name.startsWith('io.flutter.'); - - int get category { - if (name.endsWith('.ui')) { - return 1; - } - if (name.endsWith('.gpu')) { - return 2; - } - if (name.startsWith('io.flutter.')) { - return 3; - } - return 4; - } - - String get name => _name; - - @override - String toString() => name; - - @override - int compareTo(TimelineThread other) { - final int c1 = category; - final int c2 = other.category; - if (c1 != c2) { - return c1 - c2; - } - return name.compareTo(other.name); - } -} - -/// A single timeline event. -class TimelineEvent { - /// Creates a timeline event given JSON-encoded event data. - factory TimelineEvent(Map json) { - return new TimelineEvent._(json, json['name'], json['cat'], json['ph'], - json['pid'], json['tid'], json['dur'], json['ts'], json['args']); - } - - TimelineEvent._( - this.json, - this.name, - this.category, - this.phase, - this.processId, - this.threadId, - this.duration, - this.timestampMicros, - this.args); - - /// The original event JSON. - final Map json; - - /// The name of the event. - /// - /// Corresponds to the "name" field in the JSON event. - final String name; - - /// Event category. Events with different names may share the same category. - /// - /// Corresponds to the "cat" field in the JSON event. - final String category; - - /// For a given long lasting event, denotes the phase of the event, such as - /// "B" for "event began", and "E" for "event ended". - /// - /// Corresponds to the "ph" field in the JSON event. - final String phase; - - /// ID of process that emitted the event. - /// - /// Corresponds to the "pid" field in the JSON event. - final int processId; - - /// ID of thread that issues the event. - /// - /// Corresponds to the "tid" field in the JSON event. - final int threadId; - - /// The duration of the event, in microseconds. - /// - /// Note, some events are reported with duration. Others are reported as a - /// pair of begin/end events. - /// - /// Corresponds to the "dur" field in the JSON event. - final int duration; - - /// Time passed since tracing was enabled, in microseconds. - final int timestampMicros; - - /// Arbitrary data attached to the event. - final Map args; - - @override - String toString() => '[$category] [$phase] $name'; -} - class TimelineFramesUI extends CoreElement { TimelineFrameUI selectedFrame; TimelineData timelineData; @@ -689,59 +399,6 @@ class TimelineFramesBuilder { } } -class TimelineFrame { - int renderStart; - int renderDuration; - - int rastereizeStart; - int rastereizeDuration; - - TimelineFrame(); - - int get start => renderStart ?? rastereizeStart; - - int get end { - if (rastereizeStart != null) { - return rastereizeStart + rastereizeDuration; - } else { - return renderStart + renderDuration; - } - } - - void setRenderStart(int micros) { - renderStart = micros; - } - - void setRenderEnd(int micros) { - renderDuration = micros - renderStart; - } - - void setRastereizeStart(int micros) { - rastereizeStart = micros; - } - - void setRastereizeEnd(int micros) { - if (rastereizeStart != null) { - rastereizeDuration = micros - rastereizeStart; - } - } - - bool get isComplete => renderDuration != null && rastereizeDuration != null; - - String get renderAsMs { - return '${(renderDuration / 1000.0).toStringAsFixed(1)}ms'; - } - - String get gpuAsMs { - return '${(rastereizeDuration / 1000.0).toStringAsFixed(1)}ms'; - } - - @override - String toString() { - return 'frame render: $renderDuration rasterize: $rastereizeDuration'; - } -} - class FrameDetailsUI extends CoreElement { TimelineFrameData data; diff --git a/lib/timeline/timeline_protocol.dart b/lib/timeline/timeline_protocol.dart new file mode 100644 index 00000000000..a7df3e6b62c --- /dev/null +++ b/lib/timeline/timeline_protocol.dart @@ -0,0 +1,351 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// TODO: tests + +class TimelineData { + final List threads; + final Map threadMap = {}; + + final Map threadData = {}; + + TimelineData(this.threads) { + for (TimelineThread thread in threads) { + threadMap[thread.threadId] = thread; + threadData[thread.threadId] = new TimelineThreadData(this); + } + } + + void processTimelineEvent(TimelineEvent event) { + final TimelineThread thread = threadMap[event.threadId]; + if (thread == null) { + return; + } + + final TimelineThreadData data = threadData[event.threadId]; + + switch (event.phase) { + case 'B': + data.handleDurationBeginEvent(event); + break; + case 'E': + data.handleDurationEndEvent(event); + break; + case 'X': + data.handleCompleteEvent(event); + break; + + default: + // TODO(devoncarew): Support additional phases. + print('unhandled phase: ${event.phase}'); + break; + } + } + + TimelineFrameData getFrameData(TimelineFrame frame) { + if (frame == null) { + return null; + } + + final List events = []; + + for (TimelineThreadData data in threadData.values) { + for (TEvent event in data.events) { + if (!event.wellFormed) { + continue; + } + + if (event.endMicros >= frame.start && event.startMicros < frame.end) { + events.add(event); + } + } + } + + return TimelineFrameData(frame, threads, events); + } + + void printData() { + for (TimelineThread thread in threads) { + print('${thread.name}:'); + final StringBuffer buf = new StringBuffer(); + final TimelineThreadData data = threadData[thread.threadId]; + + for (TEvent event in data.events) { + event.format(buf, ' '); + print(buf.toString().trimRight()); + buf.clear(); + } + + print(''); + } + } +} + +class TimelineThread implements Comparable { + final int threadId; + + String _name; + + TimelineThread(String name, this.threadId) { + _name = name; + + // "name":"io.flutter.1.ui (42499)", + if (name.contains(' (') && name.endsWith(')')) { + _name = name.substring(0, _name.lastIndexOf(' (')); + } + } + + bool get isVisible => name.startsWith('io.flutter.'); + + int get category { + if (name.endsWith('.ui')) { + return 1; + } + if (name.endsWith('.gpu')) { + return 2; + } + if (name.startsWith('io.flutter.')) { + return 3; + } + return 4; + } + + String get name => _name; + + @override + String toString() => name; + + @override + int compareTo(TimelineThread other) { + final int c1 = category; + final int c2 = other.category; + if (c1 != c2) { + return c1 - c2; + } + return name.compareTo(other.name); + } +} + +class TimelineThreadData { + final TimelineData parent; + final List events = []; + + TimelineThreadData(this.parent); + + List durationStack = []; + + void handleDurationBeginEvent(TimelineEvent event) { + final TEvent e = new TEvent(event.threadId, event.name); + e.setStart(event.timestampMicros); + + if (durationStack.isEmpty) { + events.add(e); + } else { + durationStack.last.children.add(e); + } + + durationStack.add(e); + } + + void handleDurationEndEvent(TimelineEvent event) { + if (durationStack.isNotEmpty) { + final TEvent e = durationStack.removeLast(); + e.setEnd(event.timestampMicros); + } + } + + void handleCompleteEvent(TimelineEvent event) { + final TEvent e = new TEvent(event.threadId, event.name); + e.setStart(event.timestampMicros); + e.durationMicros = event.duration; + + if (durationStack.isEmpty) { + events.add(e); + } else { + durationStack.last.children.add(e); + } + } +} + +// TODO: rename to timelinethreadevent +class TEvent { + final int threadId; + final String name; + + List children = []; + + int startMicros; + int durationMicros; + + TEvent(this.threadId, this.name); + + void setStart(int micros) { + startMicros = micros; + } + + void setEnd(int micros) { + durationMicros = micros - startMicros; + } + + int get endMicros => startMicros + (durationMicros ?? 0); + + bool get wellFormed => startMicros != null && durationMicros != null; + + void format(StringBuffer buf, String indent) { + buf.writeln('$indent$name'); + for (TEvent child in children) { + child.format(buf, ' $indent'); + } + } + + @override + String toString() => '$name, start=$startMicros duration=$durationMicros'; +} + +class TimelineFrame { + int renderStart; + int renderDuration; + + int rastereizeStart; + int rastereizeDuration; + + TimelineFrame(); + + int get start => renderStart ?? rastereizeStart; + + int get end { + if (rastereizeStart != null) { + return rastereizeStart + rastereizeDuration; + } else { + return renderStart + renderDuration; + } + } + + void setRenderStart(int micros) { + renderStart = micros; + } + + void setRenderEnd(int micros) { + renderDuration = micros - renderStart; + } + + void setRastereizeStart(int micros) { + rastereizeStart = micros; + } + + void setRastereizeEnd(int micros) { + if (rastereizeStart != null) { + rastereizeDuration = micros - rastereizeStart; + } + } + + bool get isComplete => renderDuration != null && rastereizeDuration != null; + + String get renderAsMs { + return '${(renderDuration / 1000.0).toStringAsFixed(1)}ms'; + } + + String get gpuAsMs { + return '${(rastereizeDuration / 1000.0).toStringAsFixed(1)}ms'; + } + + @override + String toString() { + return 'frame render: $renderDuration rasterize: $rastereizeDuration'; + } +} + +class TimelineFrameData { + final TimelineFrame frame; + final List threads; + final List events; + + TimelineFrameData(this.frame, this.threads, this.events); + + void printData() { + for (TimelineThread thread in threads) { + print('${thread.name}:'); + final StringBuffer buf = new StringBuffer(); + + for (TEvent event in events) { + if (event.threadId == thread.threadId) { + event.format(buf, ' '); + print(buf.toString().trimRight()); + buf.clear(); + } + } + } + } + + Iterable eventsForThread(TimelineThread thread) { + return events.where((TEvent event) => event.threadId == thread.threadId); + } +} + +/// A single timeline event. +class TimelineEvent { + /// Creates a timeline event given JSON-encoded event data. + factory TimelineEvent(Map json) { + return new TimelineEvent._(json, json['name'], json['cat'], json['ph'], + json['pid'], json['tid'], json['dur'], json['ts'], json['args']); + } + + TimelineEvent._( + this.json, + this.name, + this.category, + this.phase, + this.processId, + this.threadId, + this.duration, + this.timestampMicros, + this.args, + ); + + /// The original event JSON. + final Map json; + + /// The name of the event. + /// + /// Corresponds to the "name" field in the JSON event. + final String name; + + /// Event category. Events with different names may share the same category. + /// + /// Corresponds to the "cat" field in the JSON event. + final String category; + + /// For a given long lasting event, denotes the phase of the event, such as + /// "B" for "event began", and "E" for "event ended". + /// + /// Corresponds to the "ph" field in the JSON event. + final String phase; + + /// ID of process that emitted the event. + /// + /// Corresponds to the "pid" field in the JSON event. + final int processId; + + /// ID of thread that issues the event. + /// + /// Corresponds to the "tid" field in the JSON event. + final int threadId; + + /// The duration of the event, in microseconds. + /// + /// Note, some events are reported with duration. Others are reported as a + /// pair of begin/end events. + /// + /// Corresponds to the "dur" field in the JSON event. + final int duration; + + /// Time passed since tracing was enabled, in microseconds. + final int timestampMicros; + + /// Arbitrary data attached to the event. + final Map args; + + @override + String toString() => '[$category] [$phase] $name'; +} From eaad5eff0136ef3d08dbe8027f85ffa580fbc20d Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 19 Sep 2018 08:25:27 -0700 Subject: [PATCH 2/6] checkpoint --- lib/timeline/timeline.dart | 23 ++-- lib/timeline/timeline_protocol.dart | 176 ++++++++++++++++++++-------- 2 files changed, 139 insertions(+), 60 deletions(-) diff --git a/lib/timeline/timeline.dart b/lib/timeline/timeline.dart index f224a0a709f..c3ffc81b45e 100644 --- a/lib/timeline/timeline.dart +++ b/lib/timeline/timeline.dart @@ -228,16 +228,17 @@ class TimelineScreen extends Screen { return event.name == 'thread_name'; }).toList(); - List threads = events - .map((TimelineEvent event) => - new TimelineThread(event.args['name'], event.threadId)) - .toList(); + final TimelineData timelineData = new TimelineData(); - threads = - threads.where((TimelineThread thread) => thread.isVisible).toList(); - threads.sort(); + for (TimelineEvent event in events) { + final TimelineThread thread = + new TimelineThread(timelineData, event.args['name'], event.threadId); + if (thread.isVisible) { + timelineData.addThread(thread); + } + } - timelineFramesUI.timelineData = new TimelineData(threads); + timelineFramesUI.timelineData = timelineData; } @override @@ -510,7 +511,7 @@ class FrameDetailsUI extends CoreElement { Function drawRecursively; - drawRecursively = (TEvent event, int row) { + drawRecursively = (TimelineThreadEvent event, int row) { if (!event.wellFormed) { print('event not well formed'); print(event); @@ -529,7 +530,7 @@ class FrameDetailsUI extends CoreElement { maxRow = row; } - for (TEvent child in event.children) { + for (TimelineThreadEvent child in event.children) { drawRecursively(child, row + 1); } }; @@ -540,7 +541,7 @@ class FrameDetailsUI extends CoreElement { maxRow = row; - for (TEvent event in data.eventsForThread(thread)) { + for (TimelineThreadEvent event in data.eventsForThread(thread)) { drawRecursively(event, row); } diff --git a/lib/timeline/timeline_protocol.dart b/lib/timeline/timeline_protocol.dart index a7df3e6b62c..24417c45e47 100644 --- a/lib/timeline/timeline_protocol.dart +++ b/lib/timeline/timeline_protocol.dart @@ -4,17 +4,21 @@ // TODO: tests +// TODO: support additional phases (s, t, f) + +// For documentation, see the Chrome "Trace Event Format" document: +// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview + class TimelineData { - final List threads; + final List threads = []; final Map threadMap = {}; - final Map threadData = {}; + TimelineData(); - TimelineData(this.threads) { - for (TimelineThread thread in threads) { - threadMap[thread.threadId] = thread; - threadData[thread.threadId] = new TimelineThreadData(this); - } + void addThread(TimelineThread thread) { + threads.add(thread); + threadMap[thread.threadId] = thread; + threads.sort(); } void processTimelineEvent(TimelineEvent event) { @@ -23,22 +27,30 @@ class TimelineData { return; } - final TimelineThreadData data = threadData[event.threadId]; - switch (event.phase) { case 'B': - data.handleDurationBeginEvent(event); + thread._handleDurationBeginEvent(event); break; case 'E': - data.handleDurationEndEvent(event); + thread._handleDurationEndEvent(event); break; case 'X': - data.handleCompleteEvent(event); + thread._handleDurationCompleteEvent(event); + break; + + case 'b': + thread._handleAsyncBeginEvent(event); + break; + case 'n': + thread._handleAsyncInstantEvent(event); + break; + case 'e': + thread._handleAsyncEndEvent(event); break; default: // TODO(devoncarew): Support additional phases. - print('unhandled phase: ${event.phase}'); + print('unhandled: ${event.json}'); break; } } @@ -48,10 +60,10 @@ class TimelineData { return null; } - final List events = []; + final List events = []; - for (TimelineThreadData data in threadData.values) { - for (TEvent event in data.events) { + for (TimelineThread thread in threads) { + for (TimelineThreadEvent event in thread.events) { if (!event.wellFormed) { continue; } @@ -69,9 +81,8 @@ class TimelineData { for (TimelineThread thread in threads) { print('${thread.name}:'); final StringBuffer buf = new StringBuffer(); - final TimelineThreadData data = threadData[thread.threadId]; - for (TEvent event in data.events) { + for (TimelineThreadEvent event in thread.events) { event.format(buf, ' '); print(buf.toString().trimRight()); buf.clear(); @@ -83,11 +94,19 @@ class TimelineData { } class TimelineThread implements Comparable { + final TimelineData parent; final int threadId; + final List events = []; + + TimelineThreadEvent durationStack; + + final Map _asyncEvents = + {}; + String _name; - TimelineThread(String name, this.threadId) { + TimelineThread(this.parent, String name, this.threadId) { _name = name; // "name":"io.flutter.1.ui (42499)", @@ -125,60 +144,99 @@ class TimelineThread implements Comparable { } return name.compareTo(other.name); } -} -class TimelineThreadData { - final TimelineData parent; - final List events = []; + void _handleDurationBeginEvent(TimelineEvent event) { + final TimelineThreadEvent e = + new TimelineThreadEvent(event.threadId, event.name); + e.setStart(event.timestampMicros); - TimelineThreadData(this.parent); + if (durationStack == null) { + events.add(e); + durationStack = e; + } else { + durationStack.children.add(e); + e.parent = durationStack; + durationStack = e; + } + } - List durationStack = []; + void _handleDurationEndEvent(TimelineEvent event) { + if (durationStack != null) { + durationStack.setEnd(event.timestampMicros); + durationStack = durationStack.parent; + } + } - void handleDurationBeginEvent(TimelineEvent event) { - final TEvent e = new TEvent(event.threadId, event.name); + void _handleDurationCompleteEvent(TimelineEvent event) { + final TimelineThreadEvent e = + new TimelineThreadEvent(event.threadId, event.name); e.setStart(event.timestampMicros); + e.durationMicros = event.duration; - if (durationStack.isEmpty) { + if (durationStack == null) { events.add(e); } else { - durationStack.last.children.add(e); + durationStack.children.add(e); } - - durationStack.add(e); } - void handleDurationEndEvent(TimelineEvent event) { - if (durationStack.isNotEmpty) { - final TEvent e = durationStack.removeLast(); - e.setEnd(event.timestampMicros); + void _handleAsyncBeginEvent(TimelineEvent event) { + final String asyncUID = event.asyncUID; + + final TimelineThreadEvent parentEvent = _asyncEvents[asyncUID]; + if (parentEvent == null) { + final TimelineThreadEvent e = + new TimelineThreadEvent(event.threadId, event.name); + e.setStart(event.timestampMicros); + _asyncEvents[asyncUID] = e; + events.add(e); + } else { + final TimelineThreadEvent e = + new TimelineThreadEvent(event.threadId, event.name); + e.setStart(event.timestampMicros); + e.parent = parentEvent; + _asyncEvents[asyncUID] = e; } } - void handleCompleteEvent(TimelineEvent event) { - final TEvent e = new TEvent(event.threadId, event.name); + void _handleAsyncInstantEvent(TimelineEvent event) { + final String asyncUID = event.asyncUID; + + final TimelineThreadEvent e = + new TimelineThreadEvent(event.threadId, event.name); e.setStart(event.timestampMicros); e.durationMicros = event.duration; - if (durationStack.isEmpty) { - events.add(e); + final TimelineThreadEvent parent = _asyncEvents[asyncUID]; + if (parent != null) { + e.parent = parent; } else { - durationStack.last.children.add(e); + events.add(e); + } + } + + void _handleAsyncEndEvent(TimelineEvent event) { + final String asyncUID = event.asyncUID; + + final TimelineThreadEvent e = _asyncEvents[asyncUID]; + if (e != null) { + e.setEnd(event.timestampMicros); + _asyncEvents[asyncUID] = e.parent; } } } -// TODO: rename to timelinethreadevent -class TEvent { +class TimelineThreadEvent { final int threadId; final String name; - List children = []; + TimelineThreadEvent parent; + List children = []; int startMicros; int durationMicros; - TEvent(this.threadId, this.name); + TimelineThreadEvent(this.threadId, this.name); void setStart(int micros) { startMicros = micros; @@ -194,7 +252,7 @@ class TEvent { void format(StringBuffer buf, String indent) { buf.writeln('$indent$name'); - for (TEvent child in children) { + for (TimelineThreadEvent child in children) { child.format(buf, ' $indent'); } } @@ -259,7 +317,7 @@ class TimelineFrame { class TimelineFrameData { final TimelineFrame frame; final List threads; - final List events; + final List events; TimelineFrameData(this.frame, this.threads, this.events); @@ -268,7 +326,7 @@ class TimelineFrameData { print('${thread.name}:'); final StringBuffer buf = new StringBuffer(); - for (TEvent event in events) { + for (TimelineThreadEvent event in events) { if (event.threadId == thread.threadId) { event.format(buf, ' '); print(buf.toString().trimRight()); @@ -278,11 +336,14 @@ class TimelineFrameData { } } - Iterable eventsForThread(TimelineThread thread) { - return events.where((TEvent event) => event.threadId == thread.threadId); + Iterable eventsForThread(TimelineThread thread) { + return events.where( + (TimelineThreadEvent event) => event.threadId == thread.threadId); } } +// TODO(devoncarew): Upstream this class to the service protocol library. + /// A single timeline event. class TimelineEvent { /// Creates a timeline event given JSON-encoded event data. @@ -332,6 +393,15 @@ class TimelineEvent { /// Corresponds to the "tid" field in the JSON event. final int threadId; + /// Each async event has an additional required parameter id. We consider the + /// events with the same category and id as events from the same event tree. + dynamic get id => json['id']; + + /// An optional scope string can be specified to avoid id conflicts, in which + /// case we consider events with the same category, scope, and id as events + /// from the same event tree. + String get scope => json['scope']; + /// The duration of the event, in microseconds. /// /// Note, some events are reported with duration. Others are reported as a @@ -346,6 +416,14 @@ class TimelineEvent { /// Arbitrary data attached to the event. final Map args; + String get asyncUID { + if (scope == null) { + return '$category:$id'; + } else { + return '$category:$scope:$id'; + } + } + @override String toString() => '[$category] [$phase] $name'; } From df42079f86829cd630919d0f64ac5d13038dda8d Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Thu, 20 Sep 2018 12:20:56 -0700 Subject: [PATCH 3/6] checkpoint --- lib/timeline/timeline.dart | 152 +++++++++++++++++++--------- lib/timeline/timeline_protocol.dart | 54 +++++----- 2 files changed, 131 insertions(+), 75 deletions(-) diff --git a/lib/timeline/timeline.dart b/lib/timeline/timeline.dart index c3ffc81b45e..c99ac29bf13 100644 --- a/lib/timeline/timeline.dart +++ b/lib/timeline/timeline.dart @@ -22,6 +22,13 @@ import 'timeline_protocol.dart'; // TODO(devoncarew): use colors for the category +// TODO:(devoncarew): show the total frame count + +// TODO(devoncarew): Have a timeline view thumbnail overview. + +// TODO(devoncarew): Switch to showing all timeline events, but highlighting the +// area associated with the selected frame. + class TimelineScreen extends Screen { TimelineScreen() : super(name: 'Timeline', id: 'timeline', iconClass: 'octicon-pulse'); @@ -118,6 +125,7 @@ class TimelineScreen extends Screen { if (frame != null && timelineFramesUI.hasStarted()) { final TimelineFrameData data = timelineFramesUI.timelineData.getFrameData(frame); + data.printData(); frameDetailsUI.updateData(data); } }); @@ -205,7 +213,7 @@ class TimelineScreen extends Screen { await serviceInfo.service .setVMTimelineFlags(['GC', 'Dart', 'Embedder']); } else if (!shouldBeRunning && isRunning) { - // TODO: turn off the events + // TODO(devoncarew): turn off the events await serviceInfo.service.setVMTimelineFlags([]); timelineFramesBuilder.pause(); @@ -256,6 +264,7 @@ class TimelineFramesUI extends CoreElement { TimelineFramesUI(TimelineFramesBuilder timelineFramesBuilder) : super('div', classes: 'timeline-frames') { timelineFramesBuilder.onFrameAdded.listen((TimelineFrame frame) { + print('frame: ${frame.renderAsMs} ${frame.gpuAsMs}'); final CoreElement frameUI = new TimelineFrameUI(this, frame); if (element.children.isEmpty) { add(frameUI); @@ -318,11 +327,11 @@ class TimelineFrameUI extends CoreElement { add(dartBar); final CoreElement gpuBar = div(c: 'perf-bar right'); - if (frame.rastereizeDuration > (FrameInfo.kTargetMaxFrameTimeMs * 1000)) { + if (frame.rasterizeDuration > (FrameInfo.kTargetMaxFrameTimeMs * 1000)) { gpuBar.clazz('slow'); isSlow = true; } - height = (frame.rastereizeDuration * pixelsPerMs / 1000.0).round(); + height = (frame.rasterizeDuration * pixelsPerMs / 1000.0).round(); height = math.min(height, 80 - 6); gpuBar.element.style.height = '${height}px'; add(gpuBar); @@ -348,6 +357,9 @@ class TimelineFramesBuilder { bool isPaused = false; + List dartSamples = []; + List gpuSamples = []; + final StreamController _frameAddedController = new StreamController.broadcast(); @@ -361,9 +373,8 @@ class TimelineFramesBuilder { void pause() { isPaused = true; - if (frames.isNotEmpty && !frames.last.isComplete) { - frames.removeLast(); - } + dartSamples.clear(); + gpuSamples.clear(); } void resume() { @@ -371,70 +382,101 @@ class TimelineFramesBuilder { } void processTimelineEvent(TimelineEvent event) { + // TODO: change from listening to events to listening to timeline item + // creation. if (event.category != 'Embedder') { return; } - // [Embedder] [B] VSYNC - if (event.name == 'VSYNC') { - if (event.phase == 'B') { - TimelineFrame frame = findFrameAfter(event.timestampMicros); - if (frame == null) { - frame = new TimelineFrame(); - frames.add(frame); + // [Embedder] [b/e] PipelineProduce + if (event.name == 'PipelineProduce') { + if (event.phase == 'b') { + final int start = event.timestampMicros; + if (dartSamples.isNotEmpty) { + if (!dartSamples.last.wellFormed) { + dartSamples.clear(); + } } - frame.setRenderStart(event.timestampMicros); - } else if (event.phase == 'E') { - final TimelineFrame frame = findFrameBefore(event.timestampMicros); - frame?.setRenderEnd(event.timestampMicros); - - if (frame != null && frame.isComplete) { - _frameAddedController.add(frame); + dartSamples.add(new Sample(start: start)); + } else if (event.phase == 'e') { + final int end = event.timestampMicros; + if (dartSamples.isNotEmpty) { + dartSamples.last.end = end; } } } - // [Embedder] [B] GPURasterizer::Draw - if (event.name == 'GPURasterizer::Draw') { + // [Embedder] [B/E] MessageLoop::RunExpiredTasks + if (event.name == 'MessageLoop::RunExpiredTasks') { if (event.phase == 'B') { - TimelineFrame frame = findFrameBefore(event.timestampMicros); - if (frame == null) { - frame = new TimelineFrame(); - frames.add(frame); - if (frames.length > maxFrames) { - frames.removeAt(0); - } + final int start = event.timestampMicros; + if (gpuSamples.isNotEmpty && !gpuSamples.last.wellFormed) { + gpuSamples.clear(); } - frame.setRastereizeStart(event.timestampMicros); + gpuSamples.add(new Sample(start: start)); } else if (event.phase == 'E') { - final TimelineFrame frame = findFrameBefore(event.timestampMicros); - frame?.setRastereizeEnd(event.timestampMicros); - - if (frame != null && frame.isComplete) { - _frameAddedController.add(frame); + final int end = event.timestampMicros; + // TODO: fix this + if (gpuSamples.isNotEmpty && gpuSamples.last.start < end) { + gpuSamples.last.end = end; } } + + _processSamplesData(); } } - TimelineFrame findFrameAfter(int micros) { - for (TimelineFrame frame in frames) { - if (frame.start > micros) { - return frame; + void _processSamplesData() { + while (dartSamples.isNotEmpty && gpuSamples.isNotEmpty) { + int dartStart = dartSamples.first.start; + + // Throw away any gpu samples that start before dart ones. + while (gpuSamples.isNotEmpty && gpuSamples.first.start < dartStart) { + gpuSamples.removeAt(0); } - } - return null; - } + if (gpuSamples.isEmpty || !gpuSamples.first.wellFormed) { + break; + } + + // Find the newest dart sample that starts before a gpu one. + final int gpuStart = gpuSamples.first.start; + + while (dartSamples.length > 1 && dartSamples.first.start < gpuStart) { + if (dartSamples[1].start < gpuStart) { + dartSamples.removeAt(0); + } + } - TimelineFrame findFrameBefore(int micros) { - for (TimelineFrame frame in frames.reversed) { - if (frame.start <= micros) { - return frame; + if (dartSamples.isEmpty || !dartSamples.first.wellFormed) { + break; } - } - return null; + // Return the pair. + if (dartSamples.isNotEmpty && gpuSamples.isNotEmpty) { + dartStart = dartSamples.first.start; + if (dartStart > gpuStart) { + break; + } + + final Sample dartSample = dartSamples.removeAt(0); + final Sample gpuSample = gpuSamples.removeAt(0); + + print('$dartSample $gpuSample'); + + final TimelineFrame frame = new TimelineFrame( + renderStart: dartSample.start, rasterizeStart: gpuSample.start); + frame.setRenderEnd(dartSample.end); + frame.setRasterizeEnd(gpuSample.end); + + frames.add(frame); + if (frames.length > maxFrames) { + frames.removeAt(0); + } + + _frameAddedController.add(frame); + } + } } void clear() { @@ -443,6 +485,18 @@ class TimelineFramesBuilder { } } +class Sample { + int start; + int end; + + Sample({this.start, this.end}); + + bool get wellFormed => start != null && end != null; + + @override + String toString() => '[$start ${end - start}]'; +} + class FrameDetailsUI extends CoreElement { TimelineFrameData data; @@ -505,7 +559,7 @@ class FrameDetailsUI extends CoreElement { int row = 0; - final int microsAdjust = data.frame.start; + final int microsAdjust = data.frame.startMicros; int maxRow = 0; diff --git a/lib/timeline/timeline_protocol.dart b/lib/timeline/timeline_protocol.dart index 24417c45e47..9929a4bb96d 100644 --- a/lib/timeline/timeline_protocol.dart +++ b/lib/timeline/timeline_protocol.dart @@ -2,10 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// TODO: tests - -// TODO: support additional phases (s, t, f) - // For documentation, see the Chrome "Trace Event Format" document: // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview @@ -21,6 +17,8 @@ class TimelineData { threads.sort(); } + // TODO: fire events for TimelineThreadEvent creation + void processTimelineEvent(TimelineEvent event) { final TimelineThread thread = threadMap[event.threadId]; if (thread == null) { @@ -49,8 +47,8 @@ class TimelineData { break; default: - // TODO(devoncarew): Support additional phases. - print('unhandled: ${event.json}'); + // TODO(devoncarew): Support additional phases (s, t, f). + //print('unhandled: ${event.json}'); break; } } @@ -68,7 +66,8 @@ class TimelineData { continue; } - if (event.endMicros >= frame.start && event.startMicros < frame.end) { + if (event.endMicros >= frame.startMicros && + event.startMicros < frame.endMicros) { events.add(event); } } @@ -251,7 +250,7 @@ class TimelineThreadEvent { bool get wellFormed => startMicros != null && durationMicros != null; void format(StringBuffer buf, String indent) { - buf.writeln('$indent$name'); + buf.writeln('$indent$name [${startMicros}u]'); for (TimelineThreadEvent child in children) { child.format(buf, ' $indent'); } @@ -265,16 +264,16 @@ class TimelineFrame { int renderStart; int renderDuration; - int rastereizeStart; - int rastereizeDuration; + int rasterizeStart; + int rasterizeDuration; - TimelineFrame(); + TimelineFrame({this.renderStart, this.rasterizeStart}); - int get start => renderStart ?? rastereizeStart; + int get startMicros => renderStart ?? rasterizeStart; - int get end { - if (rastereizeStart != null) { - return rastereizeStart + rastereizeDuration; + int get endMicros { + if (rasterizeStart != null) { + return rasterizeStart + rasterizeDuration; } else { return renderStart + renderDuration; } @@ -288,29 +287,29 @@ class TimelineFrame { renderDuration = micros - renderStart; } - void setRastereizeStart(int micros) { - rastereizeStart = micros; + void setRasterizeStart(int micros) { + rasterizeStart = micros; } - void setRastereizeEnd(int micros) { - if (rastereizeStart != null) { - rastereizeDuration = micros - rastereizeStart; + void setRasterizeEnd(int micros) { + if (rasterizeStart != null) { + rasterizeDuration = micros - rasterizeStart; } } - bool get isComplete => renderDuration != null && rastereizeDuration != null; + bool get isComplete => renderDuration != null && rasterizeDuration != null; String get renderAsMs { return '${(renderDuration / 1000.0).toStringAsFixed(1)}ms'; } String get gpuAsMs { - return '${(rastereizeDuration / 1000.0).toStringAsFixed(1)}ms'; + return '${(rasterizeDuration / 1000.0).toStringAsFixed(1)}ms'; } @override String toString() { - return 'frame render: $renderDuration rasterize: $rastereizeDuration'; + return 'frame render: $renderDuration rasterize: $rasterizeDuration'; } } @@ -322,15 +321,18 @@ class TimelineFrameData { TimelineFrameData(this.frame, this.threads, this.events); void printData() { + print(frame.startMicros); + print('${frame.renderDuration}u'); + print('${frame.rasterizeDuration}u'); + for (TimelineThread thread in threads) { - print('${thread.name}:'); + print('${thread.name}'); final StringBuffer buf = new StringBuffer(); for (TimelineThreadEvent event in events) { if (event.threadId == thread.threadId) { event.format(buf, ' '); - print(buf.toString().trimRight()); - buf.clear(); + print(' [${event.name}]'); } } } From 725dff7185b34fb5dcec23683aed85d04e592d96 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Thu, 20 Sep 2018 14:10:49 -0700 Subject: [PATCH 4/6] finish timeline cleanup --- lib/timeline/timeline.dart | 144 ++++++++++------------------ lib/timeline/timeline_protocol.dart | 30 +++++- pubspec.yaml | 2 +- web/styles.css | 2 +- 4 files changed, 80 insertions(+), 98 deletions(-) diff --git a/lib/timeline/timeline.dart b/lib/timeline/timeline.dart index c99ac29bf13..27085e5aefd 100644 --- a/lib/timeline/timeline.dart +++ b/lib/timeline/timeline.dart @@ -125,7 +125,6 @@ class TimelineScreen extends Screen { if (frame != null && timelineFramesUI.hasStarted()) { final TimelineFrameData data = timelineFramesUI.timelineData.getFrameData(frame); - data.printData(); frameDetailsUI.updateData(data); } }); @@ -168,8 +167,6 @@ class TimelineScreen extends Screen { for (Map json in events) { final TimelineEvent e = new TimelineEvent(json); - - timelineFramesBuilder.processTimelineEvent(e); timelineFramesUI.timelineData?.processTimelineEvent(e); } }); @@ -246,6 +243,11 @@ class TimelineScreen extends Screen { } } + timelineData.onTimelineThreadEvent.listen((TimelineThreadEvent event) { + timelineFramesBuilder.processTimelineEvent( + timelineData.getThread(event.threadId), event); + }); + timelineFramesUI.timelineData = timelineData; } @@ -264,7 +266,6 @@ class TimelineFramesUI extends CoreElement { TimelineFramesUI(TimelineFramesBuilder timelineFramesBuilder) : super('div', classes: 'timeline-frames') { timelineFramesBuilder.onFrameAdded.listen((TimelineFrame frame) { - print('frame: ${frame.renderAsMs} ${frame.gpuAsMs}'); final CoreElement frameUI = new TimelineFrameUI(this, frame); if (element.children.isEmpty) { add(frameUI); @@ -357,8 +358,8 @@ class TimelineFramesBuilder { bool isPaused = false; - List dartSamples = []; - List gpuSamples = []; + List dartEvents = []; + List gpuEvents = []; final StreamController _frameAddedController = new StreamController.broadcast(); @@ -373,109 +374,84 @@ class TimelineFramesBuilder { void pause() { isPaused = true; - dartSamples.clear(); - gpuSamples.clear(); + dartEvents.clear(); + gpuEvents.clear(); } void resume() { isPaused = false; } - void processTimelineEvent(TimelineEvent event) { - // TODO: change from listening to events to listening to timeline item - // creation. - if (event.category != 'Embedder') { + void processTimelineEvent(TimelineThread thread, TimelineThreadEvent event) { + if (thread == null) { return; } - // [Embedder] [b/e] PipelineProduce - if (event.name == 'PipelineProduce') { - if (event.phase == 'b') { - final int start = event.timestampMicros; - if (dartSamples.isNotEmpty) { - if (!dartSamples.last.wellFormed) { - dartSamples.clear(); - } - } - dartSamples.add(new Sample(start: start)); - } else if (event.phase == 'e') { - final int end = event.timestampMicros; - if (dartSamples.isNotEmpty) { - dartSamples.last.end = end; - } - } - } + // io.flutter.1.ui, io.flutter.1.gpu + if (thread.name.endsWith('.ui')) { + // PipelineProduce + if (event.name == 'PipelineProduce' && event.wellFormed) { + dartEvents.add(event); - // [Embedder] [B/E] MessageLoop::RunExpiredTasks - if (event.name == 'MessageLoop::RunExpiredTasks') { - if (event.phase == 'B') { - final int start = event.timestampMicros; - if (gpuSamples.isNotEmpty && !gpuSamples.last.wellFormed) { - gpuSamples.clear(); - } - gpuSamples.add(new Sample(start: start)); - } else if (event.phase == 'E') { - final int end = event.timestampMicros; - // TODO: fix this - if (gpuSamples.isNotEmpty && gpuSamples.last.start < end) { - gpuSamples.last.end = end; - } + _processSamplesData(); } + } else if (thread.name.endsWith('.gpu')) { + // MessageLoop::RunExpiredTasks + if (event.name == 'MessageLoop::RunExpiredTasks' && event.wellFormed) { + gpuEvents.add(event); - _processSamplesData(); + _processSamplesData(); + } } } void _processSamplesData() { - while (dartSamples.isNotEmpty && gpuSamples.isNotEmpty) { - int dartStart = dartSamples.first.start; + while (dartEvents.isNotEmpty && gpuEvents.isNotEmpty) { + int dartStart = dartEvents.first.startMicros; // Throw away any gpu samples that start before dart ones. - while (gpuSamples.isNotEmpty && gpuSamples.first.start < dartStart) { - gpuSamples.removeAt(0); + while (gpuEvents.isNotEmpty && gpuEvents.first.startMicros < dartStart) { + gpuEvents.removeAt(0); } - if (gpuSamples.isEmpty || !gpuSamples.first.wellFormed) { + if (gpuEvents.isEmpty) { break; } // Find the newest dart sample that starts before a gpu one. - final int gpuStart = gpuSamples.first.start; + final int gpuStart = gpuEvents.first.startMicros; - while (dartSamples.length > 1 && dartSamples.first.start < gpuStart) { - if (dartSamples[1].start < gpuStart) { - dartSamples.removeAt(0); - } + while (dartEvents.length > 1 && + (dartEvents[0].startMicros < gpuStart && + dartEvents[1].startMicros < gpuStart)) { + dartEvents.removeAt(0); } - if (dartSamples.isEmpty || !dartSamples.first.wellFormed) { + if (dartEvents.isEmpty) { break; } // Return the pair. - if (dartSamples.isNotEmpty && gpuSamples.isNotEmpty) { - dartStart = dartSamples.first.start; - if (dartStart > gpuStart) { - break; - } - - final Sample dartSample = dartSamples.removeAt(0); - final Sample gpuSample = gpuSamples.removeAt(0); - - print('$dartSample $gpuSample'); + dartStart = dartEvents.first.startMicros; + if (dartStart > gpuStart) { + break; + } - final TimelineFrame frame = new TimelineFrame( - renderStart: dartSample.start, rasterizeStart: gpuSample.start); - frame.setRenderEnd(dartSample.end); - frame.setRasterizeEnd(gpuSample.end); + final TimelineThreadEvent dartEvent = dartEvents.removeAt(0); + final TimelineThreadEvent gpuEvent = gpuEvents.removeAt(0); - frames.add(frame); - if (frames.length > maxFrames) { - frames.removeAt(0); - } + final TimelineFrame frame = new TimelineFrame( + renderStart: dartEvent.startMicros, + rasterizeStart: gpuEvent.startMicros); + frame.renderDuration = dartEvent.durationMicros; + frame.rasterizeDuration = gpuEvent.durationMicros; - _frameAddedController.add(frame); + frames.add(frame); + if (frames.length > maxFrames) { + frames.removeAt(0); } + + _frameAddedController.add(frame); } } @@ -485,18 +461,6 @@ class TimelineFramesBuilder { } } -class Sample { - int start; - int end; - - Sample({this.start, this.end}); - - bool get wellFormed => start != null && end != null; - - @override - String toString() => '[$start ${end - start}]'; -} - class FrameDetailsUI extends CoreElement { TimelineFrameData data; @@ -555,7 +519,7 @@ class FrameDetailsUI extends CoreElement { const int rowHeight = 25; const double microsPerFrame = 1000 * 1000 / 60.0; - const double pxPerMicro = microsPerFrame / 1200.0; + const double pxPerMicro = microsPerFrame / 1000.0; int row = 0; @@ -567,8 +531,7 @@ class FrameDetailsUI extends CoreElement { drawRecursively = (TimelineThreadEvent event, int row) { if (!event.wellFormed) { - print('event not well formed'); - print(event); + print('event not well formed: $event'); return; } @@ -604,8 +567,7 @@ class FrameDetailsUI extends CoreElement { row++; } } catch (e, st) { - print(e); - print(st); + print('$e\n$st'); } } diff --git a/lib/timeline/timeline_protocol.dart b/lib/timeline/timeline_protocol.dart index 9929a4bb96d..28ccc9e696a 100644 --- a/lib/timeline/timeline_protocol.dart +++ b/lib/timeline/timeline_protocol.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + // For documentation, see the Chrome "Trace Event Format" document: // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview @@ -9,6 +11,9 @@ class TimelineData { final List threads = []; final Map threadMap = {}; + final StreamController _timelineEventsController = + new StreamController.broadcast(); + TimelineData(); void addThread(TimelineThread thread) { @@ -17,7 +22,8 @@ class TimelineData { threads.sort(); } - // TODO: fire events for TimelineThreadEvent creation + Stream get onTimelineThreadEvent => + _timelineEventsController.stream; void processTimelineEvent(TimelineEvent event) { final TimelineThread thread = threadMap[event.threadId]; @@ -53,6 +59,8 @@ class TimelineData { } } + TimelineThread getThread(int threadId) => threadMap[threadId]; + TimelineFrameData getFrameData(TimelineFrame frame) { if (frame == null) { return null; @@ -161,8 +169,15 @@ class TimelineThread implements Comparable { void _handleDurationEndEvent(TimelineEvent event) { if (durationStack != null) { + final TimelineThreadEvent current = durationStack; + durationStack.setEnd(event.timestampMicros); durationStack = durationStack.parent; + + // Fire an event for a completed timeline event. + if (durationStack == null) { + parent._timelineEventsController.add(current); + } } } @@ -217,10 +232,15 @@ class TimelineThread implements Comparable { void _handleAsyncEndEvent(TimelineEvent event) { final String asyncUID = event.asyncUID; - final TimelineThreadEvent e = _asyncEvents[asyncUID]; - if (e != null) { - e.setEnd(event.timestampMicros); - _asyncEvents[asyncUID] = e.parent; + final TimelineThreadEvent current = _asyncEvents[asyncUID]; + if (current != null) { + current.setEnd(event.timestampMicros); + _asyncEvents[asyncUID] = current.parent; + + // Fire an event for a completed timeline event. + if (_asyncEvents[asyncUID] == null) { + parent._timelineEventsController.add(current); + } } } } diff --git a/pubspec.yaml b/pubspec.yaml index c885251f8bf..7a7f7fb7f20 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,5 +17,5 @@ dev_dependencies: build_runner: any build_web_compilers: any test: ^1.0.0 - webdev: 0.2.4 + webdev: 0.2.4+1 webkit_inspection_protocol: ^0.3.6 diff --git a/web/styles.css b/web/styles.css index c43d472836b..a12fd0262fe 100644 --- a/web/styles.css +++ b/web/styles.css @@ -410,7 +410,7 @@ div.tabnav { } .timeline-frame.slow { - width: 150px; + width: 125px; background: #f8e2e2; border-color: #ccc; } From 27b91ceef0a8c2c1038226514653aaf2cf42a55d Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Sun, 23 Sep 2018 12:31:44 -0700 Subject: [PATCH 5/6] add tests --- lib/timeline/timeline_protocol.dart | 2 +- test/timeline/timeline_protocol_test.dart | 90 +++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 test/timeline/timeline_protocol_test.dart diff --git a/lib/timeline/timeline_protocol.dart b/lib/timeline/timeline_protocol.dart index 28ccc9e696a..150e497f32f 100644 --- a/lib/timeline/timeline_protocol.dart +++ b/lib/timeline/timeline_protocol.dart @@ -54,7 +54,7 @@ class TimelineData { default: // TODO(devoncarew): Support additional phases (s, t, f). - //print('unhandled: ${event.json}'); + //print(jsonEncode(event.json)); break; } } diff --git a/test/timeline/timeline_protocol_test.dart b/test/timeline/timeline_protocol_test.dart new file mode 100644 index 00000000000..49a99f0f702 --- /dev/null +++ b/test/timeline/timeline_protocol_test.dart @@ -0,0 +1,90 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; + +import 'package:devtools/timeline/timeline_protocol.dart'; +import 'package:test/test.dart'; + +void main() { + group('TimelineData', () { + test('process duration events', () async { + final Iterable events = durationData + .trim() + .split('\n') + .map((String str) => new TimelineEvent(jsonDecode(str))); + + final TimelineData timelineData = new TimelineData(); + final TimelineThread thread = + new TimelineThread(timelineData, 'engine', 41219); + timelineData.addThread(thread); + + final Future resultFuture = + timelineData.onTimelineThreadEvent.first; + + events.forEach(timelineData.processTimelineEvent); + + final TimelineThreadEvent result = await resultFuture; + + expect(result.name, 'Engine::BeginFrame'); + final List children = result.children; + expect(children, hasLength(8)); + expect(children[0].name, 'Animate'); + expect(children[1].name, 'Layout'); + expect(children[2].name, 'Compositing bits'); + expect(children[3].name, 'Paint'); + expect(children[4].name, 'Compositing'); + expect(children[5].name, 'Semantics'); + expect(children[6].name, 'Finalize tree'); + expect(children[7].name, 'Frame'); + }); + + test('process async events', () async { + final Iterable events = asyncData + .trim() + .split('\n') + .map((String str) => new TimelineEvent(jsonDecode(str))); + + final TimelineData timelineData = new TimelineData(); + final TimelineThread thread = + new TimelineThread(timelineData, 'dart', 41219); + timelineData.addThread(thread); + + final Future> resultFuture = + timelineData.onTimelineThreadEvent.take(2).toList(); + + events.forEach(timelineData.processTimelineEvent); + + final List results = await resultFuture; + + expect(results, hasLength(2)); + + expect(results[0].name, 'Frame Request Pending'); + expect(results[1].name, 'PipelineProduce'); + }); + }); + + // TODO(devoncarew): Add test to locate frames. +} + +const String durationData = ''' +{"name":"Engine::BeginFrame","cat":"Embedder","tid":41219,"pid":81348,"ts":249012879086,"ph":"B","args":{}} +{"name":"Animate","cat":"Dart","tid":41219,"pid":81348,"ts":249012879135,"ph":"X","dur":27,"args":{"mode":"basic","isolateNumber":"891035574"}} +{"name":"Layout","cat":"Dart","tid":41219,"pid":81348,"ts":249012879190,"ph":"X","dur":25,"args":{"mode":"basic","isolateNumber":"891035574"}} +{"name":"Compositing bits","cat":"Dart","tid":41219,"pid":81348,"ts":249012879218,"ph":"X","dur":7,"args":{"isolateNumber":"891035574"}} +{"name":"Paint","cat":"Dart","tid":41219,"pid":81348,"ts":249012879227,"ph":"X","dur":6,"args":{"mode":"basic","isolateNumber":"891035574"}} +{"name":"Compositing","cat":"Dart","tid":41219,"pid":81348,"ts":249012879235,"ph":"X","dur":111,"args":{"mode":"basic","isolateNumber":"891035574"}} +{"name":"Semantics","cat":"Dart","tid":41219,"pid":81348,"ts":249012879349,"ph":"X","dur":23,"args":{"isolateNumber":"891035574"}} +{"name":"Finalize tree","cat":"Dart","tid":41219,"pid":81348,"ts":249012879373,"ph":"X","dur":24,"args":{"mode":"basic","isolateNumber":"891035574"}} +{"name":"Frame","cat":"Dart","tid":41219,"pid":81348,"ts":249012879119,"ph":"X","dur":292,"args":{"mode":"basic","isolateNumber":"891035574"}} +{"name":"Engine::BeginFrame","cat":"Embedder","tid":41219,"pid":81348,"ts":249012879470,"ph":"E","args":{}} +'''; + +const String asyncData = ''' +{"name":"Frame Request Pending","cat":"Embedder","tid":41219,"pid":81348,"ts":250717377278,"ph":"b","id":"2cf","args":{}} +{"name":"Frame Request Pending","cat":"Embedder","tid":41219,"pid":81348,"ts":250717391754,"ph":"e","id":"2cf","args":{}} +{"name":"PipelineProduce","cat":"Embedder","tid":41219,"pid":81348,"ts":250717391755,"ph":"b","id":"2cf","args":{}} +{"name":"PipelineProduce","cat":"Embedder","tid":41219,"pid":81348,"ts":250717392000,"ph":"e","id":"2cf","args":{"isolateNumber":"891035574"}} +'''; From 66549c90dfa5d20792705a4199900a36cd1be43b Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Mon, 24 Sep 2018 11:00:13 -0700 Subject: [PATCH 6/6] review comments --- lib/logging/logging.dart | 1 + lib/memory/memory.dart | 1 + lib/performance/performance.dart | 1 + lib/timeline/timeline.dart | 4 +++- lib/timeline/timeline_protocol.dart | 10 +++++----- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/logging/logging.dart b/lib/logging/logging.dart index c418150f624..33e686bea80 100644 --- a/lib/logging/logging.dart +++ b/lib/logging/logging.dart @@ -84,6 +84,7 @@ class LoggingScreen extends Screen { logCountStatus.element.text = '${nf.format(count)} events'; } + // TODO(devoncarew): Update this url. @override HelpInfo get helpInfo => new HelpInfo(title: 'logs view docs', url: 'http://www.cheese.com'); diff --git a/lib/memory/memory.dart b/lib/memory/memory.dart index e8fa314150a..38717352e97 100644 --- a/lib/memory/memory.dart +++ b/lib/memory/memory.dart @@ -203,6 +203,7 @@ class MemoryScreen extends Screen { return memoryTable.element; } + // TODO(devoncarew): Update this url. @override HelpInfo get helpInfo => new HelpInfo(title: 'memory view docs', url: 'http://www.cheese.com'); diff --git a/lib/performance/performance.dart b/lib/performance/performance.dart index a2f65f39a2d..3f7c807d56a 100644 --- a/lib/performance/performance.dart +++ b/lib/performance/performance.dart @@ -162,6 +162,7 @@ class PerformanceScreen extends Screen { } } + // TODO(devoncarew): Update this url. @override HelpInfo get helpInfo => new HelpInfo( title: 'performance view docs', url: 'http://www.cheese.com'); diff --git a/lib/timeline/timeline.dart b/lib/timeline/timeline.dart index a60c89fd044..1a48446a3b4 100644 --- a/lib/timeline/timeline.dart +++ b/lib/timeline/timeline.dart @@ -251,6 +251,7 @@ class TimelineScreen extends Screen { timelineFramesUI.timelineData = timelineData; } + // TODO(devoncarew): Update this url. @override HelpInfo get helpInfo => new HelpInfo(title: 'timeline docs', url: 'http://www.cheese.com'); @@ -266,6 +267,7 @@ class TimelineFramesUI extends CoreElement { TimelineFramesUI(TimelineFramesBuilder timelineFramesBuilder) : super('div', classes: 'timeline-frames') { timelineFramesBuilder.onFrameAdded.listen((TimelineFrame frame) { + // TODO(devoncarew): Make sure we respect TimelineFramesBuilder.maxFrames. final CoreElement frameUI = new TimelineFrameUI(this, frame); if (element.children.isEmpty) { add(frameUI); @@ -420,7 +422,6 @@ class TimelineFramesBuilder { // Find the newest dart sample that starts before a gpu one. final int gpuStart = gpuEvents.first.startMicros; - while (dartEvents.length > 1 && (dartEvents[0].startMicros < gpuStart && dartEvents[1].startMicros < gpuStart)) { @@ -447,6 +448,7 @@ class TimelineFramesBuilder { frame.rasterizeDuration = gpuEvent.durationMicros; frames.add(frame); + if (frames.length > maxFrames) { frames.removeAt(0); } diff --git a/lib/timeline/timeline_protocol.dart b/lib/timeline/timeline_protocol.dart index 150e497f32f..f0d878757b5 100644 --- a/lib/timeline/timeline_protocol.dart +++ b/lib/timeline/timeline_protocol.dart @@ -124,7 +124,7 @@ class TimelineThread implements Comparable { bool get isVisible => name.startsWith('io.flutter.'); - int get category { + int get sortPriority { if (name.endsWith('.ui')) { return 1; } @@ -144,10 +144,10 @@ class TimelineThread implements Comparable { @override int compareTo(TimelineThread other) { - final int c1 = category; - final int c2 = other.category; - if (c1 != c2) { - return c1 - c2; + final int sortPriority1 = sortPriority; + final int sortPriority2 = other.sortPriority; + if (sortPriority1 != sortPriority2) { + return sortPriority1 - sortPriority2; } return name.compareTo(other.name); }