blob: 2c48506494bc1881be074e9e4ca453f6ba4fbcea [file] [log] [blame]
Fyodor Kupolovca348512018-01-10 18:05:53 -08001/*
2 * Copyright (C) 2017 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
17package com.android.internal.os;
18
19import android.os.Binder;
20import android.os.SystemClock;
21import android.util.ArrayMap;
22import android.util.SparseArray;
23
24import com.android.internal.annotations.VisibleForTesting;
25import com.android.internal.util.Preconditions;
26
27import java.io.PrintWriter;
28import java.util.ArrayList;
29import java.util.HashMap;
30import java.util.List;
31import java.util.Map;
32import java.util.Queue;
33import java.util.concurrent.ConcurrentLinkedQueue;
34
35/**
36 * Collects statistics about CPU time spent per binder call across multiple dimensions, e.g.
37 * per thread, uid or call description.
38 */
39public class BinderCallsStats {
40 private static final int CALL_SESSIONS_POOL_SIZE = 100;
41 private static final BinderCallsStats sInstance = new BinderCallsStats();
42
43 private volatile boolean mTrackingEnabled = false;
44 private final SparseArray<UidEntry> mUidEntries = new SparseArray<>();
45 private final Queue<CallSession> mCallSessionsPool = new ConcurrentLinkedQueue<>();
46
47 private BinderCallsStats() {
48 }
49
50 @VisibleForTesting
51 public BinderCallsStats(boolean trackingEnabled) {
52 mTrackingEnabled = trackingEnabled;
53 }
54
55 public CallSession callStarted(Binder binder, int code) {
56 if (!mTrackingEnabled) {
57 return null;
58 }
59
60 return callStarted(binder.getClass().getName(), code);
61 }
62
63 private CallSession callStarted(String className, int code) {
64 CallSession s = mCallSessionsPool.poll();
65 if (s == null) {
66 s = new CallSession();
67 }
68 s.mCallStat.className = className;
69 s.mCallStat.msg = code;
70
71 s.mStarted = getThreadTimeMicro();
72 return s;
73 }
74
75 public void callEnded(CallSession s) {
76 if (!mTrackingEnabled) {
77 return;
78 }
79 Preconditions.checkNotNull(s);
80 final long cpuTimeNow = getThreadTimeMicro();
81 final long duration = cpuTimeNow - s.mStarted;
82 s.mCallingUId = Binder.getCallingUid();
83
84 synchronized (mUidEntries) {
85 UidEntry uidEntry = mUidEntries.get(s.mCallingUId);
86 if (uidEntry == null) {
87 uidEntry = new UidEntry(s.mCallingUId);
88 mUidEntries.put(s.mCallingUId, uidEntry);
89 }
90
91 // Find CallDesc entry and update its total time
92 CallStat callStat = uidEntry.mCallStats.get(s.mCallStat);
93 // Only create CallStat if it's a new entry, otherwise update existing instance
94 if (callStat == null) {
95 callStat = new CallStat(s.mCallStat.className, s.mCallStat.msg);
96 uidEntry.mCallStats.put(callStat, callStat);
97 }
98 uidEntry.time += duration;
99 uidEntry.callCount++;
100 callStat.callCount++;
101 callStat.time += duration;
102 }
103 if (mCallSessionsPool.size() < CALL_SESSIONS_POOL_SIZE) {
104 mCallSessionsPool.add(s);
105 }
106 }
107
108 public void dump(PrintWriter pw) {
109 Map<Integer, Long> uidTimeMap = new HashMap<>();
110 Map<Integer, Long> uidCallCountMap = new HashMap<>();
111 long totalCallsCount = 0;
112 long totalCallsTime = 0;
113 int uidEntriesSize = mUidEntries.size();
114 List<UidEntry> entries = new ArrayList<>();
115 synchronized (mUidEntries) {
116 for (int i = 0; i < uidEntriesSize; i++) {
117 UidEntry e = mUidEntries.valueAt(i);
118 entries.add(e);
119 totalCallsTime += e.time;
120 // Update per-uid totals
121 Long totalTimePerUid = uidTimeMap.get(e.uid);
122 uidTimeMap.put(e.uid,
123 totalTimePerUid == null ? e.time : totalTimePerUid + e.time);
124 Long totalCallsPerUid = uidCallCountMap.get(e.uid);
125 uidCallCountMap.put(e.uid, totalCallsPerUid == null ? e.callCount
126 : totalCallsPerUid + e.callCount);
127 totalCallsCount += e.callCount;
128 }
129 }
130 pw.println("Binder call stats:");
131 pw.println(" Raw data (uid,call_desc,time):");
132 entries.sort((o1, o2) -> {
133 if (o1.time < o2.time) {
134 return 1;
135 } else if (o1.time > o2.time) {
136 return -1;
137 }
138 return 0;
139 });
140 StringBuilder sb = new StringBuilder();
141 for (UidEntry uidEntry : entries) {
142 List<CallStat> callStats = new ArrayList<>(uidEntry.mCallStats.keySet());
143 callStats.sort((o1, o2) -> {
144 if (o1.time < o2.time) {
145 return 1;
146 } else if (o1.time > o2.time) {
147 return -1;
148 }
149 return 0;
150 });
151 for (CallStat e : callStats) {
152 sb.setLength(0);
153 sb.append(" ")
154 .append(uidEntry.uid).append(",").append(e).append(',').append(e.time);
155 pw.println(sb);
156 }
157 }
158 pw.println();
159 pw.println(" Per UID Summary(UID: time, total_time_percentage, calls_count):");
160 List<Map.Entry<Integer, Long>> uidTotals = new ArrayList<>(uidTimeMap.entrySet());
161 uidTotals.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue()));
162 for (Map.Entry<Integer, Long> uidTotal : uidTotals) {
163 Long callCount = uidCallCountMap.get(uidTotal.getKey());
164 pw.println(String.format(" %5d: %11d %3.0f%% %8d",
165 uidTotal.getKey(), uidTotal.getValue(),
166 100d * uidTotal.getValue() / totalCallsTime, callCount));
167 }
168 pw.println();
169 pw.println(String.format(" Summary: total_time=%d, "
170 + "calls_count=%d, avg_call_time=%.0f",
171 totalCallsTime, totalCallsCount,
172 (double)totalCallsTime / totalCallsCount));
173 }
174
175 private static long getThreadTimeMicro() {
176 return SystemClock.currentThreadTimeMicro();
177 }
178
179 public static BinderCallsStats getInstance() {
180 return sInstance;
181 }
182
183 public void setTrackingEnabled(boolean enabled) {
184 mTrackingEnabled = enabled;
185 }
186
187 public boolean isTrackingEnabled() {
188 return mTrackingEnabled;
189 }
190
191 private static class CallStat {
192 String className;
193 int msg;
194 long time;
195 long callCount;
196
197 CallStat() {
198 }
199
200 CallStat(String className, int msg) {
201 this.className = className;
202 this.msg = msg;
203 }
204
205 @Override
206 public boolean equals(Object o) {
207 if (this == o) {
208 return true;
209 }
210
211 CallStat callStat = (CallStat) o;
212
213 return msg == callStat.msg && (className == callStat.className);
214 }
215
216 @Override
217 public int hashCode() {
218 int result = className.hashCode();
219 result = 31 * result + msg;
220 return result;
221 }
222
223 @Override
224 public String toString() {
225 return className + "/" + msg;
226 }
227 }
228
229 public static class CallSession {
230 int mCallingUId;
231 long mStarted;
232 CallStat mCallStat = new CallStat();
233 }
234
235 private static class UidEntry {
236 int uid;
237 long time;
238 long callCount;
239
240 UidEntry(int uid) {
241 this.uid = uid;
242 }
243
244 // Aggregate time spent per each call name: call_desc -> cpu_time_micros
245 Map<CallStat, CallStat> mCallStats = new ArrayMap<>();
246
247 @Override
248 public String toString() {
249 return "UidEntry{" +
250 "time=" + time +
251 ", callCount=" + callCount +
252 ", mCallStats=" + mCallStats +
253 '}';
254 }
255
256 @Override
257 public boolean equals(Object o) {
258 if (this == o) {
259 return true;
260 }
261
262 UidEntry uidEntry = (UidEntry) o;
263 return uid == uidEntry.uid;
264 }
265
266 @Override
267 public int hashCode() {
268 return uid;
269 }
270 }
271
272}