1 /* 2 * Copyright (C) 2021 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.server.power.hint; 18 19 import static android.os.Flags.adpfUseFmqChannel; 20 21 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; 22 import static com.android.server.power.hint.Flags.adpfSessionTag; 23 import static com.android.server.power.hint.Flags.powerhintThreadCleanup; 24 import static com.android.server.power.hint.Flags.resetOnForkEnabled; 25 26 import android.Manifest; 27 import android.adpf.ISessionManager; 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.app.ActivityManager; 31 import android.app.ActivityManagerInternal; 32 import android.app.StatsManager; 33 import android.app.UidObserver; 34 import android.content.Context; 35 import android.content.pm.ApplicationInfo; 36 import android.content.pm.PackageManager; 37 import android.hardware.power.ChannelConfig; 38 import android.hardware.power.CpuHeadroomParams; 39 import android.hardware.power.CpuHeadroomResult; 40 import android.hardware.power.GpuHeadroomParams; 41 import android.hardware.power.GpuHeadroomResult; 42 import android.hardware.power.IPower; 43 import android.hardware.power.SessionConfig; 44 import android.hardware.power.SessionMode; 45 import android.hardware.power.SessionTag; 46 import android.hardware.power.SupportInfo; 47 import android.hardware.power.WorkDuration; 48 import android.os.Binder; 49 import android.os.CpuHeadroomParamsInternal; 50 import android.os.GpuHeadroomParamsInternal; 51 import android.os.Handler; 52 import android.os.IBinder; 53 import android.os.IHintManager; 54 import android.os.IHintSession; 55 import android.os.Looper; 56 import android.os.Message; 57 import android.os.PerformanceHintManager; 58 import android.os.Process; 59 import android.os.RemoteException; 60 import android.os.ServiceManager; 61 import android.os.ServiceSpecificException; 62 import android.os.SessionCreationConfig; 63 import android.os.SystemProperties; 64 import android.os.UserHandle; 65 import android.system.Os; 66 import android.system.OsConstants; 67 import android.text.TextUtils; 68 import android.util.ArrayMap; 69 import android.util.ArraySet; 70 import android.util.IntArray; 71 import android.util.Slog; 72 import android.util.SparseIntArray; 73 import android.util.StatsEvent; 74 75 import com.android.internal.annotations.GuardedBy; 76 import com.android.internal.annotations.VisibleForTesting; 77 import com.android.internal.util.DumpUtils; 78 import com.android.internal.util.FrameworkStatsLog; 79 import com.android.internal.util.Preconditions; 80 import com.android.server.FgThread; 81 import com.android.server.LocalServices; 82 import com.android.server.ServiceThread; 83 import com.android.server.SystemService; 84 import com.android.server.utils.Slogf; 85 86 import java.io.BufferedReader; 87 import java.io.FileDescriptor; 88 import java.io.FileReader; 89 import java.io.IOException; 90 import java.io.PrintWriter; 91 import java.lang.reflect.Field; 92 import java.util.ArrayList; 93 import java.util.Arrays; 94 import java.util.HashMap; 95 import java.util.LinkedList; 96 import java.util.List; 97 import java.util.Map; 98 import java.util.NoSuchElementException; 99 import java.util.Objects; 100 import java.util.PriorityQueue; 101 import java.util.Set; 102 import java.util.TreeMap; 103 import java.util.concurrent.TimeUnit; 104 import java.util.concurrent.atomic.AtomicBoolean; 105 import java.util.regex.Matcher; 106 import java.util.regex.Pattern; 107 108 /** An hint service implementation that runs in System Server process. */ 109 public final class HintManagerService extends SystemService { 110 private static final String TAG = "HintManagerService"; 111 private static final boolean DEBUG = false; 112 113 private static final int EVENT_CLEAN_UP_UID = 3; 114 @VisibleForTesting static final int CLEAN_UP_UID_DELAY_MILLIS = 1000; 115 116 // example: cpu 2255 34 2290 22625563 6290 127 456 117 private static final Pattern PROC_STAT_CPU_TIME_TOTAL_PATTERN = 118 Pattern.compile("cpu\\s+(?<user>[0-9]+)\\s(?<nice>[0-9]+).+"); 119 120 @VisibleForTesting final long mHintSessionPreferredRate; 121 122 @VisibleForTesting static final int MAX_GRAPHICS_PIPELINE_THREADS_COUNT = 5; 123 private static final int DEFAULT_MAX_CPU_HEADROOM_THREADS_COUNT = 5; 124 private static final int DEFAULT_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS = 50; 125 126 // Multi-level map storing all active AppHintSessions. 127 // First level is keyed by the UID of the client process creating the session. 128 // Second level is keyed by an IBinder passed from client process. This is used to observe 129 // when the process exits. The client generally uses the same IBinder object across multiple 130 // sessions, so the value is a set of AppHintSessions. 131 @GuardedBy("mLock") 132 private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions; 133 134 // Multi-level map storing all the channel binder token death listeners. 135 // First level is keyed by the UID of the client process owning the channel. 136 // Second level is the tgid of the process, which will often just be size one. 137 // Each channel is unique per (tgid, uid) pair, so this map associates each pair with an 138 // object that listens for the death notification of the binder token that was provided by 139 // that client when it created the channel, so we can detect when the client process dies. 140 @GuardedBy("mChannelMapLock") 141 private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap; 142 143 /* 144 * Multi-level map storing the session statistics since last pull from StatsD. 145 * The first level is keyed by the UID of the process owning the session. 146 * The second level is keyed by the tag of the session. The point of separating different 147 * tags is that since different categories (e.g. HWUI vs APP) of the sessions may have different 148 * behaviors. 149 */ 150 @GuardedBy("mSessionSnapshotMapLock") 151 private ArrayMap<Integer, ArrayMap<Integer, AppHintSessionSnapshot>> mSessionSnapshotMap; 152 153 /* 154 * App UID to Thread mapping. 155 * Thread is a sub class bookkeeping TID, thread mode (especially graphics pipeline mode) 156 * This is to bookkeep and track the thread usage. 157 */ 158 @GuardedBy("mThreadsUsageObject") 159 private ArrayMap<Integer, ArraySet<ThreadUsageTracker>> mThreadsUsageMap; 160 161 /** Lock to protect mActiveSessions and the UidObserver. */ 162 private final Object mLock = new Object(); 163 164 /** Lock to protect mChannelMap. */ 165 private final Object mChannelMapLock = new Object(); 166 167 /* 168 * Lock to protect mSessionSnapshotMap. 169 * Nested acquisition of mSessionSnapshotMapLock and mLock should be avoided. 170 * We should grab these separately. 171 * When we need to have nested acquisitions, we should always follow the order of acquiring 172 * mSessionSnapshotMapLock first then mLock. 173 */ 174 private final Object mSessionSnapshotMapLock = new Object(); 175 176 /** Lock to protect mThreadsUsageMap. */ 177 private final Object mThreadsUsageObject = new Object(); 178 179 @GuardedBy("mNonIsolatedTidsLock") 180 private final Map<Integer, Set<Long>> mNonIsolatedTids; 181 182 private final Object mNonIsolatedTidsLock = new Object(); 183 184 @VisibleForTesting final MyUidObserver mUidObserver; 185 186 private final NativeWrapper mNativeWrapper; 187 private final CleanUpHandler mCleanUpHandler; 188 189 private final ActivityManagerInternal mAmInternal; 190 191 private final Context mContext; 192 193 private AtomicBoolean mConfigCreationSupport = new AtomicBoolean(true); 194 195 private final IPower mPowerHal; 196 private int mPowerHalVersion; 197 private SupportInfo mSupportInfo = null; 198 private final PackageManager mPackageManager; 199 200 private boolean mUsesFmq; 201 202 private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint"; 203 private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager"; 204 private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms"; 205 private static final String PROPERTY_CHECK_HEADROOM_TID = "persist.hms.check_headroom_tid"; 206 private static final String PROPERTY_CHECK_HEADROOM_AFFINITY = 207 "persist.hms.check_headroom_affinity"; 208 private static final String PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS = 209 "persist.hms.check_headroom_proc_stat_min_millis"; 210 private static final String PROPERTY_CPU_HEADROOM_TID_MAX_CNT = 211 "persist.hms.cpu_headroom_tid_max_cnt"; 212 private Boolean mFMQUsesIntegratedEventFlag = false; 213 214 private final Object mCpuHeadroomLock = new Object(); 215 @VisibleForTesting 216 final float mJiffyMillis; 217 private final boolean mCheckHeadroomTid; 218 private final boolean mCheckHeadroomAffinity; 219 private final int mCheckHeadroomProcStatMinMillis; 220 private final int mCpuHeadroomMaxTidCnt; 221 @GuardedBy("mCpuHeadroomLock") 222 private long mLastCpuUserModeTimeCheckedMillis = 0; 223 @GuardedBy("mCpuHeadroomLock") 224 private long mLastCpuUserModeJiffies = 0; 225 @GuardedBy("mCpuHeadroomLock") 226 private final Map<Integer, Long> mUidToLastUserModeJiffies; 227 @VisibleForTesting 228 private String mProcStatFilePathOverride = null; 229 @VisibleForTesting 230 private boolean mEnforceCpuHeadroomUserModeCpuTimeCheck = false; 231 232 private ISessionManager mSessionManager; 233 234 // this cache tracks the expiration time of the items and performs cleanup on lookup 235 private static class HeadroomCache<K, V> { 236 final List<HeadroomCacheItem> mItemList; 237 final Map<K, HeadroomCacheItem> mKeyItemMap; 238 final long mItemExpDurationMillis; 239 240 class HeadroomCacheItem { 241 long mExpTime; 242 K mKey; 243 V mValue; 244 HeadroomCacheItem(K k, V v)245 HeadroomCacheItem(K k, V v) { 246 mExpTime = System.currentTimeMillis() + mItemExpDurationMillis; 247 mKey = k; 248 mValue = v; 249 } 250 isExpired()251 boolean isExpired() { 252 return mExpTime <= System.currentTimeMillis(); 253 } 254 } 255 add(K key, V value)256 void add(K key, V value) { 257 if (mKeyItemMap.containsKey(key)) { 258 final HeadroomCacheItem item = mKeyItemMap.get(key); 259 mItemList.remove(item); 260 } 261 final HeadroomCacheItem item = new HeadroomCacheItem(key, value); 262 mItemList.add(item); 263 mKeyItemMap.put(key, item); 264 } 265 get(K key)266 V get(K key) { 267 while (!mItemList.isEmpty() && mItemList.getFirst().isExpired()) { 268 mKeyItemMap.remove(mItemList.removeFirst().mKey); 269 } 270 final HeadroomCacheItem item = mKeyItemMap.get(key); 271 if (item == null) { 272 return null; 273 } 274 return item.mValue; 275 } 276 HeadroomCache(int size, long expDurationMillis)277 HeadroomCache(int size, long expDurationMillis) { 278 mItemList = new LinkedList<>(); 279 mKeyItemMap = new ArrayMap<>(size); 280 mItemExpDurationMillis = expDurationMillis; 281 } 282 } 283 284 @GuardedBy("mCpuHeadroomLock") 285 private final HeadroomCache<CpuHeadroomParams, CpuHeadroomResult> mCpuHeadroomCache; 286 287 private final Object mGpuHeadroomLock = new Object(); 288 289 @GuardedBy("mGpuHeadroomLock") 290 private final HeadroomCache<GpuHeadroomParams, GpuHeadroomResult> mGpuHeadroomCache; 291 292 // these are set to default values in CpuHeadroomParamsInternal and GpuHeadroomParamsInternal 293 private final int mDefaultCpuHeadroomCalculationWindowMillis; 294 private final int mDefaultGpuHeadroomCalculationWindowMillis; 295 296 @VisibleForTesting 297 final IHintManager.Stub mService = new BinderService(); 298 HintManagerService(Context context)299 public HintManagerService(Context context) { 300 this(context, new Injector()); 301 } 302 303 @VisibleForTesting HintManagerService(Context context, Injector injector)304 HintManagerService(Context context, Injector injector) { 305 super(context); 306 mContext = context; 307 if (powerhintThreadCleanup()) { 308 mCleanUpHandler = new CleanUpHandler(createCleanUpThread().getLooper()); 309 mNonIsolatedTids = new HashMap<>(); 310 } else { 311 mCleanUpHandler = null; 312 mNonIsolatedTids = null; 313 } 314 if (adpfSessionTag()) { 315 mPackageManager = mContext.getPackageManager(); 316 } else { 317 mPackageManager = null; 318 } 319 mActiveSessions = new ArrayMap<>(); 320 mChannelMap = new ArrayMap<>(); 321 mSessionSnapshotMap = new ArrayMap<>(); 322 mThreadsUsageMap = new ArrayMap<>(); 323 mNativeWrapper = injector.createNativeWrapper(); 324 mNativeWrapper.halInit(); 325 mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate(); 326 mUidObserver = new MyUidObserver(); 327 mAmInternal = Objects.requireNonNull( 328 LocalServices.getService(ActivityManagerInternal.class)); 329 mPowerHal = injector.createIPower(); 330 mPowerHalVersion = 0; 331 mUsesFmq = false; 332 if (mPowerHal != null) { 333 try { 334 mSupportInfo = getSupportInfo(); 335 } catch (RemoteException e) { 336 throw new IllegalStateException("Could not contact PowerHAL!", e); 337 } 338 } 339 mDefaultCpuHeadroomCalculationWindowMillis = 340 new CpuHeadroomParamsInternal().calculationWindowMillis; 341 mDefaultGpuHeadroomCalculationWindowMillis = 342 new GpuHeadroomParamsInternal().calculationWindowMillis; 343 if (mSupportInfo.headroom.isCpuSupported) { 344 mCpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.cpuMinIntervalMillis); 345 mUidToLastUserModeJiffies = new ArrayMap<>(); 346 long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK); 347 mJiffyMillis = 1000.0f / jiffyHz; 348 mCheckHeadroomTid = SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_TID, true); 349 mCheckHeadroomAffinity = SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_AFFINITY, 350 true); 351 mCheckHeadroomProcStatMinMillis = SystemProperties.getInt( 352 PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS, 353 DEFAULT_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS); 354 mCpuHeadroomMaxTidCnt = Math.min(SystemProperties.getInt( 355 PROPERTY_CPU_HEADROOM_TID_MAX_CNT, DEFAULT_MAX_CPU_HEADROOM_THREADS_COUNT), 356 mSupportInfo.headroom.cpuMaxTidCount); 357 } else { 358 mCpuHeadroomCache = null; 359 mUidToLastUserModeJiffies = null; 360 mJiffyMillis = 0.0f; 361 mCheckHeadroomTid = true; 362 mCheckHeadroomAffinity = true; 363 mCheckHeadroomProcStatMinMillis = 0; 364 mCpuHeadroomMaxTidCnt = 0; 365 } 366 if (mSupportInfo.headroom.isGpuSupported) { 367 mGpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.gpuMinIntervalMillis); 368 } else { 369 mGpuHeadroomCache = null; 370 } 371 } 372 getSupportInfo()373 SupportInfo getSupportInfo() throws RemoteException { 374 try { 375 mPowerHalVersion = mPowerHal.getInterfaceVersion(); 376 if (mPowerHalVersion >= 6) { 377 return mPowerHal.getSupportInfo(); 378 } 379 } catch (RemoteException e) { 380 throw new IllegalStateException("Could not contact PowerHAL!", e); 381 } 382 383 SupportInfo supportInfo = new SupportInfo(); 384 supportInfo.usesSessions = isHintSessionSupported(); 385 // Global boosts & modes aren't currently relevant for HMS clients 386 supportInfo.boosts = 0; 387 supportInfo.modes = 0; 388 supportInfo.sessionHints = 0; 389 supportInfo.sessionModes = 0; 390 supportInfo.sessionTags = 0; 391 392 supportInfo.headroom = new SupportInfo.HeadroomSupportInfo(); 393 supportInfo.headroom.isCpuSupported = false; 394 supportInfo.headroom.isGpuSupported = false; 395 396 supportInfo.compositionData = new SupportInfo.CompositionDataSupportInfo(); 397 if (isHintSessionSupported()) { 398 if (mPowerHalVersion == 4) { 399 // Assume we support the V4 hints & modes unless specified 400 // otherwise; this is to avoid breaking backwards compat 401 // since we historically just assumed they were. 402 supportInfo.sessionHints = 31; // first 5 bits are ones 403 } 404 if (mPowerHalVersion == 5) { 405 // Assume we support the V5 hints & modes unless specified 406 // otherwise; this is to avoid breaking backwards compat 407 // since we historically just assumed they were. 408 409 // Hal V5 has 8 modes, all of which it assumes are supported, 410 // so we represent that by having the first 8 bits set 411 supportInfo.sessionHints = 255; // first 8 bits are ones 412 // Hal V5 has 1 mode which it assumes is supported, so we 413 // represent that by having the first bit set 414 supportInfo.sessionModes = 1; 415 // Hal V5 has 5 tags, all of which it assumes are supported, 416 // so we represent that by having the first 5 bits set 417 supportInfo.sessionTags = 31; 418 } 419 } 420 return supportInfo; 421 } 422 423 @VisibleForTesting setProcStatPathOverride(String override)424 void setProcStatPathOverride(String override) { 425 mProcStatFilePathOverride = override; 426 mEnforceCpuHeadroomUserModeCpuTimeCheck = true; 427 } 428 tooManyPipelineThreads(int uid)429 private boolean tooManyPipelineThreads(int uid) { 430 synchronized (mThreadsUsageObject) { 431 ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(uid); 432 int graphicsPipelineThreadCount = 0; 433 if (threadsSet != null) { 434 // We count the graphics pipeline threads that are 435 // *not* in this session, since those in this session 436 // will be replaced. Then if the count plus the new tids 437 // is over max available graphics pipeline threads we raise 438 // an exception. 439 for (ThreadUsageTracker t : threadsSet) { 440 if (t.isGraphicsPipeline()) { 441 graphicsPipelineThreadCount++; 442 } 443 } 444 if (graphicsPipelineThreadCount > MAX_GRAPHICS_PIPELINE_THREADS_COUNT) { 445 return true; 446 } 447 } 448 return false; 449 } 450 } 451 createCleanUpThread()452 private ServiceThread createCleanUpThread() { 453 final ServiceThread handlerThread = new ServiceThread(TAG, 454 Process.THREAD_PRIORITY_LOWEST, true /*allowIo*/); 455 handlerThread.start(); 456 return handlerThread; 457 } 458 459 @VisibleForTesting 460 static class Injector { createNativeWrapper()461 NativeWrapper createNativeWrapper() { 462 return new NativeWrapper(); 463 } createIPower()464 IPower createIPower() { 465 return IPower.Stub.asInterface( 466 ServiceManager.waitForDeclaredService(IPower.DESCRIPTOR + "/default")); 467 } 468 } 469 470 private static class ThreadUsageTracker { 471 /* 472 * Thread object for tracking thread usage per UID 473 */ 474 int mTid; 475 boolean mIsGraphicsPipeline; 476 ThreadUsageTracker(int tid)477 ThreadUsageTracker(int tid) { 478 mTid = tid; 479 mIsGraphicsPipeline = false; 480 } 481 ThreadUsageTracker(int tid, boolean isGraphicsPipeline)482 ThreadUsageTracker(int tid, boolean isGraphicsPipeline) { 483 mTid = tid; 484 mIsGraphicsPipeline = isGraphicsPipeline; 485 } 486 getTid()487 public int getTid() { 488 return mTid; 489 } 490 isGraphicsPipeline()491 public boolean isGraphicsPipeline() { 492 return mIsGraphicsPipeline; 493 } 494 setGraphicsPipeline(boolean isGraphicsPipeline)495 public void setGraphicsPipeline(boolean isGraphicsPipeline) { 496 mIsGraphicsPipeline = isGraphicsPipeline; 497 } 498 } 499 500 private class AppHintSessionSnapshot { 501 /* 502 * Per-Uid and Per-SessionTag snapshot that tracks metrics including 503 * number of created sessions, number of power efficienct sessions, and 504 * maximum number of threads in a session. 505 * Given that it's Per-SessionTag, each uid can have multiple snapshots. 506 */ 507 int mCurrentSessionCount; 508 int mMaxConcurrentSession; 509 int mMaxThreadCount; 510 int mPowerEfficientSessionCount; 511 int mGraphicsPipelineSessionCount; 512 513 final int mTargetDurationNsCountPQSize = 100; 514 PriorityQueue<TargetDurationRecord> mTargetDurationNsCountPQ; 515 516 class TargetDurationRecord implements Comparable<TargetDurationRecord> { 517 long mTargetDurationNs; 518 long mTimestamp; 519 int mCount; TargetDurationRecord(long targetDurationNs)520 TargetDurationRecord(long targetDurationNs) { 521 mTargetDurationNs = targetDurationNs; 522 mTimestamp = System.currentTimeMillis(); 523 mCount = 1; 524 } 525 526 @Override compareTo(TargetDurationRecord t)527 public int compareTo(TargetDurationRecord t) { 528 int tCount = t.getCount(); 529 int thisCount = this.getCount(); 530 // Here we sort in the order of number of count in ascending order. 531 // i.e. the lowest count of target duration is at the head of the queue. 532 // Upon same count, the tiebreaker is the timestamp, the older item will be at the 533 // front of the queue. 534 if (tCount == thisCount) { 535 return (t.getTimestamp() < this.getTimestamp()) ? 1 : -1; 536 } 537 return (tCount < thisCount) ? 1 : -1; 538 } getTargetDurationNs()539 long getTargetDurationNs() { 540 return mTargetDurationNs; 541 } 542 getCount()543 int getCount() { 544 return mCount; 545 } 546 getTimestamp()547 long getTimestamp() { 548 return mTimestamp; 549 } 550 setCount(int count)551 void setCount(int count) { 552 mCount = count; 553 } 554 setTimestamp()555 void setTimestamp() { 556 mTimestamp = System.currentTimeMillis(); 557 } 558 setTargetDurationNs(long targetDurationNs)559 void setTargetDurationNs(long targetDurationNs) { 560 mTargetDurationNs = targetDurationNs; 561 } 562 } 563 AppHintSessionSnapshot()564 AppHintSessionSnapshot() { 565 mCurrentSessionCount = 0; 566 mMaxConcurrentSession = 0; 567 mMaxThreadCount = 0; 568 mPowerEfficientSessionCount = 0; 569 mGraphicsPipelineSessionCount = 0; 570 mTargetDurationNsCountPQ = new PriorityQueue<>(1); 571 } 572 updateUponSessionCreation(int threadCount, long targetDuration)573 void updateUponSessionCreation(int threadCount, long targetDuration) { 574 mCurrentSessionCount += 1; 575 mMaxConcurrentSession = Math.max(mMaxConcurrentSession, mCurrentSessionCount); 576 mMaxThreadCount = Math.max(mMaxThreadCount, threadCount); 577 updateTargetDurationNs(targetDuration); 578 } 579 updateUponSessionClose()580 void updateUponSessionClose() { 581 mCurrentSessionCount -= 1; 582 } 583 logPowerEfficientSession()584 void logPowerEfficientSession() { 585 mPowerEfficientSessionCount += 1; 586 } 587 logGraphicsPipelineSession()588 void logGraphicsPipelineSession() { 589 mGraphicsPipelineSessionCount += 1; 590 } 591 updateThreadCount(int threadCount)592 void updateThreadCount(int threadCount) { 593 mMaxThreadCount = Math.max(mMaxThreadCount, threadCount); 594 } 595 updateTargetDurationNs(long targetDurationNs)596 void updateTargetDurationNs(long targetDurationNs) { 597 for (TargetDurationRecord t : mTargetDurationNsCountPQ) { 598 if (t.getTargetDurationNs() == targetDurationNs) { 599 t.setCount(t.getCount() + 1); 600 t.setTimestamp(); 601 return; 602 } 603 } 604 if (mTargetDurationNsCountPQ.size() == mTargetDurationNsCountPQSize) { 605 mTargetDurationNsCountPQ.poll(); 606 } 607 mTargetDurationNsCountPQ.add(new TargetDurationRecord(targetDurationNs)); 608 } 609 getMaxConcurrentSession()610 int getMaxConcurrentSession() { 611 return mMaxConcurrentSession; 612 } 613 getMaxThreadCount()614 int getMaxThreadCount() { 615 return mMaxThreadCount; 616 } 617 getPowerEfficientSessionCount()618 int getPowerEfficientSessionCount() { 619 return mPowerEfficientSessionCount; 620 } 621 getGraphicsPipelineSessionCount()622 int getGraphicsPipelineSessionCount() { 623 return mGraphicsPipelineSessionCount; 624 } 625 targetDurationNsList()626 long[] targetDurationNsList() { 627 final int listSize = 5; 628 long[] targetDurations = new long[listSize]; 629 while (mTargetDurationNsCountPQ.size() > listSize) { 630 mTargetDurationNsCountPQ.poll(); 631 } 632 for (int i = 0; i < listSize && !mTargetDurationNsCountPQ.isEmpty(); ++i) { 633 targetDurations[i] = mTargetDurationNsCountPQ.poll().getTargetDurationNs(); 634 } 635 return targetDurations; 636 } 637 } isHintSessionSupported()638 private boolean isHintSessionSupported() { 639 return mHintSessionPreferredRate != -1; 640 } 641 642 @Override onStart()643 public void onStart() { 644 publishBinderService(Context.PERFORMANCE_HINT_SERVICE, mService); 645 } 646 647 @Override onBootPhase(int phase)648 public void onBootPhase(int phase) { 649 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { 650 systemReady(); 651 } 652 if (phase == SystemService.PHASE_BOOT_COMPLETED) { 653 registerStatsCallbacks(); 654 } 655 } 656 systemReady()657 private void systemReady() { 658 Slogf.v(TAG, "Initializing HintManager service..."); 659 try { 660 ActivityManager.getService().registerUidObserver(mUidObserver, 661 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE, 662 ActivityManager.PROCESS_STATE_UNKNOWN, null); 663 } catch (RemoteException e) { 664 // ignored; both services live in system_server 665 } 666 667 } 668 registerStatsCallbacks()669 private void registerStatsCallbacks() { 670 final StatsManager statsManager = mContext.getSystemService(StatsManager.class); 671 statsManager.setPullAtomCallback( 672 FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO, 673 null, // use default PullAtomMetadata values 674 DIRECT_EXECUTOR, 675 this::onPullAtom); 676 statsManager.setPullAtomCallback( 677 FrameworkStatsLog.ADPF_SESSION_SNAPSHOT, 678 null, // use default PullAtomMetadata values 679 DIRECT_EXECUTOR, 680 this::onPullAtom); 681 } 682 onPullAtom(int atomTag, @NonNull List<StatsEvent> data)683 private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) { 684 if (atomTag == FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO) { 685 final boolean isSurfaceFlingerUsingCpuHint = 686 SystemProperties.getBoolean(PROPERTY_SF_ENABLE_CPU_HINT, false); 687 final boolean isHwuiHintManagerEnabled = 688 SystemProperties.getBoolean(PROPERTY_HWUI_ENABLE_HINT_MANAGER, false); 689 690 data.add(FrameworkStatsLog.buildStatsEvent( 691 FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO, 692 isSurfaceFlingerUsingCpuHint, 693 isHwuiHintManagerEnabled, 694 getFmqUsage())); 695 } 696 if (atomTag == FrameworkStatsLog.ADPF_SESSION_SNAPSHOT) { 697 synchronized (mSessionSnapshotMapLock) { 698 for (int i = 0; i < mSessionSnapshotMap.size(); ++i) { 699 final int uid = mSessionSnapshotMap.keyAt(i); 700 final ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = 701 mSessionSnapshotMap.valueAt(i); 702 for (int j = 0; j < sessionSnapshots.size(); ++j) { 703 final int sessionTag = sessionSnapshots.keyAt(j); 704 final AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.valueAt(j); 705 data.add(FrameworkStatsLog.buildStatsEvent( 706 FrameworkStatsLog.ADPF_SESSION_SNAPSHOT, 707 uid, 708 sessionTag, 709 sessionSnapshot.getMaxConcurrentSession(), 710 sessionSnapshot.getMaxThreadCount(), 711 sessionSnapshot.getPowerEfficientSessionCount(), 712 sessionSnapshot.targetDurationNsList() 713 )); 714 } 715 } 716 } 717 restoreSessionSnapshot(); 718 } 719 return android.app.StatsManager.PULL_SUCCESS; 720 } 721 getFmqUsage()722 private int getFmqUsage() { 723 if (mUsesFmq) { 724 return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__SUPPORTED; 725 } else if (mPowerHalVersion < 5) { 726 return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__HAL_VERSION_NOT_MET; 727 } else { 728 return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__UNSUPPORTED; 729 } 730 } 731 restoreSessionSnapshot()732 private void restoreSessionSnapshot() { 733 // clean up snapshot map and rebuild with current active sessions 734 synchronized (mSessionSnapshotMapLock) { 735 mSessionSnapshotMap.clear(); 736 synchronized (mLock) { 737 for (int i = 0; i < mActiveSessions.size(); i++) { 738 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = 739 mActiveSessions.valueAt(i); 740 for (int j = 0; j < tokenMap.size(); j++) { 741 ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j); 742 for (int k = 0; k < sessionSet.size(); ++k) { 743 AppHintSession appHintSession = sessionSet.valueAt(k); 744 final int tag = appHintSession.getTag(); 745 final int uid = appHintSession.getUid(); 746 final long targetDuationNs = 747 appHintSession.getTargetDurationNs(); 748 final int threadCount = appHintSession.getThreadIds().length; 749 ArrayMap<Integer, AppHintSessionSnapshot> snapshots = 750 mSessionSnapshotMap.get(uid); 751 if (snapshots == null) { 752 snapshots = new ArrayMap<>(); 753 mSessionSnapshotMap.put(uid, snapshots); 754 } 755 AppHintSessionSnapshot snapshot = snapshots.get(tag); 756 if (snapshot == null) { 757 snapshot = new AppHintSessionSnapshot(); 758 snapshots.put(tag, snapshot); 759 } 760 snapshot.updateUponSessionCreation(threadCount, 761 targetDuationNs); 762 } 763 } 764 } 765 } 766 } 767 } 768 769 /** 770 * Wrapper around the static-native methods from native. 771 * 772 * This class exists to allow us to mock static native methods in our tests. If mocking static 773 * methods becomes easier than this in the future, we can delete this class. 774 */ 775 @VisibleForTesting 776 public static class NativeWrapper { nativeInit()777 private native void nativeInit(); 778 nativeGetHintSessionPreferredRate()779 private static native long nativeGetHintSessionPreferredRate(); 780 nativeCreateHintSession(int tgid, int uid, int[] tids, long durationNanos)781 private static native long nativeCreateHintSession(int tgid, int uid, int[] tids, 782 long durationNanos); 783 nativeCreateHintSessionWithConfig(int tgid, int uid, int[] tids, long durationNanos, int tag, SessionConfig config)784 private static native long nativeCreateHintSessionWithConfig(int tgid, int uid, int[] tids, 785 long durationNanos, int tag, SessionConfig config); 786 nativePauseHintSession(long halPtr)787 private static native void nativePauseHintSession(long halPtr); 788 nativeResumeHintSession(long halPtr)789 private static native void nativeResumeHintSession(long halPtr); 790 nativeCloseHintSession(long halPtr)791 private static native void nativeCloseHintSession(long halPtr); 792 nativeUpdateTargetWorkDuration( long halPtr, long targetDurationNanos)793 private static native void nativeUpdateTargetWorkDuration( 794 long halPtr, long targetDurationNanos); 795 nativeReportActualWorkDuration( long halPtr, long[] actualDurationNanos, long[] timeStampNanos)796 private static native void nativeReportActualWorkDuration( 797 long halPtr, long[] actualDurationNanos, long[] timeStampNanos); 798 nativeSendHint(long halPtr, int hint)799 private static native void nativeSendHint(long halPtr, int hint); 800 nativeSetThreads(long halPtr, int[] tids)801 private static native void nativeSetThreads(long halPtr, int[] tids); 802 nativeSetMode(long halPtr, int mode, boolean enabled)803 private static native void nativeSetMode(long halPtr, int mode, boolean enabled); 804 nativeReportActualWorkDuration( long halPtr, WorkDuration[] workDurations)805 private static native void nativeReportActualWorkDuration( 806 long halPtr, WorkDuration[] workDurations); 807 808 /** Wrapper for HintManager.nativeInit */ halInit()809 public void halInit() { 810 nativeInit(); 811 } 812 813 /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */ halGetHintSessionPreferredRate()814 public long halGetHintSessionPreferredRate() { 815 return nativeGetHintSessionPreferredRate(); 816 } 817 818 /** Wrapper for HintManager.nativeCreateHintSession */ halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos)819 public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) { 820 return nativeCreateHintSession(tgid, uid, tids, durationNanos); 821 } 822 823 /** Wrapper for HintManager.nativeCreateHintSessionWithConfig */ halCreateHintSessionWithConfig( int tgid, int uid, int[] tids, long durationNanos, int tag, SessionConfig config)824 public long halCreateHintSessionWithConfig( 825 int tgid, int uid, int[] tids, long durationNanos, int tag, SessionConfig config) { 826 return nativeCreateHintSessionWithConfig(tgid, uid, tids, durationNanos, tag, config); 827 } 828 829 /** Wrapper for HintManager.nativePauseHintSession */ halPauseHintSession(long halPtr)830 public void halPauseHintSession(long halPtr) { 831 nativePauseHintSession(halPtr); 832 } 833 834 /** Wrapper for HintManager.nativeResumeHintSession */ halResumeHintSession(long halPtr)835 public void halResumeHintSession(long halPtr) { 836 nativeResumeHintSession(halPtr); 837 } 838 839 /** Wrapper for HintManager.nativeCloseHintSession */ halCloseHintSession(long halPtr)840 public void halCloseHintSession(long halPtr) { 841 nativeCloseHintSession(halPtr); 842 } 843 844 /** Wrapper for HintManager.nativeUpdateTargetWorkDuration */ halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos)845 public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) { 846 nativeUpdateTargetWorkDuration(halPtr, targetDurationNanos); 847 } 848 849 /** Wrapper for HintManager.nativeReportActualWorkDuration */ halReportActualWorkDuration( long halPtr, long[] actualDurationNanos, long[] timeStampNanos)850 public void halReportActualWorkDuration( 851 long halPtr, long[] actualDurationNanos, long[] timeStampNanos) { 852 nativeReportActualWorkDuration(halPtr, actualDurationNanos, 853 timeStampNanos); 854 } 855 856 /** Wrapper for HintManager.sendHint */ halSendHint(long halPtr, int hint)857 public void halSendHint(long halPtr, int hint) { 858 nativeSendHint(halPtr, hint); 859 } 860 861 /** Wrapper for HintManager.nativeSetThreads */ halSetThreads(long halPtr, int[] tids)862 public void halSetThreads(long halPtr, int[] tids) { 863 nativeSetThreads(halPtr, tids); 864 } 865 866 /** Wrapper for HintManager.setMode */ halSetMode(long halPtr, int mode, boolean enabled)867 public void halSetMode(long halPtr, int mode, boolean enabled) { 868 nativeSetMode(halPtr, mode, enabled); 869 } 870 871 /** Wrapper for HintManager.nativeReportActualWorkDuration */ halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations)872 public void halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations) { 873 nativeReportActualWorkDuration(halPtr, workDurations); 874 } 875 } 876 877 @VisibleForTesting 878 final class MyUidObserver extends UidObserver { 879 @GuardedBy("mLock") 880 private final SparseIntArray mProcStatesCache = new SparseIntArray(); isUidForeground(int uid)881 public boolean isUidForeground(int uid) { 882 synchronized (mLock) { 883 return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) 884 <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; 885 } 886 } 887 888 @Override onUidGone(int uid, boolean disabled)889 public void onUidGone(int uid, boolean disabled) { 890 FgThread.getHandler().post(() -> { 891 synchronized (mLock) { 892 mProcStatesCache.delete(uid); 893 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid); 894 if (tokenMap == null) { 895 return; 896 } 897 Slog.d(TAG, "Uid gone for " + uid); 898 for (int i = tokenMap.size() - 1; i >= 0; i--) { 899 // Will remove the session from tokenMap 900 ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i); 901 IntArray closedSessionsForSf = new IntArray(); 902 // Batch the closure call to SF for all the sessions that die 903 for (int j = sessionSet.size() - 1; j >= 0; j--) { 904 AppHintSession session = sessionSet.valueAt(j); 905 if (session.isTrackedBySf()) { 906 // Mark it as untracked so we don't untrack again on close 907 session.setTrackedBySf(false); 908 closedSessionsForSf.add(session.getSessionId()); 909 } 910 } 911 if (mSessionManager != null) { 912 try { 913 mSessionManager.trackedSessionsDied(closedSessionsForSf.toArray()); 914 } catch (RemoteException e) { 915 Slog.e(TAG, "Failed to communicate with SessionManager"); 916 } 917 } 918 for (int j = sessionSet.size() - 1; j >= 0; j--) { 919 sessionSet.valueAt(j).close(); 920 } 921 } 922 } 923 synchronized (mChannelMapLock) { 924 // Clean up the uid's session channels 925 final TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid); 926 if (uidMap != null) { 927 for (Map.Entry<Integer, ChannelItem> entry : uidMap.entrySet()) { 928 entry.getValue().closeChannel(); 929 } 930 mChannelMap.remove(uid); 931 } 932 } 933 synchronized (mCpuHeadroomLock) { 934 if (mSupportInfo.headroom.isCpuSupported && mUidToLastUserModeJiffies != null) { 935 mUidToLastUserModeJiffies.remove(uid); 936 } 937 } 938 }); 939 } 940 941 /** 942 * The IUidObserver callback is called from the system_server, so it'll be a direct function 943 * call from ActivityManagerService. Do not do heavy logic here. 944 */ 945 @Override onUidStateChanged(int uid, int procState, long procStateSeq, int capability)946 public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { 947 FgThread.getHandler().post(() -> { 948 synchronized (mLock) { 949 boolean shouldCleanup = false; 950 if (mPowerHalVersion >= 4 && powerhintThreadCleanup()) { 951 int prevProcState = mProcStatesCache.get(uid, Integer.MAX_VALUE); 952 shouldCleanup = 953 prevProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND 954 && procState 955 > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; 956 } 957 958 mProcStatesCache.put(uid, procState); 959 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid); 960 if (tokenMap == null) { 961 return; 962 } 963 if (shouldCleanup && powerhintThreadCleanup()) { 964 final Message msg = mCleanUpHandler.obtainMessage(EVENT_CLEAN_UP_UID, 965 uid); 966 mCleanUpHandler.sendMessageDelayed(msg, CLEAN_UP_UID_DELAY_MILLIS); 967 Slog.d(TAG, "Sent cleanup message for uid " + uid); 968 } 969 boolean shouldAllowUpdate = isUidForeground(uid); 970 for (int i = tokenMap.size() - 1; i >= 0; i--) { 971 final ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i); 972 for (int j = sessionSet.size() - 1; j >= 0; j--) { 973 sessionSet.valueAt(j).updateHintAllowedByProcState(shouldAllowUpdate); 974 } 975 } 976 } 977 }); 978 } 979 } 980 981 /** 982 * Creates a channel item in the channel map if one does not exist, then returns 983 * the entry in the channel map. 984 */ getOrCreateMappedChannelItem(int tgid, int uid, IBinder token)985 public ChannelItem getOrCreateMappedChannelItem(int tgid, int uid, IBinder token) { 986 synchronized (mChannelMapLock) { 987 if (!mChannelMap.containsKey(uid)) { 988 mChannelMap.put(uid, new TreeMap<Integer, ChannelItem>()); 989 } 990 TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid); 991 if (!map.containsKey(tgid)) { 992 ChannelItem item = new ChannelItem(tgid, uid, token); 993 item.openChannel(); 994 map.put(tgid, item); 995 } 996 return map.get(tgid); 997 } 998 } 999 1000 /** 1001 * This removes an entry in the binder token callback map when a channel is closed, 1002 * and unregisters its callbacks. 1003 */ removeChannelItem(Integer tgid, Integer uid)1004 public void removeChannelItem(Integer tgid, Integer uid) { 1005 synchronized (mChannelMapLock) { 1006 TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid); 1007 if (map != null) { 1008 ChannelItem item = map.get(tgid); 1009 if (item != null) { 1010 item.closeChannel(); 1011 map.remove(tgid); 1012 } 1013 if (map.isEmpty()) { 1014 mChannelMap.remove(uid); 1015 } 1016 } 1017 } 1018 } 1019 1020 /** 1021 * Manages the lifecycle of a single channel. This includes caching the channel descriptor, 1022 * receiving binder token death notifications, and handling cleanup on uid termination. There 1023 * can only be one ChannelItem per (tgid, uid) pair in mChannelMap, and channel creation happens 1024 * when a ChannelItem enters the map, while destruction happens when it leaves the map. 1025 */ 1026 private class ChannelItem implements IBinder.DeathRecipient { 1027 @Override binderDied()1028 public void binderDied() { 1029 removeChannelItem(mTgid, mUid); 1030 } 1031 ChannelItem(int tgid, int uid, IBinder token)1032 ChannelItem(int tgid, int uid, IBinder token) { 1033 this.mTgid = tgid; 1034 this.mUid = uid; 1035 this.mToken = token; 1036 this.mLinked = false; 1037 this.mConfig = null; 1038 } 1039 closeChannel()1040 public void closeChannel() { 1041 if (mLinked) { 1042 mToken.unlinkToDeath(this, 0); 1043 mLinked = false; 1044 } 1045 if (mConfig != null) { 1046 try { 1047 mPowerHal.closeSessionChannel(mTgid, mUid); 1048 } catch (RemoteException e) { 1049 throw new IllegalStateException("Failed to close session channel!", e); 1050 } 1051 mConfig = null; 1052 } 1053 } 1054 openChannel()1055 public void openChannel() { 1056 if (!mLinked) { 1057 try { 1058 mToken.linkToDeath(this, 0); 1059 } catch (RemoteException e) { 1060 throw new IllegalStateException("Client already dead", e); 1061 } 1062 mLinked = true; 1063 } 1064 if (mConfig == null) { 1065 try { 1066 // This method uses PowerHAL directly through the SDK, 1067 // to avoid needing to pass the ChannelConfig through JNI. 1068 mConfig = mPowerHal.getSessionChannel(mTgid, mUid); 1069 } catch (RemoteException e) { 1070 removeChannelItem(mTgid, mUid); 1071 throw new IllegalStateException("Failed to create session channel!", e); 1072 } 1073 } 1074 } 1075 getConfig()1076 ChannelConfig getConfig() { 1077 return mConfig; 1078 } 1079 1080 // To avoid accidental double-linking / unlinking 1081 boolean mLinked; 1082 final int mTgid; 1083 final int mUid; 1084 final IBinder mToken; 1085 ChannelConfig mConfig; 1086 } 1087 1088 final class CleanUpHandler extends Handler { 1089 // status of processed tid used for caching 1090 private static final int TID_NOT_CHECKED = 0; 1091 private static final int TID_PASSED_CHECK = 1; 1092 private static final int TID_EXITED = 2; 1093 CleanUpHandler(Looper looper)1094 CleanUpHandler(Looper looper) { 1095 super(looper); 1096 } 1097 1098 @Override handleMessage(Message msg)1099 public void handleMessage(Message msg) { 1100 if (msg.what == EVENT_CLEAN_UP_UID) { 1101 if (hasEqualMessages(msg.what, msg.obj)) { 1102 removeEqualMessages(msg.what, msg.obj); 1103 final Message newMsg = obtainMessage(msg.what, msg.obj); 1104 sendMessageDelayed(newMsg, CLEAN_UP_UID_DELAY_MILLIS); 1105 Slog.d(TAG, "Duplicate messages for " + msg.obj); 1106 return; 1107 } 1108 Slog.d(TAG, "Starts cleaning for " + msg.obj); 1109 final int uid = (int) msg.obj; 1110 boolean isForeground = mUidObserver.isUidForeground(uid); 1111 // store all sessions in a list and release the global lock 1112 // we don't need to worry about stale data or racing as the session is synchronized 1113 // itself and will perform its own closed status check in setThreads call 1114 final List<AppHintSession> sessions; 1115 synchronized (mLock) { 1116 final ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = 1117 mActiveSessions.get(uid); 1118 if (tokenMap == null || tokenMap.isEmpty()) { 1119 return; 1120 } 1121 sessions = new ArrayList<>(tokenMap.size()); 1122 for (int i = tokenMap.size() - 1; i >= 0; i--) { 1123 final ArraySet<AppHintSession> set = tokenMap.valueAt(i); 1124 for (int j = set.size() - 1; j >= 0; j--) { 1125 sessions.add(set.valueAt(j)); 1126 } 1127 } 1128 } 1129 final long[] durationList = new long[sessions.size()]; 1130 final int[] invalidTidCntList = new int[sessions.size()]; 1131 final SparseIntArray checkedTids = new SparseIntArray(); 1132 int[] totalTidCnt = new int[1]; 1133 for (int i = sessions.size() - 1; i >= 0; i--) { 1134 final AppHintSession session = sessions.get(i); 1135 final long start = System.nanoTime(); 1136 try { 1137 final int invalidCnt = cleanUpSession(session, checkedTids, totalTidCnt); 1138 final long elapsed = System.nanoTime() - start; 1139 invalidTidCntList[i] = invalidCnt; 1140 durationList[i] = elapsed; 1141 } catch (Exception e) { 1142 Slog.e(TAG, "Failed to clean up session " + session.mHalSessionPtr 1143 + " for UID " + session.mUid); 1144 } 1145 } 1146 logCleanUpMetrics(uid, invalidTidCntList, durationList, sessions.size(), 1147 totalTidCnt[0], isForeground); 1148 } 1149 } 1150 logCleanUpMetrics(int uid, int[] count, long[] durationNsList, int sessionCnt, int totalTidCnt, boolean isForeground)1151 private void logCleanUpMetrics(int uid, int[] count, long[] durationNsList, int sessionCnt, 1152 int totalTidCnt, boolean isForeground) { 1153 int maxInvalidTidCnt = Integer.MIN_VALUE; 1154 int totalInvalidTidCnt = 0; 1155 for (int i = 0; i < count.length; i++) { 1156 totalInvalidTidCnt += count[i]; 1157 maxInvalidTidCnt = Math.max(maxInvalidTidCnt, count[i]); 1158 } 1159 if (DEBUG || totalInvalidTidCnt > 0) { 1160 Arrays.sort(durationNsList); 1161 long totalDurationNs = 0; 1162 for (int i = 0; i < durationNsList.length; i++) { 1163 totalDurationNs += durationNsList[i]; 1164 } 1165 int totalDurationUs = (int) TimeUnit.NANOSECONDS.toMicros(totalDurationNs); 1166 int maxDurationUs = (int) TimeUnit.NANOSECONDS.toMicros( 1167 durationNsList[durationNsList.length - 1]); 1168 int minDurationUs = (int) TimeUnit.NANOSECONDS.toMicros(durationNsList[0]); 1169 int avgDurationUs = (int) TimeUnit.NANOSECONDS.toMicros( 1170 totalDurationNs / durationNsList.length); 1171 int th90DurationUs = (int) TimeUnit.NANOSECONDS.toMicros( 1172 durationNsList[(int) (durationNsList.length * 0.9)]); 1173 FrameworkStatsLog.write(FrameworkStatsLog.ADPF_HINT_SESSION_TID_CLEANUP, uid, 1174 totalDurationUs, maxDurationUs, totalTidCnt, totalInvalidTidCnt, 1175 maxInvalidTidCnt, sessionCnt, isForeground); 1176 Slog.w(TAG, 1177 "Invalid tid found for UID" + uid + " in " + totalDurationUs + "us:\n\t" 1178 + "count(" 1179 + " session: " + sessionCnt 1180 + " totalTid: " + totalTidCnt 1181 + " maxInvalidTid: " + maxInvalidTidCnt 1182 + " totalInvalidTid: " + totalInvalidTidCnt + ")\n\t" 1183 + "time per session(" 1184 + " min: " + minDurationUs + "us" 1185 + " max: " + maxDurationUs + "us" 1186 + " avg: " + avgDurationUs + "us" 1187 + " 90%: " + th90DurationUs + "us" + ")\n\t" 1188 + "isForeground: " + isForeground); 1189 } 1190 } 1191 1192 // This will check if each TID currently linked to the session still exists. If it's 1193 // previously registered as not an isolated process, then it will run tkill(pid, tid, 0) to 1194 // verify that it's still running under the same pid. Otherwise, it will run 1195 // kill(tid, 0) to only check if it exists. The result will be cached in checkedTids 1196 // map with tid as the key and checked status as value. cleanUpSession(AppHintSession session, SparseIntArray checkedTids, int[] total)1197 public int cleanUpSession(AppHintSession session, SparseIntArray checkedTids, int[] total) { 1198 if (session.isClosed() || session.isForcePaused()) { 1199 return 0; 1200 } 1201 final int pid = session.mPid; 1202 final int[] tids = session.getTidsInternal(); 1203 if (total != null && total.length == 1) { 1204 total[0] += tids.length; 1205 } 1206 final IntArray filtered = new IntArray(tids.length); 1207 for (int i = 0; i < tids.length; i++) { 1208 int tid = tids[i]; 1209 if (checkedTids.get(tid, 0) != TID_NOT_CHECKED) { 1210 if (checkedTids.get(tid) == TID_PASSED_CHECK) { 1211 filtered.add(tid); 1212 } 1213 continue; 1214 } 1215 // if it was registered as a non-isolated then we perform more restricted check 1216 final boolean isNotIsolated; 1217 synchronized (mNonIsolatedTidsLock) { 1218 isNotIsolated = mNonIsolatedTids.containsKey(tid); 1219 } 1220 try { 1221 if (isNotIsolated) { 1222 Process.checkTid(pid, tid); 1223 } else { 1224 Process.checkPid(tid); 1225 } 1226 checkedTids.put(tid, TID_PASSED_CHECK); 1227 filtered.add(tid); 1228 } catch (NoSuchElementException e) { 1229 checkedTids.put(tid, TID_EXITED); 1230 } catch (Exception e) { 1231 Slog.w(TAG, "Unexpected exception when checking TID " + tid + " under PID " 1232 + pid + "(isolated: " + !isNotIsolated + ")", e); 1233 // if anything unexpected happens then we keep it, but don't store it as checked 1234 filtered.add(tid); 1235 } 1236 } 1237 final int diff = tids.length - filtered.size(); 1238 if (diff > 0) { 1239 synchronized (session) { 1240 // in case thread list is updated during the cleanup then we skip updating 1241 // the session but just return the number for reporting purpose 1242 final int[] newTids = session.getTidsInternal(); 1243 if (newTids.length != tids.length) { 1244 Slog.d(TAG, "Skipped cleaning up the session as new tids are added"); 1245 return diff; 1246 } 1247 Arrays.sort(newTids); 1248 Arrays.sort(tids); 1249 if (!Arrays.equals(newTids, tids)) { 1250 Slog.d(TAG, "Skipped cleaning up the session as new tids are updated"); 1251 return diff; 1252 } 1253 Slog.d(TAG, "Cleaned up " + diff + " invalid tids for session " 1254 + session.mHalSessionPtr + " with UID " + session.mUid + "\n\t" 1255 + "before: " + Arrays.toString(tids) + "\n\t" 1256 + "after: " + filtered); 1257 final int[] filteredTids = filtered.toArray(); 1258 if (filteredTids.length == 0) { 1259 session.mShouldForcePause = true; 1260 if (session.mUpdateAllowedByProcState) { 1261 session.pause(); 1262 } 1263 } else { 1264 session.setThreadsInternal(filteredTids, false); 1265 } 1266 } 1267 } 1268 return diff; 1269 } 1270 } 1271 1272 @VisibleForTesting getBinderServiceInstance()1273 IHintManager.Stub getBinderServiceInstance() { 1274 return mService; 1275 } 1276 1277 @VisibleForTesting hasChannel(int tgid, int uid)1278 Boolean hasChannel(int tgid, int uid) { 1279 synchronized (mChannelMapLock) { 1280 TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid); 1281 if (uidMap != null) { 1282 ChannelItem item = uidMap.get(tgid); 1283 return item != null; 1284 } 1285 return false; 1286 } 1287 } 1288 1289 // returns the first invalid tid or null if not found checkTidValid(int uid, int tgid, int[] tids, IntArray nonIsolated)1290 private Integer checkTidValid(int uid, int tgid, int[] tids, IntArray nonIsolated) { 1291 // Make sure all tids belongs to the same UID (including isolated UID), 1292 // tids can belong to different application processes. 1293 List<Integer> isolatedPids = null; 1294 for (int i = 0; i < tids.length; i++) { 1295 int tid = tids[i]; 1296 final String[] procStatusKeys = new String[]{ 1297 "Uid:", 1298 "Tgid:" 1299 }; 1300 long[] output = new long[procStatusKeys.length]; 1301 Process.readProcLines("/proc/" + tid + "/status", procStatusKeys, output); 1302 int uidOfThreadId = (int) output[0]; 1303 int pidOfThreadId = (int) output[1]; 1304 1305 // use PID check for non-isolated processes 1306 if (nonIsolated != null && pidOfThreadId == tgid) { 1307 nonIsolated.add(tid); 1308 continue; 1309 } 1310 // use UID check for isolated processes. 1311 if (uidOfThreadId == uid) { 1312 continue; 1313 } 1314 // Only call into AM if the tid is either isolated or invalid 1315 if (isolatedPids == null) { 1316 // To avoid deadlock, do not call into AMS if the call is from system. 1317 if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) { 1318 return tid; 1319 } 1320 isolatedPids = mAmInternal.getIsolatedProcesses(uid); 1321 if (isolatedPids == null) { 1322 return tid; 1323 } 1324 } 1325 if (isolatedPids.contains(pidOfThreadId)) { 1326 continue; 1327 } 1328 return tid; 1329 } 1330 return null; 1331 } 1332 formatTidCheckErrMsg(int callingUid, int[] tids, Integer invalidTid)1333 private String formatTidCheckErrMsg(int callingUid, int[] tids, Integer invalidTid) { 1334 return "Tid" + invalidTid + " from list " + Arrays.toString(tids) 1335 + " doesn't belong to the calling application " + callingUid; 1336 } 1337 contains(final int[] array, final int target)1338 private boolean contains(final int[] array, final int target) { 1339 for (int element : array) { 1340 if (element == target) { 1341 return true; 1342 } 1343 } 1344 return false; 1345 } 1346 1347 @VisibleForTesting 1348 final class BinderService extends IHintManager.Stub { 1349 @Override createHintSessionWithConfig( @onNull IBinder token, @SessionTag int tag, SessionCreationConfig creationConfig, SessionConfig config)1350 public IHintManager.SessionCreationReturn createHintSessionWithConfig( 1351 @NonNull IBinder token, @SessionTag int tag, 1352 SessionCreationConfig creationConfig, SessionConfig config) { 1353 if (!isHintSessionSupported()) { 1354 throw new UnsupportedOperationException("PowerHintSessions are not supported!"); 1355 } 1356 1357 java.util.Objects.requireNonNull(token); 1358 java.util.Objects.requireNonNull(creationConfig.tids); 1359 1360 final int[] tids = creationConfig.tids; 1361 Preconditions.checkArgument(tids.length != 0, "tids should" 1362 + " not be empty."); 1363 1364 1365 final int callingUid = Binder.getCallingUid(); 1366 final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); 1367 final long identity = Binder.clearCallingIdentity(); 1368 final long durationNanos = creationConfig.targetWorkDurationNanos; 1369 1370 boolean isGraphicsPipeline = false; 1371 boolean isAutoTimed = false; 1372 if (creationConfig.modesToEnable != null) { 1373 for (int mode : creationConfig.modesToEnable) { 1374 if (mode == SessionMode.GRAPHICS_PIPELINE) { 1375 isGraphicsPipeline = true; 1376 } 1377 if (mode == SessionMode.AUTO_CPU || mode == SessionMode.AUTO_GPU) { 1378 isAutoTimed = true; 1379 } 1380 } 1381 } 1382 1383 if (isAutoTimed) { 1384 Preconditions.checkArgument(isGraphicsPipeline, 1385 "graphics pipeline mode not enabled for an automatically timed session"); 1386 } 1387 1388 try { 1389 final IntArray nonIsolated = powerhintThreadCleanup() ? new IntArray(tids.length) 1390 : null; 1391 final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids, 1392 nonIsolated); 1393 if (invalidTid != null) { 1394 final String errMsg = formatTidCheckErrMsg(callingUid, tids, invalidTid); 1395 Slogf.w(TAG, errMsg); 1396 throw new SecurityException(errMsg); 1397 } 1398 if (resetOnForkEnabled()) { 1399 try { 1400 for (int tid : tids) { 1401 int policy = Process.getThreadScheduler(tid); 1402 // If the thread is not using the default scheduling policy (SCHED_OTHER), 1403 // we don't change it. 1404 if (policy != Process.SCHED_OTHER) { 1405 continue; 1406 } 1407 // set the SCHED_RESET_ON_FORK flag. 1408 int prio = Process.getThreadPriority(tid); 1409 Process.setThreadScheduler(tid, Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK, 0); 1410 Process.setThreadPriority(tid, prio); 1411 } 1412 } catch (Exception e) { 1413 Slog.e(TAG, "Failed to set SCHED_RESET_ON_FORK for tids " 1414 + Arrays.toString(tids), e); 1415 } 1416 } 1417 1418 if (adpfSessionTag() && tag == SessionTag.APP) { 1419 // If the category of the app is a game, 1420 // we change the session tag to SessionTag.GAME 1421 // as it was not previously classified 1422 switch (getUidApplicationCategory(callingUid)) { 1423 case ApplicationInfo.CATEGORY_GAME -> tag = SessionTag.GAME; 1424 case ApplicationInfo.CATEGORY_UNDEFINED -> 1425 // We use CATEGORY_UNDEFINED to filter the case when 1426 // PackageManager.NameNotFoundException is caught, 1427 // which should not happen. 1428 tag = SessionTag.APP; 1429 default -> tag = SessionTag.APP; 1430 } 1431 } 1432 config.id = -1; 1433 Long halSessionPtr = null; 1434 if (mConfigCreationSupport.get()) { 1435 try { 1436 halSessionPtr = mNativeWrapper.halCreateHintSessionWithConfig( 1437 callingTgid, callingUid, tids, durationNanos, tag, config); 1438 } catch (UnsupportedOperationException e) { 1439 mConfigCreationSupport.set(false); 1440 } catch (IllegalStateException e) { 1441 Slog.e("createHintSessionWithConfig failed: ", e.getMessage()); 1442 throw new IllegalStateException( 1443 "createHintSessionWithConfig failed: " + e.getMessage()); 1444 } 1445 } 1446 1447 if (halSessionPtr == null) { 1448 try { 1449 halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, 1450 callingUid, tids, durationNanos); 1451 } catch (UnsupportedOperationException e) { 1452 Slog.w("createHintSession unsupported: ", e.getMessage()); 1453 throw new UnsupportedOperationException( 1454 "createHintSession unsupported: " + e.getMessage()); 1455 } catch (IllegalStateException e) { 1456 Slog.e("createHintSession failed: ", e.getMessage()); 1457 throw new IllegalStateException( 1458 "createHintSession failed: " + e.getMessage()); 1459 } 1460 } 1461 1462 if (powerhintThreadCleanup()) { 1463 synchronized (mNonIsolatedTidsLock) { 1464 for (int i = nonIsolated.size() - 1; i >= 0; i--) { 1465 mNonIsolatedTids.putIfAbsent(nonIsolated.get(i), new ArraySet<>()); 1466 mNonIsolatedTids.get(nonIsolated.get(i)).add(halSessionPtr); 1467 } 1468 } 1469 } 1470 1471 final long sessionIdForTracing = config.id != -1 ? config.id : halSessionPtr; 1472 logPerformanceHintSessionAtom( 1473 callingUid, sessionIdForTracing, durationNanos, tids, tag); 1474 1475 synchronized (mSessionSnapshotMapLock) { 1476 // Update session snapshot upon session creation 1477 mSessionSnapshotMap.computeIfAbsent(callingUid, k -> new ArrayMap<>()) 1478 .computeIfAbsent(tag, k -> new AppHintSessionSnapshot()) 1479 .updateUponSessionCreation(tids.length, durationNanos); 1480 } 1481 AppHintSession hs = null; 1482 synchronized (mLock) { 1483 Integer configId = null; 1484 if (config.id != -1) { 1485 configId = new Integer((int) config.id); 1486 } 1487 hs = new AppHintSession(callingUid, callingTgid, tag, tids, 1488 token, halSessionPtr, durationNanos, configId); 1489 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = 1490 mActiveSessions.get(callingUid); 1491 if (tokenMap == null) { 1492 tokenMap = new ArrayMap<>(1); 1493 mActiveSessions.put(callingUid, tokenMap); 1494 } 1495 ArraySet<AppHintSession> sessionSet = tokenMap.get(token); 1496 if (sessionSet == null) { 1497 sessionSet = new ArraySet<>(1); 1498 tokenMap.put(token, sessionSet); 1499 } 1500 sessionSet.add(hs); 1501 mUsesFmq = mUsesFmq || hasChannel(callingTgid, callingUid); 1502 } 1503 1504 if (hs != null) { 1505 if (creationConfig.modesToEnable != null) { 1506 for (int sessionMode : creationConfig.modesToEnable) { 1507 hs.setMode(sessionMode, true); 1508 } 1509 } 1510 1511 if (creationConfig.layerTokens != null 1512 && creationConfig.layerTokens.length > 0) { 1513 hs.associateToLayers(creationConfig.layerTokens); 1514 } 1515 1516 synchronized (mThreadsUsageObject) { 1517 mThreadsUsageMap.computeIfAbsent(callingUid, k -> new ArraySet<>()); 1518 ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid); 1519 for (int i = 0; i < tids.length; ++i) { 1520 threadsSet.add(new ThreadUsageTracker(tids[i], isGraphicsPipeline)); 1521 } 1522 } 1523 } 1524 1525 IHintManager.SessionCreationReturn out = new IHintManager.SessionCreationReturn(); 1526 out.pipelineThreadLimitExceeded = tooManyPipelineThreads(callingUid); 1527 out.session = hs; 1528 return out; 1529 } finally { 1530 Binder.restoreCallingIdentity(identity); 1531 } 1532 } 1533 1534 @Override getSessionChannel(IBinder token)1535 public @Nullable ChannelConfig getSessionChannel(IBinder token) { 1536 if (mPowerHalVersion < 5 || !adpfUseFmqChannel() 1537 || mFMQUsesIntegratedEventFlag) { 1538 return null; 1539 } 1540 java.util.Objects.requireNonNull(token); 1541 final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); 1542 final int callingUid = Binder.getCallingUid(); 1543 ChannelItem item = getOrCreateMappedChannelItem(callingTgid, callingUid, token); 1544 // FMQ V1 requires a separate event flag to be passed, and the default no-op 1545 // implmenentation in PowerHAL does not return such a shared flag. This helps 1546 // avoid using the FMQ on a default impl that does not support it. 1547 if (item.getConfig().eventFlagDescriptor == null) { 1548 mFMQUsesIntegratedEventFlag = true; 1549 closeSessionChannel(); 1550 return null; 1551 } 1552 return item.getConfig(); 1553 }; 1554 1555 @Override closeSessionChannel()1556 public void closeSessionChannel() { 1557 if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) { 1558 return; 1559 } 1560 final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); 1561 final int callingUid = Binder.getCallingUid(); 1562 removeChannelItem(callingTgid, callingUid); 1563 }; 1564 getMaxGraphicsPipelineThreadsCount()1565 public int getMaxGraphicsPipelineThreadsCount() { 1566 return MAX_GRAPHICS_PIPELINE_THREADS_COUNT; 1567 } 1568 1569 @Override setHintSessionThreads(@onNull IHintSession hintSession, @NonNull int[] tids)1570 public void setHintSessionThreads(@NonNull IHintSession hintSession, @NonNull int[] tids) { 1571 AppHintSession appHintSession = (AppHintSession) hintSession; 1572 appHintSession.setThreads(tids); 1573 } 1574 1575 @Override getHintSessionThreadIds(@onNull IHintSession hintSession)1576 public int[] getHintSessionThreadIds(@NonNull IHintSession hintSession) { 1577 AppHintSession appHintSession = (AppHintSession) hintSession; 1578 return appHintSession.getThreadIds(); 1579 } 1580 1581 @Override getCpuHeadroom(@onNull CpuHeadroomParamsInternal params)1582 public CpuHeadroomResult getCpuHeadroom(@NonNull CpuHeadroomParamsInternal params) { 1583 if (!mSupportInfo.headroom.isCpuSupported) { 1584 throw new UnsupportedOperationException(); 1585 } 1586 checkCpuHeadroomParams(params); 1587 final int uid = Binder.getCallingUid(); 1588 final int pid = Binder.getCallingPid(); 1589 final CpuHeadroomParams halParams = new CpuHeadroomParams(); 1590 halParams.tids = new int[]{pid}; 1591 halParams.calculationType = params.calculationType; 1592 halParams.calculationWindowMillis = params.calculationWindowMillis; 1593 if (params.usesDeviceHeadroom) { 1594 halParams.tids = new int[]{}; 1595 } else if (params.tids != null && params.tids.length > 0) { 1596 if (UserHandle.getAppId(uid) != Process.SYSTEM_UID && mCheckHeadroomTid) { 1597 final int tgid = Process.getThreadGroupLeader(Binder.getCallingPid()); 1598 for (int tid : params.tids) { 1599 if (Process.getThreadGroupLeader(tid) != tgid) { 1600 throw new SecurityException("TID " + tid 1601 + " doesn't belong to the calling process with pid " 1602 + tgid); 1603 } 1604 } 1605 } 1606 if (mCheckHeadroomAffinity && params.tids.length > 1) { 1607 checkThreadAffinityForTids(params.tids); 1608 } 1609 halParams.tids = params.tids; 1610 } 1611 synchronized (mCpuHeadroomLock) { 1612 final CpuHeadroomResult res = mCpuHeadroomCache.get(halParams); 1613 if (res != null) return res; 1614 } 1615 final boolean shouldCheckUserModeCpuTime = 1616 mEnforceCpuHeadroomUserModeCpuTimeCheck 1617 || (UserHandle.getAppId(uid) != Process.SYSTEM_UID 1618 && mContext.checkCallingPermission( 1619 Manifest.permission.DEVICE_POWER) 1620 == PackageManager.PERMISSION_DENIED); 1621 1622 if (shouldCheckUserModeCpuTime) { 1623 synchronized (mCpuHeadroomLock) { 1624 if (!checkPerUidUserModeCpuTimeElapsedLocked(uid)) { 1625 return null; 1626 } 1627 } 1628 } 1629 // return from HAL directly 1630 try { 1631 final CpuHeadroomResult result = mPowerHal.getCpuHeadroom(halParams); 1632 if (result == null) { 1633 Slog.wtf(TAG, "CPU headroom from Power HAL is invalid"); 1634 return null; 1635 } 1636 synchronized (mCpuHeadroomLock) { 1637 mCpuHeadroomCache.add(halParams, result); 1638 } 1639 if (shouldCheckUserModeCpuTime) { 1640 synchronized (mCpuHeadroomLock) { 1641 mUidToLastUserModeJiffies.put(uid, mLastCpuUserModeJiffies); 1642 } 1643 } 1644 return result; 1645 } catch (RemoteException e) { 1646 Slog.e(TAG, "Failed to get CPU headroom from Power HAL", e); 1647 return null; 1648 } 1649 } checkThreadAffinityForTids(int[] tids)1650 private void checkThreadAffinityForTids(int[] tids) { 1651 long[] reference = null; 1652 for (int tid : tids) { 1653 long[] affinity; 1654 try { 1655 affinity = Process.getSchedAffinity(tid); 1656 } catch (Exception e) { 1657 Slog.e(TAG, "Failed to get affinity " + tid, e); 1658 throw new IllegalStateException("Could not check affinity for tid " + tid); 1659 } 1660 if (reference == null) { 1661 reference = affinity; 1662 } else if (!Arrays.equals(reference, affinity)) { 1663 Slog.d(TAG, "Thread affinity is different: tid " 1664 + tids[0] + "->" + Arrays.toString(reference) + ", tid " 1665 + tid + "->" + Arrays.toString(affinity)); 1666 throw new IllegalStateException("Thread affinity is not the same for tids " 1667 + Arrays.toString(tids)); 1668 } 1669 } 1670 } 1671 1672 // check if there has been sufficient user mode cpu time elapsed since last call 1673 // from the same uid 1674 @GuardedBy("mCpuHeadroomLock") checkPerUidUserModeCpuTimeElapsedLocked(int uid)1675 private boolean checkPerUidUserModeCpuTimeElapsedLocked(int uid) { 1676 // skip checking proc stat if it's within mCheckHeadroomProcStatMinMillis 1677 if (System.currentTimeMillis() - mLastCpuUserModeTimeCheckedMillis 1678 > mCheckHeadroomProcStatMinMillis) { 1679 try { 1680 mLastCpuUserModeJiffies = getUserModeJiffies(); 1681 } catch (Exception e) { 1682 Slog.e(TAG, "Failed to get user mode CPU time", e); 1683 return false; 1684 } 1685 mLastCpuUserModeTimeCheckedMillis = System.currentTimeMillis(); 1686 } 1687 if (mUidToLastUserModeJiffies.containsKey(uid)) { 1688 long uidLastUserModeJiffies = mUidToLastUserModeJiffies.get(uid); 1689 if ((mLastCpuUserModeJiffies - uidLastUserModeJiffies) * mJiffyMillis 1690 < mSupportInfo.headroom.cpuMinIntervalMillis) { 1691 Slog.w(TAG, "UID " + uid + " is requesting CPU headroom too soon"); 1692 Slog.d(TAG, "UID " + uid + " last request at " 1693 + uidLastUserModeJiffies * mJiffyMillis 1694 + "ms with device currently at " 1695 + mLastCpuUserModeJiffies * mJiffyMillis 1696 + "ms, the interval: " 1697 + (mLastCpuUserModeJiffies - uidLastUserModeJiffies) 1698 * mJiffyMillis + "ms is less than require minimum interval " 1699 + mSupportInfo.headroom.cpuMinIntervalMillis + "ms"); 1700 return false; 1701 } 1702 } 1703 return true; 1704 } 1705 checkCpuHeadroomParams(CpuHeadroomParamsInternal params)1706 private void checkCpuHeadroomParams(CpuHeadroomParamsInternal params) { 1707 boolean calculationTypeMatched = false; 1708 try { 1709 for (final Field field : 1710 CpuHeadroomParams.CalculationType.class.getDeclaredFields()) { 1711 if (field.getType() == byte.class) { 1712 byte value = field.getByte(null); 1713 if (value == params.calculationType) { 1714 calculationTypeMatched = true; 1715 break; 1716 } 1717 } 1718 } 1719 } catch (IllegalAccessException e) { 1720 Slog.wtf(TAG, "Checking the calculation type was unexpectedly not allowed"); 1721 } 1722 if (!calculationTypeMatched) { 1723 throw new IllegalArgumentException( 1724 "Unknown CPU headroom calculation type " + (int) params.calculationType); 1725 } 1726 if (params.calculationWindowMillis < mSupportInfo.headroom.cpuMinCalculationWindowMillis 1727 || params.calculationWindowMillis 1728 > mSupportInfo.headroom.cpuMaxCalculationWindowMillis) { 1729 throw new IllegalArgumentException( 1730 "Invalid CPU headroom calculation window, expected [" 1731 + mSupportInfo.headroom.cpuMinCalculationWindowMillis 1732 + ", " 1733 + mSupportInfo.headroom.cpuMaxCalculationWindowMillis 1734 + "] but got " 1735 + params.calculationWindowMillis); 1736 } 1737 if (!params.usesDeviceHeadroom) { 1738 if (params.tids != null && params.tids.length > mCpuHeadroomMaxTidCnt) { 1739 throw new IllegalArgumentException( 1740 "More than " + mCpuHeadroomMaxTidCnt + " TIDs requested: " 1741 + params.tids.length); 1742 } 1743 } 1744 } 1745 1746 @Override getGpuHeadroom(@onNull GpuHeadroomParamsInternal params)1747 public GpuHeadroomResult getGpuHeadroom(@NonNull GpuHeadroomParamsInternal params) { 1748 if (!mSupportInfo.headroom.isGpuSupported) { 1749 throw new UnsupportedOperationException(); 1750 } 1751 checkGpuHeadroomParams(params); 1752 final GpuHeadroomParams halParams = new GpuHeadroomParams(); 1753 halParams.calculationType = params.calculationType; 1754 halParams.calculationWindowMillis = params.calculationWindowMillis; 1755 synchronized (mGpuHeadroomLock) { 1756 final GpuHeadroomResult res = mGpuHeadroomCache.get(halParams); 1757 if (res != null) return res; 1758 } 1759 // return from HAL directly 1760 try { 1761 final GpuHeadroomResult headroom = mPowerHal.getGpuHeadroom(halParams); 1762 if (headroom == null) { 1763 Slog.wtf(TAG, "GPU headroom from Power HAL is invalid"); 1764 return null; 1765 } 1766 synchronized (mGpuHeadroomLock) { 1767 mGpuHeadroomCache.add(halParams, headroom); 1768 } 1769 return headroom; 1770 } catch (RemoteException e) { 1771 Slog.e(TAG, "Failed to get GPU headroom from Power HAL", e); 1772 return null; 1773 } 1774 } 1775 checkGpuHeadroomParams(GpuHeadroomParamsInternal params)1776 private void checkGpuHeadroomParams(GpuHeadroomParamsInternal params) { 1777 boolean calculationTypeMatched = false; 1778 try { 1779 for (final Field field : 1780 GpuHeadroomParams.CalculationType.class.getDeclaredFields()) { 1781 if (field.getType() == byte.class) { 1782 byte value = field.getByte(null); 1783 if (value == params.calculationType) { 1784 calculationTypeMatched = true; 1785 break; 1786 } 1787 } 1788 } 1789 } catch (IllegalAccessException e) { 1790 Slog.wtf(TAG, "Checking the calculation type was unexpectedly not allowed"); 1791 } 1792 if (!calculationTypeMatched) { 1793 throw new IllegalArgumentException( 1794 "Unknown GPU headroom calculation type " + (int) params.calculationType); 1795 } 1796 if (params.calculationWindowMillis < mSupportInfo.headroom.gpuMinCalculationWindowMillis 1797 || params.calculationWindowMillis 1798 > mSupportInfo.headroom.gpuMaxCalculationWindowMillis) { 1799 throw new IllegalArgumentException( 1800 "Invalid GPU headroom calculation window, expected [" 1801 + mSupportInfo.headroom.gpuMinCalculationWindowMillis + ", " 1802 + mSupportInfo.headroom.gpuMaxCalculationWindowMillis + "] but got " 1803 + params.calculationWindowMillis); 1804 } 1805 } 1806 1807 @Override getCpuHeadroomMinIntervalMillis()1808 public long getCpuHeadroomMinIntervalMillis() { 1809 if (!mSupportInfo.headroom.isCpuSupported) { 1810 throw new UnsupportedOperationException(); 1811 } 1812 return mSupportInfo.headroom.cpuMinIntervalMillis; 1813 } 1814 1815 @Override getGpuHeadroomMinIntervalMillis()1816 public long getGpuHeadroomMinIntervalMillis() { 1817 if (!mSupportInfo.headroom.isGpuSupported) { 1818 throw new UnsupportedOperationException(); 1819 } 1820 return mSupportInfo.headroom.gpuMinIntervalMillis; 1821 } 1822 1823 @Override passSessionManagerBinder(IBinder sessionManager)1824 public void passSessionManagerBinder(IBinder sessionManager) { 1825 // Ensure caller is internal 1826 if (Process.myUid() != Binder.getCallingUid()) { 1827 return; 1828 } 1829 mSessionManager = ISessionManager.Stub.asInterface(sessionManager); 1830 } 1831 1832 @Override 1833 public IHintManager.HintManagerClientData registerClient(@onNull IHintManager.IHintManagerClient clientBinder)1834 registerClient(@NonNull IHintManager.IHintManagerClient clientBinder) { 1835 return getClientData(); 1836 } 1837 1838 @Override getClientData()1839 public IHintManager.HintManagerClientData getClientData() { 1840 IHintManager.HintManagerClientData out = new IHintManager.HintManagerClientData(); 1841 out.preferredRateNanos = mHintSessionPreferredRate; 1842 out.maxGraphicsPipelineThreads = getMaxGraphicsPipelineThreadsCount(); 1843 out.maxCpuHeadroomThreads = DEFAULT_MAX_CPU_HEADROOM_THREADS_COUNT; 1844 out.powerHalVersion = mPowerHalVersion; 1845 out.supportInfo = mSupportInfo; 1846 return out; 1847 } 1848 1849 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)1850 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1851 if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) { 1852 return; 1853 } 1854 pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate); 1855 pw.println("MaxGraphicsPipelineThreadsCount: " + MAX_GRAPHICS_PIPELINE_THREADS_COUNT); 1856 pw.println("Hint Session Support: " + isHintSessionSupported()); 1857 pw.println("Active Sessions:"); 1858 synchronized (mLock) { 1859 for (int i = 0; i < mActiveSessions.size(); i++) { 1860 pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":"); 1861 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = 1862 mActiveSessions.valueAt(i); 1863 for (int j = 0; j < tokenMap.size(); j++) { 1864 ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j); 1865 for (int k = 0; k < sessionSet.size(); ++k) { 1866 pw.println(" Session:"); 1867 sessionSet.valueAt(k).dump(pw, " "); 1868 } 1869 } 1870 } 1871 } 1872 pw.println("CPU Headroom Supported: " + mSupportInfo.headroom.isCpuSupported); 1873 if (mSupportInfo.headroom.isCpuSupported) { 1874 pw.println("CPU Headroom Interval: " + mSupportInfo.headroom.cpuMinIntervalMillis); 1875 pw.println("CPU Headroom TID Max Count: " + mCpuHeadroomMaxTidCnt); 1876 pw.println("CPU Headroom TID Max Count From HAL: " 1877 + mSupportInfo.headroom.cpuMaxTidCount); 1878 pw.println("CPU Headroom Calculation Window Range: [" 1879 + mSupportInfo.headroom.cpuMinCalculationWindowMillis + ", " 1880 + mSupportInfo.headroom.cpuMaxCalculationWindowMillis + "]"); 1881 try { 1882 CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal(); 1883 params.usesDeviceHeadroom = true; 1884 CpuHeadroomResult ret = getCpuHeadroom(params); 1885 pw.println("CPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom())); 1886 } catch (Exception e) { 1887 Slog.d(TAG, "Failed to dump CPU headroom", e); 1888 pw.println("CPU headroom: N/A"); 1889 } 1890 } 1891 pw.println("GPU Headroom Supported: " + mSupportInfo.headroom.isGpuSupported); 1892 if (mSupportInfo.headroom.isGpuSupported) { 1893 pw.println("GPU Headroom Interval: " + mSupportInfo.headroom.gpuMinIntervalMillis); 1894 pw.println("GPU Headroom Calculation Window Range: [" 1895 + mSupportInfo.headroom.gpuMinCalculationWindowMillis + ", " 1896 + mSupportInfo.headroom.gpuMaxCalculationWindowMillis + "]"); 1897 try { 1898 GpuHeadroomParamsInternal params = new GpuHeadroomParamsInternal(); 1899 params.calculationWindowMillis = mDefaultGpuHeadroomCalculationWindowMillis; 1900 GpuHeadroomResult ret = getGpuHeadroom(params); 1901 pw.println("GPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom())); 1902 } catch (Exception e) { 1903 Slog.d(TAG, "Failed to dump GPU headroom", e); 1904 pw.println("GPU headroom: N/A"); 1905 } 1906 } 1907 } 1908 getUserModeJiffies()1909 private long getUserModeJiffies() throws IOException { 1910 String filePath = 1911 mProcStatFilePathOverride == null ? "/proc/stat" : mProcStatFilePathOverride; 1912 try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) { 1913 String line; 1914 while ((line = reader.readLine()) != null) { 1915 Matcher matcher = PROC_STAT_CPU_TIME_TOTAL_PATTERN.matcher(line.trim()); 1916 if (matcher.find()) { 1917 long userJiffies = Long.parseLong(matcher.group("user")); 1918 long niceJiffies = Long.parseLong(matcher.group("nice")); 1919 Slog.d(TAG, 1920 "user: " + userJiffies + " nice: " + niceJiffies 1921 + " total " + (userJiffies + niceJiffies)); 1922 reader.close(); 1923 return userJiffies + niceJiffies; 1924 } 1925 } 1926 } 1927 throw new IllegalStateException("Can't find cpu line in " + filePath); 1928 } 1929 logPerformanceHintSessionAtom(int uid, long sessionId, long targetDuration, int[] tids, @SessionTag int sessionTag)1930 private void logPerformanceHintSessionAtom(int uid, long sessionId, 1931 long targetDuration, int[] tids, @SessionTag int sessionTag) { 1932 FrameworkStatsLog.write(FrameworkStatsLog.PERFORMANCE_HINT_SESSION_REPORTED, uid, 1933 sessionId, targetDuration, tids.length, sessionTag); 1934 } 1935 getUidApplicationCategory(int uid)1936 private int getUidApplicationCategory(int uid) { 1937 try { 1938 final String packageName = mPackageManager.getNameForUid(uid); 1939 final ApplicationInfo applicationInfo = 1940 mPackageManager.getApplicationInfo(packageName, PackageManager.MATCH_ALL); 1941 return applicationInfo.category; 1942 } catch (PackageManager.NameNotFoundException e) { 1943 return ApplicationInfo.CATEGORY_UNDEFINED; 1944 } 1945 } 1946 } 1947 1948 @VisibleForTesting 1949 final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient { 1950 protected final int mUid; 1951 protected final int mPid; 1952 protected final int mTag; 1953 protected int[] mThreadIds; 1954 protected final IBinder mToken; 1955 protected long mHalSessionPtr; 1956 protected long mTargetDurationNanos; 1957 protected boolean mUpdateAllowedByProcState; 1958 protected int[] mNewThreadIds; 1959 protected boolean mPowerEfficient; 1960 protected boolean mGraphicsPipeline; 1961 protected boolean mHasBeenPowerEfficient; 1962 protected boolean mHasBeenGraphicsPipeline; 1963 protected boolean mShouldForcePause; 1964 protected Integer mSessionId; 1965 protected boolean mTrackedBySF; 1966 AppHintSession( int uid, int pid, int sessionTag, int[] threadIds, IBinder token, long halSessionPtr, long durationNanos, Integer sessionId)1967 protected AppHintSession( 1968 int uid, int pid, int sessionTag, int[] threadIds, IBinder token, 1969 long halSessionPtr, long durationNanos, Integer sessionId) { 1970 mUid = uid; 1971 mPid = pid; 1972 mTag = sessionTag; 1973 mToken = token; 1974 mThreadIds = threadIds; 1975 mHalSessionPtr = halSessionPtr; 1976 mTargetDurationNanos = durationNanos; 1977 mUpdateAllowedByProcState = true; 1978 mPowerEfficient = false; 1979 mGraphicsPipeline = false; 1980 mHasBeenPowerEfficient = false; 1981 mHasBeenGraphicsPipeline = false; 1982 mShouldForcePause = false; 1983 mSessionId = sessionId; 1984 mTrackedBySF = false; 1985 final boolean allowed = mUidObserver.isUidForeground(mUid); 1986 updateHintAllowedByProcState(allowed); 1987 try { 1988 token.linkToDeath(this, 0); 1989 } catch (RemoteException e) { 1990 mNativeWrapper.halCloseHintSession(mHalSessionPtr); 1991 throw new IllegalStateException("Client already dead", e); 1992 } 1993 } 1994 1995 @VisibleForTesting updateHintAllowedByProcState(boolean allowed)1996 boolean updateHintAllowedByProcState(boolean allowed) { 1997 synchronized (this) { 1998 if (allowed && !mUpdateAllowedByProcState && !mShouldForcePause) { 1999 resume(); 2000 } 2001 if (!allowed && mUpdateAllowedByProcState) { 2002 pause(); 2003 } 2004 mUpdateAllowedByProcState = allowed; 2005 return mUpdateAllowedByProcState; 2006 } 2007 } 2008 isHintAllowed()2009 boolean isHintAllowed() { 2010 return mHalSessionPtr != 0 && mUpdateAllowedByProcState && !mShouldForcePause; 2011 } 2012 2013 @Override updateTargetWorkDuration(long targetDurationNanos)2014 public void updateTargetWorkDuration(long targetDurationNanos) { 2015 synchronized (this) { 2016 if (!isHintAllowed()) { 2017 return; 2018 } 2019 Preconditions.checkArgument(targetDurationNanos >= 0, "Expected" 2020 + " the target duration to be greater than or equal to 0."); 2021 mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos); 2022 mTargetDurationNanos = targetDurationNanos; 2023 } 2024 synchronized (mSessionSnapshotMapLock) { 2025 ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = 2026 mSessionSnapshotMap.get(mUid); 2027 if (sessionSnapshots == null) { 2028 Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); 2029 return; 2030 } 2031 AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); 2032 if (sessionSnapshot == null) { 2033 Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag); 2034 return; 2035 } 2036 sessionSnapshot.updateTargetDurationNs(mTargetDurationNanos); 2037 } 2038 } 2039 2040 @Override reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos)2041 public void reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos) { 2042 synchronized (this) { 2043 if (!isHintAllowed()) { 2044 return; 2045 } 2046 Preconditions.checkArgument(actualDurationNanos.length != 0, "the count" 2047 + " of hint durations shouldn't be 0."); 2048 Preconditions.checkArgument(actualDurationNanos.length == timeStampNanos.length, 2049 "The length of durations and timestamps should be the same."); 2050 for (int i = 0; i < actualDurationNanos.length; i++) { 2051 if (actualDurationNanos[i] <= 0) { 2052 throw new IllegalArgumentException( 2053 String.format("durations[%d]=%d should be greater than 0", 2054 i, actualDurationNanos[i])); 2055 } 2056 } 2057 mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, actualDurationNanos, 2058 timeStampNanos); 2059 } 2060 } 2061 2062 /** TODO: consider monitor session threads and close session if any thread is dead. */ 2063 @Override close()2064 public void close() { 2065 synchronized (this) { 2066 if (mHalSessionPtr == 0) return; 2067 mNativeWrapper.halCloseHintSession(mHalSessionPtr); 2068 mHalSessionPtr = 0; 2069 try { 2070 mToken.unlinkToDeath(this, 0); 2071 } catch (NoSuchElementException ignored) { 2072 Slogf.d(TAG, "Death link does not exist for session with UID " + mUid); 2073 } 2074 if (mTrackedBySF) { 2075 if (mSessionManager != null) { 2076 try { 2077 mSessionManager.trackedSessionsDied(new int[]{mSessionId}); 2078 } catch (RemoteException e) { 2079 throw new IllegalStateException( 2080 "Could not communicate with SessionManager", e); 2081 } 2082 mTrackedBySF = false; 2083 } else { 2084 Slog.e(TAG, "SessionManager is null but there are tracked sessions"); 2085 } 2086 } 2087 } 2088 synchronized (mLock) { 2089 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid); 2090 if (tokenMap == null) { 2091 Slogf.w(TAG, "UID %d is not present in active session map", mUid); 2092 return; 2093 } 2094 ArraySet<AppHintSession> sessionSet = tokenMap.get(mToken); 2095 if (sessionSet == null) { 2096 Slogf.w(TAG, "Token %s is not present in token map", mToken.toString()); 2097 return; 2098 } 2099 sessionSet.remove(this); 2100 if (sessionSet.isEmpty()) tokenMap.remove(mToken); 2101 if (tokenMap.isEmpty()) mActiveSessions.remove(mUid); 2102 } 2103 synchronized (mSessionSnapshotMapLock) { 2104 ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = 2105 mSessionSnapshotMap.get(mUid); 2106 if (sessionSnapshots == null) { 2107 Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); 2108 return; 2109 } 2110 AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); 2111 if (sessionSnapshot == null) { 2112 Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag); 2113 return; 2114 } 2115 sessionSnapshot.updateUponSessionClose(); 2116 } 2117 2118 if (mGraphicsPipeline) { 2119 synchronized (mThreadsUsageObject) { 2120 ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(mUid); 2121 if (threadsSet == null) { 2122 Slogf.w(TAG, "Threads Set is null for uid " + mUid); 2123 return; 2124 } 2125 // remove all tids associated with this session 2126 for (int i = 0; i < threadsSet.size(); ++i) { 2127 if (contains(mThreadIds, threadsSet.valueAt(i).getTid())) { 2128 threadsSet.removeAt(i); 2129 } 2130 } 2131 if (threadsSet.isEmpty()) { 2132 mThreadsUsageMap.remove(mUid); 2133 } 2134 } 2135 } 2136 if (powerhintThreadCleanup()) { 2137 synchronized (mNonIsolatedTidsLock) { 2138 final int[] tids = getTidsInternal(); 2139 for (int tid : tids) { 2140 if (mNonIsolatedTids.containsKey(tid)) { 2141 mNonIsolatedTids.get(tid).remove(mHalSessionPtr); 2142 if (mNonIsolatedTids.get(tid).isEmpty()) { 2143 mNonIsolatedTids.remove(tid); 2144 } 2145 } 2146 } 2147 } 2148 } 2149 } 2150 2151 @Override sendHint(@erformanceHintManager.Session.Hint int hint)2152 public void sendHint(@PerformanceHintManager.Session.Hint int hint) { 2153 synchronized (this) { 2154 if (!isHintAllowed()) { 2155 return; 2156 } 2157 Preconditions.checkArgument(hint >= 0, "the hint ID value should be" 2158 + " greater than zero."); 2159 mNativeWrapper.halSendHint(mHalSessionPtr, hint); 2160 } 2161 } 2162 2163 @Override associateToLayers(IBinder[] layerTokens)2164 public void associateToLayers(IBinder[] layerTokens) { 2165 synchronized (this) { 2166 if (mSessionManager != null && mSessionId != null && layerTokens != null) { 2167 // Sf only untracks a session when it dies 2168 if (layerTokens.length > 0) { 2169 mTrackedBySF = true; 2170 } 2171 try { 2172 mSessionManager.associateSessionToLayers(mSessionId, mUid, layerTokens); 2173 } catch (RemoteException e) { 2174 throw new IllegalStateException( 2175 "Could not communicate with SessionManager", e); 2176 } 2177 } 2178 } 2179 } 2180 setThreads(@onNull int[] tids)2181 public void setThreads(@NonNull int[] tids) { 2182 setThreadsInternal(tids, true); 2183 if (tooManyPipelineThreads(Binder.getCallingUid())) { 2184 // This is technically a success but we are going to throw a fit anyway 2185 throw new ServiceSpecificException(5, 2186 "Not enough available graphics pipeline threads."); 2187 } 2188 } 2189 setThreadsInternal(int[] tids, boolean checkTid)2190 private void setThreadsInternal(int[] tids, boolean checkTid) { 2191 if (tids.length == 0) { 2192 throw new IllegalArgumentException("Thread id list can't be empty."); 2193 } 2194 2195 final int callingUid = Binder.getCallingUid(); 2196 2197 synchronized (this) { 2198 if (mHalSessionPtr == 0) { 2199 return; 2200 } 2201 if (!mUpdateAllowedByProcState) { 2202 Slogf.v(TAG, "update hint not allowed, storing tids."); 2203 mNewThreadIds = tids; 2204 mShouldForcePause = false; 2205 return; 2206 } 2207 if (checkTid) { 2208 final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); 2209 final IntArray nonIsolated = powerhintThreadCleanup() ? new IntArray() : null; 2210 final long identity = Binder.clearCallingIdentity(); 2211 try { 2212 final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids, 2213 nonIsolated); 2214 if (invalidTid != null) { 2215 final String errMsg = formatTidCheckErrMsg(callingUid, tids, 2216 invalidTid); 2217 Slogf.w(TAG, errMsg); 2218 throw new SecurityException(errMsg); 2219 } 2220 if (resetOnForkEnabled()) { 2221 try { 2222 for (int tid : tids) { 2223 int policy = Process.getThreadScheduler(tid); 2224 // If the thread is not using the default scheduling policy (SCHED_OTHER), 2225 // we don't change it. 2226 if (policy != Process.SCHED_OTHER) { 2227 continue; 2228 } 2229 // set the SCHED_RESET_ON_FORK flag. 2230 int prio = Process.getThreadPriority(tid); 2231 Process.setThreadScheduler(tid, Process.SCHED_OTHER | Process.SCHED_RESET_ON_FORK, 0); 2232 Process.setThreadPriority(tid, prio); 2233 } 2234 } catch (Exception e) { 2235 Slog.e(TAG, "Failed to set SCHED_RESET_ON_FORK for tids " 2236 + Arrays.toString(tids), e); 2237 } 2238 } 2239 if (powerhintThreadCleanup()) { 2240 synchronized (mNonIsolatedTidsLock) { 2241 for (int i = nonIsolated.size() - 1; i >= 0; i--) { 2242 mNonIsolatedTids.putIfAbsent(nonIsolated.get(i), 2243 new ArraySet<>()); 2244 mNonIsolatedTids.get(nonIsolated.get(i)).add(mHalSessionPtr); 2245 } 2246 } 2247 } 2248 } finally { 2249 Binder.restoreCallingIdentity(identity); 2250 } 2251 } 2252 mNativeWrapper.halSetThreads(mHalSessionPtr, tids); 2253 2254 synchronized (mThreadsUsageObject) { 2255 // replace old tids with new ones 2256 ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid); 2257 if (threadsSet == null) { 2258 mThreadsUsageMap.put(callingUid, new ArraySet<ThreadUsageTracker>()); 2259 threadsSet = mThreadsUsageMap.get(callingUid); 2260 } 2261 for (int i = 0; i < threadsSet.size(); ++i) { 2262 if (contains(mThreadIds, threadsSet.valueAt(i).getTid())) { 2263 threadsSet.removeAt(i); 2264 } 2265 } 2266 for (int tid : tids) { 2267 threadsSet.add(new ThreadUsageTracker(tid, mGraphicsPipeline)); 2268 } 2269 } 2270 mThreadIds = tids; 2271 mNewThreadIds = null; 2272 // if the update is allowed but the session is force paused by tid clean up, then 2273 // it's waiting for this tid update to resume 2274 if (mShouldForcePause) { 2275 resume(); 2276 mShouldForcePause = false; 2277 } 2278 } 2279 synchronized (mSessionSnapshotMapLock) { 2280 ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = 2281 mSessionSnapshotMap.get(mUid); 2282 if (sessionSnapshots == null) { 2283 Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); 2284 return; 2285 } 2286 AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); 2287 if (sessionSnapshot == null) { 2288 Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " 2289 + mTag); 2290 return; 2291 } 2292 sessionSnapshot.updateThreadCount(tids.length); 2293 } 2294 } 2295 getThreadIds()2296 public int[] getThreadIds() { 2297 synchronized (this) { 2298 return Arrays.copyOf(mThreadIds, mThreadIds.length); 2299 } 2300 } 2301 2302 @VisibleForTesting getTidsInternal()2303 int[] getTidsInternal() { 2304 synchronized (this) { 2305 return mNewThreadIds != null ? Arrays.copyOf(mNewThreadIds, mNewThreadIds.length) 2306 : Arrays.copyOf(mThreadIds, mThreadIds.length); 2307 } 2308 } 2309 isClosed()2310 boolean isClosed() { 2311 synchronized (this) { 2312 return mHalSessionPtr == 0; 2313 } 2314 } 2315 isForcePaused()2316 boolean isForcePaused() { 2317 synchronized (this) { 2318 return mShouldForcePause; 2319 } 2320 } 2321 @Override setMode(int mode, boolean enabled)2322 public void setMode(int mode, boolean enabled) { 2323 synchronized (this) { 2324 if (!isHintAllowed()) { 2325 return; 2326 } 2327 Preconditions.checkArgument(mode >= 0, "the mode Id value should be" 2328 + " greater than zero."); 2329 if (mode == SessionMode.POWER_EFFICIENCY) { 2330 mPowerEfficient = enabled; 2331 } else if (mode == SessionMode.GRAPHICS_PIPELINE) { 2332 mGraphicsPipeline = enabled; 2333 } 2334 mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled); 2335 } 2336 if (enabled) { 2337 if (mode == SessionMode.POWER_EFFICIENCY) { 2338 if (!mHasBeenPowerEfficient) { 2339 mHasBeenPowerEfficient = true; 2340 synchronized (mSessionSnapshotMapLock) { 2341 ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = 2342 mSessionSnapshotMap.get(mUid); 2343 if (sessionSnapshots == null) { 2344 Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); 2345 return; 2346 } 2347 AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); 2348 if (sessionSnapshot == null) { 2349 Slogf.w(TAG, "Session snapshot is null for uid " + mUid 2350 + " and tag " + mTag); 2351 return; 2352 } 2353 sessionSnapshot.logPowerEfficientSession(); 2354 } 2355 } 2356 } else if (mode == SessionMode.GRAPHICS_PIPELINE) { 2357 if (!mHasBeenGraphicsPipeline) { 2358 mHasBeenGraphicsPipeline = true; 2359 synchronized (mSessionSnapshotMapLock) { 2360 ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots = 2361 mSessionSnapshotMap.get(mUid); 2362 if (sessionSnapshots == null) { 2363 Slogf.w(TAG, "Session snapshot map is null for uid " + mUid); 2364 return; 2365 } 2366 AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag); 2367 if (sessionSnapshot == null) { 2368 Slogf.w(TAG, "Session snapshot is null for uid " + mUid 2369 + " and tag " + mTag); 2370 return; 2371 } 2372 sessionSnapshot.logGraphicsPipelineSession(); 2373 } 2374 } 2375 } 2376 } 2377 } 2378 2379 @Override reportActualWorkDuration2(WorkDuration[] workDurations)2380 public void reportActualWorkDuration2(WorkDuration[] workDurations) { 2381 synchronized (this) { 2382 if (!isHintAllowed()) { 2383 return; 2384 } 2385 Preconditions.checkArgument(workDurations.length != 0, "the count" 2386 + " of work durations shouldn't be 0."); 2387 for (int i = 0; i < workDurations.length; i++) { 2388 validateWorkDuration(workDurations[i]); 2389 } 2390 mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, workDurations); 2391 } 2392 } 2393 isPowerEfficient()2394 public boolean isPowerEfficient() { 2395 synchronized (this) { 2396 return mPowerEfficient; 2397 } 2398 } 2399 isGraphicsPipeline()2400 public boolean isGraphicsPipeline() { 2401 synchronized (this) { 2402 return mGraphicsPipeline; 2403 } 2404 } 2405 getUid()2406 public int getUid() { 2407 return mUid; 2408 } 2409 isTrackedBySf()2410 public boolean isTrackedBySf() { 2411 synchronized (this) { 2412 return mTrackedBySF; 2413 } 2414 } 2415 setTrackedBySf(boolean tracked)2416 public void setTrackedBySf(boolean tracked) { 2417 synchronized (this) { 2418 mTrackedBySF = tracked; 2419 } 2420 } 2421 2422 getTag()2423 public int getTag() { 2424 return mTag; 2425 } 2426 getSessionId()2427 public Integer getSessionId() { 2428 return mSessionId; 2429 } 2430 getTargetDurationNs()2431 public long getTargetDurationNs() { 2432 synchronized (this) { 2433 return mTargetDurationNanos; 2434 } 2435 } 2436 validateWorkDuration(WorkDuration workDuration)2437 void validateWorkDuration(WorkDuration workDuration) { 2438 if (DEBUG) { 2439 Slogf.d(TAG, "WorkDuration(" 2440 + workDuration.durationNanos + ", " 2441 + workDuration.workPeriodStartTimestampNanos + ", " 2442 + workDuration.cpuDurationNanos + ", " 2443 + workDuration.gpuDurationNanos + ")"); 2444 } 2445 2446 // Allow work period start timestamp to be zero in system server side because 2447 // legacy API call will use zero value. It can not be estimated with the timestamp 2448 // the sample is received because the samples could stack up. 2449 if (workDuration.durationNanos <= 0) { 2450 throw new IllegalArgumentException( 2451 TextUtils.formatSimple("Actual total duration (%d) should be greater than 0", 2452 workDuration.durationNanos)); 2453 } 2454 if (workDuration.workPeriodStartTimestampNanos < 0) { 2455 throw new IllegalArgumentException( 2456 TextUtils.formatSimple( 2457 "Work period start timestamp (%d) should be greater than 0", 2458 workDuration.workPeriodStartTimestampNanos)); 2459 } 2460 if (workDuration.cpuDurationNanos < 0) { 2461 throw new IllegalArgumentException( 2462 TextUtils.formatSimple( 2463 "Actual CPU duration (%d) should be greater than or equal to 0", 2464 workDuration.cpuDurationNanos)); 2465 } 2466 if (workDuration.gpuDurationNanos < 0) { 2467 throw new IllegalArgumentException( 2468 TextUtils.formatSimple( 2469 "Actual GPU duration (%d) should greater than or equal to 0", 2470 workDuration.gpuDurationNanos)); 2471 } 2472 if (workDuration.cpuDurationNanos 2473 + workDuration.gpuDurationNanos <= 0) { 2474 throw new IllegalArgumentException( 2475 TextUtils.formatSimple( 2476 "The actual CPU duration (%d) and the actual GPU duration (%d)" 2477 + " should not both be 0", workDuration.cpuDurationNanos, 2478 workDuration.gpuDurationNanos)); 2479 } 2480 } 2481 pause()2482 private void pause() { 2483 synchronized (this) { 2484 if (mHalSessionPtr == 0) return; 2485 mNativeWrapper.halPauseHintSession(mHalSessionPtr); 2486 } 2487 } 2488 resume()2489 private void resume() { 2490 synchronized (this) { 2491 if (mHalSessionPtr == 0) return; 2492 mNativeWrapper.halResumeHintSession(mHalSessionPtr); 2493 if (mNewThreadIds != null) { 2494 mNativeWrapper.halSetThreads(mHalSessionPtr, mNewThreadIds); 2495 mThreadIds = mNewThreadIds; 2496 mNewThreadIds = null; 2497 } 2498 } 2499 } 2500 dump(PrintWriter pw, String prefix)2501 private void dump(PrintWriter pw, String prefix) { 2502 synchronized (this) { 2503 pw.println(prefix + "SessionPID: " + mPid); 2504 pw.println(prefix + "SessionUID: " + mUid); 2505 pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds)); 2506 pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos); 2507 pw.println(prefix + "SessionAllowedByProcState: " + mUpdateAllowedByProcState); 2508 pw.println(prefix + "SessionForcePaused: " + mShouldForcePause); 2509 pw.println(prefix + "PowerEfficient: " + (mPowerEfficient ? "true" : "false")); 2510 pw.println(prefix + "GraphicsPipeline: " + (mGraphicsPipeline ? "true" : "false")); 2511 } 2512 } 2513 2514 @Override binderDied()2515 public void binderDied() { 2516 close(); 2517 } 2518 2519 } 2520 } 2521