1 /* 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 17 package com.android.server; 18 19 import android.app.AppOpsManager; 20 import android.content.Context; 21 import android.os.Binder; 22 import android.os.IBinder; 23 import android.os.MemoryFile; 24 import android.os.ParcelFileDescriptor; 25 import android.os.RemoteException; 26 import android.util.Log; 27 import android.view.IGraphicsStats; 28 import android.view.ThreadedRenderer; 29 30 import java.io.FileDescriptor; 31 import java.io.IOException; 32 import java.io.PrintWriter; 33 import java.util.ArrayList; 34 35 /** 36 * This service's job is to collect aggregate rendering profile data. It 37 * does this by allowing rendering processes to request an ashmem buffer 38 * to place their stats into. This buffer will be pre-initialized with historical 39 * data for that process if it exists (if the userId & packageName match a buffer 40 * in the historical log) 41 * 42 * This service does not itself attempt to understand the data in the buffer, 43 * its primary job is merely to manage distributing these buffers. However, 44 * it is assumed that this buffer is for ThreadedRenderer and delegates 45 * directly to ThreadedRenderer for dumping buffers. 46 * 47 * MEMORY USAGE: 48 * 49 * This class consumes UP TO: 50 * 1) [active rendering processes] * (ASHMEM_SIZE * 2) 51 * 2) ASHMEM_SIZE (for scratch space used during dumping) 52 * 3) ASHMEM_SIZE * HISTORY_SIZE 53 * 54 * This is currently under 20KiB total memory in the worst case of 55 * 20 processes in history + 10 unique active processes. 56 * 57 * @hide */ 58 public class GraphicsStatsService extends IGraphicsStats.Stub { 59 public static final String GRAPHICS_STATS_SERVICE = "graphicsstats"; 60 61 private static final String TAG = "GraphicsStatsService"; 62 private static final int ASHMEM_SIZE = 464; 63 private static final int HISTORY_SIZE = 20; 64 65 private final Context mContext; 66 private final AppOpsManager mAppOps; 67 private final Object mLock = new Object(); 68 private ArrayList<ActiveBuffer> mActive = new ArrayList<>(); 69 private HistoricalData[] mHistoricalLog = new HistoricalData[HISTORY_SIZE]; 70 private int mNextHistoricalSlot = 0; 71 private byte[] mTempBuffer = new byte[ASHMEM_SIZE]; 72 73 public GraphicsStatsService(Context context) { 74 mContext = context; 75 mAppOps = context.getSystemService(AppOpsManager.class); 76 } 77 78 @Override 79 public ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token) 80 throws RemoteException { 81 int uid = Binder.getCallingUid(); 82 int pid = Binder.getCallingPid(); 83 ParcelFileDescriptor pfd = null; 84 long callingIdentity = Binder.clearCallingIdentity(); 85 try { 86 mAppOps.checkPackage(uid, packageName); 87 synchronized (mLock) { 88 pfd = requestBufferForProcessLocked(token, uid, pid, packageName); 89 } 90 } finally { 91 Binder.restoreCallingIdentity(callingIdentity); 92 } 93 return pfd; 94 } 95 96 private ParcelFileDescriptor getPfd(MemoryFile file) { 97 try { 98 return new ParcelFileDescriptor(file.getFileDescriptor()); 99 } catch (IOException ex) { 100 throw new IllegalStateException("Failed to get PFD from memory file", ex); 101 } 102 } 103 104 private ParcelFileDescriptor requestBufferForProcessLocked(IBinder token, 105 int uid, int pid, String packageName) throws RemoteException { 106 ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName); 107 return getPfd(buffer.mProcessBuffer); 108 } 109 110 private void processDied(ActiveBuffer buffer) { 111 synchronized (mLock) { 112 mActive.remove(buffer); 113 Log.d("GraphicsStats", "Buffer count: " + mActive.size()); 114 } 115 HistoricalData data = buffer.mPreviousData; 116 buffer.mPreviousData = null; 117 if (data == null) { 118 data = mHistoricalLog[mNextHistoricalSlot]; 119 if (data == null) { 120 data = new HistoricalData(); 121 } 122 } 123 data.update(buffer.mPackageName, buffer.mUid, buffer.mProcessBuffer); 124 buffer.closeAllBuffers(); 125 126 mHistoricalLog[mNextHistoricalSlot] = data; 127 mNextHistoricalSlot = (mNextHistoricalSlot + 1) % mHistoricalLog.length; 128 } 129 130 private ActiveBuffer fetchActiveBuffersLocked(IBinder token, int uid, int pid, 131 String packageName) throws RemoteException { 132 int size = mActive.size(); 133 for (int i = 0; i < size; i++) { 134 ActiveBuffer buffers = mActive.get(i); 135 if (buffers.mPid == pid 136 && buffers.mUid == uid) { 137 return buffers; 138 } 139 } 140 // Didn't find one, need to create it 141 try { 142 ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName); 143 mActive.add(buffers); 144 return buffers; 145 } catch (IOException ex) { 146 throw new RemoteException("Failed to allocate space"); 147 } 148 } 149 150 private HistoricalData removeHistoricalDataLocked(int uid, String packageName) { 151 for (int i = 0; i < mHistoricalLog.length; i++) { 152 final HistoricalData data = mHistoricalLog[i]; 153 if (data != null && data.mUid == uid 154 && data.mPackageName.equals(packageName)) { 155 if (i == mNextHistoricalSlot) { 156 mHistoricalLog[i] = null; 157 } else { 158 mHistoricalLog[i] = mHistoricalLog[mNextHistoricalSlot]; 159 mHistoricalLog[mNextHistoricalSlot] = null; 160 } 161 return data; 162 } 163 } 164 return null; 165 } 166 167 @Override 168 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 169 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 170 synchronized (mLock) { 171 for (int i = 0; i < mActive.size(); i++) { 172 final ActiveBuffer buffer = mActive.get(i); 173 fout.print("Package: "); 174 fout.print(buffer.mPackageName); 175 fout.flush(); 176 try { 177 buffer.mProcessBuffer.readBytes(mTempBuffer, 0, 0, ASHMEM_SIZE); 178 ThreadedRenderer.dumpProfileData(mTempBuffer, fd); 179 } catch (IOException e) { 180 fout.println("Failed to dump"); 181 } 182 fout.println(); 183 } 184 for (HistoricalData buffer : mHistoricalLog) { 185 if (buffer == null) continue; 186 fout.print("Package: "); 187 fout.print(buffer.mPackageName); 188 fout.flush(); 189 ThreadedRenderer.dumpProfileData(buffer.mBuffer, fd); 190 fout.println(); 191 } 192 } 193 } 194 195 private final class ActiveBuffer implements DeathRecipient { 196 final int mUid; 197 final int mPid; 198 final String mPackageName; 199 final IBinder mToken; 200 MemoryFile mProcessBuffer; 201 HistoricalData mPreviousData; 202 203 ActiveBuffer(IBinder token, int uid, int pid, String packageName) 204 throws RemoteException, IOException { 205 mUid = uid; 206 mPid = pid; 207 mPackageName = packageName; 208 mToken = token; 209 mToken.linkToDeath(this, 0); 210 mProcessBuffer = new MemoryFile("GFXStats-" + uid, ASHMEM_SIZE); 211 mPreviousData = removeHistoricalDataLocked(mUid, mPackageName); 212 if (mPreviousData != null) { 213 mProcessBuffer.writeBytes(mPreviousData.mBuffer, 0, 0, ASHMEM_SIZE); 214 } 215 } 216 217 @Override 218 public void binderDied() { 219 mToken.unlinkToDeath(this, 0); 220 processDied(this); 221 } 222 223 void closeAllBuffers() { 224 if (mProcessBuffer != null) { 225 mProcessBuffer.close(); 226 mProcessBuffer = null; 227 } 228 } 229 } 230 231 private final static class HistoricalData { 232 final byte[] mBuffer = new byte[ASHMEM_SIZE]; 233 int mUid; 234 String mPackageName; 235 236 void update(String packageName, int uid, MemoryFile file) { 237 mUid = uid; 238 mPackageName = packageName; 239 try { 240 file.readBytes(mBuffer, 0, 0, ASHMEM_SIZE); 241 } catch (IOException e) {} 242 } 243 } 244 } 245