• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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