• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.ddmuilib;
18 
19 import com.android.ddmlib.AllocationInfo;
20 import com.android.ddmlib.Client;
21 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
22 import com.android.ddmlib.ClientData.AllocationTrackingStatus;
23 
24 import org.eclipse.jface.preference.IPreferenceStore;
25 import org.eclipse.jface.viewers.ILabelProviderListener;
26 import org.eclipse.jface.viewers.ISelection;
27 import org.eclipse.jface.viewers.ISelectionChangedListener;
28 import org.eclipse.jface.viewers.IStructuredContentProvider;
29 import org.eclipse.jface.viewers.IStructuredSelection;
30 import org.eclipse.jface.viewers.ITableLabelProvider;
31 import org.eclipse.jface.viewers.SelectionChangedEvent;
32 import org.eclipse.jface.viewers.TableViewer;
33 import org.eclipse.jface.viewers.Viewer;
34 import org.eclipse.swt.SWT;
35 import org.eclipse.swt.SWTException;
36 import org.eclipse.swt.events.SelectionAdapter;
37 import org.eclipse.swt.events.SelectionEvent;
38 import org.eclipse.swt.graphics.Color;
39 import org.eclipse.swt.graphics.Image;
40 import org.eclipse.swt.graphics.Rectangle;
41 import org.eclipse.swt.layout.FormAttachment;
42 import org.eclipse.swt.layout.FormData;
43 import org.eclipse.swt.layout.FormLayout;
44 import org.eclipse.swt.layout.GridData;
45 import org.eclipse.swt.layout.GridLayout;
46 import org.eclipse.swt.widgets.Button;
47 import org.eclipse.swt.widgets.Composite;
48 import org.eclipse.swt.widgets.Control;
49 import org.eclipse.swt.widgets.Event;
50 import org.eclipse.swt.widgets.Listener;
51 import org.eclipse.swt.widgets.Sash;
52 import org.eclipse.swt.widgets.Table;
53 
54 /**
55  * Base class for our information panels.
56  */
57 public class AllocationPanel extends TablePanel {
58 
59     private final static String PREFS_ALLOC_COL_SIZE = "allocPanel.Col0"; //$NON-NLS-1$
60     private final static String PREFS_ALLOC_COL_CLASS = "allocPanel.Col1"; //$NON-NLS-1$
61     private final static String PREFS_ALLOC_COL_THREAD = "allocPanel.Col2"; //$NON-NLS-1$
62     private final static String PREFS_ALLOC_COL_TRACE_CLASS = "allocPanel.Col3"; //$NON-NLS-1$
63     private final static String PREFS_ALLOC_COL_TRACE_METHOD = "allocPanel.Col4"; //$NON-NLS-1$
64 
65     private final static String PREFS_ALLOC_SASH = "allocPanel.sash"; //$NON-NLS-1$
66 
67     private static final String PREFS_STACK_COL_CLASS = "allocPanel.stack.col0"; //$NON-NLS-1$
68     private static final String PREFS_STACK_COL_METHOD = "allocPanel.stack.col1"; //$NON-NLS-1$
69     private static final String PREFS_STACK_COL_FILE = "allocPanel.stack.col2"; //$NON-NLS-1$
70     private static final String PREFS_STACK_COL_LINE = "allocPanel.stack.col3"; //$NON-NLS-1$
71     private static final String PREFS_STACK_COL_NATIVE = "allocPanel.stack.col4"; //$NON-NLS-1$
72 
73     private Composite mAllocationBase;
74     private Table mAllocationTable;
75     private TableViewer mAllocationViewer;
76 
77     private StackTracePanel mStackTracePanel;
78     private Table mStackTraceTable;
79     private Button mEnableButton;
80     private Button mRequestButton;
81 
82     /**
83      * Content Provider to display the allocations of a client.
84      * Expected input is a {@link Client} object, elements used in the table are of type
85      * {@link AllocationInfo}.
86      */
87     private static class AllocationContentProvider implements IStructuredContentProvider {
getElements(Object inputElement)88         public Object[] getElements(Object inputElement) {
89             if (inputElement instanceof Client) {
90                 AllocationInfo[] allocs = ((Client)inputElement).getClientData().getAllocations();
91                 if (allocs != null) {
92                     return allocs;
93                 }
94             }
95 
96             return new Object[0];
97         }
98 
dispose()99         public void dispose() {
100             // pass
101         }
102 
inputChanged(Viewer viewer, Object oldInput, Object newInput)103         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
104             // pass
105         }
106     }
107 
108     /**
109      * A Label Provider to use with {@link AllocationContentProvider}. It expects the elements to be
110      * of type {@link AllocationInfo}.
111      */
112     private static class AllocationLabelProvider implements ITableLabelProvider {
113 
getColumnImage(Object element, int columnIndex)114         public Image getColumnImage(Object element, int columnIndex) {
115             return null;
116         }
117 
getColumnText(Object element, int columnIndex)118         public String getColumnText(Object element, int columnIndex) {
119             if (element instanceof AllocationInfo) {
120                 AllocationInfo alloc = (AllocationInfo)element;
121                 switch (columnIndex) {
122                     case 0:
123                         return Integer.toString(alloc.getSize());
124                     case 1:
125                         return alloc.getAllocatedClass();
126                     case 2:
127                         return Short.toString(alloc.getThreadId());
128                     case 3:
129                         StackTraceElement[] traces = alloc.getStackTrace();
130                         if (traces.length > 0) {
131                             return traces[0].getClassName();
132                         }
133                         break;
134                     case 4:
135                         traces = alloc.getStackTrace();
136                         if (traces.length > 0) {
137                             return traces[0].getMethodName();
138                         }
139                         break;
140                 }
141             }
142 
143             return null;
144         }
145 
addListener(ILabelProviderListener listener)146         public void addListener(ILabelProviderListener listener) {
147             // pass
148         }
149 
dispose()150         public void dispose() {
151             // pass
152         }
153 
isLabelProperty(Object element, String property)154         public boolean isLabelProperty(Object element, String property) {
155             // pass
156             return false;
157         }
158 
removeListener(ILabelProviderListener listener)159         public void removeListener(ILabelProviderListener listener) {
160             // pass
161         }
162     }
163 
164     /**
165      * Create our control(s).
166      */
167     @Override
createControl(Composite parent)168     protected Control createControl(Composite parent) {
169         final IPreferenceStore store = DdmUiPreferences.getStore();
170 
171         // base composite for selected client with enabled thread update.
172         mAllocationBase = new Composite(parent, SWT.NONE);
173         mAllocationBase.setLayout(new FormLayout());
174 
175         // table above the sash
176         Composite topParent = new Composite(mAllocationBase, SWT.NONE);
177         topParent.setLayout(new GridLayout(2, false));
178 
179         mEnableButton = new Button(topParent, SWT.PUSH);
180         mEnableButton.addSelectionListener(new SelectionAdapter() {
181             @Override
182             public void widgetSelected(SelectionEvent e) {
183                 Client current = getCurrentClient();
184                 AllocationTrackingStatus status = current.getClientData().getAllocationStatus();
185                 if (status == AllocationTrackingStatus.ON) {
186                     current.enableAllocationTracker(false);
187                 } else {
188                     current.enableAllocationTracker(true);
189                 }
190                 current.requestAllocationStatus();
191             }
192         });
193 
194         mRequestButton = new Button(topParent, SWT.PUSH);
195         mRequestButton.setText("Get Allocations");
196         mRequestButton.addSelectionListener(new SelectionAdapter() {
197             @Override
198             public void widgetSelected(SelectionEvent e) {
199                 getCurrentClient().requestAllocationDetails();
200             }
201         });
202 
203         setUpButtons(false /* enabled */, AllocationTrackingStatus.OFF);
204 
205         mAllocationTable = new Table(topParent, SWT.MULTI | SWT.FULL_SELECTION);
206         GridData gridData;
207         mAllocationTable.setLayoutData(gridData = new GridData(GridData.FILL_BOTH));
208         gridData.horizontalSpan = 2;
209         mAllocationTable.setHeaderVisible(true);
210         mAllocationTable.setLinesVisible(true);
211 
212         TableHelper.createTableColumn(
213                 mAllocationTable,
214                 "Allocation Size",
215                 SWT.RIGHT,
216                 "888", //$NON-NLS-1$
217                 PREFS_ALLOC_COL_SIZE, store);
218 
219         TableHelper.createTableColumn(
220                 mAllocationTable,
221                 "Allocated Class",
222                 SWT.LEFT,
223                 "Allocated Class", //$NON-NLS-1$
224                 PREFS_ALLOC_COL_CLASS, store);
225 
226         TableHelper.createTableColumn(
227                 mAllocationTable,
228                 "Thread Id",
229                 SWT.LEFT,
230                 "999", //$NON-NLS-1$
231                 PREFS_ALLOC_COL_THREAD, store);
232 
233         TableHelper.createTableColumn(
234                 mAllocationTable,
235                 "Allocated in",
236                 SWT.LEFT,
237                 "utime", //$NON-NLS-1$
238                 PREFS_ALLOC_COL_TRACE_CLASS, store);
239 
240         TableHelper.createTableColumn(
241                 mAllocationTable,
242                 "Allocated in",
243                 SWT.LEFT,
244                 "utime", //$NON-NLS-1$
245                 PREFS_ALLOC_COL_TRACE_METHOD, store);
246 
247         mAllocationViewer = new TableViewer(mAllocationTable);
248         mAllocationViewer.setContentProvider(new AllocationContentProvider());
249         mAllocationViewer.setLabelProvider(new AllocationLabelProvider());
250 
251         mAllocationViewer.addSelectionChangedListener(new ISelectionChangedListener() {
252             public void selectionChanged(SelectionChangedEvent event) {
253                 AllocationInfo selectedAlloc = getAllocationSelection(event.getSelection());
254                 updateAllocationStackTrace(selectedAlloc);
255             }
256         });
257 
258         // the separating sash
259         final Sash sash = new Sash(mAllocationBase, SWT.HORIZONTAL);
260         Color darkGray = parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY);
261         sash.setBackground(darkGray);
262 
263         // the UI below the sash
264         mStackTracePanel = new StackTracePanel();
265         mStackTraceTable = mStackTracePanel.createPanel(mAllocationBase,
266                 PREFS_STACK_COL_CLASS,
267                 PREFS_STACK_COL_METHOD,
268                 PREFS_STACK_COL_FILE,
269                 PREFS_STACK_COL_LINE,
270                 PREFS_STACK_COL_NATIVE,
271                 store);
272 
273         // now setup the sash.
274         // form layout data
275         FormData data = new FormData();
276         data.top = new FormAttachment(0, 0);
277         data.bottom = new FormAttachment(sash, 0);
278         data.left = new FormAttachment(0, 0);
279         data.right = new FormAttachment(100, 0);
280         topParent.setLayoutData(data);
281 
282         final FormData sashData = new FormData();
283         if (store != null && store.contains(PREFS_ALLOC_SASH)) {
284             sashData.top = new FormAttachment(0, store.getInt(PREFS_ALLOC_SASH));
285         } else {
286             sashData.top = new FormAttachment(50,0); // 50% across
287         }
288         sashData.left = new FormAttachment(0, 0);
289         sashData.right = new FormAttachment(100, 0);
290         sash.setLayoutData(sashData);
291 
292         data = new FormData();
293         data.top = new FormAttachment(sash, 0);
294         data.bottom = new FormAttachment(100, 0);
295         data.left = new FormAttachment(0, 0);
296         data.right = new FormAttachment(100, 0);
297         mStackTraceTable.setLayoutData(data);
298 
299         // allow resizes, but cap at minPanelWidth
300         sash.addListener(SWT.Selection, new Listener() {
301             public void handleEvent(Event e) {
302                 Rectangle sashRect = sash.getBounds();
303                 Rectangle panelRect = mAllocationBase.getClientArea();
304                 int bottom = panelRect.height - sashRect.height - 100;
305                 e.y = Math.max(Math.min(e.y, bottom), 100);
306                 if (e.y != sashRect.y) {
307                     sashData.top = new FormAttachment(0, e.y);
308                     store.setValue(PREFS_ALLOC_SASH, e.y);
309                     mAllocationBase.layout();
310                 }
311             }
312         });
313 
314         return mAllocationBase;
315     }
316 
317     /**
318      * Sets the focus to the proper control inside the panel.
319      */
320     @Override
setFocus()321     public void setFocus() {
322         mAllocationTable.setFocus();
323     }
324 
325     /**
326      * Sent when an existing client information changed.
327      * <p/>
328      * This is sent from a non UI thread.
329      * @param client the updated client.
330      * @param changeMask the bit mask describing the changed properties. It can contain
331      * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME}
332      * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE},
333      * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE},
334      * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA}
335      *
336      * @see IClientChangeListener#clientChanged(Client, int)
337      */
clientChanged(final Client client, int changeMask)338     public void clientChanged(final Client client, int changeMask) {
339         if (client == getCurrentClient()) {
340             if ((changeMask & Client.CHANGE_HEAP_ALLOCATIONS) != 0) {
341                 try {
342                     mAllocationTable.getDisplay().asyncExec(new Runnable() {
343                         public void run() {
344                             mAllocationViewer.refresh();
345                             updateAllocationStackCall();
346                         }
347                     });
348                 } catch (SWTException e) {
349                     // widget is disposed, we do nothing
350                 }
351             } else if ((changeMask & Client.CHANGE_HEAP_ALLOCATION_STATUS) != 0) {
352                 try {
353                     mAllocationTable.getDisplay().asyncExec(new Runnable() {
354                         public void run() {
355                             setUpButtons(true, client.getClientData().getAllocationStatus());
356                         }
357                     });
358                 } catch (SWTException e) {
359                     // widget is disposed, we do nothing
360                 }
361             }
362         }
363     }
364 
365     /**
366      * Sent when a new device is selected. The new device can be accessed
367      * with {@link #getCurrentDevice()}.
368      */
369     @Override
deviceSelected()370     public void deviceSelected() {
371         // pass
372     }
373 
374     /**
375      * Sent when a new client is selected. The new client can be accessed
376      * with {@link #getCurrentClient()}.
377      */
378     @Override
clientSelected()379     public void clientSelected() {
380         if (mAllocationTable.isDisposed()) {
381             return;
382         }
383 
384         Client client = getCurrentClient();
385 
386         mStackTracePanel.setCurrentClient(client);
387         mStackTracePanel.setViewerInput(null); // always empty on client selection change.
388 
389         if (client != null) {
390             setUpButtons(true /* enabled */, client.getClientData().getAllocationStatus());
391         } else {
392             setUpButtons(false /* enabled */, AllocationTrackingStatus.OFF);
393         }
394 
395         mAllocationViewer.setInput(client);
396     }
397 
398     /**
399      * Updates the stack call of the currently selected thread.
400      * <p/>
401      * This <b>must</b> be called from the UI thread.
402      */
updateAllocationStackCall()403     private void updateAllocationStackCall() {
404         Client client = getCurrentClient();
405         if (client != null) {
406             // get the current selection in the ThreadTable
407             AllocationInfo selectedAlloc = getAllocationSelection(null);
408 
409             if (selectedAlloc != null) {
410                 updateAllocationStackTrace(selectedAlloc);
411             } else {
412                 updateAllocationStackTrace(null);
413             }
414         }
415     }
416 
417     /**
418      * updates the stackcall of the specified allocation. If <code>null</code> the UI is emptied
419      * of current data.
420      * @param thread
421      */
updateAllocationStackTrace(AllocationInfo alloc)422     private void updateAllocationStackTrace(AllocationInfo alloc) {
423         mStackTracePanel.setViewerInput(alloc);
424     }
425 
426     @Override
setTableFocusListener()427     protected void setTableFocusListener() {
428         addTableToFocusListener(mAllocationTable);
429         addTableToFocusListener(mStackTraceTable);
430     }
431 
432     /**
433      * Returns the current allocation selection or <code>null</code> if none is found.
434      * If a {@link ISelection} object is specified, the first {@link AllocationInfo} from this
435      * selection is returned, otherwise, the <code>ISelection</code> returned by
436      * {@link TableViewer#getSelection()} is used.
437      * @param selection the {@link ISelection} to use, or <code>null</code>
438      */
getAllocationSelection(ISelection selection)439     private AllocationInfo getAllocationSelection(ISelection selection) {
440         if (selection == null) {
441             selection = mAllocationViewer.getSelection();
442         }
443 
444         if (selection instanceof IStructuredSelection) {
445             IStructuredSelection structuredSelection = (IStructuredSelection)selection;
446             Object object = structuredSelection.getFirstElement();
447             if (object instanceof AllocationInfo) {
448                 return (AllocationInfo)object;
449             }
450         }
451 
452         return null;
453     }
454 
455     /**
456      *
457      * @param enabled
458      * @param trackingStatus
459      */
setUpButtons(boolean enabled, AllocationTrackingStatus trackingStatus)460     private void setUpButtons(boolean enabled, AllocationTrackingStatus trackingStatus) {
461         if (enabled) {
462             switch (trackingStatus) {
463                 case UNKNOWN:
464                     mEnableButton.setText("?");
465                     mEnableButton.setEnabled(false);
466                     mRequestButton.setEnabled(false);
467                     break;
468                 case OFF:
469                     mEnableButton.setText("Start Tracking");
470                     mEnableButton.setEnabled(true);
471                     mRequestButton.setEnabled(false);
472                     break;
473                 case ON:
474                     mEnableButton.setText("Stop Tracking");
475                     mEnableButton.setEnabled(true);
476                     mRequestButton.setEnabled(true);
477                     break;
478             }
479         } else {
480             mEnableButton.setEnabled(false);
481             mRequestButton.setEnabled(false);
482             mEnableButton.setText("Start Tracking");
483         }
484     }
485 }
486 
487