package rocks.inspectit.ui.rcp.editor.table.input;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.TableColumn;
import rocks.inspectit.shared.all.cmr.model.MethodIdent;
import rocks.inspectit.shared.all.cmr.service.ICachedDataService;
import rocks.inspectit.shared.all.communication.data.InvocationSequenceData;
import rocks.inspectit.shared.all.communication.data.TimerData;
import rocks.inspectit.shared.cs.communication.comparator.DefaultDataComparatorEnum;
import rocks.inspectit.shared.cs.communication.comparator.IDataComparator;
import rocks.inspectit.shared.cs.communication.comparator.MethodSensorDataComparatorEnum;
import rocks.inspectit.shared.cs.communication.comparator.TimerDataComparatorEnum;
import rocks.inspectit.shared.cs.indexing.aggregation.impl.AggregationPerformer;
import rocks.inspectit.shared.cs.indexing.aggregation.impl.TimerDataAggregator;
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.preferences.IPreferenceGroup;
import rocks.inspectit.ui.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent;
import rocks.inspectit.ui.rcp.editor.preferences.PreferenceId;
import rocks.inspectit.ui.rcp.editor.table.TableViewerComparator;
import rocks.inspectit.ui.rcp.editor.tree.util.TraceTreeData;
import rocks.inspectit.ui.rcp.editor.viewers.RawAggregatedResultComparator;
import rocks.inspectit.ui.rcp.editor.viewers.StyledCellIndexLabelProvider;
import rocks.inspectit.ui.rcp.formatter.NumberFormatter;
import rocks.inspectit.ui.rcp.formatter.TextFormatter;
import rocks.inspectit.ui.rcp.handlers.ShowHideColumnsHandler;
/**
* This input controller displays details of all methods involved in an invocation sequence.
*
* @author Patrice Bouillet
*
*/
public class MethodInvocInputController extends AbstractTableInputController {
/**
* The private inner enumeration used to define the used IDs which are mapped into the columns.
* The order in this enumeration represents the order of the columns. If it is reordered,
* nothing else has to be changed.
*
* @author Patrice Bouillet
*
*/
private static enum Column {
/** The timestamp column. */
TIMESTAMP("Timestamp", 130, InspectITImages.IMG_TIMESTAMP, false, true, DefaultDataComparatorEnum.TIMESTAMP),
/** The package column. */
PACKAGE("Package", 200, InspectITImages.IMG_PACKAGE, true, true, MethodSensorDataComparatorEnum.PACKAGE),
/** The class column. */
CLASS("Class", 200, InspectITImages.IMG_CLASS, true, true, MethodSensorDataComparatorEnum.CLASS),
/** The method column. */
METHOD("Method", 300, InspectITImages.IMG_METHOD, true, true, MethodSensorDataComparatorEnum.METHOD),
/** The count column. */
COUNT("Count", 60, null, true, false, TimerDataComparatorEnum.COUNT),
/** The average column. */
AVERAGE("Avg (ms)", 60, null, true, false, TimerDataComparatorEnum.AVERAGE),
/** The minimum column. */
MIN("Min (ms)", 60, null, true, false, TimerDataComparatorEnum.MIN),
/** The maximum column. */
MAX("Max (ms)", 60, null, true, false, TimerDataComparatorEnum.MAX),
/** The duration column. */
DURATION("Duration (ms)", 70, null, true, true, TimerDataComparatorEnum.DURATION),
/** The average exclusive duration column. */
EXCLUSIVEAVERAGE("Exc. Avg (ms)", 80, null, true, false, TimerDataComparatorEnum.EXCLUSIVEAVERAGE),
/** The min exclusive duration column. */
EXCLUSIVEMIN("Exc. Min (ms)", 80, null, true, false, TimerDataComparatorEnum.EXCLUSIVEMIN),
/** The max exclusive duration column. */
EXCLUSIVEMAX("Exc. Max (ms)", 80, null, true, false, TimerDataComparatorEnum.EXCLUSIVEMAX),
/** The total exclusive duration column. */
EXCLUSIVESUM("Exc. duration (ms)", 80, null, true, true, TimerDataComparatorEnum.EXCLUSIVEDURATION),
/** The cpu average column. */
CPUAVERAGE("Cpu Avg (ms)", 60, null, true, false, TimerDataComparatorEnum.CPUAVERAGE),
/** The cpu minimum column. */
CPUMIN("Cpu Min (ms)", 60, null, true, false, TimerDataComparatorEnum.CPUMIN),
/** The cpu maximum column. */
CPUMAX("Cpu Max (ms)", 60, null, true, false, TimerDataComparatorEnum.CPUMAX),
/** The cpu duration column. */
CPUDURATION("Cpu Duration (ms)", 70, null, true, true, TimerDataComparatorEnum.CPUDURATION);
/** The name. */
private String name;
/** The width of the column. */
private int width;
/** The image descriptor. Can be <code>null</code> */
private Image image;
/** If the column should be shown in aggregated mode. */
private boolean showInAggregatedMode;
/** If the column should be shown in raw mode. */
private boolean showInRawMode;
/** Comparator for the column. */
private IDataComparator<? super TimerData> dataComparator;
/**
* 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}.
* @param showInAggregatedMode
* If the column should be shown in aggregated mode.
* @param showInRawMode
* If the column should be shown in raw mode.
* @param dataComparator
* Comparator for the column.
*
*/
private Column(String name, int width, String imageName, boolean showInAggregatedMode, boolean showInRawMode, IDataComparator<? super TimerData> dataComparator) {
this.name = name;
this.width = width;
this.image = InspectIT.getDefault().getImage(imageName);
this.showInAggregatedMode = showInAggregatedMode;
this.showInRawMode = showInRawMode;
this.dataComparator = dataComparator;
}
/**
* 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];
}
}
/**
* The cached service is needed because of the ID mappings.
*/
private ICachedDataService cachedDataService;
/**
* Empty styled string.
*/
private final StyledString emptyStyledString = new StyledString();
/**
* List that is displayed after processing the invocation.
*/
private List<TimerData> timerDataList;
/**
* Should view display raw mode or not.
*/
private boolean rawMode = false;
/**
* {@inheritDoc}
*/
@Override
public void setInputDefinition(InputDefinition inputDefinition) {
super.setInputDefinition(inputDefinition);
cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService();
}
/**
* {@inheritDoc}
*/
@Override
public void createColumns(TableViewer tableViewer) {
for (Column column : Column.values()) {
TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE);
viewerColumn.getColumn().setMoveable(true);
viewerColumn.getColumn().setResizable(true);
viewerColumn.getColumn().setText(column.name);
if (column.showInAggregatedMode) {
viewerColumn.getColumn().setWidth(column.width);
} else {
viewerColumn.getColumn().setWidth(0);
}
if (null != column.image) {
viewerColumn.getColumn().setImage(column.image);
}
mapTableViewerColumn(column, viewerColumn);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean canAlterColumnWidth(TableColumn tableColumn) {
for (Column column : Column.values()) {
if (Objects.equals(getMappedTableViewerColumn(column).getColumn(), tableColumn)) {
return (column.showInRawMode && rawMode) || (column.showInAggregatedMode && !rawMode);
}
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public Set<PreferenceId> getPreferenceIds() {
Set<PreferenceId> preferences = EnumSet.noneOf(PreferenceId.class);
preferences.add(PreferenceId.INVOCATION_SUBVIEW_MODE);
return preferences;
}
/**
* {@inheritDoc}
*/
@Override
public void preferenceEventFired(PreferenceEvent preferenceEvent) {
if (PreferenceId.INVOCATION_SUBVIEW_MODE.equals(preferenceEvent.getPreferenceId())) {
Map<IPreferenceGroup, Object> preferenceMap = preferenceEvent.getPreferenceMap();
if ((null != preferenceMap) && preferenceMap.containsKey(PreferenceId.InvocationSubviewMode.RAW)) {
Boolean isRawMode = (Boolean) preferenceMap.get(PreferenceId.InvocationSubviewMode.RAW);
// first show/hide columns and then change the rawMode value
handleRawAggregatedColumnVisibility(isRawMode.booleanValue());
rawMode = isRawMode.booleanValue();
}
}
}
/**
* Handles the raw and aggregated columns hiding/showing.
*
* @param rawMode
* Is raw mode active.
*/
private void handleRawAggregatedColumnVisibility(boolean rawMode) {
for (Column column : Column.values()) {
if (rawMode) {
if (column.showInRawMode && !column.showInAggregatedMode && !ShowHideColumnsHandler.isColumnHidden(this.getClass(), column.name)) {
Integer width = ShowHideColumnsHandler.getRememberedColumnWidth(this.getClass(), column.name);
getMappedTableViewerColumn(column).getColumn().setWidth((null != width) ? width.intValue() : column.width);
} else if (!column.showInRawMode && column.showInAggregatedMode) {
getMappedTableViewerColumn(column).getColumn().setWidth(0);
}
} else {
if (!column.showInRawMode && column.showInAggregatedMode && !ShowHideColumnsHandler.isColumnHidden(this.getClass(), column.name)) {
Integer width = ShowHideColumnsHandler.getRememberedColumnWidth(this.getClass(), column.name);
getMappedTableViewerColumn(column).getColumn().setWidth((null != width) ? width.intValue() : column.width);
} else if (column.showInRawMode && !column.showInAggregatedMode) {
getMappedTableViewerColumn(column).getColumn().setWidth(0);
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public IContentProvider getContentProvider() {
return new MethodInvocContentProvider();
}
/**
* {@inheritDoc}
*/
@Override
public ViewerComparator getComparator() {
TableViewerComparator<TimerData> methodInputViewerComparator = new TableViewerComparator<>();
for (Column column : Column.values()) {
RawAggregatedResultComparator<TimerData> comparator = new RawAggregatedResultComparator<TimerData>(column.dataComparator, cachedDataService, column.showInRawMode,
column.showInAggregatedMode) {
@Override
protected boolean isRawMode() {
return rawMode;
}
};
methodInputViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), comparator);
}
return methodInputViewerComparator;
}
/**
* {@inheritDoc}
*/
@Override
public IBaseLabelProvider getLabelProvider() {
return new MethodInvocLabelProvider();
}
/**
* {@inheritDoc}
*/
@Override
public boolean canOpenInput(List<? extends Object> data) {
if (null == data) {
return false;
}
if (data.isEmpty()) {
return true;
}
// we accept invocation sequences
if (data.get(0) instanceof InvocationSequenceData) {
return true;
}
// or one trace data
if (data.get(0) instanceof TraceTreeData) {
return true;
}
return false;
}
/**
* The content provider for this view.
*
* @author Patrice Bouillet
*
*/
private final class MethodInvocContentProvider implements IStructuredContentProvider {
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public Object[] getElements(Object inputElement) {
List<? extends Object> input = (List<? extends Object>) inputElement;
if (CollectionUtils.isEmpty(input)) {
return new Object[0];
}
List<InvocationSequenceData> invocationSequenceDataList;
if (input.get(0) instanceof TraceTreeData) {
invocationSequenceDataList = TraceTreeData.collectInvocations((TraceTreeData) input.get(0), new ArrayList<InvocationSequenceData>());
} else {
invocationSequenceDataList = (List<InvocationSequenceData>) inputElement;
}
timerDataList = getRawInputList(invocationSequenceDataList, new ArrayList<TimerData>());
if (!rawMode) {
AggregationPerformer<TimerData> aggregationPerformer = new AggregationPerformer<>(new TimerDataAggregator());
aggregationPerformer.processCollection(timerDataList);
timerDataList = aggregationPerformer.getResultList();
} else {
Collections.sort(timerDataList, new Comparator<TimerData>() {
@Override
public int compare(TimerData o1, TimerData o2) {
return o1.getTimeStamp().compareTo(o2.getTimeStamp());
}
});
}
return timerDataList.toArray();
}
/**
* Creates the raw input list of timers from a list of invocations.
*
* @param invocationSequenceDataList
* List of invocations to check.
* @param resultList
* List where results will be stored. Needed because of reflection. Note that
* this list will be returned as the result.
* @return List of raw order timer data.
*/
public List<TimerData> getRawInputList(List<InvocationSequenceData> invocationSequenceDataList, List<TimerData> resultList) {
for (InvocationSequenceData invocationSequenceData : invocationSequenceDataList) {
TimerData timerData = getTimerData(invocationSequenceData);
if (null != timerData) {
resultList.add(timerData);
}
getRawInputList(invocationSequenceData.getNestedSequences(), resultList);
}
return resultList;
}
/**
* Returns the extracted timer data from the invocation.
*
* @param invocationData
* {@link InvocationSequenceData}.
* @return Timer data or null if it can not be created.
*/
private TimerData getTimerData(InvocationSequenceData invocationData) {
TimerData timerData = null;
if (null != invocationData.getTimerData()) {
timerData = invocationData.getTimerData();
} else if (null != invocationData.getSqlStatementData()) {
timerData = invocationData.getSqlStatementData();
} else if (null == invocationData.getParentSequence()) {
timerData = createTimerDataForRootInvocation(invocationData);
}
return timerData;
}
/**
* Creates the timer data from a root invocation object.
*
* @param invocationData
* Root invocation object.
* @return Timer data with set duration from the invocation and calculated exclusive
* duration.
*/
private TimerData createTimerDataForRootInvocation(InvocationSequenceData invocationData) {
TimerData timerData = new TimerData();
timerData.setPlatformIdent(invocationData.getPlatformIdent());
timerData.setMethodIdent(invocationData.getMethodIdent());
timerData.setTimeStamp(invocationData.getTimeStamp());
timerData.setDuration(invocationData.getDuration());
timerData.calculateMax(invocationData.getDuration());
timerData.calculateMin(invocationData.getDuration());
timerData.increaseCount();
double exclusiveTime = invocationData.getDuration() - computeNestedDuration(invocationData);
timerData.setExclusiveDuration(exclusiveTime);
timerData.calculateExclusiveMax(exclusiveTime);
timerData.calculateExclusiveMin(exclusiveTime);
timerData.increaseExclusiveCount();
timerData.finalizeData();
return timerData;
}
/**
* Computes the duration of the nested invocation elements.
*
* @param data
* The data objects which is inspected for its nested elements.
* @return The duration of all nested sequences (with their nested sequences as well).
*/
private double computeNestedDuration(InvocationSequenceData data) {
if (data.getNestedSequences().isEmpty()) {
return 0;
}
double nestedDuration = 0d;
boolean added = false;
for (InvocationSequenceData nestedData : data.getNestedSequences()) {
if (null == nestedData.getParentSequence()) {
nestedDuration = nestedDuration + nestedData.getDuration();
added = true;
} else if (null != nestedData.getTimerData()) {
nestedDuration = nestedDuration + nestedData.getTimerData().getDuration();
added = true;
} else if ((null != nestedData.getSqlStatementData()) && (1 == nestedData.getSqlStatementData().getCount())) {
nestedDuration = nestedDuration + nestedData.getSqlStatementData().getDuration();
added = true;
}
if (!added && !nestedData.getNestedSequences().isEmpty()) {
// nothing was added, but there could be child elements with
// time measurements
nestedDuration = nestedDuration + computeNestedDuration(nestedData);
}
added = false;
}
return nestedDuration;
}
/**
* {@inheritDoc}
*/
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
/**
* {@inheritDoc}
*/
@Override
public void dispose() {
}
}
/**
* The sql label provider used by this view.
*
* @author Patrice Bouillet
*
*/
private final class MethodInvocLabelProvider extends StyledCellIndexLabelProvider {
/**
* Creates the styled text.
*
* @param element
* The element to create the styled text for.
* @param index
* The index in the column.
* @return The created styled string.
*/
@Override
public StyledString getStyledText(Object element, int index) {
TimerData data = (TimerData) element;
MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent());
Column enumId = Column.fromOrd(index);
return getStyledTextForColumn(data, methodIdent, enumId);
}
}
/**
* Returns the styled text for a specific column.
*
* @param data
* The data object to extract the information from.
* @param methodIdent
* The method ident object.
* @param enumId
* The enumeration ID.
* @return The styled string containing the information from the data object.
*/
private StyledString getStyledTextForColumn(TimerData data, MethodIdent methodIdent, Column enumId) {
switch (enumId) {
case TIMESTAMP:
if (rawMode) {
return new StyledString(NumberFormatter.formatTimeWithMillis(data.getTimeStamp()));
} else {
return emptyStyledString;
}
case PACKAGE:
if ((methodIdent.getPackageName() != null) && !methodIdent.getPackageName().equals("")) {
return new StyledString(methodIdent.getPackageName());
} else {
return new StyledString("(default)");
}
case CLASS:
return new StyledString(methodIdent.getClassName());
case METHOD:
return new StyledString(TextFormatter.getMethodWithParameters(methodIdent));
case COUNT:
return new StyledString(String.valueOf(data.getCount()));
case AVERAGE:
// check if it is a valid data (or if timer data was available)
if (data.isTimeDataAvailable()) {
return new StyledString(NumberFormatter.formatDouble(data.getAverage()));
} else {
return emptyStyledString;
}
case MIN:
if (data.isTimeDataAvailable()) {
return new StyledString(NumberFormatter.formatDouble(data.getMin()));
} else {
return emptyStyledString;
}
case MAX:
if (data.isTimeDataAvailable()) {
return new StyledString(NumberFormatter.formatDouble(data.getMax()));
} else {
return emptyStyledString;
}
case DURATION:
if (data.isTimeDataAvailable()) {
return new StyledString(NumberFormatter.formatDouble(data.getDuration()));
} else {
return emptyStyledString;
}
case CPUAVERAGE:
// check if it is a valid data (or if timer data was available)
if (data.isCpuMetricDataAvailable()) {
return new StyledString(NumberFormatter.formatDouble(data.getCpuAverage()));
} else {
return emptyStyledString;
}
case CPUMIN:
if (data.isCpuMetricDataAvailable()) {
return new StyledString(NumberFormatter.formatDouble(data.getCpuMin()));
} else {
return emptyStyledString;
}
case CPUMAX:
if (data.isCpuMetricDataAvailable()) {
return new StyledString(NumberFormatter.formatDouble(data.getCpuMax()));
} else {
return emptyStyledString;
}
case CPUDURATION:
if (data.isCpuMetricDataAvailable()) {
return new StyledString(NumberFormatter.formatDouble(data.getCpuDuration()));
} else {
return emptyStyledString;
}
case EXCLUSIVESUM:
if (data.isExclusiveTimeDataAvailable()) {
return new StyledString(NumberFormatter.formatDouble(data.getExclusiveDuration()));
} else {
return emptyStyledString;
}
case EXCLUSIVEAVERAGE:
if (data.isExclusiveTimeDataAvailable()) {
return new StyledString(NumberFormatter.formatDouble(data.getExclusiveAverage()));
} else {
return emptyStyledString;
}
case EXCLUSIVEMIN:
if (data.isExclusiveTimeDataAvailable()) {
return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMin()));
} else {
return emptyStyledString;
}
case EXCLUSIVEMAX:
if (data.isExclusiveTimeDataAvailable()) {
return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMax()));
} else {
return emptyStyledString;
}
default:
return new StyledString("error");
}
}
/**
* {@inheritDoc}
*/
@Override
public String getReadableString(Object object) {
if (object instanceof TimerData) {
TimerData data = (TimerData) object;
StringBuilder sb = new StringBuilder();
MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent());
for (Column column : Column.values()) {
sb.append(getStyledTextForColumn(data, methodIdent, column).toString());
sb.append('\t');
}
return sb.toString();
}
throw new RuntimeException("Could not create the human readable string!");
}
/**
* {@inheritDoc}
*/
@Override
public List<String> getColumnValues(Object object) {
if (object instanceof TimerData) {
TimerData data = (TimerData) object;
MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent());
List<String> values = new ArrayList<>();
for (Column column : Column.values()) {
values.add(getStyledTextForColumn(data, methodIdent, column).toString());
}
return values;
}
throw new RuntimeException("Could not create the column values!");
}
/**
* {@inheritDoc}
*/
@Override
public Object[] getObjectsToSearch(Object tableInput) {
return timerDataList.toArray();
}
/**
* {@inheritDoc}
*/
@Override
public SubViewClassification getSubViewClassification() {
return SubViewClassification.SLAVE;
}
}