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