Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@

import 'package:flutter/material.dart';

import '../../../../primitives/utils.dart';
import '../../../../shared/common_widgets.dart';
import '../../../../shared/theme.dart';
import '../../../../ui/colors.dart';
import '../../../../ui/utils.dart';
import '../controls/enhance_tracing/enhance_tracing_controller.dart';
import 'frame_analysis_model.dart';
import 'frame_hints.dart';
import 'frame_time_visualizer.dart';

class FlutterFrameAnalysisView extends StatelessWidget {
const FlutterFrameAnalysisView({
Expand Down Expand Up @@ -47,7 +45,6 @@ class FlutterFrameAnalysisView extends StatelessWidget {
bottom: denseSpacing,
),
),
// TODO(kenz): handle missing timeline events.
Expanded(
child: FrameTimeVisualizer(frameAnalysis: frameAnalysis),
),
Expand All @@ -56,163 +53,3 @@ class FlutterFrameAnalysisView extends StatelessWidget {
);
}
}

class FrameTimeVisualizer extends StatefulWidget {
const FrameTimeVisualizer({
Key? key,
required this.frameAnalysis,
}) : super(key: key);

final FrameAnalysis frameAnalysis;

@override
State<FrameTimeVisualizer> createState() => _FrameTimeVisualizerState();
}

class _FrameTimeVisualizerState extends State<FrameTimeVisualizer> {
late FrameAnalysis frameAnalysis;

@override
void initState() {
super.initState();
frameAnalysis = widget.frameAnalysis;
frameAnalysis.selectFramePhase(frameAnalysis.longestUiPhase);
}

@override
Widget build(BuildContext context) {
// TODO(kenz): calculate ratios to use as flex values. This will be a bit
// tricky because sometimes the Build event(s) are children of Layout.
// final buildTimeRatio = widget.frameAnalysis.buildTimeRatio();
// final layoutTimeRatio = widget.frameAnalysis.layoutTimeRatio();
// final paintTimeRatio = widget.frameAnalysis.paintTimeRatio();
return ValueListenableBuilder<FramePhase?>(
valueListenable: frameAnalysis.selectedPhase,
builder: (context, selectedPhase, _) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('UI phases:'),
const SizedBox(height: denseSpacing),
Row(
children: [
Flexible(
child: FramePhaseBlock(
framePhase: frameAnalysis.buildPhase,
icon: Icons.build,
isSelected: selectedPhase == frameAnalysis.buildPhase,
onSelected: frameAnalysis.selectFramePhase,
),
),
Flexible(
child: FramePhaseBlock(
framePhase: frameAnalysis.layoutPhase,
icon: Icons.auto_awesome_mosaic,
isSelected: selectedPhase == frameAnalysis.layoutPhase,
onSelected: frameAnalysis.selectFramePhase,
),
),
Flexible(
fit: FlexFit.tight,
child: FramePhaseBlock(
framePhase: frameAnalysis.paintPhase,
icon: Icons.format_paint,
isSelected: selectedPhase == frameAnalysis.paintPhase,
onSelected: frameAnalysis.selectFramePhase,
),
),
],
),
const SizedBox(height: denseSpacing),
const Text('Raster phase:'),
const SizedBox(height: denseSpacing),
Row(
children: [
Expanded(
child: FramePhaseBlock(
framePhase: frameAnalysis.rasterPhase,
icon: Icons.grid_on,
isSelected: selectedPhase == frameAnalysis.rasterPhase,
onSelected: frameAnalysis.selectFramePhase,
),
)
],
),
// TODO(kenz): show flame chart of selected events here.
],
);
},
);
}
}

class FramePhaseBlock extends StatelessWidget {
const FramePhaseBlock({
Key? key,
required this.framePhase,
required this.icon,
required this.isSelected,
required this.onSelected,
}) : super(key: key);

static const _height = 30.0;

static const _selectedIndicatorHeight = 4.0;

static const _backgroundColor = ThemedColor(
light: Color(0xFFEEEEEE),
dark: Color(0xFF3C4043),
);

static const _selectedBackgroundColor = ThemedColor(
light: Color(0xFFFFFFFF),
dark: Color(0xFF5F6367),
);

final FramePhase framePhase;

final IconData icon;

final bool isSelected;

final void Function(FramePhase) onSelected;

@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final durationText = framePhase.duration != Duration.zero
? msText(framePhase.duration)
: '--';
return InkWell(
onTap: () => onSelected(framePhase),
child: Stack(
alignment: AlignmentDirectional.bottomStart,
children: [
Container(
color: isSelected
? _selectedBackgroundColor.colorFor(colorScheme)
: _backgroundColor.colorFor(colorScheme),
height: _height,
padding: const EdgeInsets.symmetric(horizontal: densePadding),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: defaultIconSize,
),
const SizedBox(width: denseSpacing),
Text('${framePhase.title} - $durationText'),
],
),
),
if (isSelected)
Container(
color: defaultSelectionColor,
height: _selectedIndicatorHeight,
),
],
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/foundation.dart';

