Skip to content

Instantly share code, notes, and snippets.

@ds84182
Created January 17, 2019 02:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ds84182/6a949816f860a2344735383afe43e261 to your computer and use it in GitHub Desktop.
Save ds84182/6a949816f860a2344735383afe43e261 to your computer and use it in GitHub Desktop.
Flutter GPU memory growth repro
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());
}
},
);
},
),
);
}
}
/// 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