/*******************************************************************************
* Copyright (c) 2013, 2015 Xilinx, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Xilinx - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.internal.debug.ui.profiler;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.contexts.DebugContextEvent;
import org.eclipse.debug.ui.contexts.IDebugContextListener;
import org.eclipse.debug.ui.contexts.IDebugContextService;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.tcf.internal.debug.launch.TCFSourceLookupParticipant;
import org.eclipse.tcf.internal.debug.model.TCFFunctionRef;
import org.eclipse.tcf.internal.debug.model.TCFLaunch;
import org.eclipse.tcf.internal.debug.model.TCFSourceRef;
import org.eclipse.tcf.internal.debug.ui.ImageCache;
import org.eclipse.tcf.internal.debug.ui.model.TCFModel;
import org.eclipse.tcf.internal.debug.ui.model.TCFModelManager;
import org.eclipse.tcf.internal.debug.ui.model.TCFNode;
import org.eclipse.tcf.internal.debug.ui.model.TCFNodeExecContext;
import org.eclipse.tcf.protocol.JSON;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.IProfiler;
import org.eclipse.tcf.services.ISymbols;
import org.eclipse.tcf.util.TCFDataCache;
import org.eclipse.tcf.util.TCFTask;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.part.ViewPart;
public class ProfilerView extends ViewPart {
private static final int FRAME_COUNT = 8;
private static final String PARAM_VIEW_UPDATE_PERIOD = ProfilerSettingsDlg.PARAM_VIEW_UPDATE_PERIOD;
private static final String PARAM_AGGREGATE = ProfilerSettingsDlg.PARAM_AGGREGATE;
private static final String PARAM_STACK_TRACE = ProfilerSettingsDlg.PARAM_STACK_TRACE;
private static class ProfileSample {
int cnt;
final BigInteger[] trace;
ProfileSample(BigInteger[] trace) {
this.trace = trace;
}
}
private static class ProfileData {
final String ctx;
final Map<String,Object> params;
final boolean stack_trace;
boolean stopped;
boolean unsupported;
Exception error;
int sample_count;
// Samples by frame
final Map<BigInteger,List<ProfileSample>>[] map;
int generation_inp;
int generation_out;
ProfileEntry[] entries;
@SuppressWarnings("unchecked")
ProfileData(String ctx, Map<String,Object> params) {
this.ctx = ctx;
this.params = new HashMap<String,Object>(params);
Boolean b = (Boolean)params.get(PARAM_STACK_TRACE);
stack_trace = b != null && b.booleanValue();
int frame_cnt = 1;
if (stack_trace) {
Number n = (Number)params.get(IProfiler.PARAM_FRAME_CNT);
if (n != null) frame_cnt = n.intValue();
}
map = new Map[frame_cnt];
for (int i = 0; i < frame_cnt; i++) {
map[i] = new HashMap<BigInteger,List<ProfileSample>>();
}
}
}
private static class ProfileEntry {
final BigInteger addr;
final Set<BigInteger> addr_list = new HashSet<BigInteger>();
String name;
String file_full;
String file_base;
int line;
int count;
float total;
ProfileEntryRef[] up;
ProfileEntryRef[] dw;
boolean src_info_valid;
boolean mark;
ProfileEntry(BigInteger addr) {
this.addr = addr;
}
}
private static class ProfileEntryRef {
final ProfileEntry pe;
float total;
ProfileEntryRef(ProfileEntry pe) {
this.pe = pe;
}
}
private class ProfileModel implements TCFModel.ProfilerDataListener {
final Map<String,ProfileData> data = new HashMap<String,ProfileData>();
@Override
public void onDataReceived(String ctx, Map<String, Object>[] arr) {
int cnt = 0;
ProfileData p = data.get(ctx);
if (p == null) return;
if (p.stopped) return;
for (Map<String,Object> props : arr) {
if (props == null) continue;
String format = (String)props.get(IProfiler.PROP_FORMAT);
if (format == null || !format.equals("StackTraces")) continue;
addSamples(p, props);
cnt++;
}
if (p.unsupported != (cnt == 0)) {
p.unsupported = cnt == 0;
updateView();
}
}
}
private final IDebugContextListener selection_listener = new IDebugContextListener() {
@Override
public void debugContextChanged(DebugContextEvent event) {
selectionChanged(event.getContext());
}
};
private final TCFModelManager.ModelManagerListener launch_listener = new TCFModelManager.ModelManagerListener() {
@Override
public void onConnected(TCFLaunch launch, TCFModel model) {
}
@Override
public void onDisconnected(TCFLaunch launch, TCFModel model) {
ProfileModel prf_model = models.remove(model);
if (prf_model != null) model.removeProfilerDataListener(prf_model);
updateView();
}
};
private static class ProfileEntryComparator implements Comparator<ProfileEntry> {
final int sorting;
ProfileEntryComparator(int sorting) {
this.sorting = sorting;
}
@Override
public int compare(ProfileEntry x, ProfileEntry y) {
int r = 0;
switch (sorting) {
case 0:
r = x.addr.compareTo(y.addr);
break;
case 1:
break;
case 2:
if (x.total > y.total) return -1;
if (x.total < y.total) return +1;
break;
case 3:
if (x.name == y.name) break;
if (x.name == null) return -1;
if (y.name == null) return +1;
r = x.name.compareTo(y.name);
break;
case 4:
if (x.file_base == y.file_base) break;
if (x.file_base == null) return -1;
if (y.file_base == null) return +1;
r = x.file_base.compareTo(y.file_base);
break;
}
if (r != 0) return r;
if (x.count > y.count) return -1;
if (x.count < y.count) return +1;
return x.addr.compareTo(y.addr);
}
};
private static class ProfileEntryRefComparator implements Comparator<ProfileEntryRef> {
@Override
public int compare(ProfileEntryRef x, ProfileEntryRef y) {
if (x.total > y.total) return -1;
if (x.total < y.total) return +1;
return x.pe.addr.compareTo(y.pe.addr);
}
}
private class Update implements Runnable {
final int sorting;
final TCFNode selection;
final Map<BigInteger,ProfileEntry> entries = new HashMap<BigInteger,ProfileEntry>();
final Map<BigInteger,BigInteger> addr_to_func_addr = new HashMap<BigInteger,BigInteger>();
final Map<BigInteger,String> addr_to_func_id = new HashMap<BigInteger,String>();
final TCFNodeExecContext node;
final ProfileData prof_data;
final TCFModel model;
final boolean aggrerate;
final int generation;
TCFNodeExecContext mem_node;
TCFDataCache<?> pending;
boolean done;
Update() {
assert Protocol.isDispatchThread();
selection = ProfilerView.this.selection;
sorting = ProfilerView.this.sorting;
ProfileData p = null;
if (selection != null) {
ProfileModel m = models.get(selection.getModel());
if (m != null) p = m.data.get(selection.getID());
}
prof_data = p;
if (p == null) {
node = null;
model = null;
generation = 0;
aggrerate = false;
}
else {
node = (TCFNodeExecContext)selection;
model = selection.getModel();
generation = p.generation_inp;
Boolean b = (Boolean)p.params.get(PARAM_AGGREGATE);
aggrerate = b != null && b.booleanValue();
}
last_update = this;
}
private String getFuncID(BigInteger addr) {
String func_id = addr_to_func_id.get(addr);
if (func_id == null) {
func_id = "";
TCFDataCache<TCFFunctionRef> func_cache = mem_node.getFuncInfo(addr);
if (func_cache != null) {
if (!func_cache.validate()) {
pending = func_cache;
return null;
}
TCFFunctionRef func_data = func_cache.getData();
if (func_data != null && func_data.symbol_id != null) {
func_id = func_data.symbol_id;
}
}
addr_to_func_id.put(addr, func_id);
}
return func_id;
}
private BigInteger getFuncAddress(BigInteger addr) {
if (!aggrerate) return addr;
BigInteger func_addr = addr_to_func_addr.get(addr);
if (func_addr != null) return func_addr;
String func_id = getFuncID(addr);
if (func_id == null) return null;
func_addr = addr;
if (func_id.length() > 0) {
TCFDataCache<ISymbols.Symbol> sym_cache = model.getSymbolInfoCache(func_id);
if (!sym_cache.validate()) {
pending = sym_cache;
return null;
}
ISymbols.Symbol sym_data = sym_cache.getData();
if (sym_data != null && sym_data.getAddress() != null) {
func_addr = JSON.toBigInteger(sym_data.getAddress());
}
}
addr_to_func_addr.put(addr, func_addr);
return func_addr;
}
private boolean getFuncName(ProfileEntry pe) {
String func_id = getFuncID(pe.addr);
if (func_id == null) return false;
if (func_id.length() > 0) {
TCFDataCache<ISymbols.Symbol> sym_cache = model.getSymbolInfoCache(func_id);
if (!sym_cache.validate()) {
pending = sym_cache;
return false;
}
ISymbols.Symbol sym_data = sym_cache.getData();
if (sym_data != null && sym_data.getName() != null) {
pe.name = sym_data.getName();
}
}
return true;
}
private boolean getLineInfo(ProfileEntry pe) {
TCFDataCache<TCFSourceRef> line_cache = mem_node.getLineInfo(pe.addr);
if (line_cache == null) return true;
if (!line_cache.validate()) {
pending = line_cache;
return false;
}
TCFSourceRef line_data = line_cache.getData();
if (line_data != null && line_data.area != null) {
pe.file_full = TCFSourceLookupParticipant.toFileName(line_data.area);
if (pe.file_full != null) {
pe.file_base = pe.file_full;
int i = pe.file_base.lastIndexOf('/');
int j = pe.file_base.lastIndexOf('\\');
if (i > j) pe.file_base = pe.file_base.substring(i + 1);
if (i < j) pe.file_base = pe.file_base.substring(j + 1);
pe.line = line_data.area.start_line;
}
}
return true;
}
private void linkEntry(ProfileEntry pe) {
Set<ProfileEntry> set_up = new HashSet<ProfileEntry>();
Set<ProfileEntry> set_dw = new HashSet<ProfileEntry>();
for (int n = 0; n < prof_data.map.length; n++) {
for (BigInteger addr : pe.addr_list) {
List<ProfileSample> s0 = prof_data.map[n].get(addr);
if (s0 != null) {
for (ProfileSample x : s0) {
assert addr.equals(x.trace[n]);
if (x.trace.length <= n + 1) continue;
BigInteger addr_up = getFuncAddress(x.trace[n + 1]);
ProfileEntry pe_up = entries.get(addr_up);
set_up.add(pe_up);
}
}
if (n == prof_data.map.length - 1) continue;
List<ProfileSample> s1 = prof_data.map[n + 1].get(addr);
if (s1 != null) {
for (ProfileSample x : s1) {
assert x.trace.length > n + 1;
assert addr.equals(x.trace[n + 1]);
BigInteger addr_dw = getFuncAddress(x.trace[n]);
ProfileEntry pe_dw = entries.get(addr_dw);
set_dw.add(pe_dw);
}
}
}
}
if (set_up.size() > 0) {
int n = 0;
pe.up = new ProfileEntryRef[set_up.size()];
for (ProfileEntry p : set_up) pe.up[n++] = new ProfileEntryRef(p);
}
if (set_dw.size() > 0) {
int n = 0;
pe.dw = new ProfileEntryRef[set_dw.size()];
for (ProfileEntry p : set_dw) pe.dw[n++] = new ProfileEntryRef(p);
}
}
private void addUpTotal(ProfileEntry pe, float cnt) {
if (cnt <= 0.01f || pe.up == null) return;
pe.mark = true;
int n = 0;
for (ProfileEntryRef up : pe.up) {
if (!up.pe.mark) n++;
}
if (n != 0) {
float m = cnt / n;
for (ProfileEntryRef up : pe.up) {
if (up.pe.mark) continue;
addUpTotal(up.pe, m * 1.0001f);
up.pe.total += m;
up.total += m;
for (ProfileEntryRef dw : up.pe.dw) {
if (dw.pe == pe) {
dw.total += m;
break;
}
}
}
}
pe.mark = false;
}
@Override
public void run() {
pending = null;
mem_node = null;
if (done) return;
if (last_update != this) return;
boolean capabilities_ok = false;
if (selection instanceof TCFNodeExecContext) {
TCFDataCache<Map<String,Object>> cache = selection.getModel().getProfilerCapabilities(selection.getID());
if (!cache.validate(this)) return;
Map<String,Object> map = cache.getData();
capabilities_ok = map == null || map.containsKey("StackTraces");
}
if (prof_data != null && generation != prof_data.generation_out) {
if (node.isDisposed()) {
entries.clear();
}
else {
TCFDataCache<TCFNodeExecContext> mem_cache = node.getMemoryNode();
if (!mem_cache.validate()) {
pending = mem_cache;
}
else {
mem_node = mem_cache.getData();
}
if (mem_node != null) {
for (int n = 0; n < prof_data.map.length; n++) {
for (BigInteger addr : prof_data.map[n].keySet()) {
BigInteger func_addr = getFuncAddress(addr);
if (func_addr == null) continue;
ProfileEntry pe = entries.get(func_addr);
if (pe == null) {
pe = new ProfileEntry(func_addr);
entries.put(pe.addr, pe);
}
if (!pe.addr_list.contains(addr)) {
if (n == 0) {
List<ProfileSample> s = prof_data.map[0].get(addr);
for (ProfileSample x : s) pe.count += x.cnt;
}
pe.addr_list.add(addr);
}
if (!pe.src_info_valid) {
pe.src_info_valid = true;
if (!getFuncName(pe)) pe.src_info_valid = false;
if (!getLineInfo(pe)) pe.src_info_valid = false;
}
}
}
}
if (pending != null) {
pending.wait(this);
return;
}
for (ProfileEntry pe : entries.values()) linkEntry(pe);
for (List<ProfileSample> lps : prof_data.map[0].values()) {
for (ProfileSample ps : lps) {
int n = 0;
ProfileEntry dw_pe = null;
ProfileEntry pe = null;
ProfileEntry up_pe = null;
assert(ps.trace.length <= prof_data.map.length);
while (n < ps.trace.length) {
float cnt = ps.cnt * (1.0f + n / 10000f);
if (pe == null) pe = entries.get(getFuncAddress(ps.trace[n]));
if (dw_pe != null) {
for (ProfileEntryRef r : pe.dw) {
if (r.pe == dw_pe) {
r.total += cnt;
break;
}
}
}
if (n < ps.trace.length - 1) {
up_pe = entries.get(getFuncAddress(ps.trace[n + 1]));
for (ProfileEntryRef r : pe.up) {
if (r.pe == up_pe) {
r.total += cnt;
break;
}
}
}
else {
addUpTotal(pe, cnt);
}
pe.total += cnt;
dw_pe = pe;
pe = up_pe;
n++;
}
}
}
for (ProfileEntry pe : entries.values()) {
if (pe.up != null) Arrays.sort(pe.up, new ProfileEntryRefComparator());
if (pe.dw != null) Arrays.sort(pe.dw, new ProfileEntryRefComparator());
}
}
prof_data.generation_out = generation;
prof_data.entries = entries.values().toArray(new ProfileEntry[entries.size()]);
assert pending == null;
}
if (prof_data != null && prof_data.entries != null) {
Arrays.sort(prof_data.entries, new ProfileEntryComparator(sorting));
}
done = true;
final boolean enable_start = capabilities_ok;
final boolean enable_stop = node != null && !prof_data.stopped;
final boolean stopped = node != null && prof_data.stopped;
final boolean running = node != null && !stopped;
final boolean unsupported = node != null && prof_data.unsupported;
final boolean total_count = prof_data != null && prof_data.stack_trace;
final int sample_count = prof_data == null ? 0 : prof_data.sample_count;
final String error_msg = prof_data == null || prof_data.error == null ? null :
TCFModel.getErrorMessage(prof_data.error, false);
asyncExec(new Runnable() {
@Override
public void run() {
if (last_update != Update.this) return;
if (parent.isDisposed()) return;
action_start.setEnabled(enable_start);
action_stop.setEnabled(enable_stop);
profile_node = node;
disposeColors();
Object viewer_input = prof_data != null ? prof_data.entries : null;
if (viewer_main.getInput() != viewer_input) {
ISelection s = viewer_main.getSelection();
ProfilerView.this.total_count = total_count;
ProfilerView.this.sample_count = sample_count;
viewer_main.setInput(viewer_input);
List<ProfileEntry> l = new ArrayList<ProfileEntry>();
if (s instanceof IStructuredSelection && entries.size() > 0) {
IStructuredSelection ss = (IStructuredSelection)s;
for (Object obj : ss.toArray()) {
if (obj instanceof ProfileEntry) {
ProfileEntry pe = (ProfileEntry)obj;
pe = entries.get(pe.addr);
if (pe != null) l.add(pe);
}
}
}
setSelection(l, false);
}
else {
// Sorting might be changed, need to refresh
viewer_main.refresh();
}
if (!enable_start) {
status.setText("Selected context does not support profiling");
}
else if (unsupported) {
status.setText("No suitable profiler found for selected context");
}
else if (stopped) {
status.setText("Profiler stopped. Press 'Start' button to restart profiling");
}
else if (!running) {
status.setText("Idle. Press 'Start' button to start profiling");
}
else if (error_msg != null) {
status.setText("Cannot upload profiling data: " + error_msg);
}
else {
status.setText("Profiler running. " + sample_count + " samples");
}
}
});
}
}
private class ProfileContentProvider implements IStructuredContentProvider {
public Object[] getElements(Object input) {
return (Object[])input;
}
public void inputChanged(Viewer viewer, Object old_input, Object new_input) {
}
public void dispose() {
}
}
private class ProfileEntryLabelProvider extends LabelProvider implements ITableLabelProvider {
public Image getColumnImage(Object element, int column) {
return null;
}
public String getColumnText(Object element, int column) {
ProfileEntry e = (ProfileEntry)element;
switch (column) {
case 0: return toHex(e.addr);
case 1: return "";
case 2: return "";
case 3: return e.name;
case 4: return e.file_base;
case 5: return e.line == 0 ? null : Integer.toString(e.line);
}
return null;
}
}
private class ProfileEntryRefLabelProvider extends LabelProvider implements ITableLabelProvider {
public Image getColumnImage(Object element, int column) {
return null;
}
public String getColumnText(Object element, int column) {
ProfileEntryRef e = (ProfileEntryRef)element;
switch (column) {
case 0: return toHex(e.pe.addr);
case 1: return "";
case 2: return "";
case 3: return e.pe.name;
case 4: return e.pe.file_base;
case 5: return e.pe.line == 0 ? null : Integer.toString(e.pe.line);
}
return null;
}
}
private final Action action_start = new Action("Start", ImageCache.getImageDescriptor(ImageCache.IMG_THREAD_RUNNNIG)) {
@Override
public void run() {
final TCFNode node = selection;
if (node == null) return;
Map<String,Object> conf = new TCFTask<Map<String,Object>>() {
@Override
public void run() {
done(getConfiguration(node));
}
}.getE();
ProfilerSettingsDlg dlg = new ProfilerSettingsDlg(getSite().getShell(), conf);
if (dlg.open() == Window.OK) {
final Map<String,Object> params = dlg.data;
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
configuration.put(node.getID(), params);
Integer n = (Integer)params.get(PARAM_VIEW_UPDATE_PERIOD);
if (n != null) node.getModel().setProfilerReadDelay(n.intValue());
if (selection != node) return;
start(selection);
}
});
}
}
};
private final Action action_stop = new Action("Stop", ImageCache.getImageDescriptor(ImageCache.IMG_THREAD_SUSPENDED)) {
@Override
public void run() {
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
if (selection == null) return;
stop(selection);
}
});
}
};
private boolean disposed;
private TCFNode selection;
private int sorting;
private Update last_update;
private Composite parent;
private Label status;
private TableViewer viewer_main;
private TableViewer viewer_up;
private TableViewer viewer_dw;
private Composite main_composite;
private TCFNode profile_node;
private int sample_count;
private boolean total_count;
private HashMap<RGB,Color> colors = new HashMap<RGB,Color>();
private static final String[] column_ids = {
"Address",
"% Exclusive",
"% Inclusive",
"Function",
"File",
"Line"
};
private static final int[] column_size = {
80,
60,
60,
250,
250,
60
};
private final Map<String,Map<String,Object>> configuration = new HashMap<String,Map<String,Object>>();
private final Map<TCFModel,ProfileModel> models = new HashMap<TCFModel,ProfileModel>();
@Override
public void createPartControl(Composite parent) {
assert !disposed;
this.parent = parent;
Font font = parent.getFont();
final Composite composite = new Composite(parent, SWT.NO_FOCUS | SWT.H_SCROLL);
composite.setFont(font);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
main_composite = composite;
final Composite table = createTable(composite);
composite.setLayout(new Layout() {
@Override
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
Point p = table.computeSize(SWT.DEFAULT, hHint, flushCache);
if (p.x < wHint) p.x = wHint;
p.y = hHint;
return p;
}
@Override
protected void layout(Composite composite, boolean flushCache) {
for (;;) {
Rectangle rc = composite.getClientArea();
Point p = table.computeSize(SWT.DEFAULT, rc.height, true);
if (p.x < rc.width) p.x = rc.width;
ScrollBar sb = composite.getHorizontalBar();
int pos = 0;
boolean vis = sb.getVisible();
if (p.x > rc.width) {
pos = sb.getSelection();
if (pos > p.x - rc.width) pos = p.x - rc.width;
sb.setValues(pos, 0, p.x, rc.width, 1, 1);
}
table.setBounds(-pos, rc.y, p.x, rc.height);
if (vis == (p.x > rc.width)) break;
sb.setVisible(!vis);
}
}
});
composite.getHorizontalBar().addListener(SWT.Selection, new Listener () {
public void handleEvent(Event e) {
Point location = table.getLocation();
ScrollBar sb = composite.getHorizontalBar();
int pos = sb.getSelection();
table.setLocation(-pos, location.y);
}
});
action_start.setEnabled(false);
action_stop.setEnabled(false);
IActionBars action_bars = getViewSite().getActionBars();
IToolBarManager tool_bar = action_bars.getToolBarManager();
tool_bar.add(action_start);
tool_bar.add(action_stop);
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
TCFModelManager.getModelManager().addListener(launch_listener);
}
});
IWorkbenchWindow window = getSite().getWorkbenchWindow();
IDebugContextService dcs = DebugUITools.getDebugContextManager().getContextService(window);
dcs.addDebugContextListener(selection_listener);
ISelection active_context = dcs.getActiveContext();
selectionChanged(active_context);
}
private Composite createTable(Composite parent) {
Font font = parent.getFont();
final Composite composite = new Composite(parent, SWT.NO_FOCUS);
final FormLayout layout = new FormLayout();
composite.setFont(font);
composite.setLayout(layout);
Composite main = createMainTable(composite);
final Sash sash = new Sash(composite, SWT.HORIZONTAL);
Composite details = createDetailsPane(composite);
FormData form_data_main = new FormData();
form_data_main.left = new FormAttachment(0, 0);
form_data_main.right = new FormAttachment(100, 0);
form_data_main.top = new FormAttachment(0, 0);
form_data_main.bottom = new FormAttachment(sash, 0);
main.setLayoutData(form_data_main);
final int limit = 20, percent = 60;
final FormData form_data_sash = new FormData();
form_data_sash.left = new FormAttachment(0, 0);
form_data_sash.right = new FormAttachment(100, 0);
form_data_sash.top = new FormAttachment(percent, 0);
sash.setLayoutData(form_data_sash);
sash.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event e) {
Rectangle rect_sash = sash.getBounds();
Rectangle rect_view = composite.getClientArea();
int top = rect_view.height - rect_sash.height - limit;
e.y = Math.max(Math.min(e.y, top), limit);
if (e.y != rect_sash.y) {
form_data_sash.top = new FormAttachment(0, e.y);
composite.layout();
}
}
});
FormData form_data_details = new FormData();
form_data_details.left = new FormAttachment(0, 0);
form_data_details.right = new FormAttachment(100, 0);
form_data_details.top = new FormAttachment(sash, 0);
form_data_details.bottom = new FormAttachment(100, 0);
details.setLayoutData(form_data_details);
return composite;
}
private Composite createMainTable(Composite parent) {
Font font = parent.getFont();
Composite composite = new Composite(parent, SWT.NO_FOCUS | SWT.BORDER);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = 0;
layout.marginHeight = 0;
composite.setFont(font);
composite.setLayout(layout);
status = new Label(composite, SWT.NONE);
status.setFont(font);
status.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
viewer_main = new TableViewer(composite, SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION);
final Table table = viewer_main.getTable();
table.setLayoutData(new GridData(GridData.FILL_BOTH));
table.setHeaderVisible(true);
table.setLinesVisible(true);
table.setFont(font);
viewer_main.setContentProvider(new ProfileContentProvider());
viewer_main.setLabelProvider(new ProfileEntryLabelProvider());
viewer_main.setColumnProperties(column_ids);
for (int i = 0; i < column_ids.length; i++) {
createColumn(table, i);
}
table.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
disposeColors();
if (e.item == null) return;
ProfileEntry pe = (ProfileEntry)viewer_main.getElementAt(table.indexOf((TableItem)e.item));
viewer_up.setInput(pe.up);
viewer_dw.setInput(pe.dw);
displaySource(pe);
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
table.addListener(SWT.PaintItem, new Listener() {
@Override
public void handleEvent(Event event) {
if (event.index == 1 || event.index == 2 && total_count) {
int index = table.indexOf((TableItem)event.item);
ProfileEntry pe = ((ProfileEntry[])viewer_main.getInput())[index];
float count = event.index == 1 ? (float)pe.count : pe.total;
paintPercent(event, count);
}
}
});
return composite;
}
private Composite createDetailsPane(Composite parent) {
Font font = parent.getFont();
Composite composite = new Composite(parent, SWT.NO_FOCUS | SWT.BORDER);
composite.setFont(font);
final Label label_up = new Label(composite, SWT.NONE);
label_up.setFont(font);
label_up.setText("Called From");
viewer_up = new TableViewer(composite, SWT.V_SCROLL | SWT.FULL_SELECTION);
final Table table_up = viewer_up.getTable();
table_up.setHeaderVisible(false);
table_up.setLinesVisible(true);
table_up.setFont(font);
viewer_up.setContentProvider(new ProfileContentProvider());
viewer_up.setLabelProvider(new ProfileEntryRefLabelProvider());
viewer_up.setColumnProperties(column_ids);
table_up.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
if (e.item == null) return;
displaySource(((ProfileEntryRef)viewer_up.getElementAt(table_up.indexOf((TableItem)e.item))).pe);
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
if (e.item == null) return;
displayEntry(((ProfileEntryRef)viewer_up.getElementAt(table_up.indexOf((TableItem)e.item))).pe);
}
});
table_up.addListener(SWT.PaintItem, new Listener() {
@Override
public void handleEvent(Event event) {
if (event.index == 2) {
int index = table_up.indexOf((TableItem)event.item);
ProfileEntryRef pe = ((ProfileEntryRef[])viewer_up.getInput())[index];
paintPercent(event, pe.total);
}
}
});
final Label separator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
separator.setFont(font);
final Label label_dw = new Label(composite, SWT.NONE);
label_dw.setFont(font);
label_dw.setText("Child Calls");
viewer_dw = new TableViewer(composite, SWT.V_SCROLL | SWT.FULL_SELECTION);
final Table table_dw = viewer_dw.getTable();
table_dw.setHeaderVisible(false);
table_dw.setLinesVisible(true);
table_dw.setFont(font);
viewer_dw.setContentProvider(new ProfileContentProvider());
viewer_dw.setLabelProvider(new ProfileEntryRefLabelProvider());
viewer_dw.setColumnProperties(column_ids);
table_dw.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
if (e.item == null) return;
displaySource(((ProfileEntryRef)viewer_dw.getElementAt(table_dw.indexOf((TableItem)e.item))).pe);
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
if (e.item == null) return;
displayEntry(((ProfileEntryRef)viewer_dw.getElementAt(table_dw.indexOf((TableItem)e.item))).pe);
}
});
table_dw.addListener(SWT.PaintItem, new Listener() {
@Override
public void handleEvent(Event event) {
if (event.index == 2) {
int index = table_dw.indexOf((TableItem)event.item);
ProfileEntryRef pe = ((ProfileEntryRef[])viewer_dw.getInput())[index];
paintPercent(event, pe.total);
}
}
});
for (int i = 0; i < column_ids.length; i++) {
createColumn(table_up, i);
createColumn(table_dw, i);
}
composite.setLayout(new Layout() {
@Override
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
Point l_up = label_up.computeSize(wHint, SWT.DEFAULT);
Point t_up = table_up.computeSize(wHint, SWT.DEFAULT);
Point sep = separator.computeSize(wHint, SWT.DEFAULT);
Point l_dw = label_dw.computeSize(wHint, SWT.DEFAULT);
Point t_dw = table_dw.computeSize(wHint, SWT.DEFAULT);
int w = 0;
if (l_up.x > w) w = l_up.x;
if (t_up.x > w) w = t_up.x;
if (sep.x > w) w = sep.x;
if (l_dw.x > w) w = l_dw.x;
if (t_dw.x > w) w = t_dw.x;
int h = l_up.y + t_up.y + sep.y + l_dw.y + t_dw.y;
return new Point(w, h);
}
@Override
protected void layout(Composite composite, boolean flushCache) {
Rectangle rc = composite.getClientArea();
Point l_up = label_up.computeSize(rc.width, SWT.DEFAULT);
Point sep = separator.computeSize(rc.width, SWT.DEFAULT);
Point l_dw = label_dw.computeSize(rc.width, SWT.DEFAULT);
int h = (rc.height - l_up.y - sep.y - l_dw.y) / 2;
if (h < 0) h = 0;
int y = rc.y;
label_up.setBounds(rc.x, y, rc.width, l_up.y);
y += l_up.y;
table_up.setBounds(rc.x, y, rc.width, h);
y += h;
separator.setBounds(rc.x, y, rc.width, sep.y);
y += sep.y;
label_dw.setBounds(rc.x, y, rc.width, l_dw.y);
y += l_dw.y;
table_dw.setBounds(rc.x, y, rc.width, h);
}
});
return composite;
}
private void createColumn(final Table table, int i) {
final int n = i;
final TableColumn c = new TableColumn(table, SWT.NONE, i);
c.setText(column_ids[i]);
c.setWidth(column_size[i]);
if (i != 5) {
c.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
table.setSortDirection(SWT.DOWN);
table.setSortColumn(c);
sorting = n;
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
updateView();
}
});
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
}
if (table == viewer_main.getTable()) {
c.addControlListener(new ControlListener() {
@Override
public void controlResized(ControlEvent e) {
int w = c.getWidth();
if (n == column_size.length - 1 && !System.getProperty("os.name", "").startsWith("Windows")) {
// Workaround:
// Linux GTK tries to outsmart user: last column is auto-resized when table size changes.
// This causes infinite recursion and stack overflow.
if (w > column_size[n]) w = column_size[n];
}
if (viewer_up.getTable().getColumn(n).getWidth() == w &&
viewer_dw.getTable().getColumn(n).getWidth() == w) return;
viewer_up.getTable().getColumn(n).setWidth(w);
viewer_dw.getTable().getColumn(n).setWidth(w);
main_composite.layout();
}
@Override
public void controlMoved(ControlEvent e) {
}
});
if (i == 2) {
table.setSortDirection(SWT.DOWN);
table.setSortColumn(c);
sorting = i;
}
}
}
private String toHex(BigInteger n) {
String s = n.toString(16);
if (s.length() >= 8) return s;
return "00000000".substring(s.length()) + s;
}
private void disposeColors() {
for (Color c : colors.values()) c.dispose();
colors.clear();
}
private Color getColor(RGB rgb) {
Color c = colors.get(rgb);
if (c == null) {
c = new Color(parent.getDisplay(), rgb);
colors.put(rgb, c);
}
return c;
}
private String toPercent(float x) {
float f = x * 100 / sample_count;
String s = String.format("%.3f", f);
int l = s.indexOf('.');
if (l >= 3) s = "100";
else if (l >= 2) s = s.substring(0, 4);
else if (s.charAt(0) == '0') s = s.substring(1);
else s = s.substring(0, 4);
return s;
}
private void paintPercent(Event event, float count) {
Table table = (Table)event.widget;
int cell_width = table.getColumn(event.index).getWidth() - 1;
if (cell_width > 2) {
GC gc = event.gc;
float percent = count * 100 / sample_count;
int width = (int)(cell_width * percent / 100);
if (width > cell_width) width = cell_width;
if (width >= 2) {
Color bg = gc.getBackground();
Color fg = gc.getForeground();
gc.setBackground(getColor(new RGB(bg.getRed() / 3 + 170, bg.getGreen() / 3 + 170, bg.getBlue() / 3)));
gc.setForeground(getColor(new RGB(bg.getRed() / 3 + 170, bg.getGreen() / 3, bg.getBlue() / 3)));
gc.fillGradientRectangle(event.x, event.y, width, event.height, true);
gc.drawRectangle(new Rectangle(event.x, event.y, width - 1, event.height - 1));
gc.setBackground(bg);
gc.setForeground(fg);
}
String text = toPercent(count);
Point size = event.gc.textExtent(text);
int offset = Math.max(0, (event.height - size.y) / 2);
gc.drawText(text, event.x + 2, event.y + offset, true);
}
}
private void selectionChanged(ISelection s) {
if (s instanceof IStructuredSelection) {
final Object obj = ((IStructuredSelection)s).getFirstElement();
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
if (obj instanceof TCFNode) {
selection = (TCFNode)obj;
}
else {
selection = null;
}
updateView();
}
});
}
}
private void setSelection(List<ProfileEntry> l, boolean reveal) {
viewer_main.setSelection(new StructuredSelection(l), reveal);
if (l.size() == 0) {
viewer_up.setInput(null);
viewer_dw.setInput(null);
}
else {
ProfileEntry pe = l.get(0);
viewer_up.setInput(pe.up);
viewer_dw.setInput(pe.dw);
}
}
private void setSelection(ProfileEntry pe, boolean reveal) {
List<ProfileEntry> l = new ArrayList<ProfileEntry>();
if (pe != null) l.add(pe);
setSelection(l, reveal);
}
private void displaySource(ProfileEntry pe) {
if (profile_node == null) return;
if (pe.file_full == null) return;
profile_node.getModel().displaySource(profile_node.getID(), pe.file_full, pe.line);
}
private void displayEntry(ProfileEntry pe) {
setSelection(pe, true);
}
@Override
public void setFocus() {
viewer_main.getControl().setFocus();
}
private void asyncExec(Runnable r) {
try {
parent.getDisplay().asyncExec(r);
}
catch (SWTException x) {
// Widget is disposed
}
}
private Map<String,Object> getConfiguration(TCFNode node) {
String ctx = node.getID();
Map<String,Object> params = configuration.get(ctx);
if (params == null) {
params = new HashMap<String,Object>();
params.put(IProfiler.PARAM_FRAME_CNT, FRAME_COUNT);
params.put(PARAM_AGGREGATE, Boolean.TRUE);
params.put(PARAM_STACK_TRACE, Boolean.FALSE);
configuration.put(ctx, params);
}
params.put(PARAM_VIEW_UPDATE_PERIOD, node.getModel().getProfilerReadDelay());
return params;
}
private void start(TCFNode node) {
assert Protocol.isDispatchThread();
configure(node, getConfiguration(node));
}
private void stop(TCFNode node) {
assert Protocol.isDispatchThread();
configure(node, null);
}
private void configure(final TCFNode node, final Map<String,Object> params) {
TCFModel model = node.getModel();
Map<String,Object> prf_cfg = model.getProfilerConfiguration(node.getID());
if (params != null) {
Object frame_cnt = null;
Boolean stack_trace = (Boolean)params.get(PARAM_STACK_TRACE);
if (stack_trace != null && stack_trace.booleanValue()) {
frame_cnt = params.get(IProfiler.PARAM_FRAME_CNT);
}
if (frame_cnt == null) frame_cnt = Integer.valueOf(1);
prf_cfg.put(IProfiler.PARAM_FRAME_CNT, frame_cnt);
}
else {
prf_cfg.remove(IProfiler.PARAM_FRAME_CNT);
}
model.sendProfilerConfiguration(node.getID());
ProfileModel prf_model = models.get(model);
if (params == null) {
ProfileData prf_data = null;
if (prf_model != null) prf_data = prf_model.data.get(node.getID());
if (prf_data != null) prf_data.stopped = true;
}
else {
ProfileData d = new ProfileData(node.getID(), params);
if (prf_model == null) {
models.put(node.getModel(), prf_model = new ProfileModel());
model.addProfilerDataListener(prf_model);
}
prf_model.data.put(d.ctx, d);
}
updateView();
}
private void addSamples(ProfileData p, Map<String,Object> props) {
int size = 4;
boolean big_endian = false;
Number n = (Number)props.get(IProfiler.PROP_ADDR_SIZE);
if (n != null) size = n.intValue();
Boolean b = (Boolean)props.get(IProfiler.PROP_BIG_ENDIAN);
if (b != null) big_endian = b.booleanValue();
byte[] data = JSON.toByteArray(props.get(IProfiler.PROP_DATA));
if (data == null || data.length == 0) return;
int pos = 0;
byte[] buf = new byte[size + 1];
BigInteger[] trace = new BigInteger[p.map.length];
for (;;) {
int c = -1;
int l = -1;
int i = 0;
while (pos + size <= data.length) {
for (int j = 0; j < size; j++) {
buf[big_endian ? j + 1: size - j] = data[pos++];
}
if (i >= trace.length) continue;
BigInteger a = new BigInteger(buf);
if (c < 0) {
/* Count */
c = a.intValue();
}
else if (l < 0) {
/* Trace length */
l = a.intValue();
}
else {
/* Trace addresses */
trace[i++] = a;
if (i == l) break;
}
}
if (l < 0) break;
if (i > 0) addSample(p, trace, i, c);
}
updateView();
}
private void addSample(ProfileData p, BigInteger[] trace, int len, int cnt) {
assert len > 0;
p.sample_count += cnt;
p.generation_inp++;
ProfileSample ps = null;
if (len > p.map.length) len = p.map.length;
for (int f = 0; f < p.map.length && f < len; f++) {
List<ProfileSample> lp = p.map[f].get(trace[f]);
if (lp != null) {
boolean ok = false;
for (ProfileSample s : lp) {
if (len == s.trace.length) {
int i = 0;
while (i < len && trace[i].equals(s.trace[i])) i++;
if (i == len) {
assert ps == null || ps == s;
ps = s;
ok = true;
}
}
}
if (ok) continue;
}
else {
p.map[f].put(trace[f], lp = new ArrayList<ProfileSample>());
}
if (ps == null) {
BigInteger[] t = new BigInteger[len];
for (int i = 0; i < len; i++) t[i] = trace[i];
ps = new ProfileSample(t);
}
lp.add(ps);
}
ps.cnt += cnt;
}
private void updateView() {
assert Protocol.isDispatchThread();
Protocol.invokeLater(new Update());
}
@Override
public void dispose() {
disposed = true;
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
TCFModelManager.getModelManager().removeListener(launch_listener);
models.clear();
}
});
IWorkbenchWindow window = getSite().getWorkbenchWindow();
IDebugContextService dcs = DebugUITools.getDebugContextManager().getContextService(window);
dcs.removeDebugContextListener(selection_listener);
disposeColors();
super.dispose();
}
}