flutter / devtools

Performance tools for Flutter
https://flutter.dev/docs/development/tools/devtools/
BSD 3-Clause "New" or "Revised" License
1.58k stars 326 forks source link

DevTools performance page doesn't detect jank between frames #4479

Open kenzieschmoll opened 2 years ago

kenzieschmoll commented 2 years ago

In this example, the running app (Wonderous in profile mode) visibly janks, but there are no janky frames in the DevTools performance page. Upon looking deeper into the timeline trace, it is clear that there is a large gap of latency between two frames: Screen Shot 2022-09-15 at 11 47 36 AM Screen Shot 2022-09-15 at 11 48 03 AM

How is a user supposed to debug the jank at this point? How can we help them understand what could be causing the latency between frames?

CC @chinmaygarde @dnfield @iskakaushik @jonahwilliams for ideas.

kenzieschmoll commented 2 years ago

One idea is to keep a running average of all the 'VSYNC' event times we have seen, and to tell users that we detected a longer 'VSYNC' than normal. It is still unclear what we can tell them for why this happened and what they can do to fix it, though.

chinmaygarde commented 2 years ago

Can you attach a systrace or observatory trace for that? That should tell us more about whats happening and how we can fill in the blanks.

kenzieschmoll commented 2 years ago

Here's an Observatory trace dart-timeline-2022-9-15.json.txt

chinmaygarde commented 2 years ago

That seems to be a wait for the GPU to finish work. We usually capture and instrument a GPU frame for such cases. We could add a trace around the wait to indicate to the user that the CPU is workload is fine but we are waiting for the GPU to catch up.

kenzieschmoll commented 2 years ago

That would be helpful. Especially if that message is clear by the event name. So the performance solution here would be to optimize what is happening on the raster thread?

chinmaygarde commented 2 years ago

So the performance solution here would be to optimize what is happening on the raster thread?

Unfortunately, it's not quite that simple. It could be that the application has specified a particularly expensive operation (like a non-pipeline blend). There is nothing we can do on the raster thread in that case and the application must be reworked. With Impeller, we are putting more stuff in the GPU traces but we can only capture them currently using Xcode. Would it be useful to walk over our tracing workflow to see how and what we can expose via DevTools?

kenzieschmoll commented 2 years ago

Would it be useful to walk over our tracing workflow to see how and what we can expose via DevTools?

Yes, it definitely would. Feel free to throw something on my calendar.

kenzieschmoll commented 1 year ago

@jonahwilliams this sounds similar to the issue you were describing to me yesterday wrt latency

jonahwilliams commented 1 year ago

Ahh perfect. The idea i had was to track vsync delay and add the to build time. That would surface cases where build finished later due to being delayed by jank between frames, but not cases where there were no frames being pumped.

johnpryan commented 1 year ago

Here's a sample where an expensive calculation happens in setState(), which is computed between frames:

sample code ```dart import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { int _counter = 30; late int _fib; @override void initState() { super.initState(); _fib = _fibonacci(_counter); } void _incrementCounter() { setState(() { _counter++; _fib = _fibonacci(_counter + 1); }); } int _fibonacci(int n) { return switch (n) { 0 || 1 => n, var x => _fibonacci(x - 1) + _fibonacci(x - 2), }; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: ConstrainedBox( constraints: BoxConstraints.expand(), child: Column( mainAxisAlignment: MainAxisAlignment.center, textDirection: TextDirection.ltr, children: [ Text( 'Count:', style: Theme.of(context).textTheme.headlineLarge, ), Text( '$_counter', style: Theme.of(context).textTheme.displayLarge, ), Text( 'Fibonacci:', style: Theme.of(context).textTheme.headlineLarge, ), Text( '$_fib', style: Theme.of(context).textTheme.displayLarge, ), ], ), ), floatingActionButton: RepaintBoundary( child: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ), ); } } ```