blob: d3f77b64bc2f667af479d5176b1229c90b7ec9b0 [file] [log] [blame]
John Reckedc524c2015-03-18 15:24:33 -07001/*
2 * Copyright (C) 2015 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.server;
18
John Reckdf1742e2017-01-19 15:56:21 -080019import android.app.AlarmManager;
Jeff Sharkeycd654482016-01-08 17:42:11 -070020import android.app.AppOpsManager;
John Reckedc524c2015-03-18 15:24:33 -070021import android.content.Context;
John Reckdf1742e2017-01-19 15:56:21 -080022import android.content.pm.PackageInfo;
23import android.content.pm.PackageManager;
John Reckedc524c2015-03-18 15:24:33 -070024import android.os.Binder;
John Reckdf1742e2017-01-19 15:56:21 -080025import android.os.Environment;
26import android.os.Handler;
27import android.os.HandlerThread;
John Reckedc524c2015-03-18 15:24:33 -070028import android.os.IBinder;
29import android.os.MemoryFile;
John Reckdf1742e2017-01-19 15:56:21 -080030import android.os.Message;
John Reckedc524c2015-03-18 15:24:33 -070031import android.os.ParcelFileDescriptor;
John Reckdf1742e2017-01-19 15:56:21 -080032import android.os.Process;
John Reckedc524c2015-03-18 15:24:33 -070033import android.os.RemoteException;
John Reckdf1742e2017-01-19 15:56:21 -080034import android.os.Trace;
Tony Mak82566802017-03-20 18:35:43 +000035import android.os.UserHandle;
John Reckedc524c2015-03-18 15:24:33 -070036import android.util.Log;
37import android.view.IGraphicsStats;
John Reckdf1742e2017-01-19 15:56:21 -080038import android.view.IGraphicsStatsCallback;
John Reckedc524c2015-03-18 15:24:33 -070039
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060040import com.android.internal.util.DumpUtils;
41
John Reckdf1742e2017-01-19 15:56:21 -080042import java.io.File;
John Reckedc524c2015-03-18 15:24:33 -070043import java.io.FileDescriptor;
44import java.io.IOException;
45import java.io.PrintWriter;
46import java.util.ArrayList;
John Reckdf1742e2017-01-19 15:56:21 -080047import java.util.Arrays;
48import java.util.Calendar;
49import java.util.HashSet;
50import java.util.TimeZone;
John Reckedc524c2015-03-18 15:24:33 -070051
52/**
53 * This service's job is to collect aggregate rendering profile data. It
54 * does this by allowing rendering processes to request an ashmem buffer
John Reckdf1742e2017-01-19 15:56:21 -080055 * to place their stats into.
John Reckedc524c2015-03-18 15:24:33 -070056 *
John Reckdf1742e2017-01-19 15:56:21 -080057 * Buffers are rotated on a daily (in UTC) basis and only the 3 most-recent days
58 * are kept.
John Reckedc524c2015-03-18 15:24:33 -070059 *
John Reckdf1742e2017-01-19 15:56:21 -080060 * The primary consumer of this is incident reports and automated metric checking. It is not
61 * intended for end-developer consumption, for that we have gfxinfo.
John Reckedc524c2015-03-18 15:24:33 -070062 *
John Reckdf1742e2017-01-19 15:56:21 -080063 * Buffer rotation process:
64 * 1) Alarm fires
65 * 2) onRotateGraphicsStatsBuffer() is sent to all active processes
66 * 3) Upon receiving the callback, the process will stop using the previous ashmem buffer and
67 * request a new one.
68 * 4) When that request is received we now know that the ashmem region is no longer in use so
69 * it gets queued up for saving to disk and a new ashmem region is created and returned
70 * for the process to use.
John Reckedc524c2015-03-18 15:24:33 -070071 *
72 * @hide */
73public class GraphicsStatsService extends IGraphicsStats.Stub {
74 public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";
75
76 private static final String TAG = "GraphicsStatsService";
John Reckdf1742e2017-01-19 15:56:21 -080077
78 private static final int SAVE_BUFFER = 1;
79 private static final int DELETE_OLD = 2;
80
81 // This isn't static because we need this to happen after registerNativeMethods, however
82 // the class is loaded (and thus static ctor happens) before that occurs.
83 private final int ASHMEM_SIZE = nGetAshmemSize();
84 private final byte[] ZERO_DATA = new byte[ASHMEM_SIZE];
John Reckedc524c2015-03-18 15:24:33 -070085
86 private final Context mContext;
Jeff Sharkeycd654482016-01-08 17:42:11 -070087 private final AppOpsManager mAppOps;
John Reckdf1742e2017-01-19 15:56:21 -080088 private final AlarmManager mAlarmManager;
John Reckedc524c2015-03-18 15:24:33 -070089 private final Object mLock = new Object();
90 private ArrayList<ActiveBuffer> mActive = new ArrayList<>();
John Reckdf1742e2017-01-19 15:56:21 -080091 private File mGraphicsStatsDir;
92 private final Object mFileAccessLock = new Object();
93 private Handler mWriteOutHandler;
94 private boolean mRotateIsScheduled = false;
John Reckedc524c2015-03-18 15:24:33 -070095
96 public GraphicsStatsService(Context context) {
97 mContext = context;
Jeff Sharkeycd654482016-01-08 17:42:11 -070098 mAppOps = context.getSystemService(AppOpsManager.class);
John Reckdf1742e2017-01-19 15:56:21 -080099 mAlarmManager = context.getSystemService(AlarmManager.class);
100 File systemDataDir = new File(Environment.getDataDirectory(), "system");
101 mGraphicsStatsDir = new File(systemDataDir, "graphicsstats");
102 mGraphicsStatsDir.mkdirs();
103 if (!mGraphicsStatsDir.exists()) {
104 throw new IllegalStateException("Graphics stats directory does not exist: "
105 + mGraphicsStatsDir.getAbsolutePath());
106 }
107 HandlerThread bgthread = new HandlerThread("GraphicsStats-disk", Process.THREAD_PRIORITY_BACKGROUND);
108 bgthread.start();
109
110 mWriteOutHandler = new Handler(bgthread.getLooper(), new Handler.Callback() {
111 @Override
112 public boolean handleMessage(Message msg) {
113 switch (msg.what) {
114 case SAVE_BUFFER:
115 saveBuffer((HistoricalBuffer) msg.obj);
116 break;
117 case DELETE_OLD:
118 deleteOldBuffers();
119 break;
120 }
121 return true;
122 }
123 });
124 }
125
126 /**
127 * Current rotation policy is to rotate at midnight UTC. We don't specify RTC_WAKEUP because
128 * rotation can be delayed if there's otherwise no activity. However exact is used because
129 * we don't want the system to delay it by TOO much.
130 */
131 private void scheduleRotateLocked() {
132 if (mRotateIsScheduled) {
133 return;
134 }
135 mRotateIsScheduled = true;
136 Calendar calendar = normalizeDate(System.currentTimeMillis());
137 calendar.add(Calendar.DATE, 1);
138 mAlarmManager.setExact(AlarmManager.RTC, calendar.getTimeInMillis(), TAG, this::onAlarm,
139 mWriteOutHandler);
140 }
141
142 private void onAlarm() {
John Reck37dfb422017-02-22 17:01:30 -0800143 // We need to make a copy since some of the callbacks won't be proxy and thus
144 // can result in a re-entrant acquisition of mLock that would result in a modification
145 // of mActive during iteration.
146 ActiveBuffer[] activeCopy;
John Reckdf1742e2017-01-19 15:56:21 -0800147 synchronized (mLock) {
148 mRotateIsScheduled = false;
149 scheduleRotateLocked();
John Reck37dfb422017-02-22 17:01:30 -0800150 activeCopy = mActive.toArray(new ActiveBuffer[0]);
151 }
152 for (ActiveBuffer active : activeCopy) {
153 try {
154 active.mCallback.onRotateGraphicsStatsBuffer();
155 } catch (RemoteException e) {
156 Log.w(TAG, String.format("Failed to notify '%s' (pid=%d) to rotate buffers",
157 active.mInfo.packageName, active.mPid), e);
John Reckdf1742e2017-01-19 15:56:21 -0800158 }
159 }
160 // Give a few seconds for everyone to rotate before doing the cleanup
161 mWriteOutHandler.sendEmptyMessageDelayed(DELETE_OLD, 10000);
John Reckedc524c2015-03-18 15:24:33 -0700162 }
163
164 @Override
John Reckdf1742e2017-01-19 15:56:21 -0800165 public ParcelFileDescriptor requestBufferForProcess(String packageName, IGraphicsStatsCallback token)
John Reckedc524c2015-03-18 15:24:33 -0700166 throws RemoteException {
167 int uid = Binder.getCallingUid();
168 int pid = Binder.getCallingPid();
169 ParcelFileDescriptor pfd = null;
170 long callingIdentity = Binder.clearCallingIdentity();
171 try {
Jeff Sharkeycd654482016-01-08 17:42:11 -0700172 mAppOps.checkPackage(uid, packageName);
Tony Mak82566802017-03-20 18:35:43 +0000173 PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(
174 packageName,
175 0,
176 UserHandle.getUserId(uid));
John Reckedc524c2015-03-18 15:24:33 -0700177 synchronized (mLock) {
John Reckdf1742e2017-01-19 15:56:21 -0800178 pfd = requestBufferForProcessLocked(token, uid, pid, packageName, info.versionCode);
John Reckedc524c2015-03-18 15:24:33 -0700179 }
John Reckdf1742e2017-01-19 15:56:21 -0800180 } catch (PackageManager.NameNotFoundException ex) {
181 throw new RemoteException("Unable to find package: '" + packageName + "'");
John Reckedc524c2015-03-18 15:24:33 -0700182 } finally {
183 Binder.restoreCallingIdentity(callingIdentity);
184 }
185 return pfd;
186 }
187
188 private ParcelFileDescriptor getPfd(MemoryFile file) {
189 try {
John Reckdf1742e2017-01-19 15:56:21 -0800190 if (!file.getFileDescriptor().valid()) {
191 throw new IllegalStateException("Invalid file descriptor");
192 }
John Reckedc524c2015-03-18 15:24:33 -0700193 return new ParcelFileDescriptor(file.getFileDescriptor());
194 } catch (IOException ex) {
195 throw new IllegalStateException("Failed to get PFD from memory file", ex);
196 }
197 }
198
John Reckdf1742e2017-01-19 15:56:21 -0800199 private ParcelFileDescriptor requestBufferForProcessLocked(IGraphicsStatsCallback token,
200 int uid, int pid, String packageName, int versionCode) throws RemoteException {
201 ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName, versionCode);
202 scheduleRotateLocked();
John Reckedc524c2015-03-18 15:24:33 -0700203 return getPfd(buffer.mProcessBuffer);
204 }
205
John Reckdf1742e2017-01-19 15:56:21 -0800206 private Calendar normalizeDate(long timestamp) {
207 Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
208 calendar.setTimeInMillis(timestamp);
209 calendar.set(Calendar.HOUR_OF_DAY, 0);
210 calendar.set(Calendar.MINUTE, 0);
211 calendar.set(Calendar.SECOND, 0);
212 calendar.set(Calendar.MILLISECOND, 0);
213 return calendar;
214 }
215
216 private File pathForApp(BufferInfo info) {
217 String subPath = String.format("%d/%s/%d/total",
218 normalizeDate(info.startTime).getTimeInMillis(), info.packageName, info.versionCode);
219 return new File(mGraphicsStatsDir, subPath);
220 }
221
222 private void saveBuffer(HistoricalBuffer buffer) {
223 if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
224 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "saving graphicsstats for " + buffer.mInfo.packageName);
225 }
226 synchronized (mFileAccessLock) {
227 File path = pathForApp(buffer.mInfo);
228 File parent = path.getParentFile();
229 parent.mkdirs();
230 if (!parent.exists()) {
231 Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'");
232 return;
233 }
234 nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.packageName, buffer.mInfo.versionCode,
235 buffer.mInfo.startTime, buffer.mInfo.endTime, buffer.mData);
236 }
237 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
238 }
239
240 private void deleteRecursiveLocked(File file) {
241 if (file.isDirectory()) {
242 for (File child : file.listFiles()) {
243 deleteRecursiveLocked(child);
244 }
245 }
246 if (!file.delete()) {
247 Log.w(TAG, "Failed to delete '" + file.getAbsolutePath() + "'!");
248 }
249 }
250
251 private void deleteOldBuffers() {
252 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "deleting old graphicsstats buffers");
253 synchronized (mFileAccessLock) {
254 File[] files = mGraphicsStatsDir.listFiles();
255 if (files == null || files.length <= 3) {
256 return;
257 }
258 long[] sortedDates = new long[files.length];
259 for (int i = 0; i < files.length; i++) {
260 try {
261 sortedDates[i] = Long.parseLong(files[i].getName());
262 } catch (NumberFormatException ex) {
263 // Skip unrecognized folders
264 }
265 }
266 if (sortedDates.length <= 3) {
267 return;
268 }
269 Arrays.sort(sortedDates);
270 for (int i = 0; i < sortedDates.length - 3; i++) {
271 deleteRecursiveLocked(new File(mGraphicsStatsDir, Long.toString(sortedDates[i])));
272 }
273 }
274 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
275 }
276
277 private void addToSaveQueue(ActiveBuffer buffer) {
278 try {
279 HistoricalBuffer data = new HistoricalBuffer(buffer);
280 Message.obtain(mWriteOutHandler, SAVE_BUFFER, data).sendToTarget();
281 } catch (IOException e) {
282 Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.packageName, e);
283 }
284 buffer.closeAllBuffers();
285 }
286
John Reckedc524c2015-03-18 15:24:33 -0700287 private void processDied(ActiveBuffer buffer) {
288 synchronized (mLock) {
289 mActive.remove(buffer);
John Reckedc524c2015-03-18 15:24:33 -0700290 }
John Reckdf1742e2017-01-19 15:56:21 -0800291 addToSaveQueue(buffer);
John Reckedc524c2015-03-18 15:24:33 -0700292 }
293
John Reckdf1742e2017-01-19 15:56:21 -0800294 private ActiveBuffer fetchActiveBuffersLocked(IGraphicsStatsCallback token, int uid, int pid,
295 String packageName, int versionCode) throws RemoteException {
John Reckedc524c2015-03-18 15:24:33 -0700296 int size = mActive.size();
John Reckdf1742e2017-01-19 15:56:21 -0800297 long today = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
John Reckedc524c2015-03-18 15:24:33 -0700298 for (int i = 0; i < size; i++) {
John Reckdf1742e2017-01-19 15:56:21 -0800299 ActiveBuffer buffer = mActive.get(i);
300 if (buffer.mPid == pid
301 && buffer.mUid == uid) {
302 // If the buffer is too old we remove it and return a new one
303 if (buffer.mInfo.startTime < today) {
304 buffer.binderDied();
305 break;
306 } else {
307 return buffer;
308 }
John Reckedc524c2015-03-18 15:24:33 -0700309 }
310 }
311 // Didn't find one, need to create it
312 try {
John Reckdf1742e2017-01-19 15:56:21 -0800313 ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName, versionCode);
John Reckedc524c2015-03-18 15:24:33 -0700314 mActive.add(buffers);
315 return buffers;
316 } catch (IOException ex) {
317 throw new RemoteException("Failed to allocate space");
318 }
319 }
320
John Reckdf1742e2017-01-19 15:56:21 -0800321 private HashSet<File> dumpActiveLocked(long dump, ArrayList<HistoricalBuffer> buffers) {
322 HashSet<File> skipFiles = new HashSet<>(buffers.size());
323 for (int i = 0; i < buffers.size(); i++) {
324 HistoricalBuffer buffer = buffers.get(i);
325 File path = pathForApp(buffer.mInfo);
326 skipFiles.add(path);
327 nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.packageName,
328 buffer.mInfo.versionCode, buffer.mInfo.startTime, buffer.mInfo.endTime,
329 buffer.mData);
330 }
331 return skipFiles;
332 }
333
334 private void dumpHistoricalLocked(long dump, HashSet<File> skipFiles) {
335 for (File date : mGraphicsStatsDir.listFiles()) {
336 for (File pkg : date.listFiles()) {
337 for (File version : pkg.listFiles()) {
338 File data = new File(version, "total");
339 if (skipFiles.contains(data)) {
340 continue;
341 }
342 nAddToDump(dump, data.getAbsolutePath());
John Reckedc524c2015-03-18 15:24:33 -0700343 }
John Reckedc524c2015-03-18 15:24:33 -0700344 }
345 }
John Reckedc524c2015-03-18 15:24:33 -0700346 }
347
348 @Override
349 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
Jeff Sharkey6df866a2017-03-31 14:08:23 -0600350 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, fout)) return;
John Reckdf1742e2017-01-19 15:56:21 -0800351 boolean dumpProto = false;
352 for (String str : args) {
353 if ("--proto".equals(str)) {
354 dumpProto = true;
355 break;
356 }
357 }
358 ArrayList<HistoricalBuffer> buffers;
John Reckedc524c2015-03-18 15:24:33 -0700359 synchronized (mLock) {
John Reckdf1742e2017-01-19 15:56:21 -0800360 buffers = new ArrayList<>(mActive.size());
John Reckedc524c2015-03-18 15:24:33 -0700361 for (int i = 0; i < mActive.size(); i++) {
John Reckedc524c2015-03-18 15:24:33 -0700362 try {
John Reckdf1742e2017-01-19 15:56:21 -0800363 buffers.add(new HistoricalBuffer(mActive.get(i)));
364 } catch (IOException ex) {
365 // Ignore
John Reckedc524c2015-03-18 15:24:33 -0700366 }
John Reckedc524c2015-03-18 15:24:33 -0700367 }
John Reckdf1742e2017-01-19 15:56:21 -0800368 }
369 long dump = nCreateDump(fd.getInt$(), dumpProto);
370 try {
371 synchronized (mFileAccessLock) {
372 HashSet<File> skipList = dumpActiveLocked(dump, buffers);
373 buffers.clear();
374 dumpHistoricalLocked(dump, skipList);
John Reckedc524c2015-03-18 15:24:33 -0700375 }
John Reckdf1742e2017-01-19 15:56:21 -0800376 } finally {
377 nFinishDump(dump);
378 }
379 }
380
381 private static native int nGetAshmemSize();
382 private static native long nCreateDump(int outFd, boolean isProto);
383 private static native void nAddToDump(long dump, String path, String packageName,
384 int versionCode, long startTime, long endTime, byte[] data);
385 private static native void nAddToDump(long dump, String path);
386 private static native void nFinishDump(long dump);
387 private static native void nSaveBuffer(String path, String packageName, int versionCode,
388 long startTime, long endTime, byte[] data);
389
390 private final class BufferInfo {
391 final String packageName;
392 final int versionCode;
393 long startTime;
394 long endTime;
395
396 BufferInfo(String packageName, int versionCode, long startTime) {
397 this.packageName = packageName;
398 this.versionCode = versionCode;
399 this.startTime = startTime;
John Reckedc524c2015-03-18 15:24:33 -0700400 }
401 }
402
403 private final class ActiveBuffer implements DeathRecipient {
John Reckdf1742e2017-01-19 15:56:21 -0800404 final BufferInfo mInfo;
John Reckedc524c2015-03-18 15:24:33 -0700405 final int mUid;
406 final int mPid;
John Reckdf1742e2017-01-19 15:56:21 -0800407 final IGraphicsStatsCallback mCallback;
John Reckedc524c2015-03-18 15:24:33 -0700408 final IBinder mToken;
409 MemoryFile mProcessBuffer;
John Reckedc524c2015-03-18 15:24:33 -0700410
John Reckdf1742e2017-01-19 15:56:21 -0800411 ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName, int versionCode)
John Reckedc524c2015-03-18 15:24:33 -0700412 throws RemoteException, IOException {
John Reckdf1742e2017-01-19 15:56:21 -0800413 mInfo = new BufferInfo(packageName, versionCode, System.currentTimeMillis());
John Reckedc524c2015-03-18 15:24:33 -0700414 mUid = uid;
415 mPid = pid;
John Reckdf1742e2017-01-19 15:56:21 -0800416 mCallback = token;
417 mToken = mCallback.asBinder();
John Reckedc524c2015-03-18 15:24:33 -0700418 mToken.linkToDeath(this, 0);
John Reckdf1742e2017-01-19 15:56:21 -0800419 mProcessBuffer = new MemoryFile("GFXStats-" + pid, ASHMEM_SIZE);
420 mProcessBuffer.writeBytes(ZERO_DATA, 0, 0, ASHMEM_SIZE);
John Reckedc524c2015-03-18 15:24:33 -0700421 }
422
423 @Override
424 public void binderDied() {
425 mToken.unlinkToDeath(this, 0);
426 processDied(this);
427 }
428
429 void closeAllBuffers() {
430 if (mProcessBuffer != null) {
431 mProcessBuffer.close();
432 mProcessBuffer = null;
433 }
434 }
435 }
436
John Reckdf1742e2017-01-19 15:56:21 -0800437 private final class HistoricalBuffer {
438 final BufferInfo mInfo;
439 final byte[] mData = new byte[ASHMEM_SIZE];
440 HistoricalBuffer(ActiveBuffer active) throws IOException {
441 mInfo = active.mInfo;
442 mInfo.endTime = System.currentTimeMillis();
443 active.mProcessBuffer.readBytes(mData, 0, 0, ASHMEM_SIZE);
John Reckedc524c2015-03-18 15:24:33 -0700444 }
445 }
446}