/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Binder; import android.os.IBinder; import android.os.MemoryFile; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; import android.view.IGraphicsStats; import android.view.ThreadedRenderer; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; /** * This service's job is to collect aggregate rendering profile data. It * does this by allowing rendering processes to request an ashmem buffer * to place their stats into. This buffer will be pre-initialized with historical * data for that process if it exists (if the userId & packageName match a buffer * in the historical log) * * This service does not itself attempt to understand the data in the buffer, * its primary job is merely to manage distributing these buffers. However, * it is assumed that this buffer is for ThreadedRenderer and delegates * directly to ThreadedRenderer for dumping buffers. * * MEMORY USAGE: * * This class consumes UP TO: * 1) [active rendering processes] * (ASHMEM_SIZE * 2) * 2) ASHMEM_SIZE (for scratch space used during dumping) * 3) ASHMEM_SIZE * HISTORY_SIZE * * Currently ASHMEM_SIZE is 256 bytes and HISTORY_SIZE is 20. Assuming * the system then also has 10 active rendering processes in the worst case * this would end up using under 14KiB (12KiB for the buffers, plus some overhead * for userId, pid, package name, and a couple other objects) * * @hide */ public class GraphicsStatsService extends IGraphicsStats.Stub { public static final String GRAPHICS_STATS_SERVICE = "graphicsstats"; private static final String TAG = "GraphicsStatsService"; private static final int ASHMEM_SIZE = 256; private static final int HISTORY_SIZE = 20; private final Context mContext; private final Object mLock = new Object(); private ArrayList<ActiveBuffer> mActive = new ArrayList<>(); private HistoricalData[] mHistoricalLog = new HistoricalData[HISTORY_SIZE]; private int mNextHistoricalSlot = 0; private byte[] mTempBuffer = new byte[ASHMEM_SIZE]; public GraphicsStatsService(Context context) { mContext = context; } private boolean isValid(int uid, String packageName) { try { PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0); return info.applicationInfo.uid == uid; } catch (NameNotFoundException e) { } return false; } @Override public ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token) throws RemoteException { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); ParcelFileDescriptor pfd = null; long callingIdentity = Binder.clearCallingIdentity(); try { if (!isValid(uid, packageName)) { throw new RemoteException("Invalid package name"); } synchronized (mLock) { pfd = requestBufferForProcessLocked(token, uid, pid, packageName); } } finally { Binder.restoreCallingIdentity(callingIdentity); } return pfd; } private ParcelFileDescriptor getPfd(MemoryFile file) { try { return new ParcelFileDescriptor(file.getFileDescriptor()); } catch (IOException ex) { throw new IllegalStateException("Failed to get PFD from memory file", ex); } } private ParcelFileDescriptor requestBufferForProcessLocked(IBinder token, int uid, int pid, String packageName) throws RemoteException { ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName); return getPfd(buffer.mProcessBuffer); } private void processDied(ActiveBuffer buffer) { synchronized (mLock) { mActive.remove(buffer); Log.d("GraphicsStats", "Buffer count: " + mActive.size()); } HistoricalData data = buffer.mPreviousData; buffer.mPreviousData = null; if (data == null) { data = mHistoricalLog[mNextHistoricalSlot]; if (data == null) { data = new HistoricalData(); } } data.update(buffer.mPackageName, buffer.mUid, buffer.mProcessBuffer); buffer.closeAllBuffers(); mHistoricalLog[mNextHistoricalSlot] = data; mNextHistoricalSlot = (mNextHistoricalSlot + 1) % mHistoricalLog.length; } private ActiveBuffer fetchActiveBuffersLocked(IBinder token, int uid, int pid, String packageName) throws RemoteException { int size = mActive.size(); for (int i = 0; i < size; i++) { ActiveBuffer buffers = mActive.get(i); if (buffers.mPid == pid && buffers.mUid == uid) { return buffers; } } // Didn't find one, need to create it try { ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName); mActive.add(buffers); return buffers; } catch (IOException ex) { throw new RemoteException("Failed to allocate space"); } } private HistoricalData removeHistoricalDataLocked(int uid, String packageName) { for (int i = 0; i < mHistoricalLog.length; i++) { final HistoricalData data = mHistoricalLog[i]; if (data != null && data.mUid == uid && data.mPackageName.equals(packageName)) { if (i == mNextHistoricalSlot) { mHistoricalLog[i] = null; } else { mHistoricalLog[i] = mHistoricalLog[mNextHistoricalSlot]; mHistoricalLog[mNextHistoricalSlot] = null; } return data; } } return null; } @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); synchronized (mLock) { for (int i = 0; i < mActive.size(); i++) { final ActiveBuffer buffer = mActive.get(i); fout.print("Package: "); fout.print(buffer.mPackageName); fout.flush(); try { buffer.mProcessBuffer.readBytes(mTempBuffer, 0, 0, ASHMEM_SIZE); ThreadedRenderer.dumpProfileData(mTempBuffer, fd); } catch (IOException e) { fout.println("Failed to dump"); } fout.println(); } for (HistoricalData buffer : mHistoricalLog) { if (buffer == null) continue; fout.print("Package: "); fout.print(buffer.mPackageName); fout.flush(); ThreadedRenderer.dumpProfileData(buffer.mBuffer, fd); fout.println(); } } } private final class ActiveBuffer implements DeathRecipient { final int mUid; final int mPid; final String mPackageName; final IBinder mToken; MemoryFile mProcessBuffer; HistoricalData mPreviousData; ActiveBuffer(IBinder token, int uid, int pid, String packageName) throws RemoteException, IOException { mUid = uid; mPid = pid; mPackageName = packageName; mToken = token; mToken.linkToDeath(this, 0); mProcessBuffer = new MemoryFile("GFXStats-" + uid, ASHMEM_SIZE); mPreviousData = removeHistoricalDataLocked(mUid, mPackageName); if (mPreviousData != null) { mProcessBuffer.writeBytes(mPreviousData.mBuffer, 0, 0, ASHMEM_SIZE); } } @Override public void binderDied() { mToken.unlinkToDeath(this, 0); processDied(this); } void closeAllBuffers() { if (mProcessBuffer != null) { mProcessBuffer.close(); mProcessBuffer = null; } } } private final static class HistoricalData { final byte[] mBuffer = new byte[ASHMEM_SIZE]; int mUid; String mPackageName; void update(String packageName, int uid, MemoryFile file) { mUid = uid; mPackageName = packageName; try { file.readBytes(mBuffer, 0, 0, ASHMEM_SIZE); } catch (IOException e) {} } } }