import '../../../../primitives/trees.dart';
import '../../../../primitives/utils.dart';
import '../../performance_model.dart';
Expand All @@ -17,14 +15,6 @@ class FrameAnalysis {

static const intrinsicsEventSuffix = ' intrinsics';

ValueListenable<FramePhase?> get selectedPhase => _selectedPhase;

final _selectedPhase = ValueNotifier<FramePhase?>(null);

void selectFramePhase(FramePhase block) {
_selectedPhase.value = block;
}

/// Data for the build phase of [frame].
///
/// This is drawn from all the "Build" events on the UI thread. For a single
Expand Down Expand Up @@ -128,6 +118,18 @@ class FrameAnalysis {

late FramePhase longestUiPhase = _calculateLongestFramePhase();

bool get hasUiData => _hasUiData ??= [
...buildPhase.events,
...layoutPhase.events,
...paintPhase.events
].isNotEmpty;

bool? _hasUiData;

bool get hasRasterData => _hasRasterData ??= rasterPhase.events.isNotEmpty;

bool? _hasRasterData;

FramePhase _calculateLongestFramePhase() {
var longestPhaseTime = Duration.zero;
late FramePhase longest;
Expand Down Expand Up @@ -189,33 +191,46 @@ class FrameAnalysis {
_intrinsicOperationsCount = _intrinsics;
}

// TODO(kenz): calculate ratios to use as flex values. This will be a bit
// tricky because sometimes the Build event(s) are children of Layout.
// int buildTimeRatio() {
// final totalBuildEventTimeMicros = buildTime.inMicroseconds;
// final uiEvent = frame.timelineEventData.uiEvent;
// if (uiEvent == null) return 1;
// final totalUiTimeMicros = uiEvent.time.duration.inMicroseconds;
// return ((totalBuildEventTimeMicros / totalUiTimeMicros) * 1000000).round();
// }
//
// int layoutTimeRatio() {
// final totalLayoutTimeMicros = layoutTime.inMicroseconds;
// final uiEvent = frame.timelineEventData.uiEvent;
// if (uiEvent == null) return 1;
// final totalUiTimeMicros =
// frame.timelineEventData.uiEvent.time.duration.inMicroseconds;
// return ((totalLayoutTimeMicros / totalUiTimeMicros) * 1000000).round();
// }
//
// int paintTimeRatio() {
// final totalPaintTimeMicros = paintTime.inMicroseconds;
// final uiEvent = frame.timelineEventData.uiEvent;
// if (uiEvent == null) return 1;
// final totalUiTimeMicros =
// frame.timelineEventData.uiEvent.time.duration.inMicroseconds;
// return ((totalPaintTimeMicros / totalUiTimeMicros) * 1000000).round();
// }
int? buildFlex;

int? layoutFlex;

int? paintFlex;

int? rasterFlex;

int? shaderCompilationFlex;

void calculateFramePhaseFlexValues() {
final totalUiTimeMicros =
(buildPhase.duration + layoutPhase.duration + paintPhase.duration)
.inMicroseconds;
buildFlex = _flexForPhase(buildPhase, totalUiTimeMicros);
layoutFlex = _flexForPhase(layoutPhase, totalUiTimeMicros);
paintFlex = _flexForPhase(paintPhase, totalUiTimeMicros);

if (frame.hasShaderTime) {
final totalRasterMicros = frame.rasterTime.inMicroseconds;
final shaderMicros = frame.shaderDuration.inMicroseconds;
final otherRasterMicros = totalRasterMicros - shaderMicros;
shaderCompilationFlex = _calculateFlex(shaderMicros, totalRasterMicros);
rasterFlex = _calculateFlex(otherRasterMicros, totalRasterMicros);
} else {
rasterFlex = 1;
}
}

int _flexForPhase(FramePhase phase, int totalTimeMicros) {
final totalPaintTimeMicros = phase.duration.inMicroseconds;
final uiEvent = frame.timelineEventData.uiEvent;
if (uiEvent == null) return 1;
return _calculateFlex(totalPaintTimeMicros, totalTimeMicros);
}

int _calculateFlex(int numeratorMicros, int denominatorMicros) {
if (numeratorMicros == 0 && denominatorMicros == 0) return 1;
return ((numeratorMicros / denominatorMicros) * 100).round();
}
}

enum FramePhaseType {
Expand Down Expand Up @@ -253,7 +268,7 @@ class FramePhase {
Duration? duration,
}) : title = type.eventName,
duration = duration ??
events.fold(Duration.zero, (previous, SyncTimelineEvent event) {
events.fold<Duration>(Duration.zero, (previous, event) {
return previous + event.time.duration;
});

Expand Down
Loading