• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.stats;
18 
19 import android.app.StatsManager;
20 import android.content.Context;
21 import android.os.SystemClock;
22 import android.util.Log;
23 import android.util.StatsEvent;
24 
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.internal.util.ConcurrentUtils;
28 import com.android.internal.util.FrameworkStatsLog;
29 
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Objects;
36 
37 /** A class to initialise and log metrics pulled by statsd. */
38 public class WakelockStatsFrameworkEvents {
39     // statsd has a dimensional limit on the number of different keys it can handle.
40     // Beyond that limit, statsd will drop data.
41     //
42     // When we have seem SUMMARY_THRESHOLD distinct (uid, tag, wakeLockLevel) keys,
43     // we start summarizing new keys as (uid, OVERFLOW_TAG, OVERFLOW_LEVEL) to
44     // reduce the number of keys we pass to statsd.
45     //
46     // When we reach MAX_WAKELOCK_DIMENSIONS distinct keys, we summarize all new keys
47     // as (OVERFLOW_UID, HARD_CAP_TAG, OVERFLOW_LEVEL) to hard cap the number of
48     // distinct keys we pass to statsd.
49     @VisibleForTesting public static final int SUMMARY_THRESHOLD = 500;
50     @VisibleForTesting public static final int MAX_WAKELOCK_DIMENSIONS = 1000;
51 
52     @VisibleForTesting public static final int HARD_CAP_UID = -1;
53     @VisibleForTesting public static final String OVERFLOW_TAG = "*overflow*";
54     @VisibleForTesting public static final String HARD_CAP_TAG = "*overflow hard cap*";
55     @VisibleForTesting public static final int OVERFLOW_LEVEL = 1;
56 
57     private static class WakeLockKey {
58         private int uid;
59         private String tag;
60         private int powerManagerWakeLockLevel;
61         private int hashCode;
62 
WakeLockKey(int uid, String tag, int powerManagerWakeLockLevel)63         WakeLockKey(int uid, String tag, int powerManagerWakeLockLevel) {
64             this.uid = uid;
65             this.tag = new String(tag);
66             this.powerManagerWakeLockLevel = powerManagerWakeLockLevel;
67 
68             this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel);
69         }
70 
getUid()71         int getUid() {
72             return uid;
73         }
74 
getTag()75         String getTag() {
76             return tag;
77         }
78 
getPowerManagerWakeLockLevel()79         int getPowerManagerWakeLockLevel() {
80             return powerManagerWakeLockLevel;
81         }
82 
setOverflow()83         void setOverflow() {
84             tag = OVERFLOW_TAG;
85             powerManagerWakeLockLevel = OVERFLOW_LEVEL;
86             this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel);
87         }
88 
setHardCap()89         void setHardCap() {
90             uid = HARD_CAP_UID;
91             tag = HARD_CAP_TAG;
92             powerManagerWakeLockLevel = OVERFLOW_LEVEL;
93             this.hashCode = Objects.hash(uid, tag, powerManagerWakeLockLevel);
94         }
95 
96         @Override
equals(Object o)97         public boolean equals(Object o) {
98             if (this == o) return true;
99             if (o == null || !(o instanceof WakeLockKey)) return false;
100 
101             WakeLockKey that = (WakeLockKey) o;
102             return uid == that.uid
103                     && tag.equals(that.tag)
104                     && powerManagerWakeLockLevel == that.powerManagerWakeLockLevel;
105         }
106 
107         @Override
hashCode()108         public int hashCode() {
109             return this.hashCode;
110         }
111     }
112 
113     private static class WakeLockStats {
114         // accumulated uptime attributed to this WakeLock since boot, where overlap
115         // (including nesting) is ignored
116         public long uptimeMillis = 0;
117 
118         // count of WakeLocks that have been acquired and then released
119         public long completedCount = 0;
120     }
121 
122     private final Object mLock = new Object();
123 
124     @GuardedBy("mLock")
125     private final Map<WakeLockKey, WakeLockStats> mWakeLockStats = new HashMap<>();
126 
127     private static class WakeLockData {
128         // uptime millis when first acquired
129         public long acquireUptimeMillis = 0;
130         public int refCount = 0;
131 
WakeLockData(long uptimeMillis)132         WakeLockData(long uptimeMillis) {
133             acquireUptimeMillis = uptimeMillis;
134         }
135     }
136 
137     @GuardedBy("mLock")
138     private final Map<WakeLockKey, WakeLockData> mOpenWakeLocks = new HashMap<>();
139 
noteStartWakeLock( int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis)140     public void noteStartWakeLock(
141             int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis) {
142         final WakeLockKey key = new WakeLockKey(uid, tag, powerManagerWakeLockLevel);
143 
144         synchronized (mLock) {
145             WakeLockData data =
146                     mOpenWakeLocks.computeIfAbsent(key, k -> new WakeLockData(eventUptimeMillis));
147             data.refCount++;
148             mOpenWakeLocks.put(key, data);
149         }
150     }
151 
152     @VisibleForTesting
153     @GuardedBy("mLock")
inOverflow()154     public boolean inOverflow() {
155         return mWakeLockStats.size() >= SUMMARY_THRESHOLD;
156     }
157 
158     @VisibleForTesting
159     @GuardedBy("mLock")
inHardCap()160     public boolean inHardCap() {
161         return mWakeLockStats.size() >= MAX_WAKELOCK_DIMENSIONS;
162     }
163 
noteStopWakeLock( int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis)164     public void noteStopWakeLock(
165             int uid, String tag, int powerManagerWakeLockLevel, long eventUptimeMillis) {
166         WakeLockKey key = new WakeLockKey(uid, tag, powerManagerWakeLockLevel);
167 
168         synchronized (mLock) {
169             WakeLockData data = mOpenWakeLocks.get(key);
170             if (data == null) {
171                 Log.e(TAG, "WakeLock not found when stopping: " + uid + " " + tag);
172                 return;
173             }
174 
175             if (data.refCount == 1) {
176                 mOpenWakeLocks.remove(key);
177                 long wakeLockDur = eventUptimeMillis - data.acquireUptimeMillis;
178 
179                 // Rewrite key if in an overflow state.
180                 if (inOverflow() && !mWakeLockStats.containsKey(key)) {
181                     key.setOverflow();
182                     if (inHardCap() && !mWakeLockStats.containsKey(key)) {
183                         key.setHardCap();
184                     }
185                 }
186 
187                 WakeLockStats stats = mWakeLockStats.computeIfAbsent(key, k -> new WakeLockStats());
188                 stats.uptimeMillis += wakeLockDur;
189                 stats.completedCount++;
190                 mWakeLockStats.put(key, stats);
191             } else {
192                 data.refCount--;
193                 mOpenWakeLocks.put(key, data);
194             }
195         }
196     }
197 
198     // Shim interface for testing.
199     @VisibleForTesting
200     public interface EventLogger {
logResult( int uid, String tag, int wakeLockLevel, long uptimeMillis, long completedCount)201         void logResult(
202                 int uid, String tag, int wakeLockLevel, long uptimeMillis, long completedCount);
203     }
204 
pullFrameworkWakelockInfoAtoms()205     public List<StatsEvent> pullFrameworkWakelockInfoAtoms() {
206         List<StatsEvent> result = new ArrayList<>();
207         EventLogger logger =
208                 new EventLogger() {
209                     public void logResult(
210                             int uid,
211                             String tag,
212                             int wakeLockLevel,
213                             long uptimeMillis,
214                             long completedCount) {
215                         StatsEvent event =
216                                 StatsEvent.newBuilder()
217                                         .setAtomId(FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO)
218                                         .writeInt(uid)
219                                         .writeString(tag)
220                                         .writeInt(wakeLockLevel)
221                                         .writeLong(uptimeMillis)
222                                         .writeLong(completedCount)
223                                         .build();
224                         result.add(event);
225                     }
226                 };
227         pullFrameworkWakelockInfoAtoms(SystemClock.uptimeMillis(), logger);
228         return result;
229     }
230 
231     @VisibleForTesting
pullFrameworkWakelockInfoAtoms(long nowMillis, EventLogger logger)232     public void pullFrameworkWakelockInfoAtoms(long nowMillis, EventLogger logger) {
233         HashSet<WakeLockKey> keys = new HashSet<>();
234 
235         // Used to collect open WakeLocks when in an overflow state.
236         HashMap<WakeLockKey, WakeLockStats> openOverflowStats = new HashMap<>();
237 
238         synchronized (mLock) {
239             keys.addAll(mWakeLockStats.keySet());
240 
241             // If we are in an overflow state, an open wakelock may have a new key
242             // that needs to be summarized.
243             if (inOverflow()) {
244                 for (WakeLockKey key : mOpenWakeLocks.keySet()) {
245                     if (!mWakeLockStats.containsKey(key)) {
246                         WakeLockData data = mOpenWakeLocks.get(key);
247 
248                         key.setOverflow();
249                         if (inHardCap() && !mWakeLockStats.containsKey(key)) {
250                             key.setHardCap();
251                         }
252                         keys.add(key);
253 
254                         WakeLockStats stats =
255                                 openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats());
256                         stats.uptimeMillis += nowMillis - data.acquireUptimeMillis;
257                         openOverflowStats.put(key, stats);
258                     }
259                 }
260             } else {
261                 keys.addAll(mOpenWakeLocks.keySet());
262             }
263 
264             for (WakeLockKey key : keys) {
265                 long openWakeLockUptime = 0;
266                 WakeLockData data = mOpenWakeLocks.get(key);
267                 if (data != null) {
268                     openWakeLockUptime = nowMillis - data.acquireUptimeMillis;
269                 }
270 
271                 WakeLockStats stats = mWakeLockStats.computeIfAbsent(key, k -> new WakeLockStats());
272                 WakeLockStats extraTime =
273                         openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats());
274 
275                 long totalUpdate = openWakeLockUptime + stats.uptimeMillis + extraTime.uptimeMillis;
276                 long totalCount = stats.completedCount + extraTime.completedCount;
277                 logger.logResult(key.getUid(), key.getTag(), key.getPowerManagerWakeLockLevel(),
278                         totalUpdate, totalCount);
279             }
280         }
281     }
282 
283     private static final String TAG = "BatteryStatsPulledMetrics";
284 
285     private final StatsPullCallbackHandler mStatsPullCallbackHandler =
286             new StatsPullCallbackHandler();
287 
288     private boolean mIsInitialized = false;
289 
initialize(Context context)290     public void initialize(Context context) {
291         if (mIsInitialized) {
292             return;
293         }
294 
295         final StatsManager statsManager = context.getSystemService(StatsManager.class);
296         if (statsManager == null) {
297             Log.e(
298                     TAG,
299                     "Error retrieving StatsManager. Cannot initialize BatteryStatsPulledMetrics.");
300         } else {
301             Log.d(TAG, "Registering callback with StatsManager");
302 
303             // DIRECT_EXECUTOR means that callback will run on binder thread.
304             statsManager.setPullAtomCallback(
305                     FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO,
306                     null /* metadata */,
307                     ConcurrentUtils.DIRECT_EXECUTOR,
308                     mStatsPullCallbackHandler);
309             mIsInitialized = true;
310         }
311     }
312 
313     private class StatsPullCallbackHandler implements StatsManager.StatsPullAtomCallback {
314         @Override
onPullAtom(int atomTag, List<StatsEvent> data)315         public int onPullAtom(int atomTag, List<StatsEvent> data) {
316             // handle the tags appropriately.
317             List<StatsEvent> events = pullEvents(atomTag);
318             if (events == null) {
319                 return StatsManager.PULL_SKIP;
320             }
321 
322             data.addAll(events);
323             return StatsManager.PULL_SUCCESS;
324         }
325 
pullEvents(int atomTag)326         private List<StatsEvent> pullEvents(int atomTag) {
327             switch (atomTag) {
328                 case FrameworkStatsLog.FRAMEWORK_WAKELOCK_INFO:
329                     return pullFrameworkWakelockInfoAtoms();
330                 default:
331                     return null;
332             }
333         }
334     }
335 }
336