-
-
Save hastebrot/ff065656d47f6f9f507df87b0b398172 to your computer and use it in GitHub Desktop.
Flutter GPU memory growth repro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'dart:typed_data'; | |
import 'package:flutter/material.dart'; | |
import 'dart:ui' as ui; | |
void main() => runApp(MyApp()); | |
int xorshift32(int x) { | |
x ^= x << 13; | |
x ^= x >> 17; | |
x ^= x << 5; | |
return x; | |
} | |
int seed = 0xDEADBEEF; | |
const kImageDimension = 1024; | |
Future<ui.Image> makeImage() { | |
final c = Completer<ui.Image>(); | |
final pixels = Int32List(kImageDimension * kImageDimension); | |
for (int i = 0; i < pixels.length; i++) { | |
seed = pixels[i] = xorshift32(seed); | |
} | |
ui.decodeImageFromPixels( | |
pixels.buffer.asUint8List(), | |
kImageDimension, | |
kImageDimension, | |
ui.PixelFormat.rgba8888, | |
c.complete, | |
); | |
return c.future; | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: MyHomePage(title: 'Flutter Demo Home Page'), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
MyHomePage({Key key, this.title}) : super(key: key); | |
final String title; | |
@override | |
_MyHomePageState createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(widget.title), | |
), | |
body: GridView.builder( | |
gridDelegate: | |
SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 128.0), | |
itemBuilder: (context, index) { | |
return FutureBuilder<ui.Image>( | |
future: makeImage(), | |
builder: (context, snapshot) { | |
if (snapshot.hasData) { | |
return RawImage( | |
image: snapshot.data, | |
); | |
} else { | |
return Center(child: CircularProgressIndicator()); | |
} | |
}, | |
); | |
}, | |
), | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Tracks memory usage of an Android application running on a device connected | |
/// via ADB. | |
/// | |
/// Usage: dart tracemem.dart <package name> | |
library tracemem; | |
import 'dart:io'; | |
import 'dart:convert'; | |
import 'package:meta/meta.dart'; | |
import 'package:path/path.dart' as path; | |
String adb(Iterable<String> command) { | |
return Process.runSync( | |
path.join(androidSdk, "platform-tools/adb"), command.toList()) | |
.stdout; | |
} | |
String adbShell(Iterable<String> command) => adb(["shell"].followedBy(command)); | |
int pidOf(String package) => int.tryParse(adbShell(["pidof", package]).trim()); | |
MemInfoTable memInfo(int pid) { | |
String data = | |
adbShell(["dumpsys", "meminfo", "--local", "--checkin", "$pid"]).trim(); | |
if (data.startsWith("time")) { | |
data = data.split("\n").elementAt(1); | |
} | |
final reader = RowReader(data.split(",").iterator); | |
if (reader.nextInt() != 4) throw "Unsupported checkin format"; | |
reader.next(); // PID | |
reader.next(); // Process Name | |
return MemInfoTable.from(reader); | |
} | |
class RowReader { | |
final Iterator<String> iterator; | |
const RowReader(this.iterator); | |
String next() => iterator.moveNext() ? iterator.current : null; | |
int nextInt() => int.tryParse(next()) ?? 0; | |
} | |
class HeapInfo { | |
final int native, dalvik, other, total; | |
const HeapInfo({ | |
@required this.native, | |
@required this.dalvik, | |
@required this.other, | |
@required this.total, | |
}); | |
HeapInfo.from(RowReader reader) | |
: this( | |
native: reader.nextInt(), | |
dalvik: reader.nextInt(), | |
other: reader.nextInt(), | |
total: reader.nextInt(), | |
); | |
} | |
class MemStat { | |
final String name; | |
final int pss, | |
swappablePss, | |
sharedDirty, | |
sharedClean, | |
privateDirty, | |
privateClean, | |
swappedOut, | |
swappedOutPss; | |
const MemStat({ | |
@required this.name, | |
@required this.pss, | |
@required this.swappablePss, | |
@required this.sharedDirty, | |
@required this.sharedClean, | |
@required this.privateDirty, | |
@required this.privateClean, | |
@required this.swappedOut, | |
@required this.swappedOutPss, | |
}); | |
MemStat.from(String name, RowReader reader) | |
: this( | |
name: name, | |
pss: reader.nextInt(), | |
swappablePss: reader.nextInt(), | |
sharedDirty: reader.nextInt(), | |
sharedClean: reader.nextInt(), | |
privateDirty: reader.nextInt(), | |
privateClean: reader.nextInt(), | |
swappedOut: reader.nextInt(), | |
swappedOutPss: reader.nextInt(), | |
); | |
} | |
class MemInfoTable { | |
final HeapInfo max, | |
allocated, | |
free, | |
pss, | |
swappablePss, | |
sharedDirty, | |
sharedClean, | |
privateDirty, | |
privateClean, | |
swappedOut, | |
swappedOutPss; | |
final Map<String, MemStat> stats; | |
const MemInfoTable({ | |
@required this.max, | |
@required this.allocated, | |
@required this.free, | |
@required this.pss, | |
@required this.swappablePss, | |
@required this.sharedDirty, | |
@required this.sharedClean, | |
@required this.privateDirty, | |
@required this.privateClean, | |
@required this.swappedOut, | |
@required this.swappedOutPss, | |
@required this.stats, | |
}); | |
MemInfoTable.from(RowReader reader) | |
: this( | |
max: HeapInfo.from(reader), | |
allocated: HeapInfo.from(reader), | |
free: HeapInfo.from(reader), | |
pss: HeapInfo.from(reader), | |
swappablePss: HeapInfo.from(reader), | |
sharedDirty: HeapInfo.from(reader), | |
sharedClean: HeapInfo.from(reader), | |
privateDirty: HeapInfo.from(reader), | |
privateClean: HeapInfo.from(reader), | |
swappedOut: HeapInfo.from(reader), | |
swappedOutPss: HeapInfo.from(reader), | |
stats: Map.fromEntries(() sync* { | |
while (true) { | |
final name = reader.next(); | |
if (name.isEmpty) break; | |
yield MapEntry(name, MemStat.from(name, reader)); | |
} | |
}()), | |
); | |
} | |
void main(List<String> args) { | |
final pid = pidOf(args[0]); | |
final stopwatch = Stopwatch()..start(); | |
while (true) { | |
final info = memInfo(pid); | |
final size = info.stats["Gfx dev"].pss + | |
info.stats["EGL mtrack"].pss + | |
info.stats["GL mtrack"].pss; | |
print("Graphics memory size at ${stopwatch.elapsedMilliseconds}ms: $size kb (${pretty(size)})"); | |
sleep(Duration(seconds: 1)); | |
} | |
} | |
const kPrettySize = ["k", "m", "g"]; | |
String pretty(int size, [int index = 0]) { | |
if (size > 1024) { | |
return pretty(size ~/ 1024, index + 1); | |
} else { | |
return "$size ${kPrettySize[index]}b"; | |
} | |
} | |
/// Return the absolute path of the user's home directory | |
String get homeDirPath { | |
if (_homeDirPath == null) { | |
_homeDirPath = Platform.isWindows | |
? Platform.environment['USERPROFILE'] | |
: Platform.environment['HOME']; | |
if (_homeDirPath != null) _homeDirPath = path.absolute(_homeDirPath); | |
} | |
return _homeDirPath; | |
} | |
String _homeDirPath; | |
String _userHomeDir() { | |
final String envKey = Platform.isWindows ? 'APPDATA' : 'HOME'; | |
final String value = Platform.environment[envKey]; | |
return value == null ? '.' : value; | |
} | |
final Map<String, dynamic> config = () { | |
try { | |
return jsonDecode(File(path.join(_userHomeDir(), ".flutter_settings")) | |
.readAsStringSync()); | |
} on FileSystemException { | |
return const <String, dynamic>{}; | |
} | |
}(); | |
const String kAndroidHome = 'ANDROID_HOME'; | |
const String kAndroidSdkRoot = 'ANDROID_SDK_ROOT'; | |
final String androidSdk = () { | |
if (config.containsKey('android-sdk')) { | |
return config['android-sdk']; | |
} else if (Platform.environment.containsKey(kAndroidHome)) { | |
return Platform.environment[kAndroidHome]; | |
} else if (Platform.environment.containsKey(kAndroidSdkRoot)) { | |
return Platform.environment[kAndroidSdkRoot]; | |
} else if (Platform.isLinux) { | |
if (homeDirPath != null) return path.join(homeDirPath, 'Android', 'Sdk'); | |
} else if (Platform.isMacOS) { | |
if (homeDirPath != null) | |
return path.join(homeDirPath, 'Library', 'Android', 'sdk'); | |
} else if (Platform.isWindows) { | |
if (homeDirPath != null) | |
return path.join(homeDirPath, 'AppData', 'Local', 'Android', 'sdk'); | |
} | |
throw "Android SDK not found"; | |
}(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment