blob: c79fdfc2a5ad9877a4ac8b7454802259ae9042e3 [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
19import android.content.Context;
20import android.content.pm.PackageInfo;
21import android.content.pm.PackageManager.NameNotFoundException;
22import android.os.Binder;
23import android.os.IBinder;
24import android.os.MemoryFile;
25import android.os.ParcelFileDescriptor;
26import android.os.RemoteException;
27import android.util.Log;
28import android.view.IGraphicsStats;
29import android.view.ThreadedRenderer;
30
31import java.io.FileDescriptor;
32import java.io.IOException;
33import java.io.PrintWriter;
34import 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 10. Assuming
56 * the system then also has 10 active rendering processes in the worst case
57 * this would end up using under 10KiB (8KiB for the buffers, plus some overhead
58 * for userId, pid, package name, and a couple other objects)
59 *
60 * @hide */
61public 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 = 10;
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}