Unverified Commit 20a9f1d8 authored by gaaclarke's avatar gaaclarke Committed by GitHub

Added option for Platform Channel statistics and Timeline events (#104531)

parent 5aca8bd1
...@@ -13,6 +13,18 @@ import 'hardware_keyboard.dart'; ...@@ -13,6 +13,18 @@ import 'hardware_keyboard.dart';
/// of their extent of support for keyboard API. /// of their extent of support for keyboard API.
KeyDataTransitMode? debugKeyEventSimulatorTransitModeOverride; KeyDataTransitMode? debugKeyEventSimulatorTransitModeOverride;
/// Profile and print statistics on Platform Channel usage.
///
/// When this is is true statistics about the usage of Platform Channels will be
/// printed out periodically to the console and Timeline events will show the
/// time between sending and receiving a message (encoding and decoding time
/// excluded).
///
/// The statistics include the total bytes transmitted and the average number of
/// bytes per invocation in the last quantum. "Up" means in the direction of
/// Flutter to the host platform, "down" is the host platform to flutter.
bool debugProfilePlatformChannels = false;
/// Returns true if none of the widget library debug variables have been changed. /// Returns true if none of the widget library debug variables have been changed.
/// ///
/// This function is used by the test framework to ensure that debug variables /// This function is used by the test framework to ensure that debug variables
...@@ -24,6 +36,9 @@ bool debugAssertAllServicesVarsUnset(String reason) { ...@@ -24,6 +36,9 @@ bool debugAssertAllServicesVarsUnset(String reason) {
if (debugKeyEventSimulatorTransitModeOverride != null) { if (debugKeyEventSimulatorTransitModeOverride != null) {
throw FlutterError(reason); throw FlutterError(reason);
} }
if (debugProfilePlatformChannels) {
throw FlutterError(reason);
}
return true; return true;
}()); }());
return true; return true;
......
...@@ -3,14 +3,124 @@ ...@@ -3,14 +3,124 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'dart:ui' show PlatformMessageResponseCallback;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'binary_messenger.dart'; import 'binary_messenger.dart';
import 'binding.dart'; import 'binding.dart';
import 'debug.dart' show debugProfilePlatformChannels;
import 'message_codec.dart'; import 'message_codec.dart';
import 'message_codecs.dart'; import 'message_codecs.dart';
bool _debugProfilePlatformChannelsIsRunning = false;
const Duration _debugProfilePlatformChannelsRate = Duration(seconds: 1);
final Expando<BinaryMessenger> _debugBinaryMessengers = Expando<BinaryMessenger>();
class _ProfiledBinaryMessenger implements BinaryMessenger {
const _ProfiledBinaryMessenger(this.proxy, this.channelTypeName, this.codecTypeName);
final BinaryMessenger proxy;
final String channelTypeName;
final String codecTypeName;
@override
Future<void> handlePlatformMessage(String channel, ByteData? data, PlatformMessageResponseCallback? callback) {
return proxy.handlePlatformMessage(channel, data, callback);
}
Future<ByteData?>? sendWithPostfix(String channel, String postfix, ByteData? message) async {
final TimelineTask task = TimelineTask();
_debugRecordUpStream(channelTypeName, '$channel$postfix', codecTypeName, message);
task.start('Platform Channel send $channel$postfix');
final ByteData? result;
try {
result = await proxy.send(channel, message);
} finally {
task.finish();
}
_debugRecordDownStream(channelTypeName, '$channel$postfix', codecTypeName, result);
return result;
}
@override
Future<ByteData?>? send(String channel, ByteData? message) =>
sendWithPostfix(channel, '', message);
@override
void setMessageHandler(String channel, MessageHandler? handler) {
proxy.setMessageHandler(channel, handler);
}
}
class _PlatformChannelStats {
_PlatformChannelStats(this.channel, this.codec, this.type);
final String channel;
final String codec;
final String type;
int _upCount = 0;
int _upBytes = 0;
int get upBytes => _upBytes;
void addUpStream(int bytes) {
_upCount += 1;
_upBytes += bytes;
}
int _downCount = 0;
int _downBytes = 0;
int get downBytes => _downBytes;
void addDownStream(int bytes) {
_downCount += 1;
_downBytes += bytes;
}
double get averageUpPayload => _upBytes / _upCount;
double get averageDownPayload => _downBytes / _downCount;
}
final Map<String, _PlatformChannelStats> _debugProfilePlatformChannelsStats = <String, _PlatformChannelStats>{};
Future<void> _debugLaunchProfilePlatformChannels() async {
if (!_debugProfilePlatformChannelsIsRunning) {
_debugProfilePlatformChannelsIsRunning = true;
await Future<dynamic>.delayed(_debugProfilePlatformChannelsRate);
_debugProfilePlatformChannelsIsRunning = false;
final StringBuffer log = StringBuffer();
log.writeln('Platform Channel Stats:');
final List<_PlatformChannelStats> allStats =
_debugProfilePlatformChannelsStats.values.toList();
// Sort highest combined bandwidth first.
allStats.sort((_PlatformChannelStats x, _PlatformChannelStats y) =>
(y.upBytes + y.downBytes) - (x.upBytes + x.downBytes));
for (final _PlatformChannelStats stats in allStats) {
log.writeln(
' (name:"${stats.channel}" type:"${stats.type}" codec:"${stats.codec}" upBytes:${stats.upBytes} upBytes_avg:${stats.averageUpPayload.toStringAsFixed(1)} downBytes:${stats.downBytes} downBytes_avg:${stats.averageDownPayload.toStringAsFixed(1)})');
}
debugPrint(log.toString());
_debugProfilePlatformChannelsStats.clear();
}
}
void _debugRecordUpStream(String channelTypeName, String name,
String codecTypeName, ByteData? bytes) {
final _PlatformChannelStats stats =
_debugProfilePlatformChannelsStats[name] ??=
_PlatformChannelStats(name, codecTypeName, channelTypeName);
stats.addUpStream(bytes?.lengthInBytes ?? 0);
_debugLaunchProfilePlatformChannels();
}
void _debugRecordDownStream(String channelTypeName, String name,
String codecTypeName, ByteData? bytes) {
final _PlatformChannelStats stats =
_debugProfilePlatformChannelsStats[name] ??=
_PlatformChannelStats(name, codecTypeName, channelTypeName);
stats.addDownStream(bytes?.lengthInBytes ?? 0);
_debugLaunchProfilePlatformChannels();
}
/// A named channel for communicating with platform plugins using asynchronous /// A named channel for communicating with platform plugins using asynchronous
/// message passing. /// message passing.
/// ///
...@@ -49,7 +159,15 @@ class BasicMessageChannel<T> { ...@@ -49,7 +159,15 @@ class BasicMessageChannel<T> {
final MessageCodec<T> codec; final MessageCodec<T> codec;
/// The messenger which sends the bytes for this channel, not null. /// The messenger which sends the bytes for this channel, not null.
BinaryMessenger get binaryMessenger => _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger; BinaryMessenger get binaryMessenger {
final BinaryMessenger result =
_binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
return !kReleaseMode && debugProfilePlatformChannels
? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger(
// ignore: no_runtimetype_tostring
result, runtimeType.toString(), codec.runtimeType.toString())
: result;
}
final BinaryMessenger? _binaryMessenger; final BinaryMessenger? _binaryMessenger;
/// Sends the specified [message] to the platform plugins on this channel. /// Sends the specified [message] to the platform plugins on this channel.
...@@ -129,7 +247,15 @@ class MethodChannel { ...@@ -129,7 +247,15 @@ class MethodChannel {
/// The messenger used by this channel to send platform messages. /// The messenger used by this channel to send platform messages.
/// ///
/// The messenger may not be null. /// The messenger may not be null.
BinaryMessenger get binaryMessenger => _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger; BinaryMessenger get binaryMessenger {
final BinaryMessenger result =
_binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
return !kReleaseMode && debugProfilePlatformChannels
? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger(
// ignore: no_runtimetype_tostring
result, runtimeType.toString(), codec.runtimeType.toString())
: result;
}
final BinaryMessenger? _binaryMessenger; final BinaryMessenger? _binaryMessenger;
/// Backend implementation of [invokeMethod]. /// Backend implementation of [invokeMethod].
...@@ -154,10 +280,11 @@ class MethodChannel { ...@@ -154,10 +280,11 @@ class MethodChannel {
@optionalTypeArgs @optionalTypeArgs
Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async { Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async {
assert(method != null); assert(method != null);
final ByteData? result = await binaryMessenger.send( final ByteData input = codec.encodeMethodCall(MethodCall(method, arguments));
name, final ByteData? result =
codec.encodeMethodCall(MethodCall(method, arguments)), !kReleaseMode && debugProfilePlatformChannels ?
); await (binaryMessenger as _ProfiledBinaryMessenger).sendWithPostfix(name, '#$method', input) :
await binaryMessenger.send(name, input);
if (result == null) { if (result == null) {
if (missingOk) { if (missingOk) {
return null; return null;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment