Thermally throttle jobs based on priorities.

Since low and min priority jobs can be deferred more, they can be
throttled for thermal reasons earlier.

Bug: 203621220
Test: atest CtsJobSchedulerTestCases:JobThrottlingTest
Test: atest FrameworksMockingServicesTests:ThermalStatusRestrictionTest
Change-Id: Idb77372cea6ac393c4b9d5de4a08d4e86727e5b6
This commit is contained in:
Kweku Adams
2021-11-03 15:18:02 -07:00
parent 00e2e631e5
commit 5c8df3131c
2 changed files with 270 additions and 10 deletions

View File

@@ -16,28 +16,30 @@
package com.android.server.job.restrictions;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.os.PowerManager;
import android.os.PowerManager.OnThermalStatusChangedListener;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.controllers.JobStatus;
public class ThermalStatusRestriction extends JobRestriction {
private static final String TAG = "ThermalStatusRestriction";
/** The threshold at which we start restricting non-EJ jobs. */
private static final int REGULAR_JOB_THRESHOLD = PowerManager.THERMAL_STATUS_SEVERE;
/** The threshold at which we start restricting low and min priority jobs. */
private static final int LOW_PRIORITY_THRESHOLD = PowerManager.THERMAL_STATUS_LIGHT;
/** The threshold at which we start restricting higher priority jobs. */
private static final int HIGHER_PRIORITY_THRESHOLD = PowerManager.THERMAL_STATUS_MODERATE;
/** The lowest threshold at which we start restricting jobs. */
private static final int LOWER_THRESHOLD = REGULAR_JOB_THRESHOLD;
private static final int LOWER_THRESHOLD = LOW_PRIORITY_THRESHOLD;
/** The threshold at which we start restricting ALL jobs. */
private static final int UPPER_THRESHOLD = PowerManager.THERMAL_STATUS_CRITICAL;
private static final int UPPER_THRESHOLD = PowerManager.THERMAL_STATUS_SEVERE;
private volatile int mThermalStatus = PowerManager.THERMAL_STATUS_NONE;
private PowerManager mPowerManager;
public ThermalStatusRestriction(JobSchedulerService service) {
super(service, JobParameters.STOP_REASON_DEVICE_STATE,
JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
@@ -45,9 +47,10 @@ public class ThermalStatusRestriction extends JobRestriction {
@Override
public void onSystemServicesReady() {
mPowerManager = mService.getContext().getSystemService(PowerManager.class);
final PowerManager powerManager =
mService.getTestableContext().getSystemService(PowerManager.class);
// Use MainExecutor
mPowerManager.addThermalStatusListener(new OnThermalStatusChangedListener() {
powerManager.addThermalStatusListener(new OnThermalStatusChangedListener() {
@Override
public void onThermalStatusChanged(int status) {
// This is called on the main thread. Do not do any slow operations in it.
@@ -83,12 +86,28 @@ public class ThermalStatusRestriction extends JobRestriction {
if (mThermalStatus >= UPPER_THRESHOLD) {
return true;
}
if (mThermalStatus >= REGULAR_JOB_THRESHOLD) {
return !job.shouldTreatAsExpeditedJob();
final int priority = job.getEffectivePriority();
if (mThermalStatus >= HIGHER_PRIORITY_THRESHOLD) {
// For moderate throttling, only let expedited jobs and high priority regular jobs that
// are already running run.
return !job.shouldTreatAsExpeditedJob()
&& !(priority == JobInfo.PRIORITY_HIGH
&& mService.isCurrentlyRunningLocked(job));
}
if (mThermalStatus >= LOW_PRIORITY_THRESHOLD) {
// For light throttling, throttle all min priority jobs and all low priority jobs that
// aren't already running.
return (priority == JobInfo.PRIORITY_LOW && !mService.isCurrentlyRunningLocked(job))
|| priority == JobInfo.PRIORITY_MIN;
}
return false;
}
@VisibleForTesting
int getThermalStatus() {
return mThermalStatus;
}
@Override
public void dumpConstants(IndentingPrintWriter pw) {
pw.print("Thermal status: ");

View File

@@ -0,0 +1,241 @@
/*
* Copyright (C) 2021 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.job.restrictions;
import static android.os.PowerManager.THERMAL_STATUS_CRITICAL;
import static android.os.PowerManager.THERMAL_STATUS_EMERGENCY;
import static android.os.PowerManager.THERMAL_STATUS_LIGHT;
import static android.os.PowerManager.THERMAL_STATUS_MODERATE;
import static android.os.PowerManager.THERMAL_STATUS_NONE;
import static android.os.PowerManager.THERMAL_STATUS_SEVERE;
import static android.os.PowerManager.THERMAL_STATUS_SHUTDOWN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.content.Context;
import android.os.PowerManager;
import android.provider.DeviceConfig;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.controllers.JobStatus;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@RunWith(AndroidJUnit4.class)
public class ThermalStatusRestrictionTest {
private static final int CALLING_UID = 1000;
private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final int SOURCE_USER_ID = 0;
private ThermalStatusRestriction mThermalStatusRestriction;
private PowerManager.OnThermalStatusChangedListener mStatusChangedListener;
private MockitoSession mMockingSession;
@Mock
private Context mContext;
@Mock
private JobSchedulerService mJobSchedulerService;
@Before
public void setUp() {
mMockingSession = mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
.spyStatic(DeviceConfig.class)
.mockStatic(LocalServices.class)
.startMocking();
when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
PowerManager powerManager = mock(PowerManager.class);
when(mContext.getSystemService(PowerManager.class)).thenReturn(powerManager);
// Initialize real objects.
// Capture the listeners.
ArgumentCaptor<PowerManager.OnThermalStatusChangedListener> listenerCaptor =
ArgumentCaptor.forClass(PowerManager.OnThermalStatusChangedListener.class);
mThermalStatusRestriction = new ThermalStatusRestriction(mJobSchedulerService);
mThermalStatusRestriction.onSystemServicesReady();
verify(powerManager).addThermalStatusListener(listenerCaptor.capture());
mStatusChangedListener = listenerCaptor.getValue();
}
@After
public void tearDown() {
if (mMockingSession != null) {
mMockingSession.finishMocking();
}
}
@Test
public void testStatusChanges() {
InOrder inOrder = inOrder(mJobSchedulerService);
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_NONE);
inOrder.verify(mJobSchedulerService, never()).onControllerStateChanged(any());
assertEquals(THERMAL_STATUS_NONE, mThermalStatusRestriction.getThermalStatus());
// Moving within LOW and UPPER thresholds
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_LIGHT);
inOrder.verify(mJobSchedulerService).onControllerStateChanged(any());
assertEquals(THERMAL_STATUS_LIGHT, mThermalStatusRestriction.getThermalStatus());
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_MODERATE);
inOrder.verify(mJobSchedulerService).onControllerStateChanged(any());
assertEquals(THERMAL_STATUS_MODERATE, mThermalStatusRestriction.getThermalStatus());
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_SEVERE);
inOrder.verify(mJobSchedulerService).onControllerStateChanged(any());
assertEquals(THERMAL_STATUS_SEVERE, mThermalStatusRestriction.getThermalStatus());
// Changing outside of range
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_CRITICAL);
inOrder.verify(mJobSchedulerService, never()).onControllerStateChanged(any());
assertEquals(THERMAL_STATUS_CRITICAL, mThermalStatusRestriction.getThermalStatus());
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_EMERGENCY);
inOrder.verify(mJobSchedulerService, never()).onControllerStateChanged(any());
assertEquals(THERMAL_STATUS_EMERGENCY, mThermalStatusRestriction.getThermalStatus());
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_SHUTDOWN);
inOrder.verify(mJobSchedulerService, never()).onControllerStateChanged(any());
assertEquals(THERMAL_STATUS_SHUTDOWN, mThermalStatusRestriction.getThermalStatus());
// Cross values we care about
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_NONE);
inOrder.verify(mJobSchedulerService).onControllerStateChanged(any());
assertEquals(THERMAL_STATUS_NONE, mThermalStatusRestriction.getThermalStatus());
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_EMERGENCY);
inOrder.verify(mJobSchedulerService).onControllerStateChanged(any());
assertEquals(THERMAL_STATUS_EMERGENCY, mThermalStatusRestriction.getThermalStatus());
}
@Test
public void testIsJobRestricted() {
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_NONE);
final JobStatus jobMinPriority = createJobStatus("testIsJobRestricted",
createJobBuilder(1).setPriority(JobInfo.PRIORITY_MIN).build());
final JobStatus jobLowPriority = createJobStatus("testIsJobRestricted",
createJobBuilder(2).setPriority(JobInfo.PRIORITY_LOW).build());
final JobStatus jobLowPriorityRunning = createJobStatus("testIsJobRestricted",
createJobBuilder(3).setPriority(JobInfo.PRIORITY_LOW).build());
final JobStatus jobDefaultPriority = createJobStatus("testIsJobRestricted",
createJobBuilder(4).setPriority(JobInfo.PRIORITY_DEFAULT).build());
final JobStatus jobHighPriority = createJobStatus("testIsJobRestricted",
createJobBuilder(5).setPriority(JobInfo.PRIORITY_HIGH).build());
final JobStatus jobHighPriorityRunning = createJobStatus("testIsJobRestricted",
createJobBuilder(6).setPriority(JobInfo.PRIORITY_HIGH).build());
final JobStatus ejDowngraded = createJobStatus("testIsJobRestricted",
createJobBuilder(7).setExpedited(true).build());
final JobStatus ej = spy(createJobStatus("testIsJobRestricted",
createJobBuilder(8).setExpedited(true).build()));
when(ej.shouldTreatAsExpeditedJob()).thenReturn(true);
when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunning)).thenReturn(true);
when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunning))
.thenReturn(true);
assertFalse(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning));
assertFalse(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority));
assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriority));
assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning));
assertFalse(mThermalStatusRestriction.isJobRestricted(ej));
assertFalse(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_LIGHT);
assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning));
assertFalse(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority));
assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriority));
assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning));
assertFalse(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
assertFalse(mThermalStatusRestriction.isJobRestricted(ej));
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_MODERATE);
assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriority));
assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning));
assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
assertFalse(mThermalStatusRestriction.isJobRestricted(ej));
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_SEVERE);
assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriority));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning));
assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
assertTrue(mThermalStatusRestriction.isJobRestricted(ej));
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_CRITICAL);
assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriority));
assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning));
assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
assertTrue(mThermalStatusRestriction.isJobRestricted(ej));
}
private JobInfo.Builder createJobBuilder(int jobId) {
return new JobInfo.Builder(jobId,
new ComponentName(mContext, "ThermalStatusRestrictionTest"));
}
private JobStatus createJobStatus(String testTag, JobInfo jobInfo) {
return JobStatus.createFromJobInfo(
jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
}
}