package rocks.inspectit.ui.rcp.editor.table.input; import java.util.ArrayList; import java.util.Date; import java.util.EnumSet; import java.util.List; import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.eclipse.core.runtime.IProgressMonitor; 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 rocks.inspectit.shared.all.cmr.model.MethodIdent; import rocks.inspectit.shared.all.cmr.service.ICachedDataService; import rocks.inspectit.shared.all.communication.data.TimerData; import rocks.inspectit.shared.cs.cmr.service.ITimerDataAccessService; import rocks.inspectit.shared.cs.communication.comparator.IDataComparator; import rocks.inspectit.shared.cs.communication.comparator.InvocationAwareDataComparatorEnum; import rocks.inspectit.shared.cs.communication.comparator.MethodSensorDataComparatorEnum; import rocks.inspectit.shared.cs.communication.comparator.ResultComparator; import rocks.inspectit.shared.cs.communication.comparator.TimerDataComparatorEnum; 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.PreferenceEventCallback.PreferenceEvent; import rocks.inspectit.ui.rcp.editor.preferences.PreferenceId; import rocks.inspectit.ui.rcp.editor.preferences.PreferenceId.LiveMode; import rocks.inspectit.ui.rcp.editor.root.IRootEditor; import rocks.inspectit.ui.rcp.editor.table.TableViewerComparator; 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.preferences.PreferencesConstants; import rocks.inspectit.ui.rcp.preferences.PreferencesUtils; import rocks.inspectit.ui.rcp.repository.CmrRepositoryDefinition; /** * Table input controller for the aggregated Timer data view. * * @author Ivan Senic * */ public class TimerDataInputController extends AbstractTableInputController { /** * The ID of this subview / controller. */ public static final String ID = "inspectit.subview.table.aggregatedtimerdata"; /** * 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 * @author Ivan Senic * */ private static enum Column { /** The time column. */ CHARTING("Charting", 20, null, TimerDataComparatorEnum.CHARTING), /** The package column. */ PACKAGE("Package", 200, InspectITImages.IMG_PACKAGE, MethodSensorDataComparatorEnum.PACKAGE), /** The class column. */ CLASS("Class", 200, InspectITImages.IMG_CLASS, MethodSensorDataComparatorEnum.CLASS), /** The method column. */ METHOD("Method", 300, InspectITImages.IMG_METHOD, MethodSensorDataComparatorEnum.METHOD), /** Invocation Affiliation. */ INVOCATION_AFFILLIATION("In Invocations", 120, InspectITImages.IMG_INVOCATION, InvocationAwareDataComparatorEnum.INVOCATION_AFFILIATION), /** The count column. */ COUNT("Count", 60, null, TimerDataComparatorEnum.COUNT), /** The average column. */ AVERAGE("Avg (ms)", 60, null, TimerDataComparatorEnum.AVERAGE), /** The minimum column. */ MIN("Min (ms)", 60, null, TimerDataComparatorEnum.MIN), /** The maximum column. */ MAX("Max (ms)", 60, null, TimerDataComparatorEnum.MAX), /** The duration column. */ DURATION("Duration (ms)", 70, null, TimerDataComparatorEnum.DURATION), /** The average exclusive duration column. */ EXCLUSIVEAVERAGE("Exc. Avg (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEAVERAGE), /** The min exclusive duration column. */ EXCLUSIVEMIN("Exc. Min (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEMIN), /** The max exclusive duration column. */ EXCLUSIVEMAX("Exc. Max (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEMAX), /** The total exclusive duration column. */ EXCLUSIVESUM("Exc. duration (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEDURATION), /** The cpu average column. */ CPUAVERAGE("Cpu Avg (ms)", 60, null, TimerDataComparatorEnum.CPUAVERAGE), /** The cpu minimum column. */ CPUMIN("Cpu Min (ms)", 60, null, TimerDataComparatorEnum.CPUMIN), /** The cpu maximum column. */ CPUMAX("Cpu Max (ms)", 60, null, TimerDataComparatorEnum.CPUMAX), /** The cpu duration column. */ CPUDURATION("Cpu Duration (ms)", 70, null, 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; /** 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 dataComparator * Comparator for the column. */ private Column(String name, int width, String imageName, IDataComparator<? super TimerData> dataComparator) { this.name = name; this.width = width; this.image = InspectIT.getDefault().getImage(imageName); 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]; } } /** * Timer data access service. */ private ITimerDataAccessService timerDataAccessService; /** * Global data access service. */ private ICachedDataService cachedDataService; /** * List of Timer data to be displayed. */ private List<TimerData> timerDataList = new ArrayList<>(); /** * Template object used for querying. */ private TimerData template; /** * Empty styled string. */ private final StyledString emptyStyledString = new StyledString(); /** * Date to display invocations from. */ private Date fromDate = null; /** * Date to display invocations to. */ private Date toDate = null; /** * Are we in live mode. */ private boolean autoUpdate = LiveMode.ACTIVE_DEFAULT; /** * Decimal places. */ private int timeDecimalPlaces = PreferencesUtils.getIntValue(PreferencesConstants.DECIMAL_PLACES); /** * {@inheritDoc} */ @Override public void setInputDefinition(InputDefinition inputDefinition) { super.setInputDefinition(inputDefinition); template = new TimerData(); template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); template.setMethodIdent(inputDefinition.getIdDefinition().getMethodId()); timerDataAccessService = inputDefinition.getRepositoryDefinition().getTimerDataAccessService(); 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); viewerColumn.getColumn().setWidth(column.width); if (Column.EXCLUSIVEAVERAGE.equals(column) || Column.EXCLUSIVESUM.equals(column) || Column.EXCLUSIVEMIN.equals(column) || Column.EXCLUSIVEMAX.equals(column)) { // TODO: Remove this tooltip and add it to the cell as soon as the image bug is // fixed in Eclipse. viewerColumn.getColumn() .setToolTipText("Exclusive times can only be calculated correctly if the timer is within an invocation sequence. " + "A warning marker is provided if not all timers are run within an invocation sequence. Please be aware that " + "avg, sum, min and max calculations are reflecting only the timers inside an invocation sequence."); } if (null != column.image) { viewerColumn.getColumn().setImage(column.image); } mapTableViewerColumn(column, viewerColumn); } } /** * {@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.LIVEMODE); } preferences.add(PreferenceId.UPDATE); preferences.add(PreferenceId.TIME_RESOLUTION); preferences.add(PreferenceId.TIMELINE); return preferences; } /** * {@inheritDoc} */ @Override public void preferenceEventFired(PreferenceEvent preferenceEvent) { switch (preferenceEvent.getPreferenceId()) { case TIMELINE: if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.FROM_DATE_ID)) { fromDate = (Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.FROM_DATE_ID); } if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.TO_DATE_ID)) { toDate = (Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.TO_DATE_ID); } break; case LIVEMODE: if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.LiveMode.BUTTON_LIVE_ID)) { autoUpdate = (Boolean) preferenceEvent.getPreferenceMap().get(PreferenceId.LiveMode.BUTTON_LIVE_ID); } break; 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 boolean canOpenInput(List<? extends Object> data) { if (null == data) { return false; } return true; } /** * {@inheritDoc} */ @Override public Object getTableInput() { return timerDataList; } /** * {@inheritDoc} */ @Override public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) { monitor.beginTask("Getting timer data information", IProgressMonitor.UNKNOWN); List<TimerData> aggregatedTimerData; if (autoUpdate) { aggregatedTimerData = timerDataAccessService.getAggregatedTimerData(template); } else { aggregatedTimerData = timerDataAccessService.getAggregatedTimerData(template, fromDate, toDate); } timerDataList.clear(); if (CollectionUtils.isNotEmpty(aggregatedTimerData)) { timerDataList.addAll(aggregatedTimerData); } monitor.done(); } /** * {@inheritDoc} */ @Override public IContentProvider getContentProvider() { return new TimerDataContentProvider(); } /** * {@inheritDoc} */ @Override public IBaseLabelProvider getLabelProvider() { return new TimerDataLabelProvider(); } /** * {@inheritDoc} */ @Override public ViewerComparator getComparator() { TableViewerComparator<TimerData> timerDataViewerComparator = new TableViewerComparator<>(); for (Column column : Column.values()) { ResultComparator<TimerData> resultComparator = new ResultComparator<>(column.dataComparator, cachedDataService); timerDataViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), resultComparator); } return timerDataViewerComparator; } /** * {@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!"); } /** * Content provider for the view. * * @author Ivan Senic * */ private static final class TimerDataContentProvider implements IStructuredContentProvider { /** * {@inheritDoc} */ @Override public void dispose() { } /** * {@inheritDoc} */ @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public Object[] getElements(Object inputElement) { return ((List<TimerData>) inputElement).toArray(); } } /** * Label provider for the view. * * @author Ivan Senic * */ private final class TimerDataLabelProvider extends StyledCellIndexLabelProvider { /** * {@inheritDoc} */ @Override public StyledString getStyledText(Object element, int index) { TimerData data = (TimerData) element; MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); Column enumId = Column.fromOrd(index); StyledString styledString = getStyledTextForColumn(data, methodIdent, enumId); if (addWarnSign(data, enumId)) { styledString.append(TextFormatter.getWarningSign()); } return styledString; } /** * Decides if the warn sign should be added for the specific column. * * @param data * TimerData * @param column * Column to check. * @return True if warn sign should be added. */ private boolean addWarnSign(TimerData data, Column column) { switch (column) { case EXCLUSIVEAVERAGE: case EXCLUSIVEMAX: case EXCLUSIVEMIN: case EXCLUSIVESUM: int affPercentage = (int) (data.getInvocationAffiliationPercentage() * 100); return data.isExclusiveTimeDataAvailable() && (affPercentage < 100); default: return false; } } /** * * {@inheritDoc} */ @Override protected Image getColumnImage(Object element, int index) { TimerData data = (TimerData) element; Column enumId = Column.fromOrd(index); switch (enumId) { case CHARTING: if (data.isCharting()) { return InspectIT.getDefault().getImage(InspectITImages.IMG_CHART_PIE); } default: return super.getColumnImage(element, index); } } /** * {@inheritDoc} */ @Override public String getToolTipText(Object element, int index) { TimerData data = (TimerData) element; Column enumId = Column.fromOrd(index); switch (enumId) { case CHARTING: if (data.isCharting()) { return "Duration chart can be displayed for this timer data."; } default: return super.getToolTipText(element, index); } } } /** * 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 CHARTING: 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 INVOCATION_AFFILLIATION: int percentage = (int) (data.getInvocationAffiliationPercentage() * 100); int invocations = 0; if (null != data.getInvocationParentsIdSet()) { invocations = data.getInvocationParentsIdSet().size(); } return TextFormatter.getInvocationAffilliationPercentageString(percentage, invocations); case COUNT: return new StyledString(String.valueOf(data.getCount())); case AVERAGE: if (data.isTimeDataAvailable()) { return new StyledString(NumberFormatter.formatDouble(data.getAverage(), timeDecimalPlaces)); } else { return emptyStyledString; } case MIN: if (data.isTimeDataAvailable()) { return new StyledString(NumberFormatter.formatDouble(data.getMin(), timeDecimalPlaces)); } else { return emptyStyledString; } case MAX: if (data.isTimeDataAvailable()) { return new StyledString(NumberFormatter.formatDouble(data.getMax(), timeDecimalPlaces)); } else { return emptyStyledString; } case DURATION: if (data.isTimeDataAvailable()) { return new StyledString(NumberFormatter.formatDouble(data.getDuration(), timeDecimalPlaces)); } else { return emptyStyledString; } case CPUAVERAGE: if (data.isCpuMetricDataAvailable()) { return new StyledString(NumberFormatter.formatDouble(data.getCpuAverage(), timeDecimalPlaces)); } else { return emptyStyledString; } case CPUMIN: if (data.isCpuMetricDataAvailable()) { return new StyledString(NumberFormatter.formatDouble(data.getCpuMin(), timeDecimalPlaces)); } else { return emptyStyledString; } case CPUMAX: if (data.isCpuMetricDataAvailable()) { return new StyledString(NumberFormatter.formatDouble(data.getCpuMax(), timeDecimalPlaces)); } else { return emptyStyledString; } case CPUDURATION: if (data.isCpuMetricDataAvailable()) { return new StyledString(NumberFormatter.formatDouble(data.getCpuDuration(), timeDecimalPlaces)); } else { return emptyStyledString; } case EXCLUSIVEAVERAGE: if (data.isExclusiveTimeDataAvailable()) { return new StyledString(NumberFormatter.formatDouble(data.getExclusiveAverage(), timeDecimalPlaces)); } else { return emptyStyledString; } case EXCLUSIVEMAX: if (data.isExclusiveTimeDataAvailable()) { return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMax(), timeDecimalPlaces)); } else { return emptyStyledString; } case EXCLUSIVEMIN: if (data.isExclusiveTimeDataAvailable()) { return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMin(), timeDecimalPlaces)); } else { return emptyStyledString; } case EXCLUSIVESUM: if (data.isExclusiveTimeDataAvailable()) { return new StyledString(NumberFormatter.formatDouble(data.getExclusiveDuration(), timeDecimalPlaces)); } else { return emptyStyledString; } default: return new StyledString("error"); } } /** * @return the timerDataList */ public List<TimerData> getTimerDataList() { return timerDataList; } /** * @return the timerDataAccessService */ public ITimerDataAccessService getTimerDataAccessService() { return timerDataAccessService; } }