1 /* 2 * Copyright (C) 2023 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.server.connectivity; 18 19 import static android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; 20 21 import android.annotation.NonNull; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.net.Network; 27 import android.net.NetworkCapabilities; 28 import android.net.NetworkSpecifier; 29 import android.net.TelephonyNetworkSpecifier; 30 import android.net.TransportInfo; 31 import android.net.wifi.WifiInfo; 32 import android.os.Build; 33 import android.os.Handler; 34 import android.os.SystemClock; 35 import android.telephony.SubscriptionInfo; 36 import android.telephony.SubscriptionManager; 37 import android.telephony.TelephonyManager; 38 import android.util.IndentingPrintWriter; 39 import android.util.Log; 40 import android.util.SparseArray; 41 import android.util.SparseIntArray; 42 43 import androidx.annotation.RequiresApi; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.metrics.DailykeepaliveInfoReported; 47 import com.android.metrics.DurationForNumOfKeepalive; 48 import com.android.metrics.DurationPerNumOfKeepalive; 49 import com.android.metrics.KeepaliveLifetimeForCarrier; 50 import com.android.metrics.KeepaliveLifetimePerCarrier; 51 import com.android.modules.utils.BackgroundThread; 52 import com.android.modules.utils.build.SdkLevel; 53 import com.android.net.module.util.CollectionUtils; 54 import com.android.server.ConnectivityStatsLog; 55 56 import java.util.ArrayList; 57 import java.util.HashMap; 58 import java.util.HashSet; 59 import java.util.List; 60 import java.util.Map; 61 import java.util.Objects; 62 import java.util.Set; 63 import java.util.concurrent.CompletableFuture; 64 import java.util.concurrent.atomic.AtomicBoolean; 65 66 /** 67 * Tracks carrier and duration metrics of automatic on/off keepalives. 68 * 69 * <p>This class follows AutomaticOnOffKeepaliveTracker closely and its on*Keepalive methods needs 70 * to be called in a timely manner to keep the metrics accurate. It is also not thread-safe and all 71 * public methods must be called by the same thread, namely the ConnectivityService handler thread. 72 * 73 * <p>In the case that the keepalive state becomes out of sync with the hardware, the tracker will 74 * be disabled. e.g. Calling onStartKeepalive on a given network, slot pair twice without calling 75 * onStopKeepalive is unexpected and will disable the tracker. 76 */ 77 public class KeepaliveStatsTracker { 78 private static final String TAG = KeepaliveStatsTracker.class.getSimpleName(); 79 private static final int INVALID_KEEPALIVE_ID = -1; 80 // 2 hour acceptable deviation in metrics collection duration time to account for the 1 hour 81 // window of AlarmManager. 82 private static final long MAX_EXPECTED_DURATION_MS = 83 AutomaticOnOffKeepaliveTracker.METRICS_COLLECTION_DURATION_MS + 2 * 60 * 60 * 1_000L; 84 85 @NonNull private final Handler mConnectivityServiceHandler; 86 @NonNull private final Dependencies mDependencies; 87 88 // Mapping of subId to carrierId. Updates are received from OnSubscriptionsChangedListener 89 private final SparseIntArray mCachedCarrierIdPerSubId = new SparseIntArray(); 90 // The default subscription id obtained from SubscriptionManager.getDefaultSubscriptionId. 91 // Updates are received from the ACTION_DEFAULT_SUBSCRIPTION_CHANGED broadcast. 92 private int mCachedDefaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 93 94 // Boolean to track whether the KeepaliveStatsTracker is enabled. 95 // Use a final AtomicBoolean to ensure initialization is seen on the handler thread. 96 // Repeated fields in metrics are only supported on T+ so this is enabled only on T+. 97 private final AtomicBoolean mEnabled = new AtomicBoolean(SdkLevel.isAtLeastT()); 98 99 // Class to store network information, lifetime durations and active state of a keepalive. 100 private static final class KeepaliveStats { 101 // The carrier ID for a keepalive, or TelephonyManager.UNKNOWN_CARRIER_ID(-1) if not set. 102 public final int carrierId; 103 // The transport types of the underlying network for each keepalive. A network may include 104 // multiple transport types. Each transport type is represented by a different bit, defined 105 // in NetworkCapabilities 106 public final int transportTypes; 107 // The keepalive interval in millis. 108 public final int intervalMs; 109 // The uid of the app that requested the keepalive. 110 public final int appUid; 111 // Indicates if the keepalive is an automatic keepalive. 112 public final boolean isAutoKeepalive; 113 114 // Snapshot of the lifetime stats 115 public static class LifetimeStats { 116 public final int lifetimeMs; 117 public final int activeLifetimeMs; 118 LifetimeStats(int lifetimeMs, int activeLifetimeMs)119 LifetimeStats(int lifetimeMs, int activeLifetimeMs) { 120 this.lifetimeMs = lifetimeMs; 121 this.activeLifetimeMs = activeLifetimeMs; 122 } 123 } 124 125 // The total time since the keepalive is started until it is stopped. 126 private int mLifetimeMs = 0; 127 // The total time the keepalive is active (not suspended). 128 private int mActiveLifetimeMs = 0; 129 130 // A timestamp of the most recent time the lifetime metrics was updated. 131 private long mLastUpdateLifetimeTimestamp; 132 133 // A flag to indicate if the keepalive is active. 134 private boolean mKeepaliveActive = true; 135 136 /** 137 * Gets the lifetime stats for the keepalive, updated to timeNow, and then resets it. 138 * 139 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 140 */ getAndResetLifetimeStats(long timeNow)141 public LifetimeStats getAndResetLifetimeStats(long timeNow) { 142 updateLifetimeStatsAndSetActive(timeNow, mKeepaliveActive); 143 // Get a snapshot of the stats 144 final LifetimeStats lifetimeStats = new LifetimeStats(mLifetimeMs, mActiveLifetimeMs); 145 // Reset the stats 146 resetLifetimeStats(timeNow); 147 148 return lifetimeStats; 149 } 150 isKeepaliveActive()151 public boolean isKeepaliveActive() { 152 return mKeepaliveActive; 153 } 154 KeepaliveStats( int carrierId, int transportTypes, int intervalSeconds, int appUid, boolean isAutoKeepalive, long timeNow)155 KeepaliveStats( 156 int carrierId, 157 int transportTypes, 158 int intervalSeconds, 159 int appUid, 160 boolean isAutoKeepalive, 161 long timeNow) { 162 this.carrierId = carrierId; 163 this.transportTypes = transportTypes; 164 this.intervalMs = intervalSeconds * 1000; 165 this.appUid = appUid; 166 this.isAutoKeepalive = isAutoKeepalive; 167 mLastUpdateLifetimeTimestamp = timeNow; 168 } 169 170 /** 171 * Updates the lifetime metrics to the given time and sets the active state. This should be 172 * called whenever the active state of the keepalive changes. 173 * 174 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 175 */ updateLifetimeStatsAndSetActive(long timeNow, boolean keepaliveActive)176 public void updateLifetimeStatsAndSetActive(long timeNow, boolean keepaliveActive) { 177 final int durationIncrease = (int) (timeNow - mLastUpdateLifetimeTimestamp); 178 mLifetimeMs += durationIncrease; 179 if (mKeepaliveActive) mActiveLifetimeMs += durationIncrease; 180 181 mLastUpdateLifetimeTimestamp = timeNow; 182 mKeepaliveActive = keepaliveActive; 183 } 184 185 /** 186 * Resets the lifetime metrics but does not reset the active/stopped state of the keepalive. 187 * This also updates the time to timeNow, ensuring stats will start from this time. 188 * 189 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 190 */ resetLifetimeStats(long timeNow)191 public void resetLifetimeStats(long timeNow) { 192 mLifetimeMs = 0; 193 mActiveLifetimeMs = 0; 194 mLastUpdateLifetimeTimestamp = timeNow; 195 } 196 } 197 198 // List of duration stats metric where the index is the number of concurrent keepalives. 199 // Each DurationForNumOfKeepalive message stores a registered duration and an active duration. 200 // Registered duration is the total time spent with mNumRegisteredKeepalive == index. 201 // Active duration is the total time spent with mNumActiveKeepalive == index. 202 private final List<DurationForNumOfKeepalive.Builder> mDurationPerNumOfKeepalive = 203 new ArrayList<>(); 204 205 // Map of keepalives identified by the id from getKeepaliveId to their stats information. 206 private final SparseArray<KeepaliveStats> mKeepaliveStatsPerId = new SparseArray<>(); 207 208 // Generate and return a unique integer using a given network's netId and the slot number. 209 // This is possible because netId is a 16 bit integer, so an integer with the first 16 bits as 210 // the netId and the last 16 bits as the slot number can be created. This allows slot numbers to 211 // be up to 2^16. 212 // Returns INVALID_KEEPALIVE_ID if the netId or slot is not as expected above. getKeepaliveId(@onNull Network network, int slot)213 private int getKeepaliveId(@NonNull Network network, int slot) { 214 final int netId = network.getNetId(); 215 // Since there is no enforcement that a Network's netId is valid check for it here. 216 if (netId < 0 || netId >= (1 << 16)) { 217 disableTracker("Unexpected netId value: " + netId); 218 return INVALID_KEEPALIVE_ID; 219 } 220 if (slot < 0 || slot >= (1 << 16)) { 221 disableTracker("Unexpected slot value: " + slot); 222 return INVALID_KEEPALIVE_ID; 223 } 224 225 return (netId << 16) + slot; 226 } 227 228 // Class to act as the key to aggregate the KeepaliveLifetimeForCarrier stats. 229 private static final class LifetimeKey { 230 public final int carrierId; 231 public final int transportTypes; 232 public final int intervalMs; 233 LifetimeKey(int carrierId, int transportTypes, int intervalMs)234 LifetimeKey(int carrierId, int transportTypes, int intervalMs) { 235 this.carrierId = carrierId; 236 this.transportTypes = transportTypes; 237 this.intervalMs = intervalMs; 238 } 239 240 @Override equals(Object o)241 public boolean equals(Object o) { 242 if (this == o) return true; 243 if (o == null || getClass() != o.getClass()) return false; 244 245 final LifetimeKey that = (LifetimeKey) o; 246 247 return carrierId == that.carrierId && transportTypes == that.transportTypes 248 && intervalMs == that.intervalMs; 249 } 250 251 @Override hashCode()252 public int hashCode() { 253 return carrierId + 3 * transportTypes + 5 * intervalMs; 254 } 255 } 256 257 // Map to aggregate the KeepaliveLifetimeForCarrier stats using LifetimeKey as the key. 258 final Map<LifetimeKey, KeepaliveLifetimeForCarrier.Builder> mAggregateKeepaliveLifetime = 259 new HashMap<>(); 260 261 private final Set<Integer> mAppUids = new HashSet<Integer>(); 262 private int mNumKeepaliveRequests = 0; 263 private int mNumAutomaticKeepaliveRequests = 0; 264 265 private int mNumRegisteredKeepalive = 0; 266 private int mNumActiveKeepalive = 0; 267 268 // A timestamp of the most recent time the duration metrics was updated. 269 private long mLastUpdateDurationsTimestamp; 270 271 /** Dependency class */ 272 @VisibleForTesting 273 public static class Dependencies { 274 // Returns a timestamp with the time base of SystemClock.elapsedRealtime to keep durations 275 // relative to start time and avoid timezone change, including time spent in deep sleep. getElapsedRealtime()276 public long getElapsedRealtime() { 277 return SystemClock.elapsedRealtime(); 278 } 279 280 /** 281 * Writes a DAILY_KEEPALIVE_INFO_REPORTED to ConnectivityStatsLog. 282 * 283 * @param dailyKeepaliveInfoReported the proto to write to statsD. 284 */ 285 @RequiresApi(Build.VERSION_CODES.TIRAMISU) writeStats(DailykeepaliveInfoReported dailyKeepaliveInfoReported)286 public void writeStats(DailykeepaliveInfoReported dailyKeepaliveInfoReported) { 287 ConnectivityStatsLog.write( 288 ConnectivityStatsLog.DAILY_KEEPALIVE_INFO_REPORTED, 289 dailyKeepaliveInfoReported.getDurationPerNumOfKeepalive().toByteArray(), 290 dailyKeepaliveInfoReported.getKeepaliveLifetimePerCarrier().toByteArray(), 291 dailyKeepaliveInfoReported.getKeepaliveRequests(), 292 dailyKeepaliveInfoReported.getAutomaticKeepaliveRequests(), 293 dailyKeepaliveInfoReported.getDistinctUserCount(), 294 CollectionUtils.toIntArray(dailyKeepaliveInfoReported.getUidList())); 295 } 296 } 297 KeepaliveStatsTracker(@onNull Context context, @NonNull Handler handler)298 public KeepaliveStatsTracker(@NonNull Context context, @NonNull Handler handler) { 299 this(context, handler, new Dependencies()); 300 } 301 302 private final Context mContext; 303 private final SubscriptionManager mSubscriptionManager; 304 305 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 306 @Override 307 public void onReceive(Context context, Intent intent) { 308 mCachedDefaultSubscriptionId = 309 intent.getIntExtra( 310 SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 311 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 312 } 313 }; 314 315 private final CompletableFuture<OnSubscriptionsChangedListener> mListenerFuture = 316 new CompletableFuture<>(); 317 318 @VisibleForTesting KeepaliveStatsTracker( @onNull Context context, @NonNull Handler handler, @NonNull Dependencies dependencies)319 public KeepaliveStatsTracker( 320 @NonNull Context context, 321 @NonNull Handler handler, 322 @NonNull Dependencies dependencies) { 323 mContext = Objects.requireNonNull(context); 324 mDependencies = Objects.requireNonNull(dependencies); 325 mConnectivityServiceHandler = Objects.requireNonNull(handler); 326 327 mSubscriptionManager = 328 Objects.requireNonNull(context.getSystemService(SubscriptionManager.class)); 329 330 mLastUpdateDurationsTimestamp = mDependencies.getElapsedRealtime(); 331 332 if (!isEnabled()) { 333 return; 334 } 335 336 context.registerReceiver( 337 mBroadcastReceiver, 338 new IntentFilter(SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED), 339 /* broadcastPermission= */ null, 340 mConnectivityServiceHandler); 341 342 // The default constructor for OnSubscriptionsChangedListener will always implicitly grab 343 // the looper of the current thread. In the case the current thread does not have a looper, 344 // this will throw. Therefore, post a runnable that creates it there. 345 // When the callback is called on the BackgroundThread, post a message on the CS handler 346 // thread to update the caches, which can only be touched there. 347 BackgroundThread.getHandler().post(() -> { 348 final OnSubscriptionsChangedListener listener = 349 new OnSubscriptionsChangedListener() { 350 @Override 351 public void onSubscriptionsChanged() { 352 final List<SubscriptionInfo> activeSubInfoList = 353 mSubscriptionManager.getActiveSubscriptionInfoList(); 354 // A null subInfo list here indicates the current state is unknown 355 // but not necessarily empty, simply ignore it. Another call to the 356 // listener will be invoked in the future. 357 if (activeSubInfoList == null) return; 358 mConnectivityServiceHandler.post(() -> { 359 mCachedCarrierIdPerSubId.clear(); 360 361 for (final SubscriptionInfo subInfo : activeSubInfoList) { 362 mCachedCarrierIdPerSubId.put(subInfo.getSubscriptionId(), 363 subInfo.getCarrierId()); 364 } 365 }); 366 } 367 }; 368 mListenerFuture.complete(listener); 369 mSubscriptionManager.addOnSubscriptionsChangedListener(r -> r.run(), listener); 370 }); 371 } 372 373 /** Ensures the list of duration metrics is large enough for number of registered keepalives. */ ensureDurationPerNumOfKeepaliveSize()374 private void ensureDurationPerNumOfKeepaliveSize() { 375 if (mNumActiveKeepalive < 0 || mNumRegisteredKeepalive < 0) { 376 disableTracker("Number of active or registered keepalives is negative"); 377 return; 378 } 379 if (mNumActiveKeepalive > mNumRegisteredKeepalive) { 380 disableTracker("Number of active keepalives greater than registered keepalives"); 381 return; 382 } 383 384 while (mDurationPerNumOfKeepalive.size() <= mNumRegisteredKeepalive) { 385 final DurationForNumOfKeepalive.Builder durationForNumOfKeepalive = 386 DurationForNumOfKeepalive.newBuilder(); 387 durationForNumOfKeepalive.setNumOfKeepalive(mDurationPerNumOfKeepalive.size()); 388 durationForNumOfKeepalive.setKeepaliveRegisteredDurationsMsec(0); 389 durationForNumOfKeepalive.setKeepaliveActiveDurationsMsec(0); 390 391 mDurationPerNumOfKeepalive.add(durationForNumOfKeepalive); 392 } 393 } 394 395 /** 396 * Updates the durations metrics to the given time. This should always be called before making a 397 * change to mNumRegisteredKeepalive or mNumActiveKeepalive to keep the duration metrics 398 * correct. 399 * 400 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 401 */ updateDurationsPerNumOfKeepalive(long timeNow)402 private void updateDurationsPerNumOfKeepalive(long timeNow) { 403 if (mDurationPerNumOfKeepalive.size() < mNumRegisteredKeepalive) { 404 Log.e(TAG, "Unexpected jump in number of registered keepalive"); 405 } 406 ensureDurationPerNumOfKeepaliveSize(); 407 408 final int durationIncrease = (int) (timeNow - mLastUpdateDurationsTimestamp); 409 final DurationForNumOfKeepalive.Builder durationForNumOfRegisteredKeepalive = 410 mDurationPerNumOfKeepalive.get(mNumRegisteredKeepalive); 411 412 durationForNumOfRegisteredKeepalive.setKeepaliveRegisteredDurationsMsec( 413 durationForNumOfRegisteredKeepalive.getKeepaliveRegisteredDurationsMsec() 414 + durationIncrease); 415 416 final DurationForNumOfKeepalive.Builder durationForNumOfActiveKeepalive = 417 mDurationPerNumOfKeepalive.get(mNumActiveKeepalive); 418 419 durationForNumOfActiveKeepalive.setKeepaliveActiveDurationsMsec( 420 durationForNumOfActiveKeepalive.getKeepaliveActiveDurationsMsec() 421 + durationIncrease); 422 423 mLastUpdateDurationsTimestamp = timeNow; 424 } 425 426 // TODO: Move this function to frameworks/libs/net/.../NetworkCapabilitiesUtils.java getSubId(@onNull NetworkCapabilities nc, int defaultSubId)427 private static int getSubId(@NonNull NetworkCapabilities nc, int defaultSubId) { 428 if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { 429 final NetworkSpecifier networkSpecifier = nc.getNetworkSpecifier(); 430 if (networkSpecifier instanceof TelephonyNetworkSpecifier) { 431 return ((TelephonyNetworkSpecifier) networkSpecifier).getSubscriptionId(); 432 } 433 // Use the default subscriptionId. 434 return defaultSubId; 435 } 436 if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { 437 final TransportInfo info = nc.getTransportInfo(); 438 if (info instanceof WifiInfo) { 439 return ((WifiInfo) info).getSubscriptionId(); 440 } 441 } 442 443 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 444 } 445 getCarrierId(@onNull NetworkCapabilities networkCapabilities)446 private int getCarrierId(@NonNull NetworkCapabilities networkCapabilities) { 447 // Try to get the correct subscription id. 448 final int subId = getSubId(networkCapabilities, mCachedDefaultSubscriptionId); 449 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 450 return TelephonyManager.UNKNOWN_CARRIER_ID; 451 } 452 return mCachedCarrierIdPerSubId.get(subId, TelephonyManager.UNKNOWN_CARRIER_ID); 453 } 454 getTransportTypes(@onNull NetworkCapabilities networkCapabilities)455 private int getTransportTypes(@NonNull NetworkCapabilities networkCapabilities) { 456 // Transport types are internally packed as bits starting from bit 0. Casting to int works 457 // fine since for now and the foreseeable future, there will be less than 32 transports. 458 return (int) networkCapabilities.getTransportTypesInternal(); 459 } 460 461 /** Inform the KeepaliveStatsTracker a keepalive has just started and is active. */ onStartKeepalive( @onNull Network network, int slot, @NonNull NetworkCapabilities nc, int intervalSeconds, int appUid, boolean isAutoKeepalive)462 public void onStartKeepalive( 463 @NonNull Network network, 464 int slot, 465 @NonNull NetworkCapabilities nc, 466 int intervalSeconds, 467 int appUid, 468 boolean isAutoKeepalive) { 469 ensureRunningOnHandlerThread(); 470 if (!isEnabled()) return; 471 final int keepaliveId = getKeepaliveId(network, slot); 472 if (keepaliveId == INVALID_KEEPALIVE_ID) return; 473 if (mKeepaliveStatsPerId.contains(keepaliveId)) { 474 disableTracker("Attempt to start keepalive stats on a known network, slot pair"); 475 return; 476 } 477 478 mNumKeepaliveRequests++; 479 if (isAutoKeepalive) mNumAutomaticKeepaliveRequests++; 480 mAppUids.add(appUid); 481 482 final long timeNow = mDependencies.getElapsedRealtime(); 483 updateDurationsPerNumOfKeepalive(timeNow); 484 485 mNumRegisteredKeepalive++; 486 mNumActiveKeepalive++; 487 488 final KeepaliveStats newKeepaliveStats = 489 new KeepaliveStats( 490 getCarrierId(nc), 491 getTransportTypes(nc), 492 intervalSeconds, 493 appUid, 494 isAutoKeepalive, 495 timeNow); 496 497 mKeepaliveStatsPerId.put(keepaliveId, newKeepaliveStats); 498 } 499 500 /** 501 * Inform the KeepaliveStatsTracker that the keepalive with the given network, slot pair has 502 * updated its active state to keepaliveActive. 503 */ onKeepaliveActive( @onNull Network network, int slot, boolean keepaliveActive)504 private void onKeepaliveActive( 505 @NonNull Network network, int slot, boolean keepaliveActive) { 506 final long timeNow = mDependencies.getElapsedRealtime(); 507 onKeepaliveActive(network, slot, keepaliveActive, timeNow); 508 } 509 510 /** 511 * Inform the KeepaliveStatsTracker that the keepalive with the given network, slot pair has 512 * updated its active state to keepaliveActive. 513 * 514 * @param network the network of the keepalive 515 * @param slot the slot number of the keepalive 516 * @param keepaliveActive the new active state of the keepalive 517 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 518 */ onKeepaliveActive( @onNull Network network, int slot, boolean keepaliveActive, long timeNow)519 private void onKeepaliveActive( 520 @NonNull Network network, int slot, boolean keepaliveActive, long timeNow) { 521 final int keepaliveId = getKeepaliveId(network, slot); 522 if (keepaliveId == INVALID_KEEPALIVE_ID) return; 523 524 final KeepaliveStats keepaliveStats = mKeepaliveStatsPerId.get(keepaliveId, null); 525 526 if (keepaliveStats == null) { 527 disableTracker("Attempt to set active keepalive on an unknown network, slot pair"); 528 return; 529 } 530 updateDurationsPerNumOfKeepalive(timeNow); 531 532 if (keepaliveActive != keepaliveStats.isKeepaliveActive()) { 533 mNumActiveKeepalive += keepaliveActive ? 1 : -1; 534 } 535 536 keepaliveStats.updateLifetimeStatsAndSetActive(timeNow, keepaliveActive); 537 } 538 539 /** Inform the KeepaliveStatsTracker a keepalive has just been paused. */ onPauseKeepalive(@onNull Network network, int slot)540 public void onPauseKeepalive(@NonNull Network network, int slot) { 541 ensureRunningOnHandlerThread(); 542 if (!isEnabled()) return; 543 onKeepaliveActive(network, slot, /* keepaliveActive= */ false); 544 } 545 546 /** Inform the KeepaliveStatsTracker a keepalive has just been resumed. */ onResumeKeepalive(@onNull Network network, int slot)547 public void onResumeKeepalive(@NonNull Network network, int slot) { 548 ensureRunningOnHandlerThread(); 549 if (!isEnabled()) return; 550 onKeepaliveActive(network, slot, /* keepaliveActive= */ true); 551 } 552 553 /** Inform the KeepaliveStatsTracker a keepalive has just been stopped. */ onStopKeepalive(@onNull Network network, int slot)554 public void onStopKeepalive(@NonNull Network network, int slot) { 555 ensureRunningOnHandlerThread(); 556 if (!isEnabled()) return; 557 558 final int keepaliveId = getKeepaliveId(network, slot); 559 if (keepaliveId == INVALID_KEEPALIVE_ID) return; 560 final long timeNow = mDependencies.getElapsedRealtime(); 561 562 onKeepaliveActive(network, slot, /* keepaliveActive= */ false, timeNow); 563 final KeepaliveStats keepaliveStats = mKeepaliveStatsPerId.get(keepaliveId, null); 564 if (keepaliveStats == null) return; 565 566 mNumRegisteredKeepalive--; 567 568 // add to the aggregate since it will be removed. 569 addToAggregateKeepaliveLifetime(keepaliveStats, timeNow); 570 // free up the slot. 571 mKeepaliveStatsPerId.remove(keepaliveId); 572 } 573 574 /** 575 * Updates and adds the lifetime metric of keepaliveStats to the aggregate. 576 * 577 * @param keepaliveStats the stats to add to the aggregate 578 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 579 */ addToAggregateKeepaliveLifetime( @onNull KeepaliveStats keepaliveStats, long timeNow)580 private void addToAggregateKeepaliveLifetime( 581 @NonNull KeepaliveStats keepaliveStats, long timeNow) { 582 583 final KeepaliveStats.LifetimeStats lifetimeStats = 584 keepaliveStats.getAndResetLifetimeStats(timeNow); 585 586 final LifetimeKey key = 587 new LifetimeKey( 588 keepaliveStats.carrierId, 589 keepaliveStats.transportTypes, 590 keepaliveStats.intervalMs); 591 592 KeepaliveLifetimeForCarrier.Builder keepaliveLifetimeForCarrier = 593 mAggregateKeepaliveLifetime.get(key); 594 595 if (keepaliveLifetimeForCarrier == null) { 596 keepaliveLifetimeForCarrier = 597 KeepaliveLifetimeForCarrier.newBuilder() 598 .setCarrierId(keepaliveStats.carrierId) 599 .setTransportTypes(keepaliveStats.transportTypes) 600 .setIntervalsMsec(keepaliveStats.intervalMs); 601 mAggregateKeepaliveLifetime.put(key, keepaliveLifetimeForCarrier); 602 } 603 604 keepaliveLifetimeForCarrier.setLifetimeMsec( 605 keepaliveLifetimeForCarrier.getLifetimeMsec() + lifetimeStats.lifetimeMs); 606 keepaliveLifetimeForCarrier.setActiveLifetimeMsec( 607 keepaliveLifetimeForCarrier.getActiveLifetimeMsec() 608 + lifetimeStats.activeLifetimeMs); 609 } 610 611 /** 612 * Builds and returns DailykeepaliveInfoReported proto. 613 * 614 * @return the DailykeepaliveInfoReported proto that was built. 615 */ 616 @VisibleForTesting buildKeepaliveMetrics()617 public @NonNull DailykeepaliveInfoReported buildKeepaliveMetrics() { 618 ensureRunningOnHandlerThread(); 619 final long timeNow = mDependencies.getElapsedRealtime(); 620 return buildKeepaliveMetrics(timeNow); 621 } 622 623 /** 624 * Updates the metrics to timeNow and builds and returns DailykeepaliveInfoReported proto. 625 * 626 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 627 */ buildKeepaliveMetrics(long timeNow)628 private @NonNull DailykeepaliveInfoReported buildKeepaliveMetrics(long timeNow) { 629 updateDurationsPerNumOfKeepalive(timeNow); 630 631 final DurationPerNumOfKeepalive.Builder durationPerNumOfKeepalive = 632 DurationPerNumOfKeepalive.newBuilder(); 633 634 mDurationPerNumOfKeepalive.forEach( 635 durationForNumOfKeepalive -> 636 durationPerNumOfKeepalive.addDurationForNumOfKeepalive( 637 durationForNumOfKeepalive)); 638 639 final KeepaliveLifetimePerCarrier.Builder keepaliveLifetimePerCarrier = 640 KeepaliveLifetimePerCarrier.newBuilder(); 641 642 for (int i = 0; i < mKeepaliveStatsPerId.size(); i++) { 643 final KeepaliveStats keepaliveStats = mKeepaliveStatsPerId.valueAt(i); 644 addToAggregateKeepaliveLifetime(keepaliveStats, timeNow); 645 } 646 647 // Fill keepalive carrier stats to the proto 648 mAggregateKeepaliveLifetime 649 .values() 650 .forEach( 651 keepaliveLifetimeForCarrier -> 652 keepaliveLifetimePerCarrier.addKeepaliveLifetimeForCarrier( 653 keepaliveLifetimeForCarrier)); 654 655 final DailykeepaliveInfoReported.Builder dailyKeepaliveInfoReported = 656 DailykeepaliveInfoReported.newBuilder(); 657 658 dailyKeepaliveInfoReported.setDurationPerNumOfKeepalive(durationPerNumOfKeepalive); 659 dailyKeepaliveInfoReported.setKeepaliveLifetimePerCarrier(keepaliveLifetimePerCarrier); 660 dailyKeepaliveInfoReported.setKeepaliveRequests(mNumKeepaliveRequests); 661 dailyKeepaliveInfoReported.setAutomaticKeepaliveRequests(mNumAutomaticKeepaliveRequests); 662 dailyKeepaliveInfoReported.setDistinctUserCount(mAppUids.size()); 663 dailyKeepaliveInfoReported.addAllUid(mAppUids); 664 665 return dailyKeepaliveInfoReported.build(); 666 } 667 668 /** 669 * Builds and resets the stored metrics. Similar to buildKeepaliveMetrics but also resets the 670 * metrics while maintaining the state of the keepalives. 671 * 672 * @return the DailykeepaliveInfoReported proto that was built. 673 */ 674 @VisibleForTesting buildAndResetMetrics()675 public @NonNull DailykeepaliveInfoReported buildAndResetMetrics() { 676 ensureRunningOnHandlerThread(); 677 final long timeNow = mDependencies.getElapsedRealtime(); 678 679 final DailykeepaliveInfoReported metrics = buildKeepaliveMetrics(timeNow); 680 681 mDurationPerNumOfKeepalive.clear(); 682 mAggregateKeepaliveLifetime.clear(); 683 mAppUids.clear(); 684 mNumKeepaliveRequests = 0; 685 mNumAutomaticKeepaliveRequests = 0; 686 687 // Update the metrics with the existing keepalives. 688 ensureDurationPerNumOfKeepaliveSize(); 689 690 mAggregateKeepaliveLifetime.clear(); 691 // Reset the stats for existing keepalives 692 for (int i = 0; i < mKeepaliveStatsPerId.size(); i++) { 693 final KeepaliveStats keepaliveStats = mKeepaliveStatsPerId.valueAt(i); 694 keepaliveStats.resetLifetimeStats(timeNow); 695 mAppUids.add(keepaliveStats.appUid); 696 mNumKeepaliveRequests++; 697 if (keepaliveStats.isAutoKeepalive) mNumAutomaticKeepaliveRequests++; 698 } 699 700 return metrics; 701 } 702 disableTracker(String msg)703 private void disableTracker(String msg) { 704 if (!mEnabled.compareAndSet(/* expectedValue= */ true, /* newValue= */ false)) { 705 // already disabled 706 return; 707 } 708 Log.wtf(TAG, msg + ". Disabling KeepaliveStatsTracker"); 709 mContext.unregisterReceiver(mBroadcastReceiver); 710 // The returned future is ignored since it is void and the is never completed exceptionally. 711 final CompletableFuture<Void> unused = mListenerFuture.thenAcceptAsync( 712 listener -> mSubscriptionManager.removeOnSubscriptionsChangedListener(listener), 713 BackgroundThread.getExecutor()); 714 } 715 716 /** Whether this tracker is enabled. This method is thread safe. */ isEnabled()717 public boolean isEnabled() { 718 return mEnabled.get(); 719 } 720 721 /** 722 * Checks the DailykeepaliveInfoReported for the following: 723 * 1. total active durations/lifetimes <= total registered durations/lifetimes. 724 * 2. Total time in Durations == total time in Carrier lifetime stats 725 * 3. The total elapsed real time spent is within expectations. 726 */ 727 @VisibleForTesting allMetricsExpected(DailykeepaliveInfoReported dailyKeepaliveInfoReported)728 public boolean allMetricsExpected(DailykeepaliveInfoReported dailyKeepaliveInfoReported) { 729 int totalRegistered = 0; 730 int totalActiveDurations = 0; 731 int totalTimeSpent = 0; 732 for (DurationForNumOfKeepalive durationForNumOfKeepalive: dailyKeepaliveInfoReported 733 .getDurationPerNumOfKeepalive().getDurationForNumOfKeepaliveList()) { 734 final int n = durationForNumOfKeepalive.getNumOfKeepalive(); 735 totalRegistered += durationForNumOfKeepalive.getKeepaliveRegisteredDurationsMsec() * n; 736 totalActiveDurations += durationForNumOfKeepalive.getKeepaliveActiveDurationsMsec() * n; 737 totalTimeSpent += durationForNumOfKeepalive.getKeepaliveRegisteredDurationsMsec(); 738 } 739 int totalLifetimes = 0; 740 int totalActiveLifetimes = 0; 741 for (KeepaliveLifetimeForCarrier keepaliveLifetimeForCarrier: dailyKeepaliveInfoReported 742 .getKeepaliveLifetimePerCarrier().getKeepaliveLifetimeForCarrierList()) { 743 totalLifetimes += keepaliveLifetimeForCarrier.getLifetimeMsec(); 744 totalActiveLifetimes += keepaliveLifetimeForCarrier.getActiveLifetimeMsec(); 745 } 746 return totalActiveDurations <= totalRegistered && totalActiveLifetimes <= totalLifetimes 747 && totalLifetimes == totalRegistered && totalActiveLifetimes == totalActiveDurations 748 && totalTimeSpent <= MAX_EXPECTED_DURATION_MS; 749 } 750 751 /** Writes the stored metrics to ConnectivityStatsLog and resets. */ writeAndResetMetrics()752 public void writeAndResetMetrics() { 753 ensureRunningOnHandlerThread(); 754 // Keepalive stats use repeated atoms, which are only supported on T+. If written to statsd 755 // on S- they will bootloop the system, so they must not be sent on S-. See b/289471411. 756 if (!SdkLevel.isAtLeastT()) { 757 Log.d(TAG, "KeepaliveStatsTracker is disabled before T, skipping write"); 758 return; 759 } 760 if (!isEnabled()) { 761 Log.d(TAG, "KeepaliveStatsTracker is disabled, skipping write"); 762 return; 763 } 764 765 final DailykeepaliveInfoReported dailyKeepaliveInfoReported = buildAndResetMetrics(); 766 if (!allMetricsExpected(dailyKeepaliveInfoReported)) { 767 Log.wtf(TAG, "Unexpected metrics values: " + dailyKeepaliveInfoReported.toString()); 768 } 769 mDependencies.writeStats(dailyKeepaliveInfoReported); 770 } 771 772 /** Dump KeepaliveStatsTracker state. */ dump(IndentingPrintWriter pw)773 public void dump(IndentingPrintWriter pw) { 774 ensureRunningOnHandlerThread(); 775 pw.println("KeepaliveStatsTracker enabled: " + isEnabled()); 776 pw.increaseIndent(); 777 pw.println(buildKeepaliveMetrics().toString()); 778 pw.decreaseIndent(); 779 } 780 ensureRunningOnHandlerThread()781 private void ensureRunningOnHandlerThread() { 782 if (mConnectivityServiceHandler.getLooper().getThread() != Thread.currentThread()) { 783 throw new IllegalStateException( 784 "Not running on handler thread: " + Thread.currentThread().getName()); 785 } 786 } 787 } 788