257 lines
6.6 KiB
Dart
257 lines
6.6 KiB
Dart
import 'dart:ui';
|
|
|
|
import 'dart:async';
|
|
import 'dart:developer';
|
|
import 'package:collection/collection.dart' show IterableExtension;
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:vm_service/utils.dart';
|
|
import 'package:vm_service/vm_service.dart';
|
|
import 'package:vm_service/vm_service_io.dart';
|
|
|
|
class VMHelper with ChangeNotifier {
|
|
factory VMHelper() => _vMHelper;
|
|
|
|
VMHelper._() {
|
|
assert(() {
|
|
_startConnect().whenComplete(() {
|
|
_timer = Timer.periodic(const Duration(seconds: 2), (Timer timer) {
|
|
VMHelper()._updateMemoryUsage().whenComplete(() {
|
|
notifyListeners();
|
|
});
|
|
});
|
|
});
|
|
return true;
|
|
}());
|
|
}
|
|
|
|
static final VMHelper _vMHelper = VMHelper._();
|
|
|
|
late bool connected;
|
|
VmService? serviceClient;
|
|
VM? vm;
|
|
|
|
late MemoryUsage mainMemoryUsage;
|
|
late Timer _timer;
|
|
List<MyMemoryUsage> mainHistoryMemoryInfo = <MyMemoryUsage>[];
|
|
|
|
IsolateRef? get main => vm!.isolates!
|
|
.firstWhereOrNull((IsolateRef element) => element.name == 'main');
|
|
|
|
int get count => _count;
|
|
int _count = 0;
|
|
|
|
Future<void> _startConnect() async {
|
|
try {
|
|
final ServiceProtocolInfo info = await Service.getInfo();
|
|
if (info.serverUri == null) {
|
|
print('Service protocol url is null, start vm service fail.');
|
|
return;
|
|
}
|
|
final Uri uri = convertToWebSocketUrl(
|
|
serviceProtocolUrl: info.serverUri!,
|
|
);
|
|
serviceClient = await vmServiceConnectUri(
|
|
uri.toString(),
|
|
log: StdoutLog(),
|
|
);
|
|
print('Socket connected in service $info');
|
|
connected = true;
|
|
vm = await serviceClient!.getVM();
|
|
await _updateMemoryUsage();
|
|
} catch (e) {
|
|
print(
|
|
'Socket connect failed due to $e. '
|
|
"Make sure you're not using simulators.",
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _updateMemoryUsage() async {
|
|
if (vm != null && connected) {
|
|
final MemoryUsage memoryUsage = await serviceClient!.getMemoryUsage(
|
|
main!.id!,
|
|
);
|
|
mainMemoryUsage = memoryUsage;
|
|
final MyMemoryUsage latest = MyMemoryUsage.copyFromMemoryUsage(
|
|
memoryUsage,
|
|
);
|
|
mainHistoryMemoryInfo.add(latest);
|
|
mainHistoryMemoryInfo.removeWhere(
|
|
(MyMemoryUsage element) => element.dataTime.isBefore(
|
|
latest.dataTime.subtract(const Duration(minutes: 1)),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
void clear() {
|
|
_count = 0;
|
|
mainHistoryMemoryInfo.clear();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_timer.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void forceGC() {
|
|
if (kDebugMode)
|
|
serviceClient?.getAllocationProfile(main?.id ?? '', gc: true);
|
|
}
|
|
}
|
|
|
|
class MyMemoryUsage {
|
|
MyMemoryUsage({
|
|
required int externalUsage,
|
|
required int heapCapacity,
|
|
required int heapUsage,
|
|
}) : dataTime = DateTime.now(),
|
|
externalUsage = externalUsage / 1024 / 1024,
|
|
heapCapacity = heapCapacity / 1024 / 1024,
|
|
heapUsage = heapUsage / 1024 / 1024;
|
|
|
|
final DateTime dataTime;
|
|
|
|
/// The amount of non-Dart memory that is retained by Dart objects. For
|
|
/// example, memory associated with Dart objects through APIs such as
|
|
/// Dart_NewFinalizableHandle, Dart_NewWeakPersistentHandle and
|
|
/// Dart_NewExternalTypedData. This usage is only as accurate as the values
|
|
/// supplied to these APIs from the VM embedder or native extensions. This
|
|
/// external memory applies GC pressure, but is separate from heapUsage and
|
|
/// heapCapacity.
|
|
final double externalUsage;
|
|
|
|
/// The total capacity of the heap in bytes. This is the amount of memory used
|
|
/// by the Dart heap from the perspective of the operating system.
|
|
final double heapCapacity;
|
|
|
|
/// The current heap memory usage in bytes. Heap usage is always less than or
|
|
/// equal to the heap capacity.
|
|
final double heapUsage;
|
|
|
|
static MyMemoryUsage copyFromMemoryUsage(MemoryUsage memoryUsage) =>
|
|
MyMemoryUsage(
|
|
externalUsage: memoryUsage.externalUsage!,
|
|
heapCapacity: memoryUsage.heapCapacity!,
|
|
heapUsage: memoryUsage.heapUsage!,
|
|
);
|
|
|
|
double toDouble(double d) {
|
|
return double.parse(d.toStringAsFixed(2));
|
|
}
|
|
}
|
|
|
|
class StdoutLog extends Log {
|
|
@override
|
|
void warning(String message) => print(message);
|
|
|
|
@override
|
|
void severe(String message) => print(message);
|
|
}
|
|
|
|
class ByteUtil {
|
|
const ByteUtil._();
|
|
|
|
static String toByteString(int bytes) {
|
|
if (bytes <= 1024) {
|
|
return '${bytes}B';
|
|
} else if (bytes <= 1024 * 1024) {
|
|
return '${(bytes / (1024)).toStringAsFixed(2)}K';
|
|
} else {
|
|
return '${(bytes / (1024 * 1024)).toStringAsFixed(2)}M';
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class MemoryUsageView extends StatefulWidget {
|
|
@override
|
|
_MemoryUsageViewState createState() => _MemoryUsageViewState();
|
|
}
|
|
|
|
class _MemoryUsageViewState extends State<MemoryUsageView> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
VMHelper().addListener(_updateMemoryUsage);
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
}
|
|
|
|
void _updateMemoryUsage() {
|
|
if (mounted) {
|
|
setState(() {});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
VMHelper().removeListener(_updateMemoryUsage);
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (VMHelper().serviceClient == null) {
|
|
return Container();
|
|
}
|
|
final MemoryUsage main = VMHelper().mainMemoryUsage;
|
|
|
|
return Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.max,
|
|
children: <Widget>[
|
|
const Text('Used: '),
|
|
const SizedBox(
|
|
height: 5,
|
|
),
|
|
Text(
|
|
ByteUtil.toByteString(main.heapUsage!),
|
|
style: const TextStyle(
|
|
color: Colors.red,
|
|
height: 1.4,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
Divider(
|
|
color: Colors.white.withOpacity(0.1),
|
|
thickness: 1,
|
|
),
|
|
const Text('Capacity: '),
|
|
const SizedBox(
|
|
height: 5,
|
|
),
|
|
Text(
|
|
ByteUtil.toByteString(main.heapCapacity!),
|
|
style: const TextStyle(
|
|
color: Colors.blue,
|
|
height: 1.4,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
Divider(
|
|
color: Colors.white.withOpacity(0.1),
|
|
thickness: 1,
|
|
),
|
|
const Text('External: '),
|
|
const SizedBox(
|
|
height: 5,
|
|
),
|
|
Text(
|
|
ByteUtil.toByteString(main.externalUsage!),
|
|
style: const TextStyle(
|
|
color: Colors.green,
|
|
height: 1.4,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
} |