From c0d8b4eb0a047850c3aa58e0d20156ce478e470c Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov <dplotnikov@google.com> Date: Wed, 22 May 2024 16:25:44 -0700 Subject: [PATCH] Make SystemHealthManager.takeUidSnapshot call async Bug: 339041893 Flag: backstage_power com.android.server.power.optimization.oneway_battery_stats_service Test: atest CtsOsTestCases:SystemHealthManagerTest Change-Id: Id47990cf9c3a87c81525ef02df96c04c62f1422b --- .../os/health/SystemHealthManager.java | 88 ++++++++++++++++--- .../android/internal/app/IBatteryStats.aidl | 7 ++ .../server/am/BatteryStatsService.java | 55 ++++++++++++ .../android/server/power/stats/flags.aconfig | 10 +++ 4 files changed, 147 insertions(+), 13 deletions(-) diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java index 322a8e62dbb3..deabfed365a6 100644 --- a/core/java/android/os/health/SystemHealthManager.java +++ b/core/java/android/os/health/SystemHealthManager.java @@ -33,13 +33,16 @@ import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; +import android.os.SynchronousResultReceiver; import com.android.internal.app.IBatteryStats; +import com.android.server.power.optimization.Flags; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.concurrent.Executor; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; /** @@ -67,6 +70,14 @@ public class SystemHealthManager { private final IPowerStatsService mPowerStats; private List<PowerMonitor> mPowerMonitorsInfo; private final Object mPowerMonitorsLock = new Object(); + private static final long TAKE_UID_SNAPSHOT_TIMEOUT_MILLIS = 10_000; + + private static class PendingUidSnapshots { + public int[] uids; + public SynchronousResultReceiver resultReceiver; + } + + private final PendingUidSnapshots mPendingUidSnapshots = new PendingUidSnapshots(); /** * Construct a new SystemHealthManager object. @@ -111,12 +122,19 @@ public class SystemHealthManager { * @see Process#myUid() Process.myUid() */ public HealthStats takeUidSnapshot(int uid) { - try { - final HealthStatsParceler parceler = mBatteryStats.takeUidSnapshot(uid); - return parceler.getHealthStats(); - } catch (RemoteException ex) { - throw new RuntimeException(ex); + if (!Flags.onewayBatteryStatsService()) { + try { + final HealthStatsParceler parceler = mBatteryStats.takeUidSnapshot(uid); + return parceler.getHealthStats(); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } } + final HealthStats[] result = takeUidSnapshots(new int[]{uid}); + if (result != null && result.length >= 1) { + return result[0]; + } + return null; } /** @@ -144,17 +162,61 @@ public class SystemHealthManager { * other than its own. */ public HealthStats[] takeUidSnapshots(int[] uids) { + if (!Flags.onewayBatteryStatsService()) { + try { + final HealthStatsParceler[] parcelers = mBatteryStats.takeUidSnapshots(uids); + final int count = uids.length; + final HealthStats[] results = new HealthStats[count]; + for (int i = 0; i < count; i++) { + results[i] = parcelers[i].getHealthStats(); + } + return results; + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + SynchronousResultReceiver resultReceiver; + synchronized (mPendingUidSnapshots) { + if (Arrays.equals(mPendingUidSnapshots.uids, uids)) { + resultReceiver = mPendingUidSnapshots.resultReceiver; + } else { + mPendingUidSnapshots.uids = Arrays.copyOf(uids, uids.length); + mPendingUidSnapshots.resultReceiver = resultReceiver = + new SynchronousResultReceiver("takeUidSnapshots"); + try { + mBatteryStats.takeUidSnapshotsAsync(uids, resultReceiver); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + } + + SynchronousResultReceiver.Result result; try { - final HealthStatsParceler[] parcelers = mBatteryStats.takeUidSnapshots(uids); - final HealthStats[] results = new HealthStats[uids.length]; - final int N = uids.length; - for (int i = 0; i < N; i++) { - results[i] = parcelers[i].getHealthStats(); + result = resultReceiver.awaitResult(TAKE_UID_SNAPSHOT_TIMEOUT_MILLIS); + } catch (TimeoutException e) { + throw new RuntimeException(e); + } finally { + synchronized (mPendingUidSnapshots) { + if (mPendingUidSnapshots.resultReceiver == resultReceiver) { + mPendingUidSnapshots.uids = null; + mPendingUidSnapshots.resultReceiver = null; + } + } + } + + final HealthStats[] results = new HealthStats[uids.length]; + if (result.bundle != null) { + HealthStatsParceler[] parcelers = result.bundle.getParcelableArray( + IBatteryStats.KEY_UID_SNAPSHOTS, HealthStatsParceler.class); + if (parcelers != null && parcelers.length == uids.length) { + for (int i = 0; i < parcelers.length; i++) { + results[i] = parcelers[i].getHealthStats(); + } } - return results; - } catch (RemoteException ex) { - throw new RuntimeException(ex); } + return results; } /** diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 99b3f9a16355..ebcae277c62b 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -21,6 +21,7 @@ import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.BluetoothBatteryStats; import android.os.ParcelFileDescriptor; +import android.os.ResultReceiver; import android.os.WakeLockStats; import android.os.WorkSource; import android.os.connectivity.CellularBatteryStats; @@ -33,6 +34,9 @@ import android.telephony.ModemActivityInfo; import android.telephony.SignalStrength; interface IBatteryStats { + /** @hide */ + const String KEY_UID_SNAPSHOTS = "uid_snapshots"; + // These first methods are also called by native code, so must // be kept in sync with frameworks/native/libs/binder/include_batterystats/batterystats/IBatteryStats.h @EnforcePermission("UPDATE_DEVICE_STATS") @@ -256,6 +260,9 @@ interface IBatteryStats { @PermissionManuallyEnforced HealthStatsParceler[] takeUidSnapshots(in int[] uid); + @PermissionManuallyEnforced + oneway void takeUidSnapshotsAsync(in int[] uid, in ResultReceiver result); + @EnforcePermission("UPDATE_DEVICE_STATS") oneway void noteBluetoothControllerActivity(in BluetoothActivityEnergyInfo info); @EnforcePermission("UPDATE_DEVICE_STATS") diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 7c0325e7376a..d642b02e23ea 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -60,6 +60,7 @@ import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Binder; import android.os.BluetoothBatteryStats; +import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -71,6 +72,7 @@ import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.Process; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.SystemClock; import android.os.Trace; @@ -3337,6 +3339,59 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + /** + * Gets a snapshot of the system health for a number of uids. + */ + @Override + public void takeUidSnapshotsAsync(int[] requestUids, ResultReceiver resultReceiver) { + if (!onlyCaller(requestUids)) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BATTERY_STATS, null); + } + + Future future; + if (shouldCollectExternalStats()) { + future = mWorker.scheduleSync("get-health-stats-for-uids", + BatteryExternalStatsWorker.UPDATE_ALL); + } else { + future = null; + } + + mHandler.post(() -> { + if (future != null) { + try { + // Worker uses a separate thread pool, so waiting here won't cause a deadlock + future.get(); + } catch (InterruptedException | ExecutionException e) { + Slog.e(TAG, "Sync failed", e); + } + } + + final long ident = Binder.clearCallingIdentity(); + int i = -1; + try { + final int count = requestUids.length; + final HealthStatsParceler[] results = new HealthStatsParceler[count]; + synchronized (mStats) { + for (i = 0; i < count; i++) { + results[i] = getHealthStatsForUidLocked(requestUids[i]); + } + } + Bundle resultData = new Bundle(1); + resultData.putParcelableArray(IBatteryStats.KEY_UID_SNAPSHOTS, results); + resultReceiver.send(0, resultData); + } catch (Exception ex) { + if (DBG) { + Slog.d(TAG, "Crashed while returning results for takeUidSnapshots(" + + Arrays.toString(requestUids) + ") i=" + i, ex); + } + throw ex; + } finally { + Binder.restoreCallingIdentity(ident); + } + }); + } + private boolean shouldCollectExternalStats() { return (SystemClock.elapsedRealtime() - mWorker.getLastCollectionTimeStamp()) > mStats.getExternalStatsCollectionRateLimitMs(); diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig index 9283fe9bb7b0..6a5a7ac8d240 100644 --- a/services/core/java/com/android/server/power/stats/flags.aconfig +++ b/services/core/java/com/android/server/power/stats/flags.aconfig @@ -37,3 +37,13 @@ flag { description: "Feature flag for streamlined misc (excluding CPU, Cell, Wifi, BT) battery stats" bug: "333941740" } + +flag { + name: "oneway_battery_stats_service" + namespace: "backstage_power" + description: "Bugfix flag for locking issues and watchdog kills in BatteryStatsService" + bug: "330792526" + metadata { + purpose: PURPOSE_BUGFIX + } +} -- GitLab