From 7ecded8248d63865bbbd23bdce4d4095c0ddedb6 Mon Sep 17 00:00:00 2001 From: Wayne Ma Date: Thu, 10 Mar 2022 16:19:24 +0800 Subject: [PATCH] Injecting tethering stats into statsd Fill in downstream type, upstream type, error code and user type to NetworkTetheringReported. Bug: 153942334 Test: m, flash and boot Test: atest TetheringMetricsTest Change-Id: I6ba7d9e512b1ada519f44f9f3a95667e4b0f03c0 --- Tethering/Android.bp | 9 + .../tethering/metrics/TetheringMetrics.java | 195 ++++++++++++++++++ .../metrics/TetheringMetricsTest.java | 186 +++++++++++++++++ 3 files changed, 390 insertions(+) create mode 100644 Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java create mode 100644 Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java diff --git a/Tethering/Android.bp b/Tethering/Android.bp index 29f6e126a5..026ed5406e 100644 --- a/Tethering/Android.bp +++ b/Tethering/Android.bp @@ -33,6 +33,7 @@ java_defaults { ":framework-connectivity-shared-srcs", ":tethering-module-utils-srcs", ":services-tethering-shared-srcs", + ":statslog-tethering-java-gen", ], static_libs: [ "androidx.annotation_annotation", @@ -223,3 +224,11 @@ java_library_static { apex_available: ["com.android.tethering"], min_sdk_version: "30", } + +genrule { + name: "statslog-tethering-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module network_tethering" + + " --javaPackage com.android.networkstack.tethering.metrics --javaClass TetheringStatsLog", + out: ["com/android/networkstack/tethering/metrics/TetheringStatsLog.java"], +} \ No newline at end of file diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java new file mode 100644 index 0000000000..e25f2ae1ed --- /dev/null +++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkstack.tethering.metrics; + +import static android.net.TetheringManager.TETHERING_BLUETOOTH; +import static android.net.TetheringManager.TETHERING_ETHERNET; +import static android.net.TetheringManager.TETHERING_NCM; +import static android.net.TetheringManager.TETHERING_USB; +import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.TetheringManager.TETHERING_WIFI_P2P; +import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; +import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION; +import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; +import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED; +import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; +import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE; +import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; +import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED; +import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; + +import android.stats.connectivity.DownstreamType; +import android.stats.connectivity.ErrorCode; +import android.stats.connectivity.UpstreamType; +import android.stats.connectivity.UserType; +import android.util.Log; +import android.util.SparseArray; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +/** + * Collection of utilities for tethering metrics. + * + * To see if the logs are properly sent to statsd, execute following commands + * + * $ adb shell cmd stats print-logs + * $ adb logcat | grep statsd OR $ adb logcat -b stats + * + * @hide + */ +public class TetheringMetrics { + private static final String TAG = TetheringMetrics.class.getSimpleName(); + private static final boolean DBG = false; + private static final String SETTINGS_PKG_NAME = "com.android.settings"; + private static final String SYSTEMUI_PKG_NAME = "com.android.systemui"; + private static final String GMS_PKG_NAME = "com.google.android.gms"; + private final SparseArray mBuilderMap = new SparseArray<>(); + + /** Update Tethering stats about caller's package name and downstream type. */ + public void createBuilder(final int downstreamType, final String callerPkg) { + mBuilderMap.clear(); + NetworkTetheringReported.Builder statsBuilder = + NetworkTetheringReported.newBuilder(); + statsBuilder.setDownstreamType(downstreamTypeToEnum(downstreamType)) + .setUserType(userTypeToEnum(callerPkg)) + .setUpstreamType(UpstreamType.UT_UNKNOWN) + .setErrorCode(ErrorCode.EC_NO_ERROR) + .build(); + mBuilderMap.put(downstreamType, statsBuilder); + } + + /** Update error code of given downstreamType. */ + public void updateErrorCode(final int downstreamType, final int errCode) { + NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType); + if (statsBuilder == null) { + Log.e(TAG, "Given downstreamType does not exist, this is a bug!"); + return; + } + statsBuilder.setErrorCode(errorCodeToEnum(errCode)); + } + + /** Remove Tethering stats. + * If Tethering stats is ready to write then write it before removing. + */ + public void sendReport(final int downstreamType) { + final NetworkTetheringReported.Builder statsBuilder = + mBuilderMap.get(downstreamType); + if (statsBuilder == null) { + Log.e(TAG, "Given downstreamType does not exist, this is a bug!"); + return; + } + write(statsBuilder.build()); + mBuilderMap.remove(downstreamType); + } + + /** Collect Tethering stats and write metrics data to statsd pipeline. */ + @VisibleForTesting + public void write(@NonNull final NetworkTetheringReported reported) { + TetheringStatsLog.write(TetheringStatsLog.NETWORK_TETHERING_REPORTED, + reported.getErrorCode().getNumber(), + reported.getDownstreamType().getNumber(), + reported.getUpstreamType().getNumber(), + reported.getUserType().getNumber()); + if (DBG) { + Log.d(TAG, "Write errorCode: " + reported.getErrorCode().getNumber() + + ", downstreamType: " + reported.getDownstreamType().getNumber() + + ", upstreamType: " + reported.getUpstreamType().getNumber() + + ", userType: " + reported.getUserType().getNumber()); + } + } + + /** Map {@link TetheringType} to {@link DownstreamType} */ + private DownstreamType downstreamTypeToEnum(final int ifaceType) { + switch(ifaceType) { + case TETHERING_WIFI: + return DownstreamType.DS_TETHERING_WIFI; + case TETHERING_WIFI_P2P: + return DownstreamType.DS_TETHERING_WIFI_P2P; + case TETHERING_USB: + return DownstreamType.DS_TETHERING_USB; + case TETHERING_BLUETOOTH: + return DownstreamType.DS_TETHERING_BLUETOOTH; + case TETHERING_NCM: + return DownstreamType.DS_TETHERING_NCM; + case TETHERING_ETHERNET: + return DownstreamType.DS_TETHERING_ETHERNET; + default: + return DownstreamType.DS_UNSPECIFIED; + } + } + + /** Map {@link StartTetheringError} to {@link ErrorCode} */ + private ErrorCode errorCodeToEnum(final int lastError) { + switch(lastError) { + case TETHER_ERROR_NO_ERROR: + return ErrorCode.EC_NO_ERROR; + case TETHER_ERROR_UNKNOWN_IFACE: + return ErrorCode.EC_UNKNOWN_IFACE; + case TETHER_ERROR_SERVICE_UNAVAIL: + return ErrorCode.EC_SERVICE_UNAVAIL; + case TETHER_ERROR_UNSUPPORTED: + return ErrorCode.EC_UNSUPPORTED; + case TETHER_ERROR_UNAVAIL_IFACE: + return ErrorCode.EC_UNAVAIL_IFACE; + case TETHER_ERROR_INTERNAL_ERROR: + return ErrorCode.EC_INTERNAL_ERROR; + case TETHER_ERROR_TETHER_IFACE_ERROR: + return ErrorCode.EC_TETHER_IFACE_ERROR; + case TETHER_ERROR_UNTETHER_IFACE_ERROR: + return ErrorCode.EC_UNTETHER_IFACE_ERROR; + case TETHER_ERROR_ENABLE_FORWARDING_ERROR: + return ErrorCode.EC_ENABLE_FORWARDING_ERROR; + case TETHER_ERROR_DISABLE_FORWARDING_ERROR: + return ErrorCode.EC_DISABLE_FORWARDING_ERROR; + case TETHER_ERROR_IFACE_CFG_ERROR: + return ErrorCode.EC_IFACE_CFG_ERROR; + case TETHER_ERROR_PROVISIONING_FAILED: + return ErrorCode.EC_PROVISIONING_FAILED; + case TETHER_ERROR_DHCPSERVER_ERROR: + return ErrorCode.EC_DHCPSERVER_ERROR; + case TETHER_ERROR_ENTITLEMENT_UNKNOWN: + return ErrorCode.EC_ENTITLEMENT_UNKNOWN; + case TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION: + return ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION; + case TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION: + return ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION; + default: + return ErrorCode.EC_UNKNOWN_TYPE; + } + } + + /** Map callerPkg to {@link UserType} */ + private UserType userTypeToEnum(final String callerPkg) { + if (callerPkg.equals(SETTINGS_PKG_NAME)) { + return UserType.USER_SETTINGS; + } else if (callerPkg.equals(SYSTEMUI_PKG_NAME)) { + return UserType.USER_SYSTEMUI; + } else if (callerPkg.equals(GMS_PKG_NAME)) { + return UserType.USER_GMS; + } else { + return UserType.USER_UNKNOWN; + } + } +} diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java new file mode 100644 index 0000000000..c34cf5f211 --- /dev/null +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkstack.tethering.metrics; + +import static android.net.TetheringManager.TETHERING_BLUETOOTH; +import static android.net.TetheringManager.TETHERING_ETHERNET; +import static android.net.TetheringManager.TETHERING_NCM; +import static android.net.TetheringManager.TETHERING_USB; +import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.TetheringManager.TETHERING_WIFI_P2P; +import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; +import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION; +import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; +import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED; +import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; +import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE; +import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; +import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_TYPE; +import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED; +import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; + +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.stats.connectivity.DownstreamType; +import android.stats.connectivity.ErrorCode; +import android.stats.connectivity.UpstreamType; +import android.stats.connectivity.UserType; +import android.util.Pair; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public final class TetheringMetricsTest { + private static final String TEST_CALLER_PKG = "com.test.caller.pkg"; + private static final String SETTINGS_PKG = "com.android.settings"; + private static final String SYSTEMUI_PKG = "com.android.systemui"; + private static final String GMS_PKG = "com.google.android.gms"; + private TetheringMetrics mTetheringMetrics; + + private final NetworkTetheringReported.Builder mStatsBuilder = + NetworkTetheringReported.newBuilder(); + + private class MockTetheringMetrics extends TetheringMetrics { + @Override + public void write(final NetworkTetheringReported reported) { } + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTetheringMetrics = spy(new MockTetheringMetrics()); + } + + private void runDownstreamTypesTest(final Pair... testPairs) + throws Exception { + for (Pair testPair : testPairs) { + final int type = testPair.first; + final DownstreamType expectedResult = testPair.second; + + mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG); + mTetheringMetrics.updateErrorCode(type, TETHER_ERROR_NO_ERROR); + mTetheringMetrics.sendReport(type); + NetworkTetheringReported expectedReport = + mStatsBuilder.setDownstreamType(expectedResult) + .setUserType(UserType.USER_UNKNOWN) + .setUpstreamType(UpstreamType.UT_UNKNOWN) + .setErrorCode(ErrorCode.EC_NO_ERROR) + .build(); + verify(mTetheringMetrics).write(expectedReport); + reset(mTetheringMetrics); + } + } + + @Test + public void testDownstreamTypes() throws Exception { + runDownstreamTypesTest(new Pair<>(TETHERING_WIFI, DownstreamType.DS_TETHERING_WIFI), + new Pair<>(TETHERING_WIFI_P2P, DownstreamType.DS_TETHERING_WIFI_P2P), + new Pair<>(TETHERING_BLUETOOTH, DownstreamType.DS_TETHERING_BLUETOOTH), + new Pair<>(TETHERING_USB, DownstreamType.DS_TETHERING_USB), + new Pair<>(TETHERING_NCM, DownstreamType.DS_TETHERING_NCM), + new Pair<>(TETHERING_ETHERNET, DownstreamType.DS_TETHERING_ETHERNET)); + } + + private void runErrorCodesTest(final Pair... testPairs) + throws Exception { + for (Pair testPair : testPairs) { + final int errorCode = testPair.first; + final ErrorCode expectedResult = testPair.second; + + mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG); + mTetheringMetrics.updateErrorCode(TETHERING_WIFI, errorCode); + mTetheringMetrics.sendReport(TETHERING_WIFI); + NetworkTetheringReported expectedReport = + mStatsBuilder.setDownstreamType(DownstreamType.DS_TETHERING_WIFI) + .setUserType(UserType.USER_UNKNOWN) + .setUpstreamType(UpstreamType.UT_UNKNOWN) + .setErrorCode(expectedResult) + .build(); + verify(mTetheringMetrics).write(expectedReport); + reset(mTetheringMetrics); + } + } + + @Test + public void testErrorCodes() throws Exception { + runErrorCodesTest(new Pair<>(TETHER_ERROR_NO_ERROR, ErrorCode.EC_NO_ERROR), + new Pair<>(TETHER_ERROR_UNKNOWN_IFACE, ErrorCode.EC_UNKNOWN_IFACE), + new Pair<>(TETHER_ERROR_SERVICE_UNAVAIL, ErrorCode.EC_SERVICE_UNAVAIL), + new Pair<>(TETHER_ERROR_UNSUPPORTED, ErrorCode.EC_UNSUPPORTED), + new Pair<>(TETHER_ERROR_UNAVAIL_IFACE, ErrorCode.EC_UNAVAIL_IFACE), + new Pair<>(TETHER_ERROR_INTERNAL_ERROR, ErrorCode.EC_INTERNAL_ERROR), + new Pair<>(TETHER_ERROR_TETHER_IFACE_ERROR, ErrorCode.EC_TETHER_IFACE_ERROR), + new Pair<>(TETHER_ERROR_UNTETHER_IFACE_ERROR, ErrorCode.EC_UNTETHER_IFACE_ERROR), + new Pair<>(TETHER_ERROR_ENABLE_FORWARDING_ERROR, + ErrorCode.EC_ENABLE_FORWARDING_ERROR), + new Pair<>(TETHER_ERROR_DISABLE_FORWARDING_ERROR, + ErrorCode.EC_DISABLE_FORWARDING_ERROR), + new Pair<>(TETHER_ERROR_IFACE_CFG_ERROR, ErrorCode.EC_IFACE_CFG_ERROR), + new Pair<>(TETHER_ERROR_PROVISIONING_FAILED, ErrorCode.EC_PROVISIONING_FAILED), + new Pair<>(TETHER_ERROR_DHCPSERVER_ERROR, ErrorCode.EC_DHCPSERVER_ERROR), + new Pair<>(TETHER_ERROR_ENTITLEMENT_UNKNOWN, ErrorCode.EC_ENTITLEMENT_UNKNOWN), + new Pair<>(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, + ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION), + new Pair<>(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION, + ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION), + new Pair<>(TETHER_ERROR_UNKNOWN_TYPE, ErrorCode.EC_UNKNOWN_TYPE)); + } + + private void runUserTypesTest(final Pair... testPairs) + throws Exception { + for (Pair testPair : testPairs) { + final String callerPkg = testPair.first; + final UserType expectedResult = testPair.second; + + mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg); + mTetheringMetrics.updateErrorCode(TETHERING_WIFI, TETHER_ERROR_NO_ERROR); + mTetheringMetrics.sendReport(TETHERING_WIFI); + NetworkTetheringReported expectedReport = + mStatsBuilder.setDownstreamType(DownstreamType.DS_TETHERING_WIFI) + .setUserType(expectedResult) + .setUpstreamType(UpstreamType.UT_UNKNOWN) + .setErrorCode(ErrorCode.EC_NO_ERROR) + .build(); + verify(mTetheringMetrics).write(expectedReport); + reset(mTetheringMetrics); + } + } + + @Test + public void testUserTypes() throws Exception { + runUserTypesTest(new Pair<>(TEST_CALLER_PKG, UserType.USER_UNKNOWN), + new Pair<>(SETTINGS_PKG, UserType.USER_SETTINGS), + new Pair<>(SYSTEMUI_PKG, UserType.USER_SYSTEMUI), + new Pair<>(GMS_PKG, UserType.USER_GMS)); + } +}