package rocks.inspectit.ui.rcp.editor.tree.input;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.handlers.IHandlerService;
import io.opentracing.tag.Tags;
import rocks.inspectit.shared.all.cmr.model.PlatformIdent;
import rocks.inspectit.shared.all.cmr.service.ICachedDataService;
import rocks.inspectit.shared.all.communication.data.InvocationSequenceData;
import rocks.inspectit.shared.all.communication.data.cmr.ApplicationData;
import rocks.inspectit.shared.all.communication.data.cmr.BusinessTransactionData;
import rocks.inspectit.shared.all.tracing.data.Span;
import rocks.inspectit.shared.cs.cmr.service.IInvocationDataAccessService;
import rocks.inspectit.shared.cs.cmr.service.ISpanService;
import rocks.inspectit.ui.rcp.InspectIT;
import rocks.inspectit.ui.rcp.InspectITImages;
import rocks.inspectit.ui.rcp.editor.inputdefinition.InputDefinition;
import rocks.inspectit.ui.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory;
import rocks.inspectit.ui.rcp.editor.inputdefinition.extra.TraceInputDefinitionExtra;
import rocks.inspectit.ui.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent;
import rocks.inspectit.ui.rcp.editor.preferences.PreferenceId;
import rocks.inspectit.ui.rcp.editor.root.IRootEditor;
import rocks.inspectit.ui.rcp.editor.tree.util.TraceTreeData;
import rocks.inspectit.ui.rcp.editor.viewers.StyledCellIndexLabelProvider;
import rocks.inspectit.ui.rcp.formatter.ImageFormatter;
import rocks.inspectit.ui.rcp.formatter.NumberFormatter;
import rocks.inspectit.ui.rcp.formatter.TextFormatter;
import rocks.inspectit.ui.rcp.handlers.LocateHandler;
import rocks.inspectit.ui.rcp.preferences.PreferencesConstants;
import rocks.inspectit.ui.rcp.preferences.PreferencesUtils;
import rocks.inspectit.ui.rcp.repository.CmrRepositoryDefinition;
/**
* The input controller that displays the details of one trace.
*
* @author Ivan Senic
*
*/
public class TraceDetailsTreeInputController extends AbstractTreeInputController {
/**
* Columns for the view.
*
* @author Ivan Senic
*
*/
private enum Column {
/** The method column. */
DETAILS("Details", 350, null),
/** Type. */
TYPE("Client", 40, null),
/** Propagation. */
PROPAGATION("Propagation", 100, null),
/** Error. */
ERROR("Error", 40, null),
/** Propagation. */
NESTED_DATA("Nested Data", 40, null),
/** The start time column. */
TIME("Start Time", 160, InspectITImages.IMG_TIMESTAMP),
/** The duration column. */
DURATION("Duration (ms)", 80, InspectITImages.IMG_TIME),
/** The exclusive duration column. */
EXCLUSIVE("Exc. duration (ms)", 100, null),
/** The application column. */
APPLICATION("Application", 150, null),
/** The business transaction column. */
BUSINESS_TRANSACTION("Business Transaction", 150, null),
/** Propagation. */
AGENT("Agent", 120, InspectITImages.IMG_AGENT);
/** The name. */
private String name;
/** The width of the column. */
private int width;
/** The image descriptor. Can be <code>null</code> */
private Image image;
/**
* Default constructor which creates a column enumeration object.
*
* @param name
* The name of the column.
* @param width
* The width of the column.
* @param imageName
* The name of the image. Names are defined in {@link InspectITImages}.
*/
Column(String name, int width, String imageName) {
this.name = name;
this.width = width;
this.image = InspectIT.getDefault().getImage(imageName);
}
/**
* Converts an ordinal into a column.
*
* @param i
* The ordinal.
* @return The appropriate column.
*/
public static Column fromOrd(int i) {
if ((i < 0) || (i >= Column.values().length)) {
throw new IndexOutOfBoundsException("Invalid ordinal");
}
return Column.values()[i];
}
}
/**
* Empty styled string.
*/
private static final StyledString EMPTY_STYLED_STRING = new StyledString();
/**
* Trace id to display details for.
*/
private long traceId;
/**
* The cached service is needed because of the ID mappings.
*/
private ICachedDataService cachedDataService;
/**
* Span service for loading traces.
*/
private ISpanService spanService;
/**
* Invocation services for loading invocation related to a trace.
*/
private IInvocationDataAccessService invocationDataAccessService;
/**
* Current input of the tree.
*/
private TraceTreeData input;
/**
* Decimal places.
*/
private int timeDecimalPlaces = PreferencesUtils.getIntValue(PreferencesConstants.DECIMAL_PLACES);
/**
* {@inheritDoc}
*/
@Override
public void setInputDefinition(InputDefinition inputDefinition) {
super.setInputDefinition(inputDefinition);
spanService = inputDefinition.getRepositoryDefinition().getSpanService();
invocationDataAccessService = inputDefinition.getRepositoryDefinition().getInvocationDataAccessService();
cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService();
if (inputDefinition.hasInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.TRACE_EXTRAS_MARKER)) {
TraceInputDefinitionExtra inputDefinitionExtra = inputDefinition.getInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.TRACE_EXTRAS_MARKER);
traceId = inputDefinitionExtra.getTraceId();
}
}
/**
* {@inheritDoc}
*/
@Override
public void createColumns(TreeViewer treeViewer) {
for (Column column : Column.values()) {
TreeViewerColumn viewerColumn = new TreeViewerColumn(treeViewer, SWT.NONE);
viewerColumn.getColumn().setMoveable(true);
viewerColumn.getColumn().setResizable(true);
viewerColumn.getColumn().setText(column.name);
viewerColumn.getColumn().setWidth(column.width);
if (null != column.image) {
viewerColumn.getColumn().setImage(column.image);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public Set<PreferenceId> getPreferenceIds() {
Set<PreferenceId> preferences = EnumSet.noneOf(PreferenceId.class);
if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) {
preferences.add(PreferenceId.CLEAR_BUFFER);
}
preferences.add(PreferenceId.UPDATE);
preferences.add(PreferenceId.TIME_RESOLUTION);
return preferences;
}
/**
* {@inheritDoc}
*/
@Override
public void preferenceEventFired(PreferenceEvent preferenceEvent) {
switch (preferenceEvent.getPreferenceId()) {
case TIME_RESOLUTION:
if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeResolution.TIME_DECIMAL_PLACES_ID)) {
timeDecimalPlaces = (Integer) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeResolution.TIME_DECIMAL_PLACES_ID);
}
break;
default:
break;
}
}
/**
* {@inheritDoc}
*/
@Override
public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) {
monitor.beginTask("Loading trace details..", IProgressMonitor.UNKNOWN);
Collection<? extends Span> spans = spanService.getSpans(traceId);
Collection<InvocationSequenceData> invocations = invocationDataAccessService.getInvocationSequenceDetail(traceId);
if (CollectionUtils.isNotEmpty(spans)) {
input = TraceTreeData.buildModel(spans, invocations);
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
IWorkbenchPage page = window.getActivePage();
IRootEditor rootEditor = (IRootEditor) page.getActiveEditor();
rootEditor.setDataInput(Collections.singletonList(input));
}
});
}
monitor.done();
}
/**
* {@inheritDoc}
*/
@Override
public Object getTreeInput() {
return input;
}
/**
* {@inheritDoc}
*/
@Override
public boolean canOpenInput(List<? extends Object> data) {
if (CollectionUtils.isEmpty(data)) {
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public IContentProvider getContentProvider() {
return new TraceDetailContentProvider();
}
/**
* {@inheritDoc}
*/
@Override
public IBaseLabelProvider getLabelProvider() {
return new TraceDetailLabelProvider();
}
/**
* {@inheritDoc}
*/
@Override
public int getExpandLevel() {
return TreeViewer.ALL_LEVELS;
}
/**
* {@inheritDoc}
*/
@Override
public boolean changeExpandedState(boolean expandedState, TreePath treePath) {
// only expand, never collapse
return !expandedState;
}
/**
* {@inheritDoc}
*/
@Override
public void doubleClick(DoubleClickEvent event) {
StructuredSelection selection = (StructuredSelection) event.getSelection();
if (!selection.isEmpty()) {
// open trace details view
IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class);
ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);
Command command = commandService.getCommand(LocateHandler.COMMAND);
ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event());
try {
command.executeWithChecks(executionEvent);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public List<String> getColumnValues(Object object) {
if (object instanceof Span) {
Span span = (Span) object;
List<String> values = new ArrayList<>();
for (Column column : Column.values()) {
values.add(getStyledTextForColumn(span, column).toString());
}
return values;
}
throw new RuntimeException("Could not create the column values!");
}
/**
* {@inheritDoc}
*/
@Override
public Object[] getObjectsToSearch(Object treeInput) {
TraceTreeData data = (TraceTreeData) treeInput;
if (null != data) {
List<Span> allObjects = new ArrayList<>();
extractAllChildren(allObjects, data);
return allObjects.toArray();
}
return new Object[0];
}
/**
* Extracts all children from {@link TraceTreeData} in all objects list.
*
* @param allObjects
* List to extract children.
* @param data
* trace data
*/
private void extractAllChildren(List<Span> allObjects, TraceTreeData data) {
allObjects.add(data.getSpan());
for (TraceTreeData child : data.getChildren()) {
extractAllChildren(allObjects, child);
}
}
/**
* Returns {@link StyledString} for the data / column combo.
*
* @param span
* {@link Span}.
* @param column
* {@link Column}
* @return {@link StyledString}
*/
private StyledString getStyledTextForColumn(Span span, Column column) {
TraceTreeData data = TraceTreeData.getForSpanIdent(input, span.getSpanIdent());
switch (column) {
case DETAILS:
return TextFormatter.getSpanDetailsShort(span, cachedDataService);
case PROPAGATION:
return TextFormatter.getPropagationStyled(span.getPropagationType());
case AGENT:
PlatformIdent platformIdent = cachedDataService.getPlatformIdentForId(span.getPlatformIdent());
if (null != platformIdent) {
return new StyledString(platformIdent.getAgentName());
} else {
return EMPTY_STYLED_STRING;
}
case TIME:
return new StyledString(NumberFormatter.formatTimeWithMillis(span.getTimeStamp()));
case DURATION:
StyledString durationString = new StyledString(NumberFormatter.formatDouble(span.getDuration(), timeDecimalPlaces));
if (data.isConsideredAsync()) {
durationString.append(TextFormatter.getWarningSign());
}
return durationString;
case EXCLUSIVE:
StyledString exclusive = new StyledString();
exclusive.append(NumberFormatter.formatDouble(data.getExclusiveDuration(), timeDecimalPlaces));
exclusive.append(" (", StyledString.COUNTER_STYLER);
int percentage = (int) ((data.getExclusivePercentage()) * 100);
exclusive.append(NumberFormatter.formatInteger(percentage), StyledString.COUNTER_STYLER);
exclusive.append("%)", StyledString.COUNTER_STYLER);
if (data.isConsideredAsync()) {
exclusive.append(TextFormatter.getWarningSign());
}
return exclusive;
case APPLICATION:
if (CollectionUtils.isNotEmpty(data.getInvocations())) {
Set<Integer> applicationIds = new HashSet<>(1);
for (InvocationSequenceData invoc : data.getInvocations()) {
applicationIds.add(invoc.getApplicationId());
}
if (applicationIds.size() > 1) {
return new StyledString("Multiple");
} else {
int appId = applicationIds.iterator().next();
ApplicationData application = cachedDataService.getApplicationForId(appId);
if (null != application) {
return new StyledString(application.getName());
}
}
}
return EMPTY_STYLED_STRING;
case BUSINESS_TRANSACTION:
if (CollectionUtils.isNotEmpty(data.getInvocations())) {
Set<Integer> transationsIds = new HashSet<>(1);
Set<Integer> applicationIds = new HashSet<>(1);
for (InvocationSequenceData invoc : data.getInvocations()) {
applicationIds.add(invoc.getApplicationId());
transationsIds.add(invoc.getBusinessTransactionId());
}
if ((transationsIds.size() > 1) || (applicationIds.size() > 1)) {
return new StyledString("Miltiple");
} else {
int appId = applicationIds.iterator().next();
int btId = transationsIds.iterator().next();
BusinessTransactionData transaction = cachedDataService.getBusinessTransactionForId(appId, btId);
if (null != transaction) {
return new StyledString(transaction.getName());
}
}
}
return EMPTY_STYLED_STRING;
default:
return EMPTY_STYLED_STRING;
}
}
/**
* Content provider.
*
* @author Ivan Senic
*
*/
private final class TraceDetailContentProvider implements ITreeContentProvider {
/**
* {@inheritDoc}
*/
@Override
public Object[] getElements(Object inputElement) {
// INPORTANT: We always return spans as the elements for the tree, so that all command
// like search and locate can work
if (inputElement instanceof TraceTreeData) {
return new Object[] { ((TraceTreeData) inputElement).getSpan() };
}
return new Object[] {};
}
/**
* {@inheritDoc}
*/
@Override
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof Span) {
Span span = (Span) parentElement;
TraceTreeData data = TraceTreeData.getForSpanIdent(input, span.getSpanIdent());
if (null != data) {
Object[] children = new Object[data.getChildren().size()];
for (int i = 0; i < children.length; i++) {
children[i] = data.getChildren().get(i).getSpan();
}
return children;
}
}
return new Object[0];
}
/**
* {@inheritDoc}
*/
@Override
public Object getParent(Object element) {
if (element instanceof Span) {
Span span = (Span) element;
TraceTreeData data = TraceTreeData.getForSpanIdent(input, span.getSpanIdent());
if (null != data) {
TraceTreeData parent = data.getParent();
if (null != parent) {
return parent.getSpan();
}
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasChildren(Object element) {
if (element instanceof Span) {
Span span = (Span) element;
TraceTreeData data = TraceTreeData.getForSpanIdent(input, span.getSpanIdent());
if (null != data) {
return CollectionUtils.isNotEmpty(data.getChildren());
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void dispose() {
}
/**
* {@inheritDoc}
*/
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
/**
* Label provider.
*
* @author Ivan Senic
*
*/
private final class TraceDetailLabelProvider extends StyledCellIndexLabelProvider {
/**
* The resource manager is used for the images etc.
*/
private LocalResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources());
/**
* {@inheritDoc}
*/
@Override
protected StyledString getStyledText(Object element, int index) {
Span span = (Span) element;
Column enumId = Column.fromOrd(index);
return getStyledTextForColumn(span, enumId);
}
/**
* {@inheritDoc}
*/
@Override
protected Image getColumnImage(Object element, int index) {
Span span = (Span) element;
TraceTreeData data = TraceTreeData.getForSpanIdent(input, span.getSpanIdent());
Column enumId = Column.fromOrd(index);
switch (enumId) {
case DETAILS:
return ImageFormatter.getSpanImage(span, resourceManager);
case TYPE:
if (span.isCaller()) {
return InspectIT.getDefault().getImage(InspectITImages.IMG_CHECKMARK);
}
break;
case ERROR:
boolean error = MapUtils.getBoolean(span.getTags(), Tags.ERROR.getKey(), false);
if (error) {
return InspectIT.getDefault().getImage(InspectITImages.IMG_ERROR_CIRCLE_FRAME);
}
break;
case NESTED_DATA:
if (data.hasSqlsInInvocations() && data.hasExceptionsInInvocations()) {
return ImageFormatter.getCombinedImage(resourceManager, SWT.HORIZONTAL, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_DATABASE),
InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_EXCEPTION_SENSOR));
} else if (data.hasSqlsInInvocations()) {
return InspectIT.getDefault().getImage(InspectITImages.IMG_DATABASE);
} else if (data.hasExceptionsInInvocations()) {
return InspectIT.getDefault().getImage(InspectITImages.IMG_EXCEPTION_SENSOR);
}
break;
case PROPAGATION:
return ImageFormatter.getPropagationImage(span.getPropagationType());
default:
return super.getColumnImage(element, index);
}
return super.getColumnImage(element, index);
}
/**
* {@inheritDoc}
*/
@Override
public String getToolTipText(Object element, int index) {
Span span = (Span) element;
TraceTreeData data = TraceTreeData.getForSpanIdent(input, span.getSpanIdent());
Column enumId = Column.fromOrd(index);
switch (enumId) {
case NESTED_DATA:
boolean hasSqlsInInvocations = data.hasSqlsInInvocations();
boolean hasExceptionsInInvocations = data.hasExceptionsInInvocations();
if (hasSqlsInInvocations || hasExceptionsInInvocations) {
StringBuilder toolTip = new StringBuilder("This trace span contains:");
if (hasSqlsInInvocations) {
toolTip.append("\n - SQL statement(s)");
}
if (hasExceptionsInInvocations) {
toolTip.append("\n - Exception(s)");
}
return toolTip.toString();
}
break;
case TIME:
return "Start times are reported by the clock on specific agent, thus differences can occur.";
case DURATION:
if (data.isConsideredAsync()) {
return "This span is considered asynchrouns to it's parent.\nIt's duration can not be used in reference to it's parent.";
}
break;
case EXCLUSIVE:
if (data.isConsideredAsync()) {
return "This span is considered asynchrouns to it's parent.\nIt's exclusive duration only reflects this span and it's children.";
}
break;
case PROPAGATION:
return TextFormatter.getPropagationStyled(span.getPropagationType()).getString();
default:
return super.getToolTipText(element, index);
}
return super.getToolTipText(element, index);
}
/**
* {@inheritDoc}
*/
@Override
public void dispose() {
resourceManager.dispose();
super.dispose();
}
}
}