Files
IKAR/lib/frontend/debug/performance_monitor_section.dart

224 lines
7.3 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../core/config/app_shape.dart';
import '../widgets/custom_notification.dart';
import '../widgets/glossy_pill.dart';
import 'performance_monitor.dart';
class DebugPerformanceSection extends StatefulWidget {
const DebugPerformanceSection({super.key});
@override
State<DebugPerformanceSection> createState() =>
_DebugPerformanceSectionState();
}
class _DebugPerformanceSectionState extends State<DebugPerformanceSection> {
Timer? _refreshTimer;
@override
void dispose() {
_refreshTimer?.cancel();
super.dispose();
}
void _toggle() {
final monitor = PerformanceMonitor.instance;
setState(() {
if (monitor.isActive) {
monitor.stop();
_refreshTimer?.cancel();
_refreshTimer = null;
} else {
monitor.start();
_refreshTimer = Timer.periodic(const Duration(milliseconds: 500), (_) {
if (mounted) setState(() {});
});
}
});
}
void _clear() {
setState(() => PerformanceMonitor.instance.clear());
}
Future<void> _copyLog() async {
final log = PerformanceMonitor.instance.exportLog();
await Clipboard.setData(ClipboardData(text: log));
if (!mounted) return;
showCustomNotification(
context,
'Лог скопирован (${PerformanceMonitor.instance.sampleCount} сэмплов)',
);
}
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final monitor = PerformanceMonitor.instance;
final stats = monitor.statsForLast(const Duration(seconds: 5));
return GlossyPill(
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(20),
depth: 6,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Мониторинг производительности (APM)',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
'Захватывает build/raster время каждого кадра через '
'SchedulerBinding.addTimingsCallback — задержки кадров без '
'внешних инструментов. Включи, повзаимодействуй с приложением '
'(скролл, ресайз), потом скопируй лог.',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12),
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
FilledButton(
onPressed: _toggle,
style: FilledButton.styleFrom(shape: AppShape.buttonBorder),
child: Text(monitor.isActive ? 'Стоп' : 'Начать'),
),
OutlinedButton(
onPressed: monitor.sampleCount == 0 ? null : _clear,
style: OutlinedButton.styleFrom(shape: AppShape.buttonBorder),
child: const Text('Очистить'),
),
OutlinedButton(
onPressed: monitor.sampleCount == 0 ? null : _copyLog,
style: OutlinedButton.styleFrom(shape: AppShape.buttonBorder),
child: const Text('Скопировать лог'),
),
],
),
if (monitor.sampleCount > 0) ...[
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_statRow(
cs,
'Build p95 / p99 (5с)',
'${stats.build.p95.toStringAsFixed(1)} / ${stats.build.p99.toStringAsFixed(1)} мс',
),
_statRow(
cs,
'Raster p95 / p99 (5с)',
'${stats.raster.p95.toStringAsFixed(1)} / ${stats.raster.p99.toStringAsFixed(1)} мс',
),
_statRow(
cs,
'Средний кадр (5с)',
'${stats.avgMs.toStringAsFixed(1)} мс',
),
_statRow(
cs,
'Худший кадр (5с)',
'${stats.worstMs.toStringAsFixed(1)} мс',
),
_statRow(cs, 'Сэмплов всего', '${monitor.sampleCount}'),
_statRow(
cs,
'Джанк (>16.7мс)',
'${monitor.jankCount}',
warn: monitor.jankCount > 0,
),
_statRow(
cs,
'Сильный джанк (>32мс)',
'${monitor.bigJankCount}',
warn: monitor.bigJankCount > 0,
),
],
),
),
if (monitor.worstContexts().isNotEmpty) ...[
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Что грузит кадры больше всего',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 6),
for (final e in monitor.worstContexts(top: 5))
_statRow(
cs,
e.key,
'${e.value.toStringAsFixed(0)} мс сверх бюджета',
warn: true,
),
],
),
),
],
],
],
),
);
}
Widget _statRow(
ColorScheme cs,
String label,
String value, {
bool warn = false,
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
),
Text(
value,
style: TextStyle(
color: warn ? cs.error : cs.onSurface,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
}