• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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.Client;
20 import com.android.ddmlib.ThreadInfo;
21 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
22 
23 import org.eclipse.jface.preference.IPreferenceStore;
24 import org.eclipse.jface.viewers.DoubleClickEvent;
25 import org.eclipse.jface.viewers.IDoubleClickListener;
26 import org.eclipse.jface.viewers.ILabelProviderListener;
27 import org.eclipse.jface.viewers.ISelection;
28 import org.eclipse.jface.viewers.ISelectionChangedListener;
29 import org.eclipse.jface.viewers.IStructuredContentProvider;
30 import org.eclipse.jface.viewers.IStructuredSelection;
31 import org.eclipse.jface.viewers.ITableLabelProvider;
32 import org.eclipse.jface.viewers.SelectionChangedEvent;
33 import org.eclipse.jface.viewers.TableViewer;
34 import org.eclipse.jface.viewers.Viewer;
35 import org.eclipse.swt.SWT;
36 import org.eclipse.swt.SWTException;
37 import org.eclipse.swt.custom.StackLayout;
38 import org.eclipse.swt.events.SelectionAdapter;
39 import org.eclipse.swt.events.SelectionEvent;
40 import org.eclipse.swt.graphics.Color;
41 import org.eclipse.swt.graphics.Image;
42 import org.eclipse.swt.graphics.Rectangle;
43 import org.eclipse.swt.layout.FormAttachment;
44 import org.eclipse.swt.layout.FormData;
45 import org.eclipse.swt.layout.FormLayout;
46 import org.eclipse.swt.layout.GridData;
47 import org.eclipse.swt.layout.GridLayout;
48 import org.eclipse.swt.widgets.Button;
49 import org.eclipse.swt.widgets.Composite;
50 import org.eclipse.swt.widgets.Control;
51 import org.eclipse.swt.widgets.Display;
52 import org.eclipse.swt.widgets.Event;
53 import org.eclipse.swt.widgets.Label;
54 import org.eclipse.swt.widgets.Listener;
55 import org.eclipse.swt.widgets.Sash;
56 import org.eclipse.swt.widgets.Table;
57 
58 import java.util.Date;
59 
60 /**
61  * Base class for our information panels.
62  */
63 public class ThreadPanel extends TablePanel {
64 
65     private final static String PREFS_THREAD_COL_ID = "threadPanel.Col0"; //$NON-NLS-1$
66     private final static String PREFS_THREAD_COL_TID = "threadPanel.Col1"; //$NON-NLS-1$
67     private final static String PREFS_THREAD_COL_STATUS = "threadPanel.Col2"; //$NON-NLS-1$
68     private final static String PREFS_THREAD_COL_UTIME = "threadPanel.Col3"; //$NON-NLS-1$
69     private final static String PREFS_THREAD_COL_STIME = "threadPanel.Col4"; //$NON-NLS-1$
70     private final static String PREFS_THREAD_COL_NAME = "threadPanel.Col5"; //$NON-NLS-1$
71 
72     private final static String PREFS_THREAD_SASH = "threadPanel.sash"; //$NON-NLS-1$
73 
74     private static final String PREFS_STACK_COL_CLASS = "threadPanel.stack.col0"; //$NON-NLS-1$
75     private static final String PREFS_STACK_COL_METHOD = "threadPanel.stack.col1"; //$NON-NLS-1$
76     private static final String PREFS_STACK_COL_FILE = "threadPanel.stack.col2"; //$NON-NLS-1$
77     private static final String PREFS_STACK_COL_LINE = "threadPanel.stack.col3"; //$NON-NLS-1$
78     private static final String PREFS_STACK_COL_NATIVE = "threadPanel.stack.col4"; //$NON-NLS-1$
79 
80     private Display mDisplay;
81     private Composite mBase;
82     private Label mNotEnabled;
83     private Label mNotSelected;
84 
85     private Composite mThreadBase;
86     private Table mThreadTable;
87     private TableViewer mThreadViewer;
88 
89     private Composite mStackTraceBase;
90     private Button mRefreshStackTraceButton;
91     private Label mStackTraceTimeLabel;
92     private StackTracePanel mStackTracePanel;
93     private Table mStackTraceTable;
94 
95     /** Indicates if a timer-based Runnable is current requesting thread updates regularly. */
96     private boolean mMustStopRecurringThreadUpdate = false;
97     /** Flag to tell the recurring thread update to stop running */
98     private boolean mRecurringThreadUpdateRunning = false;
99 
100     private Object mLock = new Object();
101 
102     private static final String[] THREAD_STATUS = {
103         "zombie", "running", "timed-wait", "monitor",
104         "wait", "init", "start", "native", "vmwait"
105     };
106 
107     /**
108      * Content Provider to display the threads of a client.
109      * Expected input is a {@link Client} object.
110      */
111     private static class ThreadContentProvider implements IStructuredContentProvider {
getElements(Object inputElement)112         public Object[] getElements(Object inputElement) {
113             if (inputElement instanceof Client) {
114                 return ((Client)inputElement).getClientData().getThreads();
115             }
116 
117             return new Object[0];
118         }
119 
dispose()120         public void dispose() {
121             // pass
122         }
123 
inputChanged(Viewer viewer, Object oldInput, Object newInput)124         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
125             // pass
126         }
127     }
128 
129 
130     /**
131      * A Label Provider to use with {@link ThreadContentProvider}. It expects the elements to be
132      * of type {@link ThreadInfo}.
133      */
134     private static class ThreadLabelProvider implements ITableLabelProvider {
135 
getColumnImage(Object element, int columnIndex)136         public Image getColumnImage(Object element, int columnIndex) {
137             return null;
138         }
139 
getColumnText(Object element, int columnIndex)140         public String getColumnText(Object element, int columnIndex) {
141             if (element instanceof ThreadInfo) {
142                 ThreadInfo thread = (ThreadInfo)element;
143                 switch (columnIndex) {
144                     case 0:
145                         return (thread.isDaemon() ? "*" : "") + //$NON-NLS-1$ //$NON-NLS-2$
146                             String.valueOf(thread.getThreadId());
147                     case 1:
148                         return String.valueOf(thread.getTid());
149                     case 2:
150                         if (thread.getStatus() >= 0 && thread.getStatus() < THREAD_STATUS.length)
151                             return THREAD_STATUS[thread.getStatus()];
152                         return "unknown";
153                     case 3:
154                         return String.valueOf(thread.getUtime());
155                     case 4:
156                         return String.valueOf(thread.getStime());
157                     case 5:
158                         return thread.getThreadName();
159                 }
160             }
161 
162             return null;
163         }
164 
addListener(ILabelProviderListener listener)165         public void addListener(ILabelProviderListener listener) {
166             // pass
167         }
168 
dispose()169         public void dispose() {
170             // pass
171         }
172 
isLabelProperty(Object element, String property)173         public boolean isLabelProperty(Object element, String property) {
174             // pass
175             return false;
176         }
177 
removeListener(ILabelProviderListener listener)178         public void removeListener(ILabelProviderListener listener) {
179             // pass
180         }
181     }
182 
183     /**
184      * Create our control(s).
185      */
186     @Override
createControl(Composite parent)187     protected Control createControl(Composite parent) {
188         mDisplay = parent.getDisplay();
189 
190         final IPreferenceStore store = DdmUiPreferences.getStore();
191 
192         mBase = new Composite(parent, SWT.NONE);
193         mBase.setLayout(new StackLayout());
194 
195         // UI for thread not enabled
196         mNotEnabled = new Label(mBase, SWT.CENTER | SWT.WRAP);
197         mNotEnabled.setText("Thread updates not enabled for selected client\n"
198             + "(use toolbar button to enable)");
199 
200         // UI for not client selected
201         mNotSelected = new Label(mBase, SWT.CENTER | SWT.WRAP);
202         mNotSelected.setText("no client is selected");
203 
204         // base composite for selected client with enabled thread update.
205         mThreadBase = new Composite(mBase, SWT.NONE);
206         mThreadBase.setLayout(new FormLayout());
207 
208         // table above the sash
209         mThreadTable = new Table(mThreadBase, SWT.MULTI | SWT.FULL_SELECTION);
210         mThreadTable.setHeaderVisible(true);
211         mThreadTable.setLinesVisible(true);
212 
213         TableHelper.createTableColumn(
214                 mThreadTable,
215                 "ID",
216                 SWT.RIGHT,
217                 "888", //$NON-NLS-1$
218                 PREFS_THREAD_COL_ID, store);
219 
220         TableHelper.createTableColumn(
221                 mThreadTable,
222                 "Tid",
223                 SWT.RIGHT,
224                 "88888", //$NON-NLS-1$
225                 PREFS_THREAD_COL_TID, store);
226 
227         TableHelper.createTableColumn(
228                 mThreadTable,
229                 "Status",
230                 SWT.LEFT,
231                 "timed-wait", //$NON-NLS-1$
232                 PREFS_THREAD_COL_STATUS, store);
233 
234         TableHelper.createTableColumn(
235                 mThreadTable,
236                 "utime",
237                 SWT.RIGHT,
238                 "utime", //$NON-NLS-1$
239                 PREFS_THREAD_COL_UTIME, store);
240 
241         TableHelper.createTableColumn(
242                 mThreadTable,
243                 "stime",
244                 SWT.RIGHT,
245                 "utime", //$NON-NLS-1$
246                 PREFS_THREAD_COL_STIME, store);
247 
248         TableHelper.createTableColumn(
249                 mThreadTable,
250                 "Name",
251                 SWT.LEFT,
252                 "android.class.ReallyLongClassName.MethodName", //$NON-NLS-1$
253                 PREFS_THREAD_COL_NAME, store);
254 
255         mThreadViewer = new TableViewer(mThreadTable);
256         mThreadViewer.setContentProvider(new ThreadContentProvider());
257         mThreadViewer.setLabelProvider(new ThreadLabelProvider());
258 
259         mThreadViewer.addSelectionChangedListener(new ISelectionChangedListener() {
260             public void selectionChanged(SelectionChangedEvent event) {
261                 ThreadInfo selectedThread = getThreadSelection(event.getSelection());
262                 updateThreadStackTrace(selectedThread);
263             }
264         });
265         mThreadViewer.addDoubleClickListener(new IDoubleClickListener() {
266             public void doubleClick(DoubleClickEvent event) {
267                 ThreadInfo selectedThread = getThreadSelection(event.getSelection());
268                 if (selectedThread != null) {
269                     Client client = (Client)mThreadViewer.getInput();
270 
271                     if (client != null) {
272                         client.requestThreadStackTrace(selectedThread.getThreadId());
273                     }
274                 }
275             }
276         });
277 
278         // the separating sash
279         final Sash sash = new Sash(mThreadBase, SWT.HORIZONTAL);
280         Color darkGray = parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY);
281         sash.setBackground(darkGray);
282 
283         // the UI below the sash
284         mStackTraceBase = new Composite(mThreadBase, SWT.NONE);
285         mStackTraceBase.setLayout(new GridLayout(2, false));
286 
287         mRefreshStackTraceButton = new Button(mStackTraceBase, SWT.PUSH);
288         mRefreshStackTraceButton.setText("Refresh");
289         mRefreshStackTraceButton.addSelectionListener(new SelectionAdapter() {
290             @Override
291             public void widgetSelected(SelectionEvent e) {
292                 ThreadInfo selectedThread = getThreadSelection(null);
293                 if (selectedThread != null) {
294                     Client currentClient = getCurrentClient();
295                     if (currentClient != null) {
296                         currentClient.requestThreadStackTrace(selectedThread.getThreadId());
297                     }
298                 }
299             }
300         });
301 
302         mStackTraceTimeLabel = new Label(mStackTraceBase, SWT.NONE);
303         mStackTraceTimeLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
304 
305         mStackTracePanel = new StackTracePanel();
306         mStackTraceTable = mStackTracePanel.createPanel(mStackTraceBase,
307                 PREFS_STACK_COL_CLASS,
308                 PREFS_STACK_COL_METHOD,
309                 PREFS_STACK_COL_FILE,
310                 PREFS_STACK_COL_LINE,
311                 PREFS_STACK_COL_NATIVE,
312                 store);
313 
314         GridData gd;
315         mStackTraceTable.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
316         gd.horizontalSpan = 2;
317 
318         // now setup the sash.
319         // form layout data
320         FormData data = new FormData();
321         data.top = new FormAttachment(0, 0);
322         data.bottom = new FormAttachment(sash, 0);
323         data.left = new FormAttachment(0, 0);
324         data.right = new FormAttachment(100, 0);
325         mThreadTable.setLayoutData(data);
326 
327         final FormData sashData = new FormData();
328         if (store != null && store.contains(PREFS_THREAD_SASH)) {
329             sashData.top = new FormAttachment(0, store.getInt(PREFS_THREAD_SASH));
330         } else {
331             sashData.top = new FormAttachment(50,0); // 50% across
332         }
333         sashData.left = new FormAttachment(0, 0);
334         sashData.right = new FormAttachment(100, 0);
335         sash.setLayoutData(sashData);
336 
337         data = new FormData();
338         data.top = new FormAttachment(sash, 0);
339         data.bottom = new FormAttachment(100, 0);
340         data.left = new FormAttachment(0, 0);
341         data.right = new FormAttachment(100, 0);
342         mStackTraceBase.setLayoutData(data);
343 
344         // allow resizes, but cap at minPanelWidth
345         sash.addListener(SWT.Selection, new Listener() {
346             public void handleEvent(Event e) {
347                 Rectangle sashRect = sash.getBounds();
348                 Rectangle panelRect = mThreadBase.getClientArea();
349                 int bottom = panelRect.height - sashRect.height - 100;
350                 e.y = Math.max(Math.min(e.y, bottom), 100);
351                 if (e.y != sashRect.y) {
352                     sashData.top = new FormAttachment(0, e.y);
353                     store.setValue(PREFS_THREAD_SASH, e.y);
354                     mThreadBase.layout();
355                 }
356             }
357         });
358 
359         ((StackLayout)mBase.getLayout()).topControl = mNotSelected;
360 
361         return mBase;
362     }
363 
364     /**
365      * Sets the focus to the proper control inside the panel.
366      */
367     @Override
setFocus()368     public void setFocus() {
369         mThreadTable.setFocus();
370     }
371 
372     /**
373      * Sent when an existing client information changed.
374      * <p/>
375      * This is sent from a non UI thread.
376      * @param client the updated client.
377      * @param changeMask the bit mask describing the changed properties. It can contain
378      * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME}
379      * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE},
380      * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE},
381      * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA}
382      *
383      * @see IClientChangeListener#clientChanged(Client, int)
384      */
clientChanged(final Client client, int changeMask)385     public void clientChanged(final Client client, int changeMask) {
386         if (client == getCurrentClient()) {
387             if ((changeMask & Client.CHANGE_THREAD_MODE) != 0 ||
388                     (changeMask & Client.CHANGE_THREAD_DATA) != 0) {
389                 try {
390                     mThreadTable.getDisplay().asyncExec(new Runnable() {
391                         public void run() {
392                             clientSelected();
393                         }
394                     });
395                 } catch (SWTException e) {
396                     // widget is disposed, we do nothing
397                 }
398             } else if ((changeMask & Client.CHANGE_THREAD_STACKTRACE) != 0) {
399                 try {
400                     mThreadTable.getDisplay().asyncExec(new Runnable() {
401                         public void run() {
402                             updateThreadStackCall();
403                         }
404                     });
405                 } catch (SWTException e) {
406                     // widget is disposed, we do nothing
407                 }
408             }
409         }
410     }
411 
412     /**
413      * Sent when a new device is selected. The new device can be accessed
414      * with {@link #getCurrentDevice()}.
415      */
416     @Override
deviceSelected()417     public void deviceSelected() {
418         // pass
419     }
420 
421     /**
422      * Sent when a new client is selected. The new client can be accessed
423      * with {@link #getCurrentClient()}.
424      */
425     @Override
clientSelected()426     public void clientSelected() {
427         if (mThreadTable.isDisposed()) {
428             return;
429         }
430 
431         Client client = getCurrentClient();
432 
433         mStackTracePanel.setCurrentClient(client);
434 
435         if (client != null) {
436             if (!client.isThreadUpdateEnabled()) {
437                 ((StackLayout)mBase.getLayout()).topControl = mNotEnabled;
438                 mThreadViewer.setInput(null);
439 
440                 // if we are currently updating the thread, stop doing it.
441                 mMustStopRecurringThreadUpdate = true;
442             } else {
443                 ((StackLayout)mBase.getLayout()).topControl = mThreadBase;
444                 mThreadViewer.setInput(client);
445 
446                 synchronized (mLock) {
447                     // if we're not updating we start the process
448                     if (mRecurringThreadUpdateRunning == false) {
449                         startRecurringThreadUpdate();
450                     } else if (mMustStopRecurringThreadUpdate) {
451                         // else if there's a runnable that's still going to get called, lets
452                         // simply cancel the stop, and keep going
453                         mMustStopRecurringThreadUpdate = false;
454                     }
455                 }
456             }
457         } else {
458             ((StackLayout)mBase.getLayout()).topControl = mNotSelected;
459             mThreadViewer.setInput(null);
460         }
461 
462         mBase.layout();
463     }
464 
465     /**
466      * Updates the stack call of the currently selected thread.
467      * <p/>
468      * This <b>must</b> be called from the UI thread.
469      */
updateThreadStackCall()470     private void updateThreadStackCall() {
471         Client client = getCurrentClient();
472         if (client != null) {
473             // get the current selection in the ThreadTable
474             ThreadInfo selectedThread = getThreadSelection(null);
475 
476             if (selectedThread != null) {
477                 updateThreadStackTrace(selectedThread);
478             } else {
479                 updateThreadStackTrace(null);
480             }
481         }
482     }
483 
484     /**
485      * updates the stackcall of the specified thread. If <code>null</code> the UI is emptied
486      * of current data.
487      * @param thread
488      */
updateThreadStackTrace(ThreadInfo thread)489     private void updateThreadStackTrace(ThreadInfo thread) {
490         mStackTracePanel.setViewerInput(thread);
491 
492         if (thread != null) {
493             mRefreshStackTraceButton.setEnabled(true);
494             long stackcallTime = thread.getStackCallTime();
495             if (stackcallTime != 0) {
496                 String label = new Date(stackcallTime).toString();
497                 mStackTraceTimeLabel.setText(label);
498             } else {
499                 mStackTraceTimeLabel.setText(""); //$NON-NLS-1$
500             }
501         } else {
502             mRefreshStackTraceButton.setEnabled(true);
503             mStackTraceTimeLabel.setText(""); //$NON-NLS-1$
504         }
505     }
506 
507     @Override
setTableFocusListener()508     protected void setTableFocusListener() {
509         addTableToFocusListener(mThreadTable);
510         addTableToFocusListener(mStackTraceTable);
511     }
512 
513     /**
514      * Initiate recurring events. We use a shorter "initialWait" so we do the
515      * first execution sooner. We don't do it immediately because we want to
516      * give the clients a chance to get set up.
517      */
startRecurringThreadUpdate()518     private void startRecurringThreadUpdate() {
519         mRecurringThreadUpdateRunning = true;
520         int initialWait = 1000;
521 
522         mDisplay.timerExec(initialWait, new Runnable() {
523             public void run() {
524                 synchronized (mLock) {
525                     // lets check we still want updates.
526                     if (mMustStopRecurringThreadUpdate == false) {
527                         Client client = getCurrentClient();
528                         if (client != null) {
529                             client.requestThreadUpdate();
530 
531                             mDisplay.timerExec(
532                                     DdmUiPreferences.getThreadRefreshInterval() * 1000, this);
533                         } else {
534                             // we don't have a Client, which means the runnable is not
535                             // going to be called through the timer. We reset the running flag.
536                             mRecurringThreadUpdateRunning = false;
537                         }
538                     } else {
539                         // else actually stops (don't call the timerExec) and reset the flags.
540                         mRecurringThreadUpdateRunning = false;
541                         mMustStopRecurringThreadUpdate = false;
542                     }
543                 }
544             }
545         });
546     }
547 
548     /**
549      * Returns the current thread selection or <code>null</code> if none is found.
550      * If a {@link ISelection} object is specified, the first {@link ThreadInfo} from this selection
551      * is returned, otherwise, the <code>ISelection</code> returned by
552      * {@link TableViewer#getSelection()} is used.
553      * @param selection the {@link ISelection} to use, or <code>null</code>
554      */
getThreadSelection(ISelection selection)555     private ThreadInfo getThreadSelection(ISelection selection) {
556         if (selection == null) {
557             selection = mThreadViewer.getSelection();
558         }
559 
560         if (selection instanceof IStructuredSelection) {
561             IStructuredSelection structuredSelection = (IStructuredSelection)selection;
562             Object object = structuredSelection.getFirstElement();
563             if (object instanceof ThreadInfo) {
564                 return (ThreadInfo)object;
565             }
566         }
567 
568         return null;
569     }
570 
571 }
572 
573