1 /* 2 * Copyright (C) 2025 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.am.compaction; 18 19 import android.annotation.IntDef; 20 import android.app.ActivityManagerInternal; 21 import android.util.Slog; 22 23 import com.android.internal.annotations.GuardedBy; 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.server.am.CachedAppOptimizer; 26 27 import dalvik.annotation.optimization.NeverCompile; 28 29 import java.io.PrintWriter; 30 import java.lang.annotation.Retention; 31 import java.lang.annotation.RetentionPolicy; 32 import java.util.EnumMap; 33 import java.util.LinkedHashMap; 34 import java.util.LinkedList; 35 import java.util.Map; 36 37 public final class CompactionStatsManager { 38 private static CompactionStatsManager sInstance; 39 40 private static String TAG = "CompactionStatsManager"; 41 42 // Size of history for the last 20 compactions for any process 43 static final int LAST_COMPACTED_ANY_PROCESS_STATS_HISTORY_SIZE = 20; 44 45 // Amount of processes supported to record for their last compaction. 46 static final int LAST_COMPACTION_FOR_PROCESS_STATS_SIZE = 256; 47 48 public static final int COMPACT_THROTTLE_REASON_NO_PID = 0; 49 public static final int COMPACT_THROTTLE_REASON_OOM_ADJ = 1; 50 public static final int COMPACT_THROTTLE_REASON_TIME_TOO_SOON = 2; 51 public static final int COMPACT_THROTTLE_REASON_PROC_STATE = 3; 52 public static final int COMPACT_THROTTLE_REASON_DELTA_RSS = 4; 53 @IntDef(value = { 54 COMPACT_THROTTLE_REASON_NO_PID, COMPACT_THROTTLE_REASON_OOM_ADJ, 55 COMPACT_THROTTLE_REASON_TIME_TOO_SOON, COMPACT_THROTTLE_REASON_PROC_STATE, 56 COMPACT_THROTTLE_REASON_DELTA_RSS 57 }) 58 @Retention(RetentionPolicy.SOURCE) 59 public @interface CompactThrottleReason {} 60 61 private final LinkedHashMap<String, AggregatedProcessCompactionStats> mPerProcessCompactStats = 62 new LinkedHashMap<>(256); 63 private final EnumMap<CachedAppOptimizer.CompactSource, AggregatedSourceCompactionStats> 64 mPerSourceCompactStats = 65 new EnumMap<>(CachedAppOptimizer.CompactSource.class); 66 67 private long mTotalCompactionDowngrades; 68 private long mSystemCompactionsPerformed; 69 private long mSystemTotalMemFreed; 70 private EnumMap<CachedAppOptimizer.CancelCompactReason, Integer> mTotalCompactionsCancelled = 71 new EnumMap<>(CachedAppOptimizer.CancelCompactReason.class); 72 73 // Maps process ID to last compaction statistics for processes that we've fully compacted. Used 74 // when evaluating throttles that we only consider for "full" compaction, so we don't store 75 // data for "some" compactions. Uses LinkedHashMap to ensure insertion order is kept and 76 // facilitate removal of the oldest entry. 77 @VisibleForTesting 78 @GuardedBy("mProcLock") 79 LinkedHashMap<Integer, SingleCompactionStats> mLastCompactionStats = 80 new LinkedHashMap<Integer, SingleCompactionStats>() { 81 @Override 82 protected boolean removeEldestEntry(Map.Entry eldest) { 83 return size() > LAST_COMPACTION_FOR_PROCESS_STATS_SIZE; 84 } 85 }; 86 87 LinkedList<SingleCompactionStats> mCompactionStatsHistory = 88 new LinkedList<SingleCompactionStats>() { 89 @Override 90 public boolean add(SingleCompactionStats e) { 91 if (size() >= LAST_COMPACTED_ANY_PROCESS_STATS_HISTORY_SIZE) { 92 this.remove(); 93 } 94 return super.add(e); 95 } 96 }; 97 getInstance()98 public static CompactionStatsManager getInstance() { 99 if (sInstance == null) { 100 sInstance = new CompactionStatsManager(); 101 } 102 return sInstance; 103 } 104 getLastCompactionStats(int pid)105 public SingleCompactionStats getLastCompactionStats(int pid) { 106 return mLastCompactionStats.get(pid); 107 } 108 109 @VisibleForTesting getLastCompactionStats()110 public LinkedHashMap<Integer, SingleCompactionStats> getLastCompactionStats() { 111 return mLastCompactionStats; 112 } 113 114 @VisibleForTesting reinit()115 public void reinit() { 116 sInstance = new CompactionStatsManager(); 117 } 118 119 logCompactionRequested(CachedAppOptimizer.CompactSource source, CachedAppOptimizer.CompactProfile compactProfile, String processName)120 public void logCompactionRequested(CachedAppOptimizer.CompactSource source, 121 CachedAppOptimizer.CompactProfile compactProfile, String processName) { 122 AggregatedSourceCompactionStats perSourceStats = getPerSourceAggregatedCompactStat(source); 123 AggregatedCompactionStats perProcStats = 124 getPerProcessAggregatedCompactStat(processName); 125 126 switch (compactProfile) { 127 case SOME: 128 ++perProcStats.mSomeCompactRequested; 129 ++perSourceStats.mSomeCompactRequested; 130 break; 131 case FULL: 132 ++perProcStats.mFullCompactRequested; 133 ++perSourceStats.mFullCompactRequested; 134 break; 135 default: 136 Slog.e(TAG, 137 "Stats cannot be logged for compaction type."+compactProfile); 138 } 139 } logCompactionThrottled(@ompactThrottleReason int reason, CachedAppOptimizer.CompactSource source, String processName)140 public void logCompactionThrottled(@CompactThrottleReason int reason, 141 CachedAppOptimizer.CompactSource source, String processName) { 142 AggregatedSourceCompactionStats perSourceStats = 143 getPerSourceAggregatedCompactStat(source); 144 AggregatedProcessCompactionStats perProcessStats = 145 getPerProcessAggregatedCompactStat(processName); 146 147 switch(reason) { 148 case COMPACT_THROTTLE_REASON_NO_PID: 149 ++perSourceStats.mProcCompactionsNoPidThrottled; 150 ++perProcessStats.mProcCompactionsNoPidThrottled; 151 break; 152 case COMPACT_THROTTLE_REASON_OOM_ADJ: 153 ++perProcessStats.mProcCompactionsOomAdjThrottled; 154 ++perSourceStats.mProcCompactionsOomAdjThrottled; 155 break; 156 case COMPACT_THROTTLE_REASON_TIME_TOO_SOON: 157 ++perProcessStats.mProcCompactionsTimeThrottled; 158 ++perSourceStats.mProcCompactionsTimeThrottled; 159 break; 160 case COMPACT_THROTTLE_REASON_PROC_STATE: 161 ++perProcessStats.mProcCompactionsMiscThrottled; 162 ++perSourceStats.mProcCompactionsMiscThrottled; 163 break; 164 case COMPACT_THROTTLE_REASON_DELTA_RSS: 165 ++perProcessStats.mProcCompactionsRSSThrottled; 166 ++perSourceStats.mProcCompactionsRSSThrottled; 167 break; 168 default: 169 break; 170 } 171 } 172 logSomeCompactionPerformed(CachedAppOptimizer.CompactSource source, String processName)173 public void logSomeCompactionPerformed(CachedAppOptimizer.CompactSource source, 174 String processName) { 175 AggregatedSourceCompactionStats perSourceStats = 176 getPerSourceAggregatedCompactStat(source); 177 AggregatedProcessCompactionStats perProcessStats = 178 getPerProcessAggregatedCompactStat(processName); 179 180 ++perSourceStats.mSomeCompactPerformed; 181 ++perProcessStats.mSomeCompactPerformed; 182 } 183 logFullCompactionPerformed( CachedAppOptimizer.CompactSource source, String processName, long anonRssSavings, long zramConsumed, long memFreed, long origAnonRss, long totalCpuTimeMillis, long[] rssAfterCompact, int procState, int newOomAdj, @ActivityManagerInternal.OomAdjReason int oomAdjReason, int uid, int pid, boolean logFieldMetric)184 public void logFullCompactionPerformed( 185 CachedAppOptimizer.CompactSource source, String processName, long anonRssSavings, 186 long zramConsumed, long memFreed, long origAnonRss, long totalCpuTimeMillis, 187 long[] rssAfterCompact, int procState, int newOomAdj, 188 @ActivityManagerInternal.OomAdjReason int oomAdjReason, int uid, int pid, 189 boolean logFieldMetric) { 190 AggregatedSourceCompactionStats perSourceStats = 191 getPerSourceAggregatedCompactStat(source); 192 AggregatedProcessCompactionStats perProcessStats = 193 getPerProcessAggregatedCompactStat(processName); 194 195 ++perSourceStats.mFullCompactPerformed; 196 ++perProcessStats.mFullCompactPerformed; 197 198 // Negative stats would skew averages and will likely be due to 199 // noise of system doing other things so we put a floor at 0 to 200 // avoid negative values. 201 anonRssSavings = anonRssSavings > 0 ? anonRssSavings : 0; 202 zramConsumed = zramConsumed > 0 ? zramConsumed : 0; 203 memFreed = memFreed > 0 ? memFreed : 0; 204 205 perProcessStats.addMemStats(anonRssSavings, zramConsumed, memFreed, 206 origAnonRss, totalCpuTimeMillis); 207 perSourceStats.addMemStats(anonRssSavings, zramConsumed, memFreed, 208 origAnonRss, totalCpuTimeMillis); 209 SingleCompactionStats memStats = new SingleCompactionStats(rssAfterCompact, 210 source, processName, anonRssSavings, zramConsumed, memFreed, 211 origAnonRss, totalCpuTimeMillis, procState, newOomAdj, 212 oomAdjReason, uid); 213 mLastCompactionStats.remove(pid); 214 mLastCompactionStats.put(pid, memStats); 215 mCompactionStatsHistory.add(memStats); 216 if (!logFieldMetric) { 217 memStats.sendStat(); 218 } 219 } 220 logCompactionDowngrade()221 public void logCompactionDowngrade() { 222 ++mTotalCompactionDowngrades; 223 } 224 logSystemCompactionPerformed(long memFreed)225 public void logSystemCompactionPerformed(long memFreed) { 226 ++mSystemCompactionsPerformed; 227 mSystemTotalMemFreed += memFreed; 228 } 229 logCompactionCancelled(CachedAppOptimizer.CancelCompactReason cancelReason)230 public void logCompactionCancelled(CachedAppOptimizer.CancelCompactReason cancelReason) { 231 if (mTotalCompactionsCancelled.containsKey(cancelReason)) { 232 int count = mTotalCompactionsCancelled.get(cancelReason); 233 mTotalCompactionsCancelled.put(cancelReason, count + 1); 234 } else { 235 mTotalCompactionsCancelled.put(cancelReason, 1); 236 } 237 } 238 getPerProcessAggregatedCompactStat( String processName)239 private AggregatedProcessCompactionStats getPerProcessAggregatedCompactStat( 240 String processName) { 241 if (processName == null) { 242 processName = ""; 243 } 244 AggregatedProcessCompactionStats stats = mPerProcessCompactStats.get(processName); 245 if (stats == null) { 246 stats = new AggregatedProcessCompactionStats(processName); 247 mPerProcessCompactStats.put(processName, stats); 248 } 249 return stats; 250 } 251 getPerSourceAggregatedCompactStat( CachedAppOptimizer.CompactSource source)252 private AggregatedSourceCompactionStats getPerSourceAggregatedCompactStat( 253 CachedAppOptimizer.CompactSource source) { 254 AggregatedSourceCompactionStats stats = mPerSourceCompactStats.get(source); 255 if (stats == null) { 256 stats = new AggregatedSourceCompactionStats(source); 257 mPerSourceCompactStats.put(source, stats); 258 } 259 return stats; 260 } 261 262 @NeverCompile dump(PrintWriter pw)263 public void dump(PrintWriter pw) { 264 pw.println(" Per-Process Compaction Stats"); 265 long totalCompactPerformedSome = 0; 266 long totalCompactPerformedFull = 0; 267 for (AggregatedProcessCompactionStats stats : mPerProcessCompactStats.values()) { 268 pw.println("-----" + stats.mProcessName + "-----"); 269 totalCompactPerformedSome += stats.mSomeCompactPerformed; 270 totalCompactPerformedFull += stats.mFullCompactPerformed; 271 stats.dump(pw); 272 pw.println(); 273 } 274 pw.println(); 275 pw.println(" Per-Source Compaction Stats"); 276 for (AggregatedSourceCompactionStats stats : mPerSourceCompactStats.values()) { 277 pw.println("-----" + stats.mSourceType + "-----"); 278 stats.dump(pw); 279 pw.println(); 280 } 281 pw.println(); 282 283 pw.println("Total Compactions Performed by profile: " + totalCompactPerformedSome 284 + " some, " + totalCompactPerformedFull + " full"); 285 pw.println("Total compactions downgraded: " + mTotalCompactionDowngrades); 286 pw.println("Total compactions cancelled by reason: "); 287 for (CachedAppOptimizer.CancelCompactReason reason : mTotalCompactionsCancelled.keySet()) { 288 pw.println(" " + reason + ": " + mTotalCompactionsCancelled.get(reason)); 289 } 290 pw.println(); 291 292 pw.println(" System Compaction Memory Stats"); 293 pw.println(" Compactions Performed: " + mSystemCompactionsPerformed); 294 pw.println(" Total Memory Freed (KB): " + mSystemTotalMemFreed); 295 double avgKBsPerSystemCompact = mSystemCompactionsPerformed > 0 296 ? mSystemTotalMemFreed / mSystemCompactionsPerformed 297 : 0; 298 pw.println(" Avg Mem Freed per Compact (KB): " + avgKBsPerSystemCompact); 299 pw.println(); 300 pw.println(" Tracking last compaction stats for " + mLastCompactionStats.size() 301 + " processes."); 302 pw.println("Last Compaction per process stats:"); 303 pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs" 304 + ",SwapEfficiency,CompactEfficiency,CompactCost(ms/MB),procState,oomAdj," 305 + "oomAdjReason)"); 306 for (Map.Entry<Integer, SingleCompactionStats> entry : 307 mLastCompactionStats.entrySet()) { 308 SingleCompactionStats stats = entry.getValue(); 309 stats.dump(pw); 310 } 311 pw.println(); 312 pw.println("Last 20 Compactions Stats:"); 313 pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs," 314 + "SwapEfficiency,CompactEfficiency,CompactCost(ms/MB),procState,oomAdj," 315 + "oomAdjReason)"); 316 for (SingleCompactionStats stats : mCompactionStatsHistory) { 317 stats.dump(pw); 318 } 319 } 320 } 321