blob: 4639d7586f8371a6f6b3372e4a05497eefca52c9 [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) {
Dianne Hackborn73453e42017-12-11 16:30:36 -0800178 pfd = requestBufferForProcessLocked(token, uid, pid, packageName,
179 info.getLongVersionCode());
John Reckedc524c2015-03-18 15:24:33 -0700180 }
John Reckdf1742e2017-01-19 15:56:21 -0800181 } catch (PackageManager.NameNotFoundException ex) {
182 throw new RemoteException("Unable to find package: '" + packageName + "'");
John Reckedc524c2015-03-18 15:24:33 -0700183 } finally {
184 Binder.restoreCallingIdentity(callingIdentity);
185 }
186 return pfd;
187 }
188
189 private ParcelFileDescriptor getPfd(MemoryFile file) {
190 try {
John Reckdf1742e2017-01-19 15:56:21 -0800191 if (!file.getFileDescriptor().valid()) {
192 throw new IllegalStateException("Invalid file descriptor");
193 }
John Reckedc524c2015-03-18 15:24:33 -0700194 return new ParcelFileDescriptor(file.getFileDescriptor());
195 } catch (IOException ex) {
196 throw new IllegalStateException("Failed to get PFD from memory file", ex);
197 }
198 }
199
John Reckdf1742e2017-01-19 15:56:21 -0800200 private ParcelFileDescriptor requestBufferForProcessLocked(IGraphicsStatsCallback token,
Dianne Hackborn73453e42017-12-11 16:30:36 -0800201 int uid, int pid, String packageName, long versionCode) throws RemoteException {
John Reckdf1742e2017-01-19 15:56:21 -0800202 ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName, versionCode);
203 scheduleRotateLocked();
John Reckedc524c2015-03-18 15:24:33 -0700204 return getPfd(buffer.mProcessBuffer);
205 }
206
John Reckdf1742e2017-01-19 15:56:21 -0800207 private Calendar normalizeDate(long timestamp) {
208 Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
209 calendar.setTimeInMillis(timestamp);
210 calendar.set(Calendar.HOUR_OF_DAY, 0);
211 calendar.set(Calendar.MINUTE, 0);
212 calendar.set(Calendar.SECOND, 0);
213 calendar.set(Calendar.MILLISECOND, 0);
214 return calendar;
215 }
216
217 private File pathForApp(BufferInfo info) {
218 String subPath = String.format("%d/%s/%d/total",
219 normalizeDate(info.startTime).getTimeInMillis(), info.packageName, info.versionCode);
220 return new File(mGraphicsStatsDir, subPath);
221 }
222
223 private void saveBuffer(HistoricalBuffer buffer) {
224 if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
225 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "saving graphicsstats for " + buffer.mInfo.packageName);
226 }
227 synchronized (mFileAccessLock) {
228 File path = pathForApp(buffer.mInfo);
229 File parent = path.getParentFile();
230 parent.mkdirs();
231 if (!parent.exists()) {
232 Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'");
233 return;
234 }
235 nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.packageName, buffer.mInfo.versionCode,
236 buffer.mInfo.startTime, buffer.mInfo.endTime, buffer.mData);
237 }
238 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
239 }
240
241 private void deleteRecursiveLocked(File file) {
242 if (file.isDirectory()) {
243 for (File child : file.listFiles()) {
244 deleteRecursiveLocked(child);
245 }
246 }
247 if (!file.delete()) {
248 Log.w(TAG, "Failed to delete '" + file.getAbsolutePath() + "'!");
249 }
250 }
251
252 private void deleteOldBuffers() {
253 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "deleting old graphicsstats buffers");
254 synchronized (mFileAccessLock) {
255 File[] files = mGraphicsStatsDir.listFiles();
256 if (files == null || files.length <= 3) {
257 return;
258 }
259 long[] sortedDates = new long[files.length];
260 for (int i = 0; i < files.length; i++) {
261 try {
262 sortedDates[i] = Long.parseLong(files[i].getName());
263 } catch (NumberFormatException ex) {
264 // Skip unrecognized folders
265 }
266 }
267 if (sortedDates.length <= 3) {
268 return;
269 }
270 Arrays.sort(sortedDates);
271 for (int i = 0; i < sortedDates.length - 3; i++) {
272 deleteRecursiveLocked(new File(mGraphicsStatsDir, Long.toString(sortedDates[i])));
273 }
274 }
275 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
276 }
277
278 private void addToSaveQueue(ActiveBuffer buffer) {
279 try {
280 HistoricalBuffer data = new HistoricalBuffer(buffer);
281 Message.obtain(mWriteOutHandler, SAVE_BUFFER, data).sendToTarget();
282 } catch (IOException e) {
283 Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.packageName, e);
284 }
285 buffer.closeAllBuffers();
286 }
287
John Reckedc524c2015-03-18 15:24:33 -0700288 private void processDied(ActiveBuffer buffer) {
289 synchronized (mLock) {
290 mActive.remove(buffer);
John Reckedc524c2015-03-18 15:24:33 -0700291 }
John Reckdf1742e2017-01-19 15:56:21 -0800292 addToSaveQueue(buffer);
John Reckedc524c2015-03-18 15:24:33 -0700293 }
294
John Reckdf1742e2017-01-19 15:56:21 -0800295 private ActiveBuffer fetchActiveBuffersLocked(IGraphicsStatsCallback token, int uid, int pid,
Dianne Hackborn73453e42017-12-11 16:30:36 -0800296 String packageName, long versionCode) throws RemoteException {
John Reckedc524c2015-03-18 15:24:33 -0700297 int size = mActive.size();
John Reckdf1742e2017-01-19 15:56:21 -0800298 long today = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
John Reckedc524c2015-03-18 15:24:33 -0700299 for (int i = 0; i < size; i++) {
John Reckdf1742e2017-01-19 15:56:21 -0800300 ActiveBuffer buffer = mActive.get(i);
301 if (buffer.mPid == pid
302 && buffer.mUid == uid) {
303 // If the buffer is too old we remove it and return a new one
304 if (buffer.mInfo.startTime < today) {
305 buffer.binderDied();
306 break;
307 } else {
308 return buffer;
309 }
John Reckedc524c2015-03-18 15:24:33 -0700310 }
311 }
312 // Didn't find one, need to create it
313 try {
John Reckdf1742e2017-01-19 15:56:21 -0800314 ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName, versionCode);
John Reckedc524c2015-03-18 15:24:33 -0700315 mActive.add(buffers);
316 return buffers;
317 } catch (IOException ex) {
318 throw new RemoteException("Failed to allocate space");
319 }
320 }
321
John Reckdf1742e2017-01-19 15:56:21 -0800322 private HashSet<File> dumpActiveLocked(long dump, ArrayList<HistoricalBuffer> buffers) {
323 HashSet<File> skipFiles = new HashSet<>(buffers.size());
324 for (int i = 0; i < buffers.size(); i++) {
325 HistoricalBuffer buffer = buffers.get(i);
326 File path = pathForApp(buffer.mInfo);
327 skipFiles.add(path);
328 nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.packageName,
329 buffer.mInfo.versionCode, buffer.mInfo.startTime, buffer.mInfo.endTime,
330 buffer.mData);
331 }
332 return skipFiles;
333 }
334
335 private void dumpHistoricalLocked(long dump, HashSet<File> skipFiles) {
336 for (File date : mGraphicsStatsDir.listFiles()) {
337 for (File pkg : date.listFiles()) {
338 for (File version : pkg.listFiles()) {
339 File data = new File(version, "total");
340 if (skipFiles.contains(data)) {
341 continue;
342 }
343 nAddToDump(dump, data.getAbsolutePath());
John Reckedc524c2015-03-18 15:24:33 -0700344 }
John Reckedc524c2015-03-18 15:24:33 -0700345 }
346 }
John Reckedc524c2015-03-18 15:24:33 -0700347 }
348
349 @Override
350 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
Jeff Sharkey6df866a2017-03-31 14:08:23 -0600351 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, fout)) return;
John Reckdf1742e2017-01-19 15:56:21 -0800352 boolean dumpProto = false;
353 for (String str : args) {
354 if ("--proto".equals(str)) {
355 dumpProto = true;
356 break;
357 }
358 }
359 ArrayList<HistoricalBuffer> buffers;
John Reckedc524c2015-03-18 15:24:33 -0700360 synchronized (mLock) {
John Reckdf1742e2017-01-19 15:56:21 -0800361 buffers = new ArrayList<>(mActive.size());
John Reckedc524c2015-03-18 15:24:33 -0700362 for (int i = 0; i < mActive.size(); i++) {
John Reckedc524c2015-03-18 15:24:33 -0700363 try {
John Reckdf1742e2017-01-19 15:56:21 -0800364 buffers.add(new HistoricalBuffer(mActive.get(i)));
365 } catch (IOException ex) {
366 // Ignore
John Reckedc524c2015-03-18 15:24:33 -0700367 }
John Reckedc524c2015-03-18 15:24:33 -0700368 }
John Reckdf1742e2017-01-19 15:56:21 -0800369 }
370 long dump = nCreateDump(fd.getInt$(), dumpProto);
371 try {
372 synchronized (mFileAccessLock) {
373 HashSet<File> skipList = dumpActiveLocked(dump, buffers);
374 buffers.clear();
375 dumpHistoricalLocked(dump, skipList);
John Reckedc524c2015-03-18 15:24:33 -0700376 }
John Reckdf1742e2017-01-19 15:56:21 -0800377 } finally {
378 nFinishDump(dump);
379 }
380 }
381
382 private static native int nGetAshmemSize();
383 private static native long nCreateDump(int outFd, boolean isProto);
384 private static native void nAddToDump(long dump, String path, String packageName,
Dianne Hackborn73453e42017-12-11 16:30:36 -0800385 long versionCode, long startTime, long endTime, byte[] data);
John Reckdf1742e2017-01-19 15:56:21 -0800386 private static native void nAddToDump(long dump, String path);
387 private static native void nFinishDump(long dump);
Dianne Hackborn73453e42017-12-11 16:30:36 -0800388 private static native void nSaveBuffer(String path, String packageName, long versionCode,
John Reckdf1742e2017-01-19 15:56:21 -0800389 long startTime, long endTime, byte[] data);
390
391 private final class BufferInfo {
392 final String packageName;
Dianne Hackborn73453e42017-12-11 16:30:36 -0800393 final long versionCode;
John Reckdf1742e2017-01-19 15:56:21 -0800394 long startTime;
395 long endTime;
396
Dianne Hackborn73453e42017-12-11 16:30:36 -0800397 BufferInfo(String packageName, long versionCode, long startTime) {
John Reckdf1742e2017-01-19 15:56:21 -0800398 this.packageName = packageName;
399 this.versionCode = versionCode;
400 this.startTime = startTime;
John Reckedc524c2015-03-18 15:24:33 -0700401 }
402 }
403
404 private final class ActiveBuffer implements DeathRecipient {
John Reckdf1742e2017-01-19 15:56:21 -0800405 final BufferInfo mInfo;
John Reckedc524c2015-03-18 15:24:33 -0700406 final int mUid;
407 final int mPid;
John Reckdf1742e2017-01-19 15:56:21 -0800408 final IGraphicsStatsCallback mCallback;
John Reckedc524c2015-03-18 15:24:33 -0700409 final IBinder mToken;
410 MemoryFile mProcessBuffer;
John Reckedc524c2015-03-18 15:24:33 -0700411
Dianne Hackborn73453e42017-12-11 16:30:36 -0800412 ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName,
413 long versionCode)
John Reckedc524c2015-03-18 15:24:33 -0700414 throws RemoteException, IOException {
John Reckdf1742e2017-01-19 15:56:21 -0800415 mInfo = new BufferInfo(packageName, versionCode, System.currentTimeMillis());
John Reckedc524c2015-03-18 15:24:33 -0700416 mUid = uid;
417 mPid = pid;
John Reckdf1742e2017-01-19 15:56:21 -0800418 mCallback = token;
419 mToken = mCallback.asBinder();
John Reckedc524c2015-03-18 15:24:33 -0700420 mToken.linkToDeath(this, 0);
John Reckdf1742e2017-01-19 15:56:21 -0800421 mProcessBuffer = new MemoryFile("GFXStats-" + pid, ASHMEM_SIZE);
422 mProcessBuffer.writeBytes(ZERO_DATA, 0, 0, ASHMEM_SIZE);
John Reckedc524c2015-03-18 15:24:33 -0700423 }
424
425 @Override
426 public void binderDied() {
427 mToken.unlinkToDeath(this, 0);
428 processDied(this);
429 }
430
431 void closeAllBuffers() {
432 if (mProcessBuffer != null) {
433 mProcessBuffer.close();
434 mProcessBuffer = null;
435 }
436 }
437 }
438
John Reckdf1742e2017-01-19 15:56:21 -0800439 private final class HistoricalBuffer {
440 final BufferInfo mInfo;
441 final byte[] mData = new byte[ASHMEM_SIZE];
442 HistoricalBuffer(ActiveBuffer active) throws IOException {
443 mInfo = active.mInfo;
444 mInfo.endTime = System.currentTimeMillis();
445 active.mProcessBuffer.readBytes(mData, 0, 0, ASHMEM_SIZE);
John Reckedc524c2015-03-18 15:24:33 -0700446 }
447 }
448}