diff options
11 files changed, 468 insertions, 158 deletions
diff --git a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java index 66a2600d946a..d8d4a6efb6e8 100644 --- a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java +++ b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java @@ -23,8 +23,7 @@ import android.support.test.runner.AndroidJUnit4; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.BinderInternal.CallSession; - -import java.util.Random; +import com.android.internal.os.CachedDeviceState; import org.junit.After; import org.junit.Before; @@ -32,8 +31,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.assertNull; - /** * Performance tests for {@link BinderCallsStats} @@ -49,6 +46,8 @@ public class BinderCallsStatsPerfTest { @Before public void setUp() { mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector()); + CachedDeviceState deviceState = new CachedDeviceState(false, false); + mBinderCallsStats.setDeviceState(deviceState.getReadonlyClient()); } @After diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index 4aa30f6fb31e..c0c358d9b44b 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -16,16 +16,9 @@ package com.android.internal.os; +import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.BatteryManager; -import android.os.BatteryManagerInternal; import android.os.Binder; -import android.os.OsProtoEnums; -import android.os.PowerManager; import android.os.SystemClock; import android.os.UserHandle; import android.text.format.DateFormat; @@ -37,7 +30,6 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BinderInternal.CallSession; -import com.android.server.LocalServices; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; @@ -63,7 +55,6 @@ public class BinderCallsStats implements BinderInternal.Observer { private static final String TAG = "BinderCallsStats"; private static final int CALL_SESSIONS_POOL_SIZE = 100; - private static final int PERIODIC_SAMPLING_INTERVAL = 10; private static final int MAX_EXCEPTION_COUNT_SIZE = 50; private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow"; @@ -81,25 +72,7 @@ public class BinderCallsStats implements BinderInternal.Observer { private final Random mRandom; private long mStartTime = System.currentTimeMillis(); - // State updated by the broadcast receiver below. - private boolean mScreenInteractive; - private boolean mCharging; - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - switch (intent.getAction()) { - case Intent.ACTION_BATTERY_CHANGED: - mCharging = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; - break; - case Intent.ACTION_SCREEN_ON: - mScreenInteractive = true; - break; - case Intent.ACTION_SCREEN_OFF: - mScreenInteractive = false; - break; - } - } - }; + private CachedDeviceState.Readonly mDeviceState; /** Injector for {@link BinderCallsStats}. */ public static class Injector { @@ -112,65 +85,14 @@ public class BinderCallsStats implements BinderInternal.Observer { this.mRandom = injector.getRandomGenerator(); } - public void systemReady(Context context) { - registerBroadcastReceiver(context); - setInitialState(queryScreenInteractive(context), queryIsCharging()); - } - - /** - * Listens for screen/battery state changes. - */ - @VisibleForTesting - public void registerBroadcastReceiver(Context context) { - final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - context.registerReceiver(mBroadcastReceiver, filter); - } - - /** - * Sets the battery/screen initial state. - * - * This has to be updated *after* the broadcast receiver is installed. - */ - @VisibleForTesting - public void setInitialState(boolean isScreenInteractive, boolean isCharging) { - this.mScreenInteractive = isScreenInteractive; - this.mCharging = isCharging; - // Data collected previously was not accurate since the battery/screen state was not set. - reset(); - } - - private boolean queryIsCharging() { - final BatteryManagerInternal batteryManager = - LocalServices.getService(BatteryManagerInternal.class); - if (batteryManager == null) { - Slog.wtf(TAG, "BatteryManager null while starting BinderCallsStatsService"); - // Default to true to not collect any data. - return true; - } else { - return batteryManager.getPlugType() != OsProtoEnums.BATTERY_PLUGGED_NONE; - } - } - - private boolean queryScreenInteractive(Context context) { - final PowerManager powerManager = context.getSystemService(PowerManager.class); - final boolean screenInteractive; - if (powerManager == null) { - Slog.wtf(TAG, "PowerManager null while starting BinderCallsStatsService", - new Throwable()); - return true; - } else { - return powerManager.isInteractive(); - } + public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) { + mDeviceState = deviceState; } @Override @Nullable public CallSession callStarted(Binder binder, int code) { - if (mCharging) { + if (mDeviceState == null || mDeviceState.isCharging()) { return null; } @@ -221,7 +143,7 @@ public class BinderCallsStats implements BinderInternal.Observer { synchronized (mLock) { // This was already checked in #callStart but check again while synchronized. - if (mCharging) { + if (mDeviceState == null || mDeviceState.isCharging()) { return; } @@ -233,7 +155,7 @@ public class BinderCallsStats implements BinderInternal.Observer { uidEntry.recordedCallCount++; final CallStat callStat = uidEntry.getOrCreate( - s.binderClass, s.transactionCode, mScreenInteractive); + s.binderClass, s.transactionCode, mDeviceState.isScreenInteractive()); callStat.callCount++; callStat.recordedCallCount++; callStat.cpuTimeMicros += duration; @@ -252,7 +174,7 @@ public class BinderCallsStats implements BinderInternal.Observer { // Only record the total call count if we already track data for this key. // It helps to keep the memory usage down when sampling is enabled. final CallStat callStat = uidEntry.get( - s.binderClass, s.transactionCode, mScreenInteractive); + s.binderClass, s.transactionCode, mDeviceState.isScreenInteractive()); if (callStat != null) { callStat.callCount++; } @@ -319,13 +241,13 @@ public class BinderCallsStats implements BinderInternal.Observer { public ArrayList<ExportedCallStat> getExportedCallStats() { // We do not collect all the data if detailed tracking is off. if (!mDetailedTracking) { - return new ArrayList<ExportedCallStat>(); + return new ArrayList<>(); } ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>(); synchronized (mLock) { final int uidEntriesSize = mUidEntries.size(); - for (int entryIdx = 0; entryIdx < uidEntriesSize; entryIdx++){ + for (int entryIdx = 0; entryIdx < uidEntriesSize; entryIdx++) { final UidEntry entry = mUidEntries.valueAt(entryIdx); for (CallStat stat : entry.getCallStatsList()) { ExportedCallStat exported = new ExportedCallStat(); @@ -387,13 +309,15 @@ public class BinderCallsStats implements BinderInternal.Observer { } } - public void dump(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) { + /** Writes the collected statistics to the supplied {@link PrintWriter}.*/ + public void dump(PrintWriter pw, Map<Integer, String> appIdToPkgNameMap, boolean verbose) { synchronized (mLock) { dumpLocked(pw, appIdToPkgNameMap, verbose); } } - private void dumpLocked(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) { + private void dumpLocked(PrintWriter pw, Map<Integer, String> appIdToPkgNameMap, + boolean verbose) { long totalCallsCount = 0; long totalRecordedCallsCount = 0; long totalCpuTime = 0; @@ -450,13 +374,13 @@ public class BinderCallsStats implements BinderInternal.Observer { for (UidEntry entry : summaryEntries) { String uidStr = uidToString(entry.uid, appIdToPkgNameMap); pw.println(String.format(" %10d %3.0f%% %8d %8d %s", - entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime, - entry.recordedCallCount, entry.callCount, uidStr)); + entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime, + entry.recordedCallCount, entry.callCount, uidStr)); } pw.println(); pw.println(String.format(" Summary: total_cpu_time=%d, " - + "calls_count=%d, avg_call_cpu_time=%.0f", - totalCpuTime, totalCallsCount, (double)totalCpuTime / totalRecordedCallsCount)); + + "calls_count=%d, avg_call_cpu_time=%.0f", + totalCpuTime, totalCallsCount, (double) totalCpuTime / totalRecordedCallsCount)); pw.println(); pw.println("Exceptions thrown (exception_count, class_name):"); @@ -723,11 +647,6 @@ public class BinderCallsStats implements BinderInternal.Observer { return result; } - @VisibleForTesting - public BroadcastReceiver getBroadcastReceiver() { - return mBroadcastReceiver; - } - private static int compareByCpuDesc( ExportedCallStat a, ExportedCallStat b) { return Long.compare(b.cpuTimeMicros, a.cpuTimeMicros); diff --git a/core/java/com/android/internal/os/CachedDeviceState.java b/core/java/com/android/internal/os/CachedDeviceState.java new file mode 100644 index 000000000000..8c90682ec281 --- /dev/null +++ b/core/java/com/android/internal/os/CachedDeviceState.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2018 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.internal.os; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Stores the device state (e.g. charging/on battery, screen on/off) to be shared with + * the System Server telemetry services. + * + * @hide + */ +public class CachedDeviceState { + private volatile boolean mScreenInteractive; + private volatile boolean mCharging; + + public CachedDeviceState() { + mCharging = true; + mScreenInteractive = false; + } + + @VisibleForTesting + public CachedDeviceState(boolean isCharging, boolean isScreenInteractive) { + mCharging = isCharging; + mScreenInteractive = isScreenInteractive; + } + + public void setScreenInteractive(boolean screenInteractive) { + mScreenInteractive = screenInteractive; + } + + public void setCharging(boolean charging) { + mCharging = charging; + } + + public Readonly getReadonlyClient() { + return new CachedDeviceState.Readonly(); + } + + /** + * Allows for only a readonly access to the device state. + */ + public class Readonly { + public boolean isCharging() { + return mCharging; + } + + public boolean isScreenInteractive() { + return mScreenInteractive; + } + } +} diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java index 5b8224e30033..02a8b224e3c2 100644 --- a/core/java/com/android/internal/os/LooperStats.java +++ b/core/java/com/android/internal/os/LooperStats.java @@ -39,7 +39,7 @@ public class LooperStats implements Looper.Observer { private static final int TOKEN_POOL_SIZE = 50; @GuardedBy("mLock") - private final SparseArray<Entry> mEntries = new SparseArray<>(256); + private final SparseArray<Entry> mEntries = new SparseArray<>(512); private final Object mLock = new Object(); private final Entry mOverflowEntry = new Entry("OVERFLOW"); private final Entry mHashCollisionEntry = new Entry("HASH_COLLISION"); @@ -47,15 +47,20 @@ public class LooperStats implements Looper.Observer { new ConcurrentLinkedQueue<>(); private final int mEntriesSizeCap; private int mSamplingInterval; + private CachedDeviceState.Readonly mDeviceState; public LooperStats(int samplingInterval, int entriesSizeCap) { this.mSamplingInterval = samplingInterval; this.mEntriesSizeCap = entriesSizeCap; } + public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) { + mDeviceState = deviceState; + } + @Override public Object messageDispatchStarting() { - if (shouldCollectDetailedData()) { + if (deviceStateAllowsCollection() && shouldCollectDetailedData()) { DispatchSession session = mSessionPool.poll(); session = session == null ? new DispatchSession() : session; session.startTimeMicro = getElapsedRealtimeMicro(); @@ -68,6 +73,10 @@ public class LooperStats implements Looper.Observer { @Override public void messageDispatched(Object token, Message msg) { + if (!deviceStateAllowsCollection()) { + return; + } + DispatchSession session = (DispatchSession) token; Entry entry = getOrCreateEntry(msg); synchronized (entry) { @@ -88,6 +97,10 @@ public class LooperStats implements Looper.Observer { @Override public void dispatchingThrewException(Object token, Message msg, Exception exception) { + if (!deviceStateAllowsCollection()) { + return; + } + DispatchSession session = (DispatchSession) token; Entry entry = getOrCreateEntry(msg); synchronized (entry) { @@ -96,6 +109,11 @@ public class LooperStats implements Looper.Observer { recycleSession(session); } + private boolean deviceStateAllowsCollection() { + // Do not collect data if on charger or the state is not set. + return mDeviceState != null && !mDeviceState.isCharging(); + } + /** Returns an array of {@link ExportedEntry entries} with the aggregated statistics. */ public List<ExportedEntry> getEntries() { final ArrayList<ExportedEntry> entries; @@ -142,7 +160,8 @@ public class LooperStats implements Looper.Observer { @NonNull private Entry getOrCreateEntry(Message msg) { - final int id = Entry.idFor(msg); + final boolean isInteractive = mDeviceState.isScreenInteractive(); + final int id = Entry.idFor(msg, isInteractive); Entry entry; synchronized (mLock) { entry = mEntries.get(id); @@ -151,14 +170,14 @@ public class LooperStats implements Looper.Observer { // If over the size cap, track totals under a single entry. return mOverflowEntry; } - entry = new Entry(msg); + entry = new Entry(msg, isInteractive); mEntries.put(id, entry); } } if (entry.handler.getClass() != msg.getTarget().getClass() - || entry.handler.getLooper().getThread() - != msg.getTarget().getLooper().getThread()) { + || entry.handler.getLooper().getThread() != msg.getTarget().getLooper().getThread() + || entry.isInteractive != isInteractive) { // If a hash collision happened, track totals under a single entry. return mHashCollisionEntry; } @@ -192,6 +211,7 @@ public class LooperStats implements Looper.Observer { private static class Entry { public final Handler handler; public final String messageName; + public final boolean isInteractive; public long messageCount; public long recordedMessageCount; public long exceptionCount; @@ -200,14 +220,16 @@ public class LooperStats implements Looper.Observer { public long cpuUsageMicro; public long maxCpuUsageMicro; - Entry(Message msg) { - handler = msg.getTarget(); - messageName = handler.getMessageName(msg); + Entry(Message msg, boolean isInteractive) { + this.handler = msg.getTarget(); + this.messageName = handler.getMessageName(msg); + this.isInteractive = isInteractive; } Entry(String specialEntryName) { - handler = null; - messageName = specialEntryName; + this.messageName = specialEntryName; + this.handler = null; + this.isInteractive = false; } void reset() { @@ -220,10 +242,11 @@ public class LooperStats implements Looper.Observer { maxCpuUsageMicro = 0; } - static int idFor(Message msg) { + static int idFor(Message msg, boolean isInteractive) { int result = 7; result = 31 * result + msg.getTarget().getLooper().getThread().hashCode(); result = 31 * result + msg.getTarget().getClass().hashCode(); + result = 31 * result + (isInteractive ? 1231 : 1237); if (msg.getCallback() != null) { return 31 * result + msg.getCallback().getClass().hashCode(); } else { @@ -237,6 +260,7 @@ public class LooperStats implements Looper.Observer { public final String handlerClassName; public final String threadName; public final String messageName; + public final boolean isInteractive; public final long messageCount; public final long recordedMessageCount; public final long exceptionCount; @@ -254,6 +278,7 @@ public class LooperStats implements Looper.Observer { this.handlerClassName = ""; this.threadName = ""; } + this.isInteractive = entry.isInteractive; this.messageName = entry.messageName; this.messageCount = entry.messageCount; this.recordedMessageCount = entry.recordedMessageCount; diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java index ace6b2d984a9..364dcfd4f471 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java @@ -18,10 +18,7 @@ package com.android.internal.os; import static org.junit.Assert.assertEquals; -import android.content.Intent; -import android.os.BatteryManager; import android.os.Binder; -import android.os.OsProtoEnums; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -34,7 +31,6 @@ import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; - import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; @@ -50,6 +46,7 @@ public class BinderCallsStatsTest { private static final int TEST_UID = 1; private static final int REQUEST_SIZE = 2; private static final int REPLY_SIZE = 3; + private final CachedDeviceState mDeviceState = new CachedDeviceState(false, true); @Test public void testDetailedOff() { @@ -388,43 +385,27 @@ public class BinderCallsStatsTest { } @Test - public void testDataResetWhenInitialStateSet() { + public void testNoDataCollectedBeforeInitialDeviceStateSet() { TestBinderCallsStats bcs = new TestBinderCallsStats(); + bcs.setDeviceState(null); bcs.setDetailedTracking(true); Binder binder = new Binder(); CallSession callSession = bcs.callStarted(binder, 1); bcs.time += 10; bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); - bcs.setInitialState(true, true); + bcs.setDeviceState(mDeviceState.getReadonlyClient()); SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries(); assertEquals(0, uidEntries.size()); } @Test - public void testScreenAndChargerInitialStates() { - TestBinderCallsStats bcs = new TestBinderCallsStats(); - bcs.setDetailedTracking(true); - Binder binder = new Binder(); - bcs.setInitialState(true /** screen iteractive */, false); - - CallSession callSession = bcs.callStarted(binder, 1); - bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); - - List<BinderCallsStats.CallStat> callStatsList = - new ArrayList(bcs.getUidEntries().get(TEST_UID).getCallStatsList()); - assertEquals(true, callStatsList.get(0).screenInteractive); - } - - @Test public void testNoDataCollectedOnCharger() { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); - Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED) - .putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_AC); - bcs.getBroadcastReceiver().onReceive(null, intent); + mDeviceState.setCharging(true); + Binder binder = new Binder(); CallSession callSession = bcs.callStarted(binder, 1); bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); @@ -436,7 +417,7 @@ public class BinderCallsStatsTest { public void testScreenOff() { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); - bcs.getBroadcastReceiver().onReceive(null, new Intent(Intent.ACTION_SCREEN_OFF)); + mDeviceState.setScreenInteractive(false); Binder binder = new Binder(); CallSession callSession = bcs.callStarted(binder, 1); bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); @@ -453,7 +434,7 @@ public class BinderCallsStatsTest { public void testScreenOn() { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); - bcs.getBroadcastReceiver().onReceive(null, new Intent(Intent.ACTION_SCREEN_ON)); + mDeviceState.setScreenInteractive(true); Binder binder = new Binder(); CallSession callSession = bcs.callStarted(binder, 1); bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); @@ -470,9 +451,8 @@ public class BinderCallsStatsTest { public void testOnCharger() { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); - Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED) - .putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_AC); - bcs.getBroadcastReceiver().onReceive(null, intent); + mDeviceState.setCharging(true); + Binder binder = new Binder(); CallSession callSession = bcs.callStarted(binder, 1); bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); @@ -484,9 +464,8 @@ public class BinderCallsStatsTest { public void testOnBattery() { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); - Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED) - .putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_NONE); - bcs.getBroadcastReceiver().onReceive(null, intent); + mDeviceState.setCharging(false); + Binder binder = new Binder(); CallSession callSession = bcs.callStarted(binder, 1); bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); @@ -522,7 +501,6 @@ public class BinderCallsStatsTest { public void testGetExportedStatsWhenDetailedTrackingEnabled() { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); - bcs.getBroadcastReceiver().onReceive(null, new Intent(Intent.ACTION_SCREEN_ON)); Binder binder = new Binder(); CallSession callSession = bcs.callStarted(binder, 1); @@ -561,7 +539,7 @@ public class BinderCallsStatsTest { assertEquals(0, bcs.getExceptionCounts().size()); } - static class TestBinderCallsStats extends BinderCallsStats { + class TestBinderCallsStats extends BinderCallsStats { int callingUid = TEST_UID; long time = 1234; long elapsedTime = 0; @@ -580,6 +558,7 @@ public class BinderCallsStatsTest { } }); setSamplingInterval(1); + setDeviceState(mDeviceState.getReadonlyClient()); } @Override diff --git a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java index 297202bf4d71..0eb3d06e79de 100644 --- a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java @@ -43,6 +43,7 @@ public final class LooperStatsTest { private Handler mHandlerFirst; private Handler mHandlerSecond; private Handler mHandlerAnonymous; + private CachedDeviceState mDeviceState; @Before public void setUp() { @@ -58,6 +59,9 @@ public final class LooperStatsTest { mHandlerAnonymous = new Handler(mThreadFirst.getLooper()) { /* To create an anonymous subclass. */ }; + mDeviceState = new CachedDeviceState(); + mDeviceState.setCharging(false); + mDeviceState.setScreenInteractive(true); } @After @@ -82,6 +86,7 @@ public final class LooperStatsTest { assertThat(entry.handlerClassName).isEqualTo( "com.android.internal.os.LooperStatsTest$TestHandlerFirst"); assertThat(entry.messageName).isEqualTo("0x3e8" /* 1000 in hex */); + assertThat(entry.isInteractive).isEqualTo(true); assertThat(entry.messageCount).isEqualTo(1); assertThat(entry.recordedMessageCount).isEqualTo(1); assertThat(entry.exceptionCount).isEqualTo(0); @@ -108,6 +113,7 @@ public final class LooperStatsTest { assertThat(entry.handlerClassName).isEqualTo( "com.android.internal.os.LooperStatsTest$TestHandlerFirst"); assertThat(entry.messageName).isEqualTo("0x7" /* 7 in hex */); + assertThat(entry.isInteractive).isEqualTo(true); assertThat(entry.messageCount).isEqualTo(0); assertThat(entry.recordedMessageCount).isEqualTo(0); assertThat(entry.exceptionCount).isEqualTo(1); @@ -194,6 +200,70 @@ public final class LooperStatsTest { } @Test + public void testDataNotCollectedBeforeDeviceStateSet() { + TestableLooperStats looperStats = new TestableLooperStats(1, 100); + looperStats.setDeviceState(null); + + Object token1 = looperStats.messageDispatchStarting(); + looperStats.messageDispatched(token1, mHandlerFirst.obtainMessage(1000)); + Object token2 = looperStats.messageDispatchStarting(); + looperStats.dispatchingThrewException(token2, mHandlerFirst.obtainMessage(1000), + new IllegalArgumentException()); + + List<LooperStats.ExportedEntry> entries = looperStats.getEntries(); + assertThat(entries).hasSize(0); + } + + @Test + public void testDataNotCollectedOnCharger() { + TestableLooperStats looperStats = new TestableLooperStats(1, 100); + mDeviceState.setCharging(true); + + Object token1 = looperStats.messageDispatchStarting(); + looperStats.messageDispatched(token1, mHandlerFirst.obtainMessage(1000)); + Object token2 = looperStats.messageDispatchStarting(); + looperStats.dispatchingThrewException(token2, mHandlerFirst.obtainMessage(1000), + new IllegalArgumentException()); + + List<LooperStats.ExportedEntry> entries = looperStats.getEntries(); + assertThat(entries).hasSize(0); + } + + @Test + public void testScreenStateCollected() { + TestableLooperStats looperStats = new TestableLooperStats(1, 100); + + mDeviceState.setScreenInteractive(true); + Object token1 = looperStats.messageDispatchStarting(); + looperStats.messageDispatched(token1, mHandlerFirst.obtainMessage(1000)); + Object token2 = looperStats.messageDispatchStarting(); + looperStats.dispatchingThrewException(token2, mHandlerFirst.obtainMessage(1000), + new IllegalArgumentException()); + + Object token3 = looperStats.messageDispatchStarting(); + // If screen state changed during the call, we take the final state into account. + mDeviceState.setScreenInteractive(false); + looperStats.messageDispatched(token3, mHandlerFirst.obtainMessage(1000)); + Object token4 = looperStats.messageDispatchStarting(); + looperStats.dispatchingThrewException(token4, mHandlerFirst.obtainMessage(1000), + new IllegalArgumentException()); + + List<LooperStats.ExportedEntry> entries = looperStats.getEntries(); + assertThat(entries).hasSize(2); + entries.sort(Comparator.comparing(e -> e.isInteractive)); + + LooperStats.ExportedEntry entry1 = entries.get(0); + assertThat(entry1.isInteractive).isEqualTo(false); + assertThat(entry1.messageCount).isEqualTo(1); + assertThat(entry1.exceptionCount).isEqualTo(1); + + LooperStats.ExportedEntry entry2 = entries.get(1); + assertThat(entry2.isInteractive).isEqualTo(true); + assertThat(entry2.messageCount).isEqualTo(1); + assertThat(entry2.exceptionCount).isEqualTo(1); + } + + @Test public void testMessagesOverSizeCap() { TestableLooperStats looperStats = new TestableLooperStats(2, 1 /* sizeCap */); @@ -281,7 +351,7 @@ public final class LooperStatsTest { } } - private static final class TestableLooperStats extends LooperStats { + private final class TestableLooperStats extends LooperStats { private static final long INITIAL_MICROS = 10001000123L; private int mCount; private long mRealtimeMicros; @@ -291,6 +361,7 @@ public final class LooperStatsTest { TestableLooperStats(int samplingInterval, int sizeCap) { super(samplingInterval, sizeCap); this.mSamplingInterval = samplingInterval; + this.setDeviceState(mDeviceState.getReadonlyClient()); } void tickRealtime(long micros) { diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java index 9a7c345808f2..15673a7d12f9 100644 --- a/services/core/java/com/android/server/BinderCallsStatsService.java +++ b/services/core/java/com/android/server/BinderCallsStatsService.java @@ -33,7 +33,7 @@ import android.util.Slog; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BinderCallsStats; -import com.android.internal.os.BinderInternal; +import com.android.internal.os.CachedDeviceState; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -41,7 +41,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Random; public class BinderCallsStatsService extends Binder { @@ -156,8 +155,10 @@ public class BinderCallsStatsService extends Binder { @Override public void onBootPhase(int phase) { if (SystemService.PHASE_SYSTEM_SERVICES_READY == phase) { + CachedDeviceState.Readonly deviceState = getLocalService( + CachedDeviceState.Readonly.class); mService.systemReady(getContext()); - mBinderCallsStats.systemReady(getContext()); + mBinderCallsStats.setDeviceState(deviceState); } } } diff --git a/services/core/java/com/android/server/CachedDeviceStateService.java b/services/core/java/com/android/server/CachedDeviceStateService.java new file mode 100644 index 000000000000..38269d35583c --- /dev/null +++ b/services/core/java/com/android/server/CachedDeviceStateService.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2018 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.server; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.BatteryManagerInternal; +import android.os.OsProtoEnums; +import android.os.PowerManager; +import android.util.Slog; + +import com.android.internal.os.CachedDeviceState; + +/** + * Tracks changes to the device state (e.g. charging/on battery, screen on/off) to share it with + * the System Server telemetry services. + * + * @hide Only for use within the system server. + */ +public class CachedDeviceStateService extends SystemService { + private static final String TAG = "CachedDeviceStateService"; + private final CachedDeviceState mDeviceState = new CachedDeviceState(); + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case Intent.ACTION_BATTERY_CHANGED: + mDeviceState.setCharging( + intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, + OsProtoEnums.BATTERY_PLUGGED_NONE) + != OsProtoEnums.BATTERY_PLUGGED_NONE); + break; + case Intent.ACTION_SCREEN_ON: + mDeviceState.setScreenInteractive(true); + break; + case Intent.ACTION_SCREEN_OFF: + mDeviceState.setScreenInteractive(false); + break; + } + } + }; + + public CachedDeviceStateService(Context context) { + super(context); + } + + @Override + public void onStart() { + publishLocalService(CachedDeviceState.Readonly.class, mDeviceState.getReadonlyClient()); + } + + @Override + public void onBootPhase(int phase) { + if (SystemService.PHASE_SYSTEM_SERVICES_READY == phase) { + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + getContext().registerReceiver(mBroadcastReceiver, filter); + mDeviceState.setCharging(queryIsCharging()); + mDeviceState.setScreenInteractive(queryScreenInteractive(getContext())); + } + } + + private boolean queryIsCharging() { + final BatteryManagerInternal batteryManager = + LocalServices.getService(BatteryManagerInternal.class); + if (batteryManager == null) { + Slog.wtf(TAG, "BatteryManager null while starting CachedDeviceStateService"); + // Default to true to not collect any data. + return true; + } else { + return batteryManager.getPlugType() != OsProtoEnums.BATTERY_PLUGGED_NONE; + } + } + + private boolean queryScreenInteractive(Context context) { + final PowerManager powerManager = context.getSystemService(PowerManager.class); + if (powerManager == null) { + Slog.wtf(TAG, "PowerManager null while starting CachedDeviceStateService"); + return false; + } else { + return powerManager.isInteractive(); + } + } +} diff --git a/services/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java index 70c2cabee740..23b30cc5b1b0 100644 --- a/services/core/java/com/android/server/LooperStatsService.java +++ b/services/core/java/com/android/server/LooperStatsService.java @@ -31,6 +31,7 @@ import android.util.KeyValueListParser; import android.util.Slog; import com.android.internal.os.BackgroundThread; +import com.android.internal.os.CachedDeviceState; import com.android.internal.os.LooperStats; import com.android.internal.util.DumpUtils; @@ -99,6 +100,7 @@ public class LooperStatsService extends Binder { "thread_name", "handler_class", "message_name", + "is_interactive", "message_count", "recorded_message_count", "total_latency_micros", @@ -108,10 +110,11 @@ public class LooperStatsService extends Binder { "exception_count")); pw.println(header); for (LooperStats.ExportedEntry entry : entries) { - pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", entry.threadName, entry.handlerClassName, - entry.messageName, entry.messageCount, entry.recordedMessageCount, - entry.totalLatencyMicros, entry.maxLatencyMicros, entry.cpuUsageMicros, - entry.maxCpuUsageMicros, entry.exceptionCount); + pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", entry.threadName, + entry.handlerClassName, entry.messageName, entry.isInteractive, + entry.messageCount, entry.recordedMessageCount, entry.totalLatencyMicros, + entry.maxLatencyMicros, entry.cpuUsageMicros, entry.maxCpuUsageMicros, + entry.exceptionCount); } } @@ -155,6 +158,7 @@ public class LooperStatsService extends Binder { Uri settingsUri = Settings.Global.getUriFor(Settings.Global.LOOPER_STATS); getContext().getContentResolver().registerContentObserver( settingsUri, false, mSettingsObserver, UserHandle.USER_SYSTEM); + mStats.setDeviceState(getLocalService(CachedDeviceState.Readonly.class)); } } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 6431344f218b..b9f8fdbc7674 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -743,6 +743,11 @@ public final class SystemServer { traceEnd(); } + // Tracks and caches the device state. + traceBeginAndSlog("StartCachedDeviceStateService"); + mSystemServiceManager.startService(CachedDeviceStateService.class); + traceEnd(); + // Tracks cpu time spent in binder calls traceBeginAndSlog("StartBinderCallsStatsService"); mSystemServiceManager.startService(BinderCallsStatsService.LifeCycle.class); diff --git a/services/tests/servicestests/src/com/android/server/CachedDeviceStateServiceTest.java b/services/tests/servicestests/src/com/android/server/CachedDeviceStateServiceTest.java new file mode 100644 index 000000000000..81107cf2ef4f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/CachedDeviceStateServiceTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2018 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.server; + + +import static org.mockito.Mockito.when; +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.Intent; +import android.os.BatteryManager; +import android.os.BatteryManagerInternal; +import android.os.IPowerManager; +import android.os.OsProtoEnums; +import android.os.PowerManager; +import android.os.RemoteException; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.CachedDeviceState; +import com.android.internal.util.test.BroadcastInterceptingContext; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link CachedDeviceStateService}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class CachedDeviceStateServiceTest { + @Mock private BatteryManagerInternal mBatteryManager; + @Mock private IPowerManager mPowerManager; + private BroadcastInterceptingContext mContext; + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + Context context = InstrumentationRegistry.getContext(); + PowerManager powerManager = new PowerManager(context, mPowerManager, null); + mContext = new BroadcastInterceptingContext(context) { + @Override + public Object getSystemService(String name) { + switch (name) { + case Context.POWER_SERVICE: + return powerManager; + default: + return super.getSystemService(name); + } + } + }; + + LocalServices.addService(BatteryManagerInternal.class, mBatteryManager); + + when(mBatteryManager.getPlugType()).thenReturn(OsProtoEnums.BATTERY_PLUGGED_NONE); + when(mPowerManager.isInteractive()).thenReturn(true); + } + + @After + public void tearDown() { + // Added by the CachedDeviceStateService.onStart(). + LocalServices.removeServiceForTest(CachedDeviceState.Readonly.class); + + // Added in @Before. + LocalServices.removeServiceForTest(BatteryManagerInternal.class); + } + + @Test + public void correctlyReportsScreenInteractive() throws RemoteException { + CachedDeviceStateService service = new CachedDeviceStateService(mContext); + when(mPowerManager.isInteractive()).thenReturn(true); // Screen on. + + service.onStart(); + CachedDeviceState.Readonly deviceState = + LocalServices.getService(CachedDeviceState.Readonly.class); + + // State can be initialized correctly only after PHASE_SYSTEM_SERVICES_READY. + assertThat(deviceState.isScreenInteractive()).isFalse(); + + service.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + + assertThat(deviceState.isScreenInteractive()).isTrue(); + + mContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF)); + assertThat(deviceState.isScreenInteractive()).isFalse(); + + mContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON)); + assertThat(deviceState.isScreenInteractive()).isTrue(); + } + + @Test + public void correctlyReportsCharging() { + CachedDeviceStateService service = new CachedDeviceStateService(mContext); + when(mBatteryManager.getPlugType()).thenReturn(OsProtoEnums.BATTERY_PLUGGED_NONE); + + service.onStart(); + CachedDeviceState.Readonly deviceState = + LocalServices.getService(CachedDeviceState.Readonly.class); + + // State can be initialized correctly only after PHASE_SYSTEM_SERVICES_READY. + assertThat(deviceState.isCharging()).isTrue(); + + service.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + + assertThat(deviceState.isCharging()).isFalse(); + + Intent intentPluggedIn = new Intent(Intent.ACTION_BATTERY_CHANGED); + intentPluggedIn.putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_AC); + mContext.sendBroadcast(intentPluggedIn); + assertThat(deviceState.isCharging()).isTrue(); + + Intent intentUnplugged = new Intent(Intent.ACTION_BATTERY_CHANGED); + intentUnplugged.putExtra(BatteryManager.EXTRA_PLUGGED, OsProtoEnums.BATTERY_PLUGGED_NONE); + mContext.sendBroadcast(intentUnplugged); + assertThat(deviceState.isCharging()).isFalse(); + } +} |