1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.networkstack.tethering.metrics; 18 19 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; 20 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 21 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; 22 import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN; 23 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 24 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; 25 import static android.net.TetheringManager.TETHERING_BLUETOOTH; 26 import static android.net.TetheringManager.TETHERING_ETHERNET; 27 import static android.net.TetheringManager.TETHERING_NCM; 28 import static android.net.TetheringManager.TETHERING_USB; 29 import static android.net.TetheringManager.TETHERING_WIFI; 30 import static android.net.TetheringManager.TETHERING_WIFI_P2P; 31 import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; 32 import static android.net.TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR; 33 import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR; 34 import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; 35 import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; 36 import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR; 37 import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION; 38 import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; 39 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; 40 import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED; 41 import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; 42 import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; 43 import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE; 44 import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; 45 import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED; 46 import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; 47 48 import android.annotation.Nullable; 49 import android.net.NetworkCapabilities; 50 import android.stats.connectivity.DownstreamType; 51 import android.stats.connectivity.ErrorCode; 52 import android.stats.connectivity.UpstreamType; 53 import android.stats.connectivity.UserType; 54 import android.util.Log; 55 import android.util.SparseArray; 56 57 import androidx.annotation.NonNull; 58 import androidx.annotation.VisibleForTesting; 59 60 import com.android.networkstack.tethering.UpstreamNetworkState; 61 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 65 /** 66 * Collection of utilities for tethering metrics. 67 * 68 * To see if the logs are properly sent to statsd, execute following commands 69 * 70 * $ adb shell cmd stats print-logs 71 * $ adb logcat | grep statsd OR $ adb logcat -b stats 72 * 73 * @hide 74 */ 75 public class TetheringMetrics { 76 private static final String TAG = TetheringMetrics.class.getSimpleName(); 77 private static final boolean DBG = false; 78 private static final String SETTINGS_PKG_NAME = "com.android.settings"; 79 private static final String SYSTEMUI_PKG_NAME = "com.android.systemui"; 80 private static final String GMS_PKG_NAME = "com.google.android.gms"; 81 private final SparseArray<NetworkTetheringReported.Builder> mBuilderMap = new SparseArray<>(); 82 private final SparseArray<Long> mDownstreamStartTime = new SparseArray<Long>(); 83 private final ArrayList<RecordUpstreamEvent> mUpstreamEventList = new ArrayList<>(); 84 private UpstreamType mCurrentUpstream = null; 85 private Long mCurrentUpStreamStartTime = 0L; 86 87 88 /** 89 * Return the current system time in milliseconds. 90 * @return the current system time in milliseconds. 91 */ timeNow()92 public long timeNow() { 93 return System.currentTimeMillis(); 94 } 95 96 private static class RecordUpstreamEvent { 97 public final long mStartTime; 98 public final long mStopTime; 99 public final UpstreamType mUpstreamType; 100 RecordUpstreamEvent(final long startTime, final long stopTime, final UpstreamType upstream)101 RecordUpstreamEvent(final long startTime, final long stopTime, 102 final UpstreamType upstream) { 103 mStartTime = startTime; 104 mStopTime = stopTime; 105 mUpstreamType = upstream; 106 } 107 } 108 109 /** 110 * Creates a |NetworkTetheringReported.Builder| object to update the tethering stats for the 111 * specified downstream type and caller's package name. Initializes the upstream events, error 112 * code, and duration to default values. Sets the start time for the downstream type in the 113 * |mDownstreamStartTime| map. 114 * @param downstreamType The type of downstream connection (e.g. Wifi, USB, Bluetooth). 115 * @param callerPkg The package name of the caller. 116 */ createBuilder(final int downstreamType, final String callerPkg)117 public void createBuilder(final int downstreamType, final String callerPkg) { 118 NetworkTetheringReported.Builder statsBuilder = NetworkTetheringReported.newBuilder() 119 .setDownstreamType(downstreamTypeToEnum(downstreamType)) 120 .setUserType(userTypeToEnum(callerPkg)) 121 .setUpstreamType(UpstreamType.UT_UNKNOWN) 122 .setErrorCode(ErrorCode.EC_NO_ERROR) 123 .setUpstreamEvents(UpstreamEvents.newBuilder()) 124 .setDurationMillis(0); 125 mBuilderMap.put(downstreamType, statsBuilder); 126 mDownstreamStartTime.put(downstreamType, timeNow()); 127 } 128 129 /** 130 * Update the error code of the given downstream type in the Tethering stats. 131 * @param downstreamType The downstream type whose error code to update. 132 * @param errCode The error code to set. 133 */ updateErrorCode(final int downstreamType, final int errCode)134 public void updateErrorCode(final int downstreamType, final int errCode) { 135 NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType); 136 if (statsBuilder == null) { 137 Log.e(TAG, "Given downstreamType does not exist, this is a bug!"); 138 return; 139 } 140 statsBuilder.setErrorCode(errorCodeToEnum(errCode)); 141 } 142 143 /** 144 * Update the list of upstream types and their duration whenever the current upstream type 145 * changes. 146 * @param ns The UpstreamNetworkState object representing the current upstream network state. 147 */ maybeUpdateUpstreamType(@ullable final UpstreamNetworkState ns)148 public void maybeUpdateUpstreamType(@Nullable final UpstreamNetworkState ns) { 149 UpstreamType upstream = transportTypeToUpstreamTypeEnum(ns); 150 if (upstream.equals(mCurrentUpstream)) return; 151 152 final long newTime = timeNow(); 153 if (mCurrentUpstream != null) { 154 mUpstreamEventList.add(new RecordUpstreamEvent(mCurrentUpStreamStartTime, newTime, 155 mCurrentUpstream)); 156 } 157 mCurrentUpstream = upstream; 158 mCurrentUpStreamStartTime = newTime; 159 } 160 161 /** 162 * Updates the upstream events builder with a new upstream event. 163 * @param upstreamEventsBuilder the builder for the upstream events list 164 * @param start the start time of the upstream event 165 * @param stop the stop time of the upstream event 166 * @param upstream the type of upstream type (e.g. Wifi, Cellular, Bluetooth, ...) 167 */ addUpstreamEvent(final UpstreamEvents.Builder upstreamEventsBuilder, final long start, final long stop, @Nullable final UpstreamType upstream, final long txBytes, final long rxBytes)168 private void addUpstreamEvent(final UpstreamEvents.Builder upstreamEventsBuilder, 169 final long start, final long stop, @Nullable final UpstreamType upstream, 170 final long txBytes, final long rxBytes) { 171 final UpstreamEvent.Builder upstreamEventBuilder = UpstreamEvent.newBuilder() 172 .setUpstreamType(upstream == null ? UpstreamType.UT_NO_NETWORK : upstream) 173 .setDurationMillis(stop - start) 174 .setTxBytes(txBytes) 175 .setRxBytes(rxBytes); 176 upstreamEventsBuilder.addUpstreamEvent(upstreamEventBuilder); 177 } 178 179 /** 180 * Updates the |NetworkTetheringReported.Builder| with relevant upstream events associated with 181 * the downstream event identified by the given downstream start time. 182 * 183 * This method iterates through the list of upstream events and adds any relevant events to a 184 * |UpstreamEvents.Builder|. Upstream events are considered relevant if their stop time is 185 * greater than or equal to the given downstream start time. The method also adds the last 186 * upstream event that occurred up until the current time. 187 * 188 * The resulting |UpstreamEvents.Builder| is then added to the 189 * |NetworkTetheringReported.Builder|, along with the duration of the downstream event 190 * (i.e., stop time minus downstream start time). 191 * 192 * @param statsBuilder the builder for the NetworkTetheringReported message 193 * @param downstreamStartTime the start time of the downstream event to find relevant upstream 194 * events for 195 */ noteDownstreamStopped(final NetworkTetheringReported.Builder statsBuilder, final long downstreamStartTime)196 private void noteDownstreamStopped(final NetworkTetheringReported.Builder statsBuilder, 197 final long downstreamStartTime) { 198 UpstreamEvents.Builder upstreamEventsBuilder = UpstreamEvents.newBuilder(); 199 200 for (RecordUpstreamEvent event : mUpstreamEventList) { 201 if (downstreamStartTime > event.mStopTime) continue; 202 203 final long startTime = Math.max(downstreamStartTime, event.mStartTime); 204 // Handle completed upstream events. 205 addUpstreamEvent(upstreamEventsBuilder, startTime, event.mStopTime, 206 event.mUpstreamType, 0L /* txBytes */, 0L /* rxBytes */); 207 } 208 final long startTime = Math.max(downstreamStartTime, mCurrentUpStreamStartTime); 209 final long stopTime = timeNow(); 210 // Handle the last upstream event. 211 addUpstreamEvent(upstreamEventsBuilder, startTime, stopTime, mCurrentUpstream, 212 0L /* txBytes */, 0L /* rxBytes */); 213 statsBuilder.setUpstreamEvents(upstreamEventsBuilder); 214 statsBuilder.setDurationMillis(stopTime - downstreamStartTime); 215 } 216 217 /** 218 * Removes tethering statistics for the given downstream type. If there are any stats to write 219 * for the downstream event associated with the type, they are written before removing the 220 * statistics. 221 * 222 * If the given downstream type does not exist in the map, an error message is logged and the 223 * method returns without doing anything. 224 * 225 * @param downstreamType the type of downstream event to remove statistics for 226 */ sendReport(final int downstreamType)227 public void sendReport(final int downstreamType) { 228 final NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType); 229 if (statsBuilder == null) { 230 Log.e(TAG, "Given downstreamType does not exist, this is a bug!"); 231 return; 232 } 233 234 noteDownstreamStopped(statsBuilder, mDownstreamStartTime.get(downstreamType)); 235 write(statsBuilder.build()); 236 237 mBuilderMap.remove(downstreamType); 238 mDownstreamStartTime.remove(downstreamType); 239 } 240 241 /** 242 * Collects tethering statistics and writes them to the statsd pipeline. This method takes in a 243 * NetworkTetheringReported object, extracts its fields and uses them to write statistics data 244 * to the statsd pipeline. 245 * 246 * @param reported a NetworkTetheringReported object containing statistics to write 247 */ 248 @VisibleForTesting write(@onNull final NetworkTetheringReported reported)249 public void write(@NonNull final NetworkTetheringReported reported) { 250 final byte[] upstreamEvents = reported.getUpstreamEvents().toByteArray(); 251 252 TetheringStatsLog.write( 253 TetheringStatsLog.NETWORK_TETHERING_REPORTED, 254 reported.getErrorCode().getNumber(), 255 reported.getDownstreamType().getNumber(), 256 reported.getUpstreamType().getNumber(), 257 reported.getUserType().getNumber(), 258 upstreamEvents, 259 reported.getDurationMillis()); 260 if (DBG) { 261 Log.d( 262 TAG, 263 "Write errorCode: " 264 + reported.getErrorCode().getNumber() 265 + ", downstreamType: " 266 + reported.getDownstreamType().getNumber() 267 + ", upstreamType: " 268 + reported.getUpstreamType().getNumber() 269 + ", userType: " 270 + reported.getUserType().getNumber() 271 + ", upstreamTypes: " 272 + Arrays.toString(upstreamEvents) 273 + ", durationMillis: " 274 + reported.getDurationMillis()); 275 } 276 } 277 278 /** 279 * Cleans up the variables related to upstream events when tethering is turned off. 280 */ cleanup()281 public void cleanup() { 282 mUpstreamEventList.clear(); 283 mCurrentUpstream = null; 284 mCurrentUpStreamStartTime = 0L; 285 } 286 downstreamTypeToEnum(final int ifaceType)287 private DownstreamType downstreamTypeToEnum(final int ifaceType) { 288 switch(ifaceType) { 289 case TETHERING_WIFI: 290 return DownstreamType.DS_TETHERING_WIFI; 291 case TETHERING_WIFI_P2P: 292 return DownstreamType.DS_TETHERING_WIFI_P2P; 293 case TETHERING_USB: 294 return DownstreamType.DS_TETHERING_USB; 295 case TETHERING_BLUETOOTH: 296 return DownstreamType.DS_TETHERING_BLUETOOTH; 297 case TETHERING_NCM: 298 return DownstreamType.DS_TETHERING_NCM; 299 case TETHERING_ETHERNET: 300 return DownstreamType.DS_TETHERING_ETHERNET; 301 default: 302 return DownstreamType.DS_UNSPECIFIED; 303 } 304 } 305 errorCodeToEnum(final int lastError)306 private ErrorCode errorCodeToEnum(final int lastError) { 307 switch(lastError) { 308 case TETHER_ERROR_NO_ERROR: 309 return ErrorCode.EC_NO_ERROR; 310 case TETHER_ERROR_UNKNOWN_IFACE: 311 return ErrorCode.EC_UNKNOWN_IFACE; 312 case TETHER_ERROR_SERVICE_UNAVAIL: 313 return ErrorCode.EC_SERVICE_UNAVAIL; 314 case TETHER_ERROR_UNSUPPORTED: 315 return ErrorCode.EC_UNSUPPORTED; 316 case TETHER_ERROR_UNAVAIL_IFACE: 317 return ErrorCode.EC_UNAVAIL_IFACE; 318 case TETHER_ERROR_INTERNAL_ERROR: 319 return ErrorCode.EC_INTERNAL_ERROR; 320 case TETHER_ERROR_TETHER_IFACE_ERROR: 321 return ErrorCode.EC_TETHER_IFACE_ERROR; 322 case TETHER_ERROR_UNTETHER_IFACE_ERROR: 323 return ErrorCode.EC_UNTETHER_IFACE_ERROR; 324 case TETHER_ERROR_ENABLE_FORWARDING_ERROR: 325 return ErrorCode.EC_ENABLE_FORWARDING_ERROR; 326 case TETHER_ERROR_DISABLE_FORWARDING_ERROR: 327 return ErrorCode.EC_DISABLE_FORWARDING_ERROR; 328 case TETHER_ERROR_IFACE_CFG_ERROR: 329 return ErrorCode.EC_IFACE_CFG_ERROR; 330 case TETHER_ERROR_PROVISIONING_FAILED: 331 return ErrorCode.EC_PROVISIONING_FAILED; 332 case TETHER_ERROR_DHCPSERVER_ERROR: 333 return ErrorCode.EC_DHCPSERVER_ERROR; 334 case TETHER_ERROR_ENTITLEMENT_UNKNOWN: 335 return ErrorCode.EC_ENTITLEMENT_UNKNOWN; 336 case TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION: 337 return ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION; 338 case TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION: 339 return ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION; 340 default: 341 return ErrorCode.EC_UNKNOWN_TYPE; 342 } 343 } 344 userTypeToEnum(final String callerPkg)345 private UserType userTypeToEnum(final String callerPkg) { 346 if (callerPkg.equals(SETTINGS_PKG_NAME)) { 347 return UserType.USER_SETTINGS; 348 } else if (callerPkg.equals(SYSTEMUI_PKG_NAME)) { 349 return UserType.USER_SYSTEMUI; 350 } else if (callerPkg.equals(GMS_PKG_NAME)) { 351 return UserType.USER_GMS; 352 } else { 353 return UserType.USER_UNKNOWN; 354 } 355 } 356 transportTypeToUpstreamTypeEnum(final UpstreamNetworkState ns)357 private UpstreamType transportTypeToUpstreamTypeEnum(final UpstreamNetworkState ns) { 358 final NetworkCapabilities nc = (ns != null) ? ns.networkCapabilities : null; 359 if (nc == null) return UpstreamType.UT_NO_NETWORK; 360 361 final int typeCount = nc.getTransportTypes().length; 362 // It's possible for a VCN network to be mapped to UT_UNKNOWN, as it may consist of both 363 // Wi-Fi and cellular transport. 364 // TODO: It's necessary to define a new upstream type for VCN, which can be identified by 365 // NET_CAPABILITY_NOT_VCN_MANAGED. 366 if (typeCount > 1) return UpstreamType.UT_UNKNOWN; 367 368 if (nc.hasTransport(TRANSPORT_CELLULAR)) return UpstreamType.UT_CELLULAR; 369 if (nc.hasTransport(TRANSPORT_WIFI)) return UpstreamType.UT_WIFI; 370 if (nc.hasTransport(TRANSPORT_BLUETOOTH)) return UpstreamType.UT_BLUETOOTH; 371 if (nc.hasTransport(TRANSPORT_ETHERNET)) return UpstreamType.UT_ETHERNET; 372 if (nc.hasTransport(TRANSPORT_WIFI_AWARE)) return UpstreamType.UT_WIFI_AWARE; 373 if (nc.hasTransport(TRANSPORT_LOWPAN)) return UpstreamType.UT_LOWPAN; 374 375 return UpstreamType.UT_UNKNOWN; 376 } 377 } 378