Add profile stats to the CPU profiler (#5340)

diff --git a/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/legacy/event_details.dart b/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/legacy/event_details.dart
index 9527c7e..936baf2 100644
--- a/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/legacy/event_details.dart
+++ b/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/legacy/event_details.dart
@@ -14,6 +14,7 @@
 import '../../../../profiler/cpu_profile_controller.dart';
 import '../../../../profiler/cpu_profile_model.dart';
 import '../../../../profiler/cpu_profiler.dart';
+import '../../../../profiler/panes/controls/profiler_controls.dart';
 import '../../../performance_model.dart';
 import '../../../performance_screen.dart';
 import 'legacy_events_controller.dart';
diff --git a/packages/devtools_app/lib/src/screens/profiler/cpu_profiler.dart b/packages/devtools_app/lib/src/screens/profiler/cpu_profiler.dart
index 094f34e..df074a0 100644
--- a/packages/devtools_app/lib/src/screens/profiler/cpu_profiler.dart
+++ b/packages/devtools_app/lib/src/screens/profiler/cpu_profiler.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:developer';
 
 import 'package:flutter/material.dart';
 
@@ -14,9 +13,9 @@
 import '../../shared/feature_flags.dart';
 import '../../shared/globals.dart';
 import '../../shared/primitives/auto_dispose.dart';
+import '../../shared/primitives/utils.dart';
 import '../../shared/theme.dart';
 import '../../shared/ui/colors.dart';
-import '../../shared/ui/filter.dart';
 import '../../shared/ui/search.dart';
 import '../../shared/ui/tab.dart';
 import '../../shared/utils.dart';
@@ -24,6 +23,7 @@
 import 'cpu_profile_model.dart';
 import 'panes/bottom_up.dart';
 import 'panes/call_tree.dart';
+import 'panes/controls/profiler_controls.dart';
 import 'panes/cpu_flame_chart.dart';
 import 'panes/method_table/method_table.dart';
 
@@ -269,6 +269,8 @@
             );
           },
         ),
+        if (currentTab.key != CpuProfiler.summaryTab)
+          CpuProfileStats(metadata: data.profileMetaData),
       ],
     );
   }
@@ -365,224 +367,65 @@
   }
 }
 
