Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 35b18ba

Browse files
authored
Made flag for debugging build time of user created widgets (#100926)
* Added a bool that allows us to limit debugProfileBuildsEnabled to user created widgets. * made it turned on by default * switched to hashmap * Cleaned everything up and added tests * fixed an odd test where it wants to be able to add asserts and run in profile mode * hixie feedback * hixie2 * made it default to false * updated docstring as per dans request
1 parent 839a183 commit 35b18ba

File tree

7 files changed

+139
-25
lines changed

7 files changed

+139
-25
lines changed

dev/tracing_tests/test/timeline_test.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@ void main() {
110110
expect(args['color'], 'Color(0xffffffff)');
111111
debugProfileBuildsEnabled = false;
112112

113+
debugProfileBuildsEnabledUserWidgets = true;
114+
await runFrame(() { TestRoot.state.updateWidget(Placeholder(key: UniqueKey(), color: const Color(0xFFFFFFFF))); });
115+
events = await fetchInterestingEvents(interestingLabels);
116+
expect(
117+
events.map<String>(eventToName),
118+
<String>['BUILD', 'Placeholder', 'LAYOUT', 'UPDATING COMPOSITING BITS', 'PAINT', 'COMPOSITING', 'FINALIZE TREE'],
119+
);
120+
args = (events.where((TimelineEvent event) => event.json!['name'] == '$Placeholder').single.json!['args'] as Map<String, Object?>).cast<String, String>();
121+
expect(args['color'], 'Color(0xffffffff)');
122+
debugProfileBuildsEnabledUserWidgets = false;
123+
113124
debugProfileLayoutsEnabled = true;
114125
await runFrame(() { TestRoot.state.updateWidget(Placeholder(key: UniqueKey())); });
115126
events = await fetchInterestingEvents(interestingLabels);

packages/flutter/lib/src/widgets/debug.dart

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,24 @@ bool debugPrintGlobalKeyedWidgetLifecycle = false;
114114
/// * [debugProfileLayoutsEnabled], which does something similar for layout,
115115
/// and [debugPrintLayouts], its console equivalent.
116116
/// * [debugProfilePaintsEnabled], which does something similar for painting.
117+
/// * [debugProfileBuildsEnabledUserWidgets], which adds events for user-created
118+
/// [Widget] build times and incurs less overhead.
117119
bool debugProfileBuildsEnabled = false;
118120

121+
/// Adds [Timeline] events for every user-created [Widget] built.
122+
///
123+
/// A user-created [Widget] is any [Widget] that is constructed in the root
124+
/// library. Often [Widget]s contain child [Widget]s that are constructed in
125+
/// libraries (for example, a [TextButton] having a [RichText] child). Timeline
126+
/// events for those children will be omitted with this flag. This works for any
127+
/// [Widget] not just ones declared in the root library.
128+
///
129+
/// See also:
130+
///
131+
/// * [debugProfileBuildsEnabled], which functions similarly but shows events
132+
/// for every widget and has a higher overhead cost.
133+
bool debugProfileBuildsEnabledUserWidgets = false;
134+
119135
/// Show banners for deprecated widgets.
120136
bool debugHighlightDeprecatedWidgets = false;
121137

@@ -423,7 +439,8 @@ bool debugAssertAllWidgetVarsUnset(String reason) {
423439
debugPrintScheduleBuildForStacks ||
424440
debugPrintGlobalKeyedWidgetLifecycle ||
425441
debugProfileBuildsEnabled ||
426-
debugHighlightDeprecatedWidgets) {
442+
debugHighlightDeprecatedWidgets ||
443+
debugProfileBuildsEnabledUserWidgets) {
427444
throw FlutterError(reason);
428445
}
429446
return true;

packages/flutter/lib/src/widgets/framework.dart

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'debug.dart';
1414
import 'focus_manager.dart';
1515
import 'inherited_model.dart';
1616
import 'notification_listener.dart';
17+
import 'widget_inspector.dart';
1718

1819
export 'package:flutter/foundation.dart' show
1920
factory,
@@ -2640,10 +2641,13 @@ class BuildOwner {
26402641
}
26412642
return true;
26422643
}());
2643-
if (!kReleaseMode && debugProfileBuildsEnabled) {
2644+
final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(element.widget);
2645+
if (isTimelineTracked) {
26442646
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
26452647
assert(() {
2646-
debugTimelineArguments = element.widget.toDiagnosticsNode().toTimelineArguments();
2648+
if (kDebugMode) {
2649+
debugTimelineArguments = element.widget.toDiagnosticsNode().toTimelineArguments();
2650+
}
26472651
return true;
26482652
}());
26492653
Timeline.startSync(
@@ -2668,7 +2672,7 @@ class BuildOwner {
26682672
],
26692673
);
26702674
}
2671-
if (!kReleaseMode && debugProfileBuildsEnabled)
2675+
if (isTimelineTracked)
26722676
Timeline.finishSync();
26732677
index += 1;
26742678
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
@@ -3078,6 +3082,12 @@ class _NotificationNode {
30783082
}
30793083
}
30803084

3085+
bool _isProfileBuildsEnabledFor(Widget widget) {
3086+
return debugProfileBuildsEnabled ||
3087+
(debugProfileBuildsEnabledUserWidgets &&
3088+
debugIsWidgetLocalCreation(widget));
3089+
}
3090+
30813091
/// An instantiation of a [Widget] at a particular location in the tree.
30823092
///
30833093
/// Widgets describe how to configure a subtree but the same widget can be used
@@ -3503,10 +3513,13 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
35033513
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
35043514
if (child.slot != newSlot)
35053515
updateSlotForChild(child, newSlot);
3506-
if (!kReleaseMode && debugProfileBuildsEnabled) {
3516+
final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
3517+
if (isTimelineTracked) {
35073518
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
35083519
assert(() {
3509-
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
3520+
if (kDebugMode) {
3521+
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
3522+
}
35103523
return true;
35113524
}());
35123525
Timeline.startSync(
@@ -3515,7 +3528,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
35153528
);
35163529
}
35173530
child.update(newWidget);
3518-
if (!kReleaseMode && debugProfileBuildsEnabled)
3531+
if (isTimelineTracked)
35193532
Timeline.finishSync();
35203533
assert(child.widget == newWidget);
35213534
assert(() {
@@ -3765,10 +3778,13 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
37653778
Element inflateWidget(Widget newWidget, Object? newSlot) {
37663779
assert(newWidget != null);
37673780

3768-
if (!kReleaseMode && debugProfileBuildsEnabled) {
3781+
final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
3782+
if (isTimelineTracked) {
37693783
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
37703784
assert(() {
3771-
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
3785+
if (kDebugMode) {
3786+
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
3787+
}
37723788
return true;
37733789
}());
37743790
Timeline.startSync(
@@ -3800,7 +3816,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
38003816
newChild.mount(this, newSlot);
38013817
assert(newChild._lifecycleState == _ElementLifecycle.active);
38023818

3803-
if (!kReleaseMode && debugProfileBuildsEnabled)
3819+
if (isTimelineTracked)
38043820
Timeline.finishSync();
38053821

38063822
return newChild;

packages/flutter/lib/src/widgets/widget_inspector.dart

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:collection' show HashMap;
67
import 'dart:convert';
78
import 'dart:developer' as developer;
89
import 'dart:math' as math;
@@ -742,6 +743,8 @@ mixin WidgetInspectorService {
742743
int _nextId = 0;
743744

744745
List<String>? _pubRootDirectories;
746+
/// Memoization for [_isLocalCreationLocation].
747+
final HashMap<String, bool> _isLocalCreationCache = HashMap<String, bool>();
745748

746749
bool _trackRebuildDirtyWidgets = false;
747750
bool _trackRepaintWidgets = false;
@@ -1345,6 +1348,7 @@ mixin WidgetInspectorService {
13451348
_pubRootDirectories = pubRootDirectories
13461349
.map<String>((String directory) => Uri.parse(directory).path)
13471350
.toList();
1351+
_isLocalCreationCache.clear();
13481352
}
13491353

13501354
/// Set the [WidgetInspector] selection to the object matching the specified
@@ -1518,14 +1522,11 @@ mixin WidgetInspectorService {
15181522
if (creationLocation == null) {
15191523
return false;
15201524
}
1521-
return _isLocalCreationLocation(creationLocation);
1525+
return _isLocalCreationLocation(creationLocation.file);
15221526
}
15231527

1524-
bool _isLocalCreationLocation(_Location? location) {
1525-
if (location == null) {
1526-
return false;
1527-
}
1528-
final String file = Uri.parse(location.file).path;
1528+
bool _isLocalCreationLocationImpl(String locationUri) {
1529+
final String file = Uri.parse(locationUri).path;
15291530

15301531
// By default check whether the creation location was within package:flutter.
15311532
if (_pubRootDirectories == null) {
@@ -1541,6 +1542,17 @@ mixin WidgetInspectorService {
15411542
return false;
15421543
}
15431544

1545+
/// Memoized version of [_isLocalCreationLocationImpl].
1546+
bool _isLocalCreationLocation(String locationUri) {
1547+
final bool? cachedValue = _isLocalCreationCache[locationUri];
1548+
if (cachedValue != null) {
1549+
return cachedValue;
1550+
}
1551+
final bool result = _isLocalCreationLocationImpl(locationUri);
1552+
_isLocalCreationCache[locationUri] = result;
1553+
return result;
1554+
}
1555+
15441556
/// Wrapper around `json.encode` that uses a ring of cached values to prevent
15451557
/// the Dart garbage collector from collecting objects between when
15461558
/// the value is returned over the Observatory protocol and when the
@@ -2066,7 +2078,7 @@ class _ElementLocationStatsTracker {
20662078
entry = _LocationCount(
20672079
location: location,
20682080
id: id,
2069-
local: WidgetInspectorService.instance._isLocalCreationLocation(location),
2081+
local: WidgetInspectorService.instance._isLocalCreationLocation(location.file),
20702082
);
20712083
if (entry.local) {
20722084
newLocations.add(entry);
@@ -3072,14 +3084,24 @@ bool debugIsLocalCreationLocation(Object object) {
30723084
bool isLocal = false;
30733085
assert(() {
30743086
final _Location? location = _getCreationLocation(object);
3075-
if (location == null)
3076-
isLocal = false;
3077-
isLocal = WidgetInspectorService.instance._isLocalCreationLocation(location);
3087+
if (location != null) {
3088+
isLocal = WidgetInspectorService.instance._isLocalCreationLocation(location.file);
3089+
}
30783090
return true;
30793091
}());
30803092
return isLocal;
30813093
}
30823094

3095+
/// Returns true if a [Widget] is user created.
3096+
///
3097+
/// This is a faster variant of `debugIsLocalCreationLocation` that is available
3098+
/// in debug and profile builds but only works for [Widget].
3099+
bool debugIsWidgetLocalCreation(Widget widget) {
3100+
final _Location? location = _getObjectCreationLocation(widget);
3101+
return location != null &&
3102+
WidgetInspectorService.instance._isLocalCreationLocation(location.file);
3103+
}
3104+
30833105
/// Returns the creation location of an object in String format if one is available.
30843106
///
30853107
/// ex: "file:///path/to/main.dart:4:3"
@@ -3092,14 +3114,18 @@ String? _describeCreationLocation(Object object) {
30923114
return location?.toString();
30933115
}
30943116

3117+
_Location? _getObjectCreationLocation(Object object) {
3118+
return object is _HasCreationLocation ? object._location : null;
3119+
}
3120+
30953121
/// Returns the creation location of an object if one is available.
30963122
///
30973123
/// {@macro flutter.widgets.WidgetInspectorService.getChildrenSummaryTree}
30983124
///
30993125
/// Currently creation locations are only available for [Widget] and [Element].
31003126
_Location? _getCreationLocation(Object? object) {
3101-
final Object? candidate = object is Element && !object.debugIsDefunct ? object.widget : object;
3102-
return candidate is _HasCreationLocation ? candidate._location : null;
3127+
final Object? candidate = object is Element && !object.debugIsDefunct ? object.widget : object;
3128+
return candidate == null ? null : _getObjectCreationLocation(candidate);
31033129
}
31043130

31053131
// _Location objects are always const so we don't need to worry about the GC
@@ -3226,7 +3252,7 @@ class InspectorSerializationDelegate implements DiagnosticsSerializationDelegate
32263252
if (creationLocation != null) {
32273253
result['locationId'] = _toLocationId(creationLocation);
32283254
result['creationLocation'] = creationLocation.toJsonMap();
3229-
if (service._isLocalCreationLocation(creationLocation)) {
3255+
if (service._isLocalCreationLocation(creationLocation.file)) {
32303256
_nodesCreatedByLocalProject.add(node);
32313257
result['createdByLocalProject'] = true;
32323258
}

packages/flutter/test/widgets/widget_inspector_test.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2998,6 +2998,46 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
29982998
expect(debugIsLocalCreationLocation(paddingElement.widget), isFalse);
29992999
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // [intended] Test requires --track-widget-creation flag.
30003000

3001+
testWidgets('debugIsWidgetLocalCreation test', (WidgetTester tester) async {
3002+
setupDefaultPubRootDirectory(service);
3003+
3004+
final GlobalKey key = GlobalKey();
3005+
3006+
await tester.pumpWidget(
3007+
Directionality(
3008+
textDirection: TextDirection.ltr,
3009+
child: Container(
3010+
padding: const EdgeInsets.all(8),
3011+
child: Text('target', key: key, textDirection: TextDirection.ltr),
3012+
),
3013+
),
3014+
);
3015+
3016+
final Element element = key.currentContext! as Element;
3017+
expect(debugIsWidgetLocalCreation(element.widget), isTrue);
3018+
3019+
final Finder paddingFinder = find.byType(Padding);
3020+
final Element paddingElement = paddingFinder.evaluate().first;
3021+
expect(debugIsWidgetLocalCreation(paddingElement.widget), isFalse);
3022+
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // [intended] Test requires --track-widget-creation flag.
3023+
3024+
testWidgets('debugIsWidgetLocalCreation false test', (WidgetTester tester) async {
3025+
final GlobalKey key = GlobalKey();
3026+
3027+
await tester.pumpWidget(
3028+
Directionality(
3029+
textDirection: TextDirection.ltr,
3030+
child: Container(
3031+
padding: const EdgeInsets.all(8),
3032+
child: Text('target', key: key, textDirection: TextDirection.ltr),
3033+
),
3034+
),
3035+
);
3036+
3037+
final Element element = key.currentContext! as Element;
3038+
expect(debugIsWidgetLocalCreation(element.widget), isFalse);
3039+
}, skip: WidgetInspectorService.instance.isWidgetCreationTracked()); // [intended] Test requires --no-track-widget-creation flag.
3040+
30013041
test('devToolsInspectorUri test', () {
30023042
activeDevToolsServerAddress = 'http://127.0.0.1:9100';
30033043
connectedVmServiceUri = 'http://127.0.0.1:55269/798ay5al_FM=/';

packages/flutter_tools/lib/src/build_system/targets/common.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ class KernelSnapshot extends Target {
222222
),
223223
aot: buildMode.isPrecompiled,
224224
buildMode: buildMode,
225-
trackWidgetCreation: trackWidgetCreation && buildMode == BuildMode.debug,
225+
trackWidgetCreation: trackWidgetCreation && buildMode != BuildMode.release,
226226
targetModel: targetModel,
227227
outputFilePath: environment.buildDir.childFile('app.dill').path,
228228
packagesPath: packagesFile.path,

packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ void main() {
9393
'--target=flutter',
9494
'--no-print-incremental-dependencies',
9595
...buildModeOptions(BuildMode.profile, <String>[]),
96+
'--track-widget-creation',
9697
'--aot',
9798
'--tfa',
9899
'--packages',
@@ -109,7 +110,7 @@ void main() {
109110
expect(processManager, hasNoRemainingExpectations);
110111
});
111112

112-
testWithoutContext('KernelSnapshot does not use track widget creation on profile builds', () async {
113+
testWithoutContext('KernelSnapshot does use track widget creation on profile builds', () async {
113114
fileSystem.file('.dart_tool/package_config.json')
114115
..createSync(recursive: true)
115116
..writeAsStringSync('{"configVersion": 2, "packages":[]}');
@@ -129,6 +130,7 @@ void main() {
129130
'--target=flutter',
130131
'--no-print-incremental-dependencies',
131132
...buildModeOptions(BuildMode.profile, <String>[]),
133+
'--track-widget-creation',
132134
'--aot',
133135
'--tfa',
134136
'--packages',
@@ -166,6 +168,7 @@ void main() {
166168
'--target=flutter',
167169
'--no-print-incremental-dependencies',
168170
...buildModeOptions(BuildMode.profile, <String>[]),
171+
'--track-widget-creation',
169172
'--aot',
170173
'--tfa',
171174
'--packages',
@@ -204,6 +207,7 @@ void main() {
204207
'--target=flutter',
205208
'--no-print-incremental-dependencies',
206209
...buildModeOptions(BuildMode.profile, <String>[]),
210+
'--track-widget-creation',
207211
'--aot',
208212
'--tfa',
209213
'--packages',

0 commit comments

Comments
 (0)