1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.os; 18 19 import static android.car.PlatformVersion.VERSION_CODES; 20 import static android.car.os.CpuAvailabilityMonitoringConfig.CPUSET_ALL; 21 import static android.car.os.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND; 22 import static android.car.os.CpuAvailabilityMonitoringConfig.IGNORE_PERCENT_LOWER_BOUND; 23 import static android.car.os.CpuAvailabilityMonitoringConfig.IGNORE_PERCENT_UPPER_BOUND; 24 import static android.car.os.CpuAvailabilityMonitoringConfig.TIMEOUT_ACTION_NOTIFICATION; 25 import static android.car.os.CpuAvailabilityMonitoringConfig.TIMEOUT_ACTION_REMOVE; 26 27 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 28 import static com.android.car.internal.util.VersionUtils.assertPlatformVersionAtLeast; 29 30 import android.annotation.NonNull; 31 import android.car.Car; 32 import android.car.builtin.os.BinderHelper; 33 import android.car.builtin.util.Slogf; 34 import android.car.os.CpuAvailabilityMonitoringConfig; 35 import android.car.os.ICarPerformanceService; 36 import android.car.os.ICpuAvailabilityChangeListener; 37 import android.car.os.ThreadPolicyWithPriority; 38 import android.content.Context; 39 import android.os.Binder; 40 import android.os.RemoteCallbackList; 41 import android.os.RemoteException; 42 import android.util.Log; 43 44 import com.android.car.CarLocalServices; 45 import com.android.car.CarLog; 46 import com.android.car.CarServiceBase; 47 import com.android.car.CarServiceUtils; 48 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 49 import com.android.car.internal.util.IndentingPrintWriter; 50 import com.android.car.watchdog.CarWatchdogService; 51 import com.android.internal.util.Preconditions; 52 53 import java.util.Objects; 54 55 /** 56 * Service to implement CarPerformanceManager API. 57 */ 58 public final class CarPerformanceService extends ICarPerformanceService.Stub 59 implements CarServiceBase { 60 static final String TAG = CarLog.tagFor(CarPerformanceService.class); 61 62 private static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG); 63 64 private CarWatchdogService mCarWatchdogService; 65 private final Context mContext; 66 private final RemoteCallbackList<ICpuAvailabilityChangeListener> 67 mCpuAvailabilityChangeListeners = new RemoteCallbackList<>(); 68 CarPerformanceService(Context context)69 public CarPerformanceService(Context context) { 70 mContext = context; 71 } 72 73 @Override init()74 public void init() { 75 mCarWatchdogService = CarLocalServices.getService(CarWatchdogService.class); 76 // TODO(b/217422127): Start performance monitoring on a looper handler. 77 if (DEBUG) { 78 Slogf.d(TAG, "CarPerformanceService is initialized"); 79 } 80 } 81 82 @Override release()83 public void release() { 84 // TODO(b/156400843): Disconnect from the watchdog daemon helper instance. 85 mCpuAvailabilityChangeListeners.kill(); 86 } 87 88 @Override 89 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)90 public void dump(IndentingPrintWriter writer) { 91 writer.printf("*%s*\n", getClass().getSimpleName()); 92 writer.increaseIndent(); 93 writer.println("CPU availability change listeners:"); 94 writer.increaseIndent(); 95 BinderHelper.dumpRemoteCallbackList(mCpuAvailabilityChangeListeners, writer); 96 writer.decreaseIndent(); 97 writer.decreaseIndent(); 98 } 99 100 /** 101 * Adds {@link android.car.performance.ICpuAvailabilityChangeListener} for CPU availability 102 * change notifications. 103 */ 104 @Override addCpuAvailabilityChangeListener(@onNull CpuAvailabilityMonitoringConfig config, @NonNull ICpuAvailabilityChangeListener listener)105 public void addCpuAvailabilityChangeListener(@NonNull CpuAvailabilityMonitoringConfig config, 106 @NonNull ICpuAvailabilityChangeListener listener) { 107 CarServiceUtils.assertPermission(mContext, Car.PERMISSION_COLLECT_CAR_CPU_INFO); 108 Objects.requireNonNull(config, "Configuration must be non-null"); 109 Objects.requireNonNull(listener, "Listener must be non-null"); 110 verifyCpuAvailabilityMonitoringConfig(config); 111 112 int callingPid = Binder.getCallingPid(); 113 int callingUid = Binder.getCallingUid(); 114 CpuAvailabilityChangeListenerInfo listenerInfo = 115 new CpuAvailabilityChangeListenerInfo(config, callingPid, callingUid); 116 if (!mCpuAvailabilityChangeListeners.register(listener, listenerInfo)) { 117 Slogf.w(TAG, 118 "Failed to add CPU availability change listener %s as it is already registered", 119 listenerInfo); 120 throw new IllegalStateException( 121 "Failed to add CPU availability change listener as it is already registered" 122 + listenerInfo); 123 } 124 } 125 126 /** 127 * Removes the previously added {@link android.car.performance.ICpuAvailabilityChangeListener}. 128 */ 129 @Override removeCpuAvailabilityChangeListener(ICpuAvailabilityChangeListener listener)130 public void removeCpuAvailabilityChangeListener(ICpuAvailabilityChangeListener listener) { 131 CarServiceUtils.assertPermission(mContext, Car.PERMISSION_COLLECT_CAR_CPU_INFO); 132 Objects.requireNonNull(listener, "Listener must be non-null"); 133 134 // Note: RemoteCallbackList already handles removing the listener on binderDeath. However, 135 // when any internal state needs to be cleared for a listener beyond just unregistering 136 // the listener, override RemoteCallbackList.onCallbackDied methods to clean up the internal 137 // state on binder death and on removeCpuAvailabilityChangeListener. 138 mCpuAvailabilityChangeListeners.unregister(listener); 139 } 140 141 /** 142 * Sets the thread priority for a specific thread. 143 * 144 * The thread must belong to the calling process. 145 * 146 * @throws IllegalArgumentException If the given policy/priority is not valid. 147 * @throws IllegalStateException If the provided tid does not belong to the calling process. 148 * @throws RemoteException If binder error happens. 149 * @throws SecurityException If permission check failed. 150 * @throws ServiceSpecificException If the operation failed. 151 * @throws UnsupportedOperationException If the current android release doesn't support the API. 152 */ 153 @Override setThreadPriority(int tid, ThreadPolicyWithPriority threadPolicyWithPriority)154 public void setThreadPriority(int tid, ThreadPolicyWithPriority threadPolicyWithPriority) 155 throws RemoteException { 156 assertPlatformVersionAtLeast(VERSION_CODES.TIRAMISU_1); 157 CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MANAGE_THREAD_PRIORITY); 158 159 int pid = Binder.getCallingPid(); 160 int uid = Binder.getCallingUid(); 161 mCarWatchdogService.setThreadPriority(pid, tid, uid, threadPolicyWithPriority.getPolicy(), 162 threadPolicyWithPriority.getPriority()); 163 } 164 165 /** 166 * Gets the thread scheduling policy and priority for the specified thread. 167 * 168 * The thread must belong to the calling process. 169 * 170 * @throws IllegalStateException If the operation failed or the provided tid does not belong to 171 * the calling process. 172 * @throws RemoteException If binder error happens. 173 * @throws SecurityException If permission check failed. 174 * @throws UnsupportedOperationException If the current android release doesn't support the API. 175 */ 176 @Override getThreadPriority(int tid)177 public ThreadPolicyWithPriority getThreadPriority(int tid) throws RemoteException { 178 assertPlatformVersionAtLeast(VERSION_CODES.TIRAMISU_1); 179 CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MANAGE_THREAD_PRIORITY); 180 181 int pid = Binder.getCallingPid(); 182 int uid = Binder.getCallingUid(); 183 try { 184 int[] result = mCarWatchdogService.getThreadPriority(pid, tid, uid); 185 return new ThreadPolicyWithPriority(result[0], result[1]); 186 } catch (IllegalArgumentException e) { 187 throw new IllegalStateException( 188 "current scheduling policy doesn't support getting priority, error: ", e); 189 } 190 } 191 verifyCpuAvailabilityMonitoringConfig(CpuAvailabilityMonitoringConfig config)192 private void verifyCpuAvailabilityMonitoringConfig(CpuAvailabilityMonitoringConfig config) { 193 int lowerBoundPercent = config.getLowerBoundPercent(); 194 int upperBoundPercent = config.getUpperBoundPercent(); 195 196 Preconditions.checkArgument(lowerBoundPercent != IGNORE_PERCENT_LOWER_BOUND 197 || upperBoundPercent == IGNORE_PERCENT_UPPER_BOUND, 198 "Cannot ignore both lower bound percent(%d) and upper bound percent(%d) values", 199 lowerBoundPercent, upperBoundPercent); 200 201 Preconditions.checkArgument(lowerBoundPercent > 0 && upperBoundPercent < 100 202 && lowerBoundPercent < upperBoundPercent, 203 "Must provide valid lower bound percent(%d) and upper bound percent(%d) values", 204 lowerBoundPercent, upperBoundPercent); 205 206 int cpuset = config.getCpuset(); 207 Preconditions.checkArgumentInRange(cpuset, CPUSET_ALL, CPUSET_BACKGROUND, "cpuset"); 208 209 int timeoutAction = config.getTimeoutAction(); 210 Preconditions.checkArgumentInRange(timeoutAction, TIMEOUT_ACTION_NOTIFICATION, 211 TIMEOUT_ACTION_REMOVE, "timeout action"); 212 } 213 214 private static final class CpuAvailabilityChangeListenerInfo { 215 public final CpuAvailabilityMonitoringConfig config; 216 public final int pid; 217 public final int uid; 218 CpuAvailabilityChangeListenerInfo(@onNull CpuAvailabilityMonitoringConfig config, int pid, int uid)219 CpuAvailabilityChangeListenerInfo(@NonNull CpuAvailabilityMonitoringConfig config, int pid, 220 int uid) { 221 this.config = config; 222 this.pid = pid; 223 this.uid = uid; 224 } 225 226 @Override toString()227 public String toString() { 228 return new StringBuilder("CpuAvailabilityChangeListenerInfo{ ") 229 .append(", config = ").append(config) 230 .append(", pid = ").append(pid) 231 .append(", uid = ").append(uid) 232 .append(" }").toString(); 233 } 234 } 235 } 236