-class DisplayTreeGuidelinesToggle extends StatelessWidget {
-  const DisplayTreeGuidelinesToggle();
+// TODO(kenz): one improvement we could make on this is to show the denominator
+// for filtered profiles (e.g. 'Sample count: 10/14), or to at least show the
+// original value in the tooltip for each of these stats.
+class CpuProfileStats extends StatelessWidget {
+  CpuProfileStats({required this.metadata});
+
+  final CpuProfileMetaData metadata;
+
+  final _statsRowHeight = scaleByFontFactor(25.0);
 
   @override
   Widget build(BuildContext context) {
-    return ValueListenableBuilder<bool>(
-      valueListenable: preferences.cpuProfiler.displayTreeGuidelines,
-      builder: (context, displayTreeGuidelines, _) {
-        return ToggleButton(
-          onPressed: () {
-            preferences.cpuProfiler.displayTreeGuidelines.value =
-                !displayTreeGuidelines;
-          },
-          isSelected: displayTreeGuidelines,
-          message: 'Display guidelines',
-          icon: Icons.stacked_bar_chart,
-        );
-      },
-    );
-  }
-}
-
-class CpuProfileFilterDialog extends StatelessWidget {
-  const CpuProfileFilterDialog({required this.controller, Key? key})
-      : super(key: key);
-
-  static const filterQueryInstructions = '''
-Type a filter query to show or hide specific stack frames.
-
-Any text that is not paired with an available filter key below will be queried against all categories (method, uri).
-
-Available filters:
-    'uri', 'u'       (e.g. 'uri:my_dart_package/some_lib.dart', '-u:some_lib_to_hide')
-
-Example queries:
-    'someMethodName uri:my_dart_package,b_dart_package'
-    '.toString -uri:flutter'
-''';
-
-  double get _filterDialogWidth => scaleByFontFactor(400.0);
-
-  final CpuProfilerController controller;
-
-  @override
-  Widget build(BuildContext context) {
-    return FilterDialog<CpuStackFrame>(
-      dialogWidth: _filterDialogWidth,
-      controller: controller,
-      queryInstructions: filterQueryInstructions,
-    );
-  }
-}
-
-class CpuProfilerDisabled extends StatelessWidget {
-  const CpuProfilerDisabled(this.controller);
-
-  final CpuProfilerController controller;
-
-  @override
-  Widget build(BuildContext context) {
-    return Center(
-      child: Column(
-        mainAxisAlignment: MainAxisAlignment.center,
-        children: <Widget>[
-          const Text('CPU profiler is disabled.'),
-          Padding(
-            padding: const EdgeInsets.all(8.0),
-            child: ElevatedButton(
-              onPressed: controller.enableCpuProfiler,
-              child: const Text('Enable profiler'),
+    final samplePeriodValid = metadata.samplePeriod > 0;
+    final samplingPeriodDisplay = samplePeriodValid
+        ? const Duration(seconds: 1).inMicroseconds ~/ metadata.samplePeriod
+        : '--';
+    return OutlineDecoration(
+      child: Container(
+        height: _statsRowHeight,
+        padding: const EdgeInsets.all(densePadding),
+        child: Row(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          children: [
+            _stat(
+              tooltip: 'The duration of time spanned by the CPU samples',
+              text: 'Duration: ${msText(metadata.time!.duration)}',
             ),
-          ),
-        ],
+            _stat(
+              tooltip: 'The number of samples included in the profile',
+              text: 'Sample count: ${metadata.sampleCount}',
+            ),
+            _stat(
+              tooltip:
+                  'The frequency at which samples are collected by the profiler'
+                  '${samplePeriodValid ? ' (once every ${metadata.samplePeriod} micros)' : ''}',
+              text: 'Sampling rate: $samplingPeriodDisplay Hz',
+            ),
+            _stat(
+              tooltip: 'The maximum stack trace depth of a collected sample',
+              text: 'Sampling depth: ${metadata.stackDepth}',
+            ),
+          ],
+        ),
       ),
     );
   }
-}
 
-/// DropdownButton that controls the value of
-/// [ProfilerScreenController.userTagFilter].
-class UserTagDropdown extends StatelessWidget {
-  const UserTagDropdown(this.controller);
-
-  final CpuProfilerController controller;
-
-  @override
-  Widget build(BuildContext context) {
-    const filterByTag = 'Filter by tag:';
-    return ValueListenableBuilder<String>(
-      valueListenable: controller.userTagFilter,
-      builder: (context, userTag, _) {
-        final userTags = controller.userTags;
-        final tooltip = userTags.isNotEmpty
-            ? 'Filter the CPU profile by the given UserTag'
-            : 'No UserTags found for this CPU profile';
-        return SizedBox(
-          height: defaultButtonHeight,
-          child: DevToolsTooltip(
-            message: tooltip,
-            child: ValueListenableBuilder<bool>(
-              valueListenable: preferences.vmDeveloperModeEnabled,
-              builder: (context, vmDeveloperModeEnabled, _) {
-                return RoundedDropDownButton<String>(
-                  isDense: true,
-                  style: Theme.of(context).textTheme.bodyMedium,
-                  value: userTag,
-                  items: [
-                    _buildMenuItem(
-                      display:
-                          '$filterByTag ${CpuProfilerController.userTagNone}',
-                      value: CpuProfilerController.userTagNone,
-                    ),
-                    // We don't want to show the 'Default' tag if it is the only
-                    // tag available. The 'none' tag above is equivalent in this
-                    // case.
-                    if (!(userTags.length == 1 &&
-                        userTags.first == UserTag.defaultTag.label)) ...[
-                      for (final tag in userTags)
-                        _buildMenuItem(
-                          display: '$filterByTag $tag',
-                          value: tag,
-                        ),
-                      _buildMenuItem(
-                        display: 'Group by: User Tag',
-                        value: CpuProfilerController.groupByUserTag,
-                      ),
-                    ],
-                    if (vmDeveloperModeEnabled)
-                      _buildMenuItem(
-                        display: 'Group by: VM Tag',
-                        value: CpuProfilerController.groupByVmTag,
-                      ),
-                  ],
-                  onChanged: userTags.isEmpty ||
-                          (userTags.length == 1 &&
-                              userTags.first == UserTag.defaultTag.label)
-                      ? null
-                      : (String? tag) => _onUserTagChanged(tag!),
-                );
-              },
-            ),
-          ),
-        );
-      },
-    );
-  }
-
-  DropdownMenuItem<String> _buildMenuItem({
-    required String display,
-    required String value,
+  Widget _stat({
+    required String text,
+    required String tooltip,
   }) {
-    return DropdownMenuItem<String>(
-      value: value,
-      child: Text(display),
-    );
-  }
-
-  void _onUserTagChanged(String newTag) async {
-    try {
-      await controller.loadDataWithTag(newTag);
-    } catch (e) {
-      notificationService.push(e.toString());
-    }
-  }
-}
-
-/// DropdownButton that controls the value of
-/// [ProfilerScreenController.viewType].
-class ModeDropdown extends StatelessWidget {
-  const ModeDropdown(this.controller);
-
-  final CpuProfilerController controller;
-
-  @override
-  Widget build(BuildContext context) {
-    const mode = 'View:';
-    return ValueListenableBuilder<CpuProfilerViewType>(
-      valueListenable: controller.viewType,
-      builder: (context, viewType, _) {
-        final tooltip = viewType == CpuProfilerViewType.function
-            ? 'Display the profile in terms of the Dart call stack '
-                '(i.e., inlined frames are expanded)'
-            : 'Display the profile in terms of native stack frames '
-                '(i.e., inlined frames are not expanded, display code objects '
-                'rather than individual functions)';
-        return SizedBox(
-          height: defaultButtonHeight,
-          child: DevToolsTooltip(
-            message: tooltip,
-            child: RoundedDropDownButton<CpuProfilerViewType>(
-              isDense: true,
-              style: Theme.of(context).textTheme.bodyMedium,
-              value: viewType,
-              items: [
-                _buildMenuItem(
-                  display: '$mode ${CpuProfilerViewType.function}',
-                  value: CpuProfilerViewType.function,
-                ),
-                _buildMenuItem(
-                  display: '$mode ${CpuProfilerViewType.code}',
-                  value: CpuProfilerViewType.code,
-                ),
-              ],
-              onChanged: (type) => controller.updateView(type!),
-            ),
-          ),
-        );
-      },
-    );
-  }
-
-  DropdownMenuItem<CpuProfilerViewType> _buildMenuItem({
-    required String display,
-    required CpuProfilerViewType value,
-  }) {
-    return DropdownMenuItem<CpuProfilerViewType>(
-      value: value,
-      child: Text(display),
+    return Flexible(
+      child: DevToolsTooltip(
+        message: tooltip,
+        child: Text(
+          text,
+          overflow: TextOverflow.ellipsis,
+        ),
+      ),
     );
   }
 }
diff --git a/packages/devtools_app/lib/src/screens/profiler/panes/controls/profiler_controls.dart b/packages/devtools_app/lib/src/screens/profiler/panes/controls/profiler_controls.dart
new file mode 100644
index 0000000..9ccef12
--- /dev/null
+++ b/packages/devtools_app/lib/src/screens/profiler/panes/controls/profiler_controls.dart
@@ -0,0 +1,241 @@
+// Copyright 2023 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:developer';
+
+import 'package:flutter/material.dart';
+
+import '../../../../shared/common_widgets.dart';
+import '../../../../shared/globals.dart';
+import '../../../../shared/theme.dart';
+import '../../../../shared/ui/filter.dart';
+import '../../../../shared/utils.dart';
+import '../../cpu_profile_controller.dart';
+import '../../cpu_profile_model.dart';
+import '../../profiler_screen_controller.dart';
+
+final profilerScreenSearchFieldKey =
+    GlobalKey(debugLabel: 'ProfilerScreenSearchFieldKey');
+
+class DisplayTreeGuidelinesToggle extends StatelessWidget {
+  const DisplayTreeGuidelinesToggle();
+
+  @override
+  Widget build(BuildContext context) {
+    return ValueListenableBuilder<bool>(
+      valueListenable: preferences.cpuProfiler.displayTreeGuidelines,
+      builder: (context, displayTreeGuidelines, _) {
+        return ToggleButton(
+          onPressed: () {
+            preferences.cpuProfiler.displayTreeGuidelines.value =
+                !displayTreeGuidelines;
+          },
+          isSelected: displayTreeGuidelines,
+          message: 'Display guidelines',
+          icon: Icons.stacked_bar_chart,
+        );
+      },
+    );
+  }
+}
+
+class CpuProfileFilterDialog extends StatelessWidget {
+  const CpuProfileFilterDialog({required this.controller, Key? key})
+      : super(key: key);
+
+  static const filterQueryInstructions = '''
+Type a filter query to show or hide specific stack frames.
+
+Any text that is not paired with an available filter key below will be queried against all categories (method, uri).
+
+Available filters:
+    'uri', 'u'       (e.g. 'uri:my_dart_package/some_lib.dart', '-u:some_lib_to_hide')
+
+Example queries:
+    'someMethodName uri:my_dart_package,b_dart_package'
+    '.toString -uri:flutter'
+''';
+
+  double get _filterDialogWidth => scaleByFontFactor(400.0);
+
+  final CpuProfilerController controller;
+
+  @override
+  Widget build(BuildContext context) {
+    return FilterDialog<CpuStackFrame>(
+      dialogWidth: _filterDialogWidth,
+      controller: controller,
+      queryInstructions: filterQueryInstructions,
+    );
+  }
+}
+
+class CpuProfilerDisabled extends StatelessWidget {
+  const CpuProfilerDisabled(this.controller);
+
+  final CpuProfilerController controller;
+
+  @override
+  Widget build(BuildContext context) {
+    return Center(
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        children: <Widget>[
+          const Text('CPU profiler is disabled.'),
+          Padding(
+            padding: const EdgeInsets.all(8.0),
+            child: ElevatedButton(
+              onPressed: controller.enableCpuProfiler,
+              child: const Text('Enable profiler'),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+/// DropdownButton that controls the value of
+/// [ProfilerScreenController.userTagFilter].
+class UserTagDropdown extends StatelessWidget {
+  const UserTagDropdown(this.controller);
+
+  final CpuProfilerController controller;
+
+  @override
+  Widget build(BuildContext context) {
+    const filterByTag = 'Filter by tag:';
+    return ValueListenableBuilder<String>(
+      valueListenable: controller.userTagFilter,
+      builder: (context, userTag, _) {
+        final userTags = controller.userTags;
+        final tooltip = userTags.isNotEmpty
+            ? 'Filter the CPU profile by the given UserTag'
+            : 'No UserTags found for this CPU profile';
+        return SizedBox(
+          height: defaultButtonHeight,
+          child: DevToolsTooltip(
+            message: tooltip,
+            child: ValueListenableBuilder<bool>(
+              valueListenable: preferences.vmDeveloperModeEnabled,
+              builder: (context, vmDeveloperModeEnabled, _) {
+                return RoundedDropDownButton<String>(
+                  isDense: true,
+                  style: Theme.of(context).textTheme.bodyMedium,
+                  value: userTag,
+                  items: [
+                    _buildMenuItem(
+                      display:
+                          '$filterByTag ${CpuProfilerController.userTagNone}',
+                      value: CpuProfilerController.userTagNone,
+                    ),
+                    // We don't want to show the 'Default' tag if it is the only
+                    // tag available. The 'none' tag above is equivalent in this
+                    // case.
+                    if (!(userTags.length == 1 &&
+                        userTags.first == UserTag.defaultTag.label)) ...[
+                      for (final tag in userTags)
+                        _buildMenuItem(
+                          display: '$filterByTag $tag',
+                          value: tag,
+                        ),
+                      _buildMenuItem(
+                        display: 'Group by: User Tag',
+                        value: CpuProfilerController.groupByUserTag,
+                      ),
+                    ],
+                    if (vmDeveloperModeEnabled)
+                      _buildMenuItem(
+                        display: 'Group by: VM Tag',
+                        value: CpuProfilerController.groupByVmTag,
+                      ),
+                  ],
+                  onChanged: userTags.isEmpty ||
+                          (userTags.length == 1 &&
+                              userTags.first == UserTag.defaultTag.label)
+                      ? null
+                      : (String? tag) => _onUserTagChanged(tag!),
+                );
+              },
+            ),
+          ),
+        );
+      },
+    );
+  }
+
+  DropdownMenuItem<String> _buildMenuItem({
+    required String display,
+    required String value,
+  }) {
+    return DropdownMenuItem<String>(
+      value: value,
+      child: Text(display),
+    );
+  }
+
+  void _onUserTagChanged(String newTag) async {
+    try {
+      await controller.loadDataWithTag(newTag);
+    } catch (e) {
+      notificationService.push(e.toString());
+    }
+  }
+}
+
+/// DropdownButton that controls the value of
+/// [ProfilerScreenController.viewType].
+class ModeDropdown extends StatelessWidget {
+  const ModeDropdown(this.controller);
+
+  final CpuProfilerController controller;
+
+  @override
+  Widget build(BuildContext context) {
+    const mode = 'View:';
+    return ValueListenableBuilder<CpuProfilerViewType>(
+      valueListenable: controller.viewType,
+      builder: (context, viewType, _) {
+        final tooltip = viewType == CpuProfilerViewType.function
+            ? 'Display the profile in terms of the Dart call stack '
+                '(i.e., inlined frames are expanded)'
+            : 'Display the profile in terms of native stack frames '
+                '(i.e., inlined frames are not expanded, display code objects '
+                'rather than individual functions)';
+        return SizedBox(
+          height: defaultButtonHeight,
+          child: DevToolsTooltip(
+            message: tooltip,
+            child: RoundedDropDownButton<CpuProfilerViewType>(
+              isDense: true,
+              style: Theme.of(context).textTheme.bodyMedium,
+              value: viewType,
+              items: [
+                _buildMenuItem(
+                  display: '$mode ${CpuProfilerViewType.function}',
+                  value: CpuProfilerViewType.function,
+                ),
+                _buildMenuItem(
+                  display: '$mode ${CpuProfilerViewType.code}',
+                  value: CpuProfilerViewType.code,
+                ),
+              ],
+              onChanged: (type) => controller.updateView(type!),
+            ),
+          ),
+        );
+      },
+    );
+  }
+
+  DropdownMenuItem<CpuProfilerViewType> _buildMenuItem({
+    required String display,
+    required CpuProfilerViewType value,
+  }) {
+    return DropdownMenuItem<CpuProfilerViewType>(
+      value: value,
+      child: Text(display),
+    );
+  }
+}
diff --git a/packages/devtools_app/lib/src/screens/profiler/profiler_screen.dart b/packages/devtools_app/lib/src/screens/profiler/profiler_screen.dart
index aac6eb1..a109fc9 100644
--- a/packages/devtools_app/lib/src/screens/profiler/profiler_screen.dart
+++ b/packages/devtools_app/lib/src/screens/profiler/profiler_screen.dart
@@ -24,14 +24,12 @@
 import 'cpu_profile_controller.dart';
 import 'cpu_profile_model.dart';
 import 'cpu_profiler.dart';
+import 'panes/controls/profiler_controls.dart';
 import 'profiler_screen_controller.dart';
 
 final profilerScreenSearchFieldKey =
     GlobalKey(debugLabel: 'ProfilerScreenSearchFieldKey');
 
-const iosProfilerWorkaround =
-    'https://github.com/flutter/flutter/issues/88466#issuecomment-905830680';
-
 class ProfilerScreen extends Screen {
   ProfilerScreen()
       : super.conditional(
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics/isolate_statistics_view.dart b/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics/isolate_statistics_view.dart
index 7f5f3fa..072e5ad 100644
--- a/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics/isolate_statistics_view.dart
+++ b/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics/isolate_statistics_view.dart
@@ -12,7 +12,7 @@
 import '../../../shared/table/table.dart';
 import '../../../shared/table/table_data.dart';
 import '../../../shared/theme.dart';
-import '../../profiler/cpu_profiler.dart';
+import '../../profiler/panes/controls/profiler_controls.dart';
 import '../vm_developer_common_widgets.dart';
 import '../vm_developer_tools_screen.dart';
 import '../vm_service_private_extensions.dart';
diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
index d9b7ca8..2df6916 100644
--- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
+++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
@@ -15,7 +15,7 @@
 TODO: Remove this section if there are not any general updates.
 
 ## CPU profiler updates
-TODO: Remove this section if there are not any general updates.
+* Add ability to inspect statistics for a CPU profile - [#5340](https://github.com/flutter/devtools/pull/5340)
 
 ## Memory updates
 TODO: Remove this section if there are not any general updates.
diff --git a/packages/devtools_app/test/cpu_profiler/cpu_profiler_test.dart b/packages/devtools_app/test/cpu_profiler/cpu_profiler_test.dart
index ed8e607..7217928 100644
--- a/packages/devtools_app/test/cpu_profiler/cpu_profiler_test.dart
+++ b/packages/devtools_app/test/cpu_profiler/cpu_profiler_test.dart
@@ -8,6 +8,7 @@
 import 'package:devtools_app/src/screens/profiler/cpu_profiler.dart';
 import 'package:devtools_app/src/screens/profiler/panes/bottom_up.dart';
 import 'package:devtools_app/src/screens/profiler/panes/call_tree.dart';
+import 'package:devtools_app/src/screens/profiler/panes/controls/profiler_controls.dart';
 import 'package:devtools_app/src/screens/profiler/panes/cpu_flame_chart.dart';
 import 'package:devtools_app/src/screens/profiler/panes/method_table/method_table.dart';
 import 'package:devtools_app/src/shared/charts/flame_chart.dart';
@@ -87,6 +88,7 @@
         expect(find.byType(CpuCallTreeTable), findsNothing);
         expect(find.byType(CpuMethodTable), findsNothing);
         expect(find.byType(CpuProfileFlameChart), findsNothing);
+        expect(find.byType(CpuProfileStats), findsOneWidget);
         expect(find.byType(DisplayTreeGuidelinesToggle), findsOneWidget);
         expect(find.byType(UserTagDropdown), findsOneWidget);
         expect(find.byType(ExpandAllButton), findsOneWidget);
@@ -120,6 +122,7 @@
         expect(find.byType(CpuCallTreeTable), findsNothing);
         expect(find.byType(CpuMethodTable), findsNothing);
         expect(find.byType(CpuProfileFlameChart), findsNothing);
+        expect(find.byType(CpuProfileStats), findsNothing);
         expect(find.byType(DisplayTreeGuidelinesToggle), findsNothing);
         expect(find.byType(UserTagDropdown), findsNothing);
         expect(find.byType(ExpandAllButton), findsNothing);
@@ -148,6 +151,7 @@
         expect(find.byType(TabBar), findsOneWidget);
         expect(find.byKey(CpuProfiler.dataProcessingKey), findsNothing);
         expect(find.byType(CpuBottomUpTable), findsOneWidget);
+        expect(find.byType(CpuProfileStats), findsOneWidget);
         expect(find.byType(DisplayTreeGuidelinesToggle), findsOneWidget);
         expect(find.byType(UserTagDropdown), findsOneWidget);
         expect(find.byType(ExpandAllButton), findsOneWidget);
@@ -177,8 +181,9 @@
         expect(find.byType(TabBar), findsOneWidget);
         expect(find.byKey(CpuProfiler.dataProcessingKey), findsNothing);
         expect(find.byKey(summaryViewKey), findsOneWidget);
-        expect(find.byType(UserTagDropdown), findsNothing);
+        expect(find.byType(CpuProfileStats), findsNothing);
         expect(find.byType(DisplayTreeGuidelinesToggle), findsNothing);
+        expect(find.byType(UserTagDropdown), findsNothing);
         expect(find.byType(ExpandAllButton), findsNothing);
         expect(find.byType(CollapseAllButton), findsNothing);
         expect(find.byType(FlameChartHelpButton), findsNothing);
@@ -780,4 +785,80 @@
       });
     });
   });
+
+  group('$CpuProfileStats', () {
+    testWidgets('displays correctly', (WidgetTester tester) async {
+      final metadata = CpuProfileMetaData(
+        sampleCount: 100,
+        samplePeriod: 50,
+        stackDepth: 128,
+        time: TimeRange()
+          ..start = const Duration()
+          ..end = const Duration(seconds: 1),
+      );
+      await tester.pumpWidget(wrap(CpuProfileStats(metadata: metadata)));
+      await tester.pumpAndSettle();
+
+      expect(
+        find.byTooltip('The duration of time spanned by the CPU samples'),
+        findsOneWidget,
+      );
+      expect(
+        find.byTooltip('The number of samples included in the profile'),
+        findsOneWidget,
+      );
+      expect(
+        find.byTooltip(
+          'The frequency at which samples are collected by the profiler'
+          ' (once every 50 micros)',
+        ),
+        findsOneWidget,
+      );
+      expect(
+        find.byTooltip('The maximum stack trace depth of a collected sample'),
+        findsOneWidget,
+      );
+      expect(find.text('Duration: 1000.0 ms'), findsOneWidget);
+      expect(find.text('Sample count: 100'), findsOneWidget);
+      expect(find.text('Sampling rate: 20000 Hz'), findsOneWidget);
+      expect(find.text('Sampling depth: 128'), findsOneWidget);
+    });
+
+    testWidgets('displays correctly for invalid data',
+        (WidgetTester tester) async {
+      final metadata = CpuProfileMetaData(
+        sampleCount: 100,
+        samplePeriod: 0,
+        stackDepth: 128,
+        time: TimeRange()
+          ..start = const Duration()
+          ..end = const Duration(seconds: 1),
+      );
+      await tester.pumpWidget(wrap(CpuProfileStats(metadata: metadata)));
+      await tester.pumpAndSettle();
+
+      expect(
+        find.byTooltip('The duration of time spanned by the CPU samples'),
+        findsOneWidget,
+      );
+      expect(
+        find.byTooltip('The number of samples included in the profile'),
+        findsOneWidget,
+      );
+      expect(
+        find.byTooltip(
+          'The frequency at which samples are collected by the profiler',
+        ),
+        findsOneWidget,
+      );
+      expect(
+        find.byTooltip('The maximum stack trace depth of a collected sample'),
+        findsOneWidget,
+      );
+      expect(find.text('Duration: 1000.0 ms'), findsOneWidget);
+      expect(find.text('Sample count: 100'), findsOneWidget);
+      expect(find.text('Sampling rate: -- Hz'), findsOneWidget);
+      expect(find.text('Sampling depth: 128'), findsOneWidget);
+    });
+  });
 }
diff --git a/packages/devtools_app/test/cpu_profiler/profiler_screen_test.dart b/packages/devtools_app/test/cpu_profiler/profiler_screen_test.dart
index bc468c6..48f30d3 100644
--- a/packages/devtools_app/test/cpu_profiler/profiler_screen_test.dart
+++ b/packages/devtools_app/test/cpu_profiler/profiler_screen_test.dart
@@ -4,6 +4,7 @@
 
 import 'package:devtools_app/devtools_app.dart';
 import 'package:devtools_app/src/screens/profiler/cpu_profiler.dart';
+import 'package:devtools_app/src/screens/profiler/panes/controls/profiler_controls.dart';
 import 'package:devtools_app/src/service/vm_flags.dart' as vm_flags;
 import 'package:devtools_app/src/shared/ui/vm_flag_widgets.dart';
 import 'package:devtools_test/devtools_test.dart';
diff --git a/packages/devtools_app/test/performance/timeline_events/legacy/event_details_test.dart b/packages/devtools_app/test/performance/timeline_events/legacy/event_details_test.dart
index ddef23d..fce2e73 100644
--- a/packages/devtools_app/test/performance/timeline_events/legacy/event_details_test.dart
+++ b/packages/devtools_app/test/performance/timeline_events/legacy/event_details_test.dart
@@ -5,6 +5,7 @@
 import 'package:devtools_app/devtools_app.dart';
 import 'package:devtools_app/src/screens/performance/panes/timeline_events/legacy/event_details.dart';
 import 'package:devtools_app/src/screens/profiler/cpu_profiler.dart';
+import 'package:devtools_app/src/screens/profiler/panes/controls/profiler_controls.dart';
 import 'package:devtools_app/src/service/vm_flags.dart' as vm_flags;
 import 'package:devtools_app/src/shared/config_specific/import_export/import_export.dart';
 import 'package:devtools_app/src/shared/ui/vm_flag_widgets.dart';
diff --git a/packages/devtools_app/test/test_infra/goldens/cpu_profiler_bottom_up_guidelines.png b/packages/devtools_app/test/test_infra/goldens/cpu_profiler_bottom_up_guidelines.png
index ec278c8..c9c0f5a 100644
--- a/packages/devtools_app/test/test_infra/goldens/cpu_profiler_bottom_up_guidelines.png
+++ b/packages/devtools_app/test/test_infra/goldens/cpu_profiler_bottom_up_guidelines.png
Binary files differ
diff --git a/packages/devtools_app/test/test_infra/goldens/cpu_profiler_bottom_up_no_guidelines.png b/packages/devtools_app/test/test_infra/goldens/cpu_profiler_bottom_up_no_guidelines.png
index 1a483fd..df7109f 100644
--- a/packages/devtools_app/test/test_infra/goldens/cpu_profiler_bottom_up_no_guidelines.png
+++ b/packages/devtools_app/test/test_infra/goldens/cpu_profiler_bottom_up_no_guidelines.png
Binary files differ
diff --git a/packages/devtools_app/test/test_infra/goldens/cpu_profiler_call_tree_guidelines.png b/packages/devtools_app/test/test_infra/goldens/cpu_profiler_call_tree_guidelines.png
index cfc8b77..e01819b 100644
--- a/packages/devtools_app/test/test_infra/goldens/cpu_profiler_call_tree_guidelines.png
+++ b/packages/devtools_app/test/test_infra/goldens/cpu_profiler_call_tree_guidelines.png
Binary files differ
diff --git a/packages/devtools_app/test/test_infra/goldens/cpu_profiler_call_tree_no_guidelines.png b/packages/devtools_app/test/test_infra/goldens/cpu_profiler_call_tree_no_guidelines.png
index 1828222..19b7ae0 100644
--- a/packages/devtools_app/test/test_infra/goldens/cpu_profiler_call_tree_no_guidelines.png
+++ b/packages/devtools_app/test/test_infra/goldens/cpu_profiler_call_tree_no_guidelines.png
Binary files differ