Skip to content

Commit 20a9f1d

Browse files
authored
Added option for Platform Channel statistics and Timeline events (#104531)
1 parent 5aca8bd commit 20a9f1d

File tree

2 files changed

+148
-6
lines changed

2 files changed

+148
-6
lines changed

packages/flutter/lib/src/services/debug.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ import 'hardware_keyboard.dart';
1313
/// of their extent of support for keyboard API.
1414
KeyDataTransitMode? debugKeyEventSimulatorTransitModeOverride;
1515

16+
/// Profile and print statistics on Platform Channel usage.
17+
///
18+
/// When this is is true statistics about the usage of Platform Channels will be
19+
/// printed out periodically to the console and Timeline events will show the
20+
/// time between sending and receiving a message (encoding and decoding time
21+
/// excluded).
22+
///
23+
/// The statistics include the total bytes transmitted and the average number of
24+
/// bytes per invocation in the last quantum. "Up" means in the direction of
25+
/// Flutter to the host platform, "down" is the host platform to flutter.
26+
bool debugProfilePlatformChannels = false;
27+
1628
/// Returns true if none of the widget library debug variables have been changed.
1729
///
1830
/// This function is used by the test framework to ensure that debug variables
@@ -24,6 +36,9 @@ bool debugAssertAllServicesVarsUnset(String reason) {
2436
if (debugKeyEventSimulatorTransitModeOverride != null) {
2537
throw FlutterError(reason);
2638
}
39+
if (debugProfilePlatformChannels) {
40+
throw FlutterError(reason);
41+
}
2742
return true;
2843
}());
2944
return true;

packages/flutter/lib/src/services/platform_channel.dart

Lines changed: 133 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,124 @@
33
// found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:developer';
7+
import 'dart:ui' show PlatformMessageResponseCallback;
68

79
import 'package:flutter/foundation.dart';
810

911
import 'binary_messenger.dart';
1012
import 'binding.dart';
13+
import 'debug.dart' show debugProfilePlatformChannels;
1114
import 'message_codec.dart';
1215
import 'message_codecs.dart';
1316

17+
bool _debugProfilePlatformChannelsIsRunning = false;
18+
const Duration _debugProfilePlatformChannelsRate = Duration(seconds: 1);
19+
final Expando<BinaryMessenger> _debugBinaryMessengers = Expando<BinaryMessenger>();
20+
21+
class _ProfiledBinaryMessenger implements BinaryMessenger {
22+
const _ProfiledBinaryMessenger(this.proxy, this.channelTypeName, this.codecTypeName);
23+
final BinaryMessenger proxy;
24+
final String channelTypeName;
25+
final String codecTypeName;
26+
27+
@override
28+
Future<void> handlePlatformMessage(String channel, ByteData? data, PlatformMessageResponseCallback? callback) {
29+
return proxy.handlePlatformMessage(channel, data, callback);
30+
}
31+
32+
Future<ByteData?>? sendWithPostfix(String channel, String postfix, ByteData? message) async {
33+
final TimelineTask task = TimelineTask();
34+
_debugRecordUpStream(channelTypeName, '$channel$postfix', codecTypeName, message);
35+
task.start('Platform Channel send $channel$postfix');
36+
final ByteData? result;
37+
try {
38+
result = await proxy.send(channel, message);
39+
} finally {
40+
task.finish();
41+
}
42+
_debugRecordDownStream(channelTypeName, '$channel$postfix', codecTypeName, result);
43+
return result;
44+
}
45+
46+
@override
47+
Future<ByteData?>? send(String channel, ByteData? message) =>
48+
sendWithPostfix(channel, '', message);
49+
50+
@override
51+
void setMessageHandler(String channel, MessageHandler? handler) {
52+
proxy.setMessageHandler(channel, handler);
53+
}
54+
}
55+
56+
class _PlatformChannelStats {
57+
_PlatformChannelStats(this.channel, this.codec, this.type);
58+
59+
final String channel;
60+
final String codec;
61+
final String type;
62+
63+
int _upCount = 0;
64+
int _upBytes = 0;
65+
int get upBytes => _upBytes;
66+
void addUpStream(int bytes) {
67+
_upCount += 1;
68+
_upBytes += bytes;
69+
}
70+
71+
int _downCount = 0;
72+
int _downBytes = 0;
73+
int get downBytes => _downBytes;
74+
void addDownStream(int bytes) {
75+
_downCount += 1;
76+
_downBytes += bytes;
77+
}
78+
79+
double get averageUpPayload => _upBytes / _upCount;
80+
double get averageDownPayload => _downBytes / _downCount;
81+
}
82+
83+
final Map<String, _PlatformChannelStats> _debugProfilePlatformChannelsStats = <String, _PlatformChannelStats>{};
84+
85+
Future<void> _debugLaunchProfilePlatformChannels() async {
86+
if (!_debugProfilePlatformChannelsIsRunning) {
87+
_debugProfilePlatformChannelsIsRunning = true;
88+
await Future<dynamic>.delayed(_debugProfilePlatformChannelsRate);
89+
_debugProfilePlatformChannelsIsRunning = false;
90+
final StringBuffer log = StringBuffer();
91+
log.writeln('Platform Channel Stats:');
92+
final List<_PlatformChannelStats> allStats =
93+
_debugProfilePlatformChannelsStats.values.toList();
94+
// Sort highest combined bandwidth first.
95+
allStats.sort((_PlatformChannelStats x, _PlatformChannelStats y) =>
96+
(y.upBytes + y.downBytes) - (x.upBytes + x.downBytes));
97+
for (final _PlatformChannelStats stats in allStats) {
98+
log.writeln(
99+
' (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)})');
100+
}
101+
debugPrint(log.toString());
102+
_debugProfilePlatformChannelsStats.clear();
103+
}
104+
}
105+
106+
void _debugRecordUpStream(String channelTypeName, String name,
107+
String codecTypeName, ByteData? bytes) {
108+
final _PlatformChannelStats stats =
109+
_debugProfilePlatformChannelsStats[name] ??=
110+
_PlatformChannelStats(name, codecTypeName, channelTypeName);
111+
stats.addUpStream(bytes?.lengthInBytes ?? 0);
112+
_debugLaunchProfilePlatformChannels();
113+
}
114+
115+
void _debugRecordDownStream(String channelTypeName, String name,
116+
String codecTypeName, ByteData? bytes) {
117+
final _PlatformChannelStats stats =
118+
_debugProfilePlatformChannelsStats[name] ??=
119+
_PlatformChannelStats(name, codecTypeName, channelTypeName);
120+
stats.addDownStream(bytes?.lengthInBytes ?? 0);
121+
_debugLaunchProfilePlatformChannels();
122+
}
123+
14124
/// A named channel for communicating with platform plugins using asynchronous
15125
/// message passing.
16126
///
@@ -49,7 +159,15 @@ class BasicMessageChannel<T> {
49159
final MessageCodec<T> codec;
50160

51161
/// The messenger which sends the bytes for this channel, not null.
52-
BinaryMessenger get binaryMessenger => _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
162+
BinaryMessenger get binaryMessenger {
163+
final BinaryMessenger result =
164+
_binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
165+
return !kReleaseMode && debugProfilePlatformChannels
166+
? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger(
167+
// ignore: no_runtimetype_tostring
168+
result, runtimeType.toString(), codec.runtimeType.toString())
169+
: result;
170+
}
53171
final BinaryMessenger? _binaryMessenger;
54172

55173
/// Sends the specified [message] to the platform plugins on this channel.
@@ -129,7 +247,15 @@ class MethodChannel {
129247
/// The messenger used by this channel to send platform messages.
130248
///
131249
/// The messenger may not be null.
132-
BinaryMessenger get binaryMessenger => _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
250+
BinaryMessenger get binaryMessenger {
251+
final BinaryMessenger result =
252+
_binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
253+
return !kReleaseMode && debugProfilePlatformChannels
254+
? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger(
255+
// ignore: no_runtimetype_tostring
256+
result, runtimeType.toString(), codec.runtimeType.toString())
257+
: result;
258+
}
133259
final BinaryMessenger? _binaryMessenger;
134260

135261
/// Backend implementation of [invokeMethod].
@@ -154,10 +280,11 @@ class MethodChannel {
154280
@optionalTypeArgs
155281
Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async {
156282
assert(method != null);
157-
final ByteData? result = await binaryMessenger.send(
158-
name,
159-
codec.encodeMethodCall(MethodCall(method, arguments)),
160-
);
283+
final ByteData input = codec.encodeMethodCall(MethodCall(method, arguments));
284+
final ByteData? result =
285+
!kReleaseMode && debugProfilePlatformChannels ?
286+
await (binaryMessenger as _ProfiledBinaryMessenger).sendWithPostfix(name, '#$method', input) :
287+
await binaryMessenger.send(name, input);
161288
if (result == null) {
162289
if (missingOk) {
163290
return null;

0 commit comments

Comments
 (0)