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
This commit is contained in:
gaaclarke 2022-04-05 10:54:21 -07:00 committed by GitHub
parent 839a183ea6
commit 35b18ba2e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 139 additions and 25 deletions

View file

@ -110,6 +110,17 @@ void main() {
expect(args['color'], 'Color(0xffffffff)');
debugProfileBuildsEnabled = false;
debugProfileBuildsEnabledUserWidgets = true;
await runFrame(() { TestRoot.state.updateWidget(Placeholder(key: UniqueKey(), color: const Color(0xFFFFFFFF))); });
events = await fetchInterestingEvents(interestingLabels);
expect(
events.map<String>(eventToName),
<String>['BUILD', 'Placeholder', 'LAYOUT', 'UPDATING COMPOSITING BITS', 'PAINT', 'COMPOSITING', 'FINALIZE TREE'],
);
args = (events.where((TimelineEvent event) => event.json!['name'] == '$Placeholder').single.json!['args'] as Map<String, Object?>).cast<String, String>();
expect(args['color'], 'Color(0xffffffff)');
debugProfileBuildsEnabledUserWidgets = false;
debugProfileLayoutsEnabled = true;
await runFrame(() { TestRoot.state.updateWidget(Placeholder(key: UniqueKey())); });
events = await fetchInterestingEvents(interestingLabels);

View file

@ -114,8 +114,24 @@ bool debugPrintGlobalKeyedWidgetLifecycle = false;
/// * [debugProfileLayoutsEnabled], which does something similar for layout,
/// and [debugPrintLayouts], its console equivalent.
/// * [debugProfilePaintsEnabled], which does something similar for painting.
/// * [debugProfileBuildsEnabledUserWidgets], which adds events for user-created
/// [Widget] build times and incurs less overhead.
bool debugProfileBuildsEnabled = false;
/// Adds [Timeline] events for every user-created [Widget] built.
///
/// A user-created [Widget] is any [Widget] that is constructed in the root
/// library. Often [Widget]s contain child [Widget]s that are constructed in
/// libraries (for example, a [TextButton] having a [RichText] child). Timeline
/// events for those children will be omitted with this flag. This works for any
/// [Widget] not just ones declared in the root library.
///
/// See also:
///
/// * [debugProfileBuildsEnabled], which functions similarly but shows events
/// for every widget and has a higher overhead cost.
bool debugProfileBuildsEnabledUserWidgets = false;
/// Show banners for deprecated widgets.
bool debugHighlightDeprecatedWidgets = false;
@ -423,7 +439,8 @@ bool debugAssertAllWidgetVarsUnset(String reason) {
debugPrintScheduleBuildForStacks ||
debugPrintGlobalKeyedWidgetLifecycle ||
debugProfileBuildsEnabled ||
debugHighlightDeprecatedWidgets) {
debugHighlightDeprecatedWidgets ||
debugProfileBuildsEnabledUserWidgets) {
throw FlutterError(reason);
}
return true;

View file

@ -14,6 +14,7 @@ import 'debug.dart';
import 'focus_manager.dart';
import 'inherited_model.dart';
import 'notification_listener.dart';
import 'widget_inspector.dart';
export 'package:flutter/foundation.dart' show
factory,
@ -2640,10 +2641,13 @@ class BuildOwner {
}
return true;
}());
if (!kReleaseMode && debugProfileBuildsEnabled) {
final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(element.widget);
if (isTimelineTracked) {
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
assert(() {
debugTimelineArguments = element.widget.toDiagnosticsNode().toTimelineArguments();
if (kDebugMode) {
debugTimelineArguments = element.widget.toDiagnosticsNode().toTimelineArguments();
}
return true;
}());
Timeline.startSync(
@ -2668,7 +2672,7 @@ class BuildOwner {
],
);
}
if (!kReleaseMode && debugProfileBuildsEnabled)
if (isTimelineTracked)
Timeline.finishSync();
index += 1;
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
@ -3078,6 +3082,12 @@ class _NotificationNode {
}
}
bool _isProfileBuildsEnabledFor(Widget widget) {
return debugProfileBuildsEnabled ||
(debugProfileBuildsEnabledUserWidgets &&
debugIsWidgetLocalCreation(widget));
}
/// An instantiation of a [Widget] at a particular location in the tree.
///
/// 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 {
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
if (!kReleaseMode && debugProfileBuildsEnabled) {
final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
if (isTimelineTracked) {
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
assert(() {
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
if (kDebugMode) {
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
}
return true;
}());
Timeline.startSync(
@ -3515,7 +3528,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
);
}
child.update(newWidget);
if (!kReleaseMode && debugProfileBuildsEnabled)
if (isTimelineTracked)
Timeline.finishSync();
assert(child.widget == newWidget);
assert(() {
@ -3765,10 +3778,13 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
Element inflateWidget(Widget newWidget, Object? newSlot) {
assert(newWidget != null);
if (!kReleaseMode && debugProfileBuildsEnabled) {
final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
if (isTimelineTracked) {
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
assert(() {
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
if (kDebugMode) {
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
}
return true;
}());
Timeline.startSync(
@ -3800,7 +3816,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
newChild.mount(this, newSlot);
assert(newChild._lifecycleState == _ElementLifecycle.active);
if (!kReleaseMode && debugProfileBuildsEnabled)
if (isTimelineTracked)
Timeline.finishSync();
return newChild;

View file

@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:collection' show HashMap;
import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:math' as math;
@ -742,6 +743,8 @@ mixin WidgetInspectorService {
int _nextId = 0;
List<String>? _pubRootDirectories;
/// Memoization for [_isLocalCreationLocation].
final HashMap<String, bool> _isLocalCreationCache = HashMap<String, bool>();
bool _trackRebuildDirtyWidgets = false;
bool _trackRepaintWidgets = false;
@ -1345,6 +1348,7 @@ mixin WidgetInspectorService {
_pubRootDirectories = pubRootDirectories
.map<String>((String directory) => Uri.parse(directory).path)
.toList();
_isLocalCreationCache.clear();
}
/// Set the [WidgetInspector] selection to the object matching the specified
@ -1518,14 +1522,11 @@ mixin WidgetInspectorService {
if (creationLocation == null) {
return false;
}
return _isLocalCreationLocation(creationLocation);
return _isLocalCreationLocation(creationLocation.file);
}
bool _isLocalCreationLocation(_Location? location) {
if (location == null) {
return false;
}
final String file = Uri.parse(location.file).path;
bool _isLocalCreationLocationImpl(String locationUri) {
final String file = Uri.parse(locationUri).path;
// By default check whether the creation location was within package:flutter.
if (_pubRootDirectories == null) {
@ -1541,6 +1542,17 @@ mixin WidgetInspectorService {
return false;
}
/// Memoized version of [_isLocalCreationLocationImpl].
bool _isLocalCreationLocation(String locationUri) {
final bool? cachedValue = _isLocalCreationCache[locationUri];
if (cachedValue != null) {
return cachedValue;
}
final bool result = _isLocalCreationLocationImpl(locationUri);
_isLocalCreationCache[locationUri] = result;
return result;
}
/// Wrapper around `json.encode` that uses a ring of cached values to prevent
/// the Dart garbage collector from collecting objects between when
/// the value is returned over the Observatory protocol and when the
@ -2066,7 +2078,7 @@ class _ElementLocationStatsTracker {
entry = _LocationCount(
location: location,
id: id,
local: WidgetInspectorService.instance._isLocalCreationLocation(location),
local: WidgetInspectorService.instance._isLocalCreationLocation(location.file),
);
if (entry.local) {
newLocations.add(entry);
@ -3072,14 +3084,24 @@ bool debugIsLocalCreationLocation(Object object) {
bool isLocal = false;
assert(() {
final _Location? location = _getCreationLocation(object);
if (location == null)
isLocal = false;
isLocal = WidgetInspectorService.instance._isLocalCreationLocation(location);
if (location != null) {
isLocal = WidgetInspectorService.instance._isLocalCreationLocation(location.file);
}
return true;
}());
return isLocal;
}
/// Returns true if a [Widget] is user created.
///
/// This is a faster variant of `debugIsLocalCreationLocation` that is available
/// in debug and profile builds but only works for [Widget].
bool debugIsWidgetLocalCreation(Widget widget) {
final _Location? location = _getObjectCreationLocation(widget);
return location != null &&
WidgetInspectorService.instance._isLocalCreationLocation(location.file);
}
/// Returns the creation location of an object in String format if one is available.
///
/// ex: "file:///path/to/main.dart:4:3"
@ -3092,14 +3114,18 @@ String? _describeCreationLocation(Object object) {
return location?.toString();
}
_Location? _getObjectCreationLocation(Object object) {
return object is _HasCreationLocation ? object._location : null;
}
/// Returns the creation location of an object if one is available.
///
/// {@macro flutter.widgets.WidgetInspectorService.getChildrenSummaryTree}
///
/// Currently creation locations are only available for [Widget] and [Element].
_Location? _getCreationLocation(Object? object) {
final Object? candidate = object is Element && !object.debugIsDefunct ? object.widget : object;
return candidate is _HasCreationLocation ? candidate._location : null;
final Object? candidate = object is Element && !object.debugIsDefunct ? object.widget : object;
return candidate == null ? null : _getObjectCreationLocation(candidate);
}
// _Location objects are always const so we don't need to worry about the GC
@ -3226,7 +3252,7 @@ class InspectorSerializationDelegate implements DiagnosticsSerializationDelegate
if (creationLocation != null) {
result['locationId'] = _toLocationId(creationLocation);
result['creationLocation'] = creationLocation.toJsonMap();
if (service._isLocalCreationLocation(creationLocation)) {
if (service._isLocalCreationLocation(creationLocation.file)) {
_nodesCreatedByLocalProject.add(node);
result['createdByLocalProject'] = true;
}

View file

@ -2998,6 +2998,46 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
expect(debugIsLocalCreationLocation(paddingElement.widget), isFalse);
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // [intended] Test requires --track-widget-creation flag.
testWidgets('debugIsWidgetLocalCreation test', (WidgetTester tester) async {
setupDefaultPubRootDirectory(service);
final GlobalKey key = GlobalKey();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Container(
padding: const EdgeInsets.all(8),
child: Text('target', key: key, textDirection: TextDirection.ltr),
),
),
);
final Element element = key.currentContext! as Element;
expect(debugIsWidgetLocalCreation(element.widget), isTrue);
final Finder paddingFinder = find.byType(Padding);
final Element paddingElement = paddingFinder.evaluate().first;
expect(debugIsWidgetLocalCreation(paddingElement.widget), isFalse);
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // [intended] Test requires --track-widget-creation flag.
testWidgets('debugIsWidgetLocalCreation false test', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Container(
padding: const EdgeInsets.all(8),
child: Text('target', key: key, textDirection: TextDirection.ltr),
),
),
);
final Element element = key.currentContext! as Element;
expect(debugIsWidgetLocalCreation(element.widget), isFalse);
}, skip: WidgetInspectorService.instance.isWidgetCreationTracked()); // [intended] Test requires --no-track-widget-creation flag.
test('devToolsInspectorUri test', () {
activeDevToolsServerAddress = 'http://127.0.0.1:9100';
connectedVmServiceUri = 'http://127.0.0.1:55269/798ay5al_FM=/';

View file

@ -222,7 +222,7 @@ class KernelSnapshot extends Target {
),
aot: buildMode.isPrecompiled,
buildMode: buildMode,
trackWidgetCreation: trackWidgetCreation && buildMode == BuildMode.debug,
trackWidgetCreation: trackWidgetCreation && buildMode != BuildMode.release,
targetModel: targetModel,
outputFilePath: environment.buildDir.childFile('app.dill').path,
packagesPath: packagesFile.path,

View file

@ -93,6 +93,7 @@ void main() {
'--target=flutter',
'--no-print-incremental-dependencies',
...buildModeOptions(BuildMode.profile, <String>[]),
'--track-widget-creation',
'--aot',
'--tfa',
'--packages',
@ -109,7 +110,7 @@ void main() {
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('KernelSnapshot does not use track widget creation on profile builds', () async {
testWithoutContext('KernelSnapshot does use track widget creation on profile builds', () async {
fileSystem.file('.dart_tool/package_config.json')
..createSync(recursive: true)
..writeAsStringSync('{"configVersion": 2, "packages":[]}');
@ -129,6 +130,7 @@ void main() {
'--target=flutter',
'--no-print-incremental-dependencies',
...buildModeOptions(BuildMode.profile, <String>[]),
'--track-widget-creation',
'--aot',
'--tfa',
'--packages',
@ -166,6 +168,7 @@ void main() {
'--target=flutter',
'--no-print-incremental-dependencies',
...buildModeOptions(BuildMode.profile, <String>[]),
'--track-widget-creation',
'--aot',
'--tfa',
'--packages',
@ -204,6 +207,7 @@ void main() {
'--target=flutter',
'--no-print-incremental-dependencies',
...buildModeOptions(BuildMode.profile, <String>[]),
'--track-widget-creation',
'--aot',
'--tfa',
'--packages',