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