Unverified Commit 81052a7d authored by Victoria Ashworth's avatar Victoria Ashworth Committed by GitHub

Add usage event to track when a iOS network device is used (#118915)

* Add usage event to track when a iOS network device is used

* update usage event to track percentage of iOS network vs usb devices, update and fix tests

* refactor tracking to happen in usageValues with a custom dimension
parent e85547b3
...@@ -19,6 +19,8 @@ import '../daemon.dart'; ...@@ -19,6 +19,8 @@ import '../daemon.dart';
import '../device.dart'; import '../device.dart';
import '../features.dart'; import '../features.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../ios/devices.dart';
import '../ios/iproxy.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/reporting.dart'; import '../reporting/reporting.dart';
import '../resident_runner.dart'; import '../resident_runner.dart';
...@@ -407,18 +409,23 @@ class RunCommand extends RunCommandBase { ...@@ -407,18 +409,23 @@ class RunCommand extends RunCommandBase {
bool isEmulator; bool isEmulator;
bool anyAndroidDevices = false; bool anyAndroidDevices = false;
bool anyIOSDevices = false; bool anyIOSDevices = false;
bool anyIOSNetworkDevices = false;
if (devices == null || devices!.isEmpty) { if (devices == null || devices!.isEmpty) {
deviceType = 'none'; deviceType = 'none';
deviceOsVersion = 'none'; deviceOsVersion = 'none';
isEmulator = false; isEmulator = false;
} else if (devices!.length == 1) { } else if (devices!.length == 1) {
final TargetPlatform platform = await devices![0].targetPlatform; final Device device = devices![0];
final TargetPlatform platform = await device.targetPlatform;
anyAndroidDevices = platform == TargetPlatform.android; anyAndroidDevices = platform == TargetPlatform.android;
anyIOSDevices = platform == TargetPlatform.ios; anyIOSDevices = platform == TargetPlatform.ios;
if (device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network) {
anyIOSNetworkDevices = true;
}
deviceType = getNameForTargetPlatform(platform); deviceType = getNameForTargetPlatform(platform);
deviceOsVersion = await devices![0].sdkNameAndVersion; deviceOsVersion = await device.sdkNameAndVersion;
isEmulator = await devices![0].isLocalEmulator; isEmulator = await device.isLocalEmulator;
} else { } else {
deviceType = 'multiple'; deviceType = 'multiple';
deviceOsVersion = 'multiple'; deviceOsVersion = 'multiple';
...@@ -427,12 +434,20 @@ class RunCommand extends RunCommandBase { ...@@ -427,12 +434,20 @@ class RunCommand extends RunCommandBase {
final TargetPlatform platform = await device.targetPlatform; final TargetPlatform platform = await device.targetPlatform;
anyAndroidDevices = anyAndroidDevices || (platform == TargetPlatform.android); anyAndroidDevices = anyAndroidDevices || (platform == TargetPlatform.android);
anyIOSDevices = anyIOSDevices || (platform == TargetPlatform.ios); anyIOSDevices = anyIOSDevices || (platform == TargetPlatform.ios);
if (device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network) {
anyIOSNetworkDevices = true;
}
if (anyAndroidDevices && anyIOSDevices) { if (anyAndroidDevices && anyIOSDevices) {
break; break;
} }
} }
} }
String? iOSInterfaceType;
if (anyIOSDevices) {
iOSInterfaceType = anyIOSNetworkDevices ? 'wireless' : 'usb';
}
String? androidEmbeddingVersion; String? androidEmbeddingVersion;
final List<String> hostLanguage = <String>[]; final List<String> hostLanguage = <String>[];
if (anyAndroidDevices) { if (anyAndroidDevices) {
...@@ -464,6 +479,7 @@ class RunCommand extends RunCommandBase { ...@@ -464,6 +479,7 @@ class RunCommand extends RunCommandBase {
commandRunProjectHostLanguage: hostLanguage.join(','), commandRunProjectHostLanguage: hostLanguage.join(','),
commandRunAndroidEmbeddingVersion: androidEmbeddingVersion, commandRunAndroidEmbeddingVersion: androidEmbeddingVersion,
commandRunEnableImpeller: enableImpeller, commandRunEnableImpeller: enableImpeller,
commandRunIOSInterfaceType: iOSInterfaceType,
); );
} }
......
...@@ -67,6 +67,7 @@ class CustomDimensions { ...@@ -67,6 +67,7 @@ class CustomDimensions {
this.hotEventReassembleTimeInMs, this.hotEventReassembleTimeInMs,
this.hotEventReloadVMTimeInMs, this.hotEventReloadVMTimeInMs,
this.commandRunEnableImpeller, this.commandRunEnableImpeller,
this.commandRunIOSInterfaceType,
}); });
final String? sessionHostOsDetails; // cd1 final String? sessionHostOsDetails; // cd1
...@@ -125,6 +126,7 @@ class CustomDimensions { ...@@ -125,6 +126,7 @@ class CustomDimensions {
final int? hotEventReassembleTimeInMs; // cd 54 final int? hotEventReassembleTimeInMs; // cd 54
final int? hotEventReloadVMTimeInMs; // cd 55 final int? hotEventReloadVMTimeInMs; // cd 55
final bool? commandRunEnableImpeller; // cd 56 final bool? commandRunEnableImpeller; // cd 56
final String? commandRunIOSInterfaceType; // cd 57
/// Convert to a map that will be used to upload to the analytics backend. /// Convert to a map that will be used to upload to the analytics backend.
Map<String, String> toMap() => <String, String>{ Map<String, String> toMap() => <String, String>{
...@@ -184,6 +186,7 @@ class CustomDimensions { ...@@ -184,6 +186,7 @@ class CustomDimensions {
if (hotEventReassembleTimeInMs != null) cdKey(CustomDimensionsEnum.hotEventReassembleTimeInMs): hotEventReassembleTimeInMs.toString(), if (hotEventReassembleTimeInMs != null) cdKey(CustomDimensionsEnum.hotEventReassembleTimeInMs): hotEventReassembleTimeInMs.toString(),
if (hotEventReloadVMTimeInMs != null) cdKey(CustomDimensionsEnum.hotEventReloadVMTimeInMs): hotEventReloadVMTimeInMs.toString(), if (hotEventReloadVMTimeInMs != null) cdKey(CustomDimensionsEnum.hotEventReloadVMTimeInMs): hotEventReloadVMTimeInMs.toString(),
if (commandRunEnableImpeller != null) cdKey(CustomDimensionsEnum.commandRunEnableImpeller): commandRunEnableImpeller.toString(), if (commandRunEnableImpeller != null) cdKey(CustomDimensionsEnum.commandRunEnableImpeller): commandRunEnableImpeller.toString(),
if (commandRunIOSInterfaceType != null) cdKey(CustomDimensionsEnum.commandRunIOSInterfaceType): commandRunIOSInterfaceType.toString(),
}; };
/// Merge the values of two [CustomDimensions] into one. If a value is defined /// Merge the values of two [CustomDimensions] into one. If a value is defined
...@@ -250,6 +253,7 @@ class CustomDimensions { ...@@ -250,6 +253,7 @@ class CustomDimensions {
hotEventReassembleTimeInMs: other.hotEventReassembleTimeInMs ?? hotEventReassembleTimeInMs, hotEventReassembleTimeInMs: other.hotEventReassembleTimeInMs ?? hotEventReassembleTimeInMs,
hotEventReloadVMTimeInMs: other.hotEventReloadVMTimeInMs ?? hotEventReloadVMTimeInMs, hotEventReloadVMTimeInMs: other.hotEventReloadVMTimeInMs ?? hotEventReloadVMTimeInMs,
commandRunEnableImpeller: other.commandRunEnableImpeller ?? commandRunEnableImpeller, commandRunEnableImpeller: other.commandRunEnableImpeller ?? commandRunEnableImpeller,
commandRunIOSInterfaceType: other.commandRunIOSInterfaceType ?? commandRunIOSInterfaceType,
); );
} }
...@@ -310,6 +314,7 @@ class CustomDimensions { ...@@ -310,6 +314,7 @@ class CustomDimensions {
hotEventReassembleTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventReassembleTimeInMs), hotEventReassembleTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventReassembleTimeInMs),
hotEventReloadVMTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventReloadVMTimeInMs), hotEventReloadVMTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventReloadVMTimeInMs),
commandRunEnableImpeller: _extractBool(map, CustomDimensionsEnum.commandRunEnableImpeller), commandRunEnableImpeller: _extractBool(map, CustomDimensionsEnum.commandRunEnableImpeller),
commandRunIOSInterfaceType: _extractString(map, CustomDimensionsEnum.commandRunIOSInterfaceType),
); );
static bool? _extractBool(Map<String, String> map, CustomDimensionsEnum field) => static bool? _extractBool(Map<String, String> map, CustomDimensionsEnum field) =>
...@@ -396,6 +401,7 @@ enum CustomDimensionsEnum { ...@@ -396,6 +401,7 @@ enum CustomDimensionsEnum {
hotEventReassembleTimeInMs, // cd54 hotEventReassembleTimeInMs, // cd54
hotEventReloadVMTimeInMs, // cd55 hotEventReloadVMTimeInMs, // cd55
commandRunEnableImpeller, // cd56 commandRunEnableImpeller, // cd56
commandRunIOSInterfaceType, // cd57
} }
String cdKey(CustomDimensionsEnum cd) => 'cd${cd.index + 1}'; String cdKey(CustomDimensionsEnum cd) => 'cd${cd.index + 1}';
...@@ -24,6 +24,8 @@ import 'package:flutter_tools/src/commands/run.dart'; ...@@ -24,6 +24,8 @@ import 'package:flutter_tools/src/commands/run.dart';
import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/resident_runner.dart';
...@@ -426,7 +428,7 @@ void main() { ...@@ -426,7 +428,7 @@ void main() {
TestUsageCommand('run', parameters: CustomDimensions.fromMap(<String, String>{ TestUsageCommand('run', parameters: CustomDimensions.fromMap(<String, String>{
'cd3': 'false', 'cd4': 'ios', 'cd22': 'iOS 13', 'cd3': 'false', 'cd4': 'ios', 'cd22': 'iOS 13',
'cd23': 'debug', 'cd18': 'false', 'cd15': 'swift', 'cd31': 'true', 'cd23': 'debug', 'cd18': 'false', 'cd15': 'swift', 'cd31': 'true',
'cd56': 'false', 'cd56': 'false', 'cd57': 'usb',
}) })
))); )));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -664,6 +666,104 @@ void main() { ...@@ -664,6 +666,104 @@ void main() {
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
group('usageValues', () {
testUsingContext('with only non-iOS usb device', () async {
final List<Device> devices = <Device>[
FakeDevice(targetPlatform: TargetPlatform.android_arm, platformType: PlatformType.android),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CustomDimensions dimensions = await command.usageValues;
expect(dimensions, const CustomDimensions(
commandRunIsEmulator: false,
commandRunTargetName: 'android-arm',
commandRunTargetOsVersion: '',
commandRunModeName: 'debug',
commandRunProjectModule: false,
commandRunProjectHostLanguage: '',
commandRunEnableImpeller: false,
));
}, overrides: <Type, Generator>{
DeviceManager: () => testDeviceManager,
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('with only iOS usb device', () async {
final List<Device> devices = <Device>[
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.usb, sdkNameAndVersion: 'iOS 16.2'),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CustomDimensions dimensions = await command.usageValues;
expect(dimensions, const CustomDimensions(
commandRunIsEmulator: false,
commandRunTargetName: 'ios',
commandRunTargetOsVersion: 'iOS 16.2',
commandRunModeName: 'debug',
commandRunProjectModule: false,
commandRunProjectHostLanguage: '',
commandRunEnableImpeller: false,
commandRunIOSInterfaceType: 'usb',
));
}, overrides: <Type, Generator>{
DeviceManager: () => testDeviceManager,
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('with only iOS network device', () async {
final List<Device> devices = <Device>[
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.network, sdkNameAndVersion: 'iOS 16.2'),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CustomDimensions dimensions = await command.usageValues;
expect(dimensions, const CustomDimensions(
commandRunIsEmulator: false,
commandRunTargetName: 'ios',
commandRunTargetOsVersion: 'iOS 16.2',
commandRunModeName: 'debug',
commandRunProjectModule: false,
commandRunProjectHostLanguage: '',
commandRunEnableImpeller: false,
commandRunIOSInterfaceType: 'wireless',
));
}, overrides: <Type, Generator>{
DeviceManager: () => testDeviceManager,
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('with both iOS usb and network devices', () async {
final List<Device> devices = <Device>[
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.network, sdkNameAndVersion: 'iOS 16.2'),
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.usb, sdkNameAndVersion: 'iOS 16.2'),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CustomDimensions dimensions = await command.usageValues;
expect(dimensions, const CustomDimensions(
commandRunIsEmulator: false,
commandRunTargetName: 'multiple',
commandRunTargetOsVersion: 'multiple',
commandRunModeName: 'debug',
commandRunProjectModule: false,
commandRunProjectHostLanguage: '',
commandRunEnableImpeller: false,
commandRunIOSInterfaceType: 'wireless',
));
}, overrides: <Type, Generator>{
DeviceManager: () => testDeviceManager,
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
});
}); });
group('dart-defines and web-renderer options', () { group('dart-defines and web-renderer options', () {
...@@ -1032,6 +1132,49 @@ class FakeDevice extends Fake implements Device { ...@@ -1032,6 +1132,49 @@ class FakeDevice extends Fake implements Device {
} }
} }
// Unfortunately Device, despite not being immutable, has an `operator ==`.
// Until we fix that, we have to also ignore related lints here.
// ignore: avoid_implementing_value_types
class FakeIOSDevice extends Fake implements IOSDevice {
FakeIOSDevice({
this.interfaceType = IOSDeviceConnectionInterface.none,
bool isLocalEmulator = false,
String sdkNameAndVersion = '',
}): _isLocalEmulator = isLocalEmulator,
_sdkNameAndVersion = sdkNameAndVersion;
final bool _isLocalEmulator;
final String _sdkNameAndVersion;
@override
Future<bool> get isLocalEmulator => Future<bool>.value(_isLocalEmulator);
@override
Future<String> get sdkNameAndVersion => Future<String>.value(_sdkNameAndVersion);
@override
final IOSDeviceConnectionInterface interfaceType;
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
}
class TestRunCommandForUsageValues extends RunCommand {
TestRunCommandForUsageValues({
this.devices,
});
@override
// devices is not set within usageValues, so we override the field
// ignore: overridden_fields
List<Device>? devices;
@override
Future<BuildInfo> getBuildInfo({ BuildMode? forcedBuildMode, File? forcedTargetFile }) async {
return const BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
}
}
class TestRunCommandWithFakeResidentRunner extends RunCommand { class TestRunCommandWithFakeResidentRunner extends RunCommand {
late FakeResidentRunner fakeResidentRunner; late FakeResidentRunner fakeResidentRunner;
......
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