package rocks.inspectit.ui.rcp.editor.table.input; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.apache.commons.collections.CollectionUtils; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.viewers.IBaseLabelProvider; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import rocks.inspectit.shared.all.cmr.model.MethodSensorTypeIdent; import rocks.inspectit.shared.all.cmr.model.MethodSensorTypeIdentHelper; import rocks.inspectit.shared.all.cmr.service.ICachedDataService; import rocks.inspectit.shared.all.communication.IAggregatedData; import rocks.inspectit.shared.all.communication.data.HttpTimerData; import rocks.inspectit.shared.all.communication.data.TimerData; import rocks.inspectit.shared.cs.communication.comparator.HttpTimerDataComparatorEnum; import rocks.inspectit.shared.cs.communication.comparator.IDataComparator; import rocks.inspectit.shared.cs.communication.comparator.InvocationAwareDataComparatorEnum; import rocks.inspectit.shared.cs.communication.comparator.ResultComparator; 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.HttpTimerDataAggregator; 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.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.util.data.RegExAggregatedHttpTimerData; /** * InputController for <code>HttpTimerData</code> view. * * @author Stefan Siegl */ public class HttpTimerDataInputController extends AbstractHttpInputController { /** * The ID of this subview / controller. */ public static final String ID = "inspectit.subview.table.aggregatedhttptimerdata"; /** * 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 Stefan Siegl * */ private static enum Column { /** The time column. */ CHARTING("Charting", 20, null, TimerDataComparatorEnum.CHARTING), /** The package column. */ URI("URI", 300, InspectITImages.IMG_HTTP_URL, HttpTimerDataComparatorEnum.URI), /** The http method. */ HTTP_METHOD("Method", 80, null, HttpTimerDataComparatorEnum.HTTP_METHOD), /** Invocation Affiliation. */ INVOCATION_AFFILLIATION("In Invocations", 130, 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 HttpTimerData> 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 HttpTimerData> 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]; } } /** * Defines if correct regular expression is defined in sensor. */ private boolean regExEnabledInSensor = false; /** * If the regular expression transformation of the URI is active. */ private boolean regExActive = PreferenceId.HttpUriTransformation.DEFAULT; /** * The active HTTP sensor type ident. */ private MethodSensorTypeIdent httpSensorTypeIdent; /** * Cached data service. */ private ICachedDataService cachedDataService; @Override public void setInputDefinition(InputDefinition inputDefinition) { super.setInputDefinition(inputDefinition); cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); if (0 != inputDefinition.getIdDefinition().getSensorTypeId()) { httpSensorTypeIdent = (MethodSensorTypeIdent) cachedDataService.getSensorTypeIdentForId(inputDefinition.getIdDefinition().getSensorTypeId()); String regEx = MethodSensorTypeIdentHelper.getRegEx(httpSensorTypeIdent); if (null != regEx) { try { Pattern.compile(regEx); regExEnabledInSensor = true; } catch (PatternSyntaxException e) { InspectIT.getDefault().createInfoDialog("The HTTP sensor defines the Regular expression " + regEx + " for URI transformation that can not be compiled. The transformation option will not be available.\n\n Reason: " + e.getMessage(), -1); } } } } /** * {@inheritDoc} */ @Override public Set<PreferenceId> getPreferenceIds() { Set<PreferenceId> preferences = super.getPreferenceIds(); if (regExEnabledInSensor) { preferences.add(PreferenceId.HTTP_URI_TRANSFORMING); } return preferences; } /** * {@inheritDoc} */ @Override public void preferenceEventFired(PreferenceEvent preferenceEvent) { super.preferenceEventFired(preferenceEvent); switch (preferenceEvent.getPreferenceId()) { case HTTP_URI_TRANSFORMING: if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.HttpUriTransformation.URI_TRANSFORMATION_ACTIVE)) { regExActive = (Boolean) preferenceEvent.getPreferenceMap().get(PreferenceId.HttpUriTransformation.URI_TRANSFORMATION_ACTIVE); } break; default: break; } } /** * {@inheritDoc} */ @Override public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) { monitor.beginTask("Getting HTTP timer data information", IProgressMonitor.UNKNOWN); List<HttpTimerData> aggregatedHttpData; if (autoUpdate) { aggregatedHttpData = httptimerDataAccessService.getAggregatedTimerData(template, httpCatorizationOnRequestMethodActive); } else { aggregatedHttpData = httptimerDataAccessService.getAggregatedTimerData(template, httpCatorizationOnRequestMethodActive, fromDate, toDate); } if (regExActive && CollectionUtils.isNotEmpty(aggregatedHttpData)) { AggregationPerformer<HttpTimerData> aggregationPerformer = new AggregationPerformer<>(new RegExHttpAggregator(httpSensorTypeIdent, httpCatorizationOnRequestMethodActive)); aggregationPerformer.processCollection(aggregatedHttpData); aggregatedHttpData = aggregationPerformer.getResultList(); } timerDataList.clear(); if (CollectionUtils.isNotEmpty(aggregatedHttpData)) { timerDataList.addAll(aggregatedHttpData); } monitor.done(); } /** * {@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 (null != column.image) { viewerColumn.getColumn().setImage(column.image); } mapTableViewerColumn(column, viewerColumn); } } /** * {@inheritDoc} */ @Override public IBaseLabelProvider getLabelProvider() { return new StyledCellIndexLabelProvider() { /** * {@inheritDoc} */ @Override public StyledString getStyledText(Object element, int index) { HttpTimerData data = (HttpTimerData) element; Column enumId = Column.fromOrd(index); StyledString styledString = getStyledTextForColumn(data, 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) { HttpTimerData data = (HttpTimerData) 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) { HttpTimerData data = (HttpTimerData) element; Column enumId = Column.fromOrd(index); switch (enumId) { case CHARTING: if (data.isCharting()) { return "Duration chart can be displayed for this HTTP data."; } default: return super.getToolTipText(element, index); } } }; } /** * {@inheritDoc} */ @Override public ViewerComparator getComparator() { ICachedDataService cachedDataService = getInputDefinition().getRepositoryDefinition().getCachedDataService(); TableViewerComparator<HttpTimerData> httpTimerDataViewerComparator = new TableViewerComparator<>(); for (Column column : Column.values()) { ResultComparator<HttpTimerData> resultComparator; if (Column.URI.equals(column)) { resultComparator = new ResultComparator<>(new UriOrRegExComparator(column.dataComparator), cachedDataService); } else { resultComparator = new ResultComparator<>(column.dataComparator, cachedDataService); } httpTimerDataViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), resultComparator); } return httpTimerDataViewerComparator; } /** * {@inheritDoc} */ @Override public String getReadableString(Object object) { if (object instanceof HttpTimerData) { HttpTimerData data = (HttpTimerData) object; StringBuilder sb = new StringBuilder(); for (Column column : Column.values()) { sb.append(getStyledTextForColumn(data, column).toString()); sb.append('\t'); } return sb.toString(); } else { throw new RuntimeException("Could not create the human readable string! Class is: " + object.getClass().getName()); } } /** * {@inheritDoc} */ @Override public List<String> getColumnValues(Object object) { if (object instanceof HttpTimerData) { HttpTimerData data = (HttpTimerData) object; List<String> values = new ArrayList<>(); for (Column column : Column.values()) { values.add(getStyledTextForColumn(data, column).toString()); } return values; } throw new RuntimeException("Could not create the column values!"); } /** * Returns the styled text for a specific column. * * @param data * The data object to extract the information from. * @param enumId * The enumeration ID. * @return The styled string containing the information from the data object. */ private StyledString getStyledTextForColumn(HttpTimerData data, Column enumId) { switch (enumId) { case CHARTING: return emptyStyledString; case URI: if (data instanceof RegExAggregatedHttpTimerData) { return new StyledString(((RegExAggregatedHttpTimerData) data).getTransformedUri()); } else { return new StyledString(data.getHttpInfo().getUri()); } case HTTP_METHOD: return new StyledString(data.getHttpInfo().getRequestMethod()); 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"); } } /** * The RegEx aggregator. * * @author Ivan Senic * */ @SuppressWarnings("serial") private static final class RegExHttpAggregator extends HttpTimerDataAggregator { /** * */ private static final long serialVersionUID = 4613696140758499143L; /** * HTTP sensor type ident. */ private MethodSensorTypeIdent httpSensorTypeIdent; /** * Default constructor. * * @param httpSensorTypeIdent * HTTP sensor type ident. * @param includeRequestMethod * If request method should be included. */ public RegExHttpAggregator(MethodSensorTypeIdent httpSensorTypeIdent, boolean includeRequestMethod) { super(true, includeRequestMethod); this.httpSensorTypeIdent = httpSensorTypeIdent; } /** * {@inheritDoc} */ @Override public void aggregate(IAggregatedData<HttpTimerData> aggregatedObject, HttpTimerData objectToAdd) { super.aggregate(aggregatedObject, objectToAdd); ((RegExAggregatedHttpTimerData) aggregatedObject).getAggregatedDataList().add(objectToAdd); } /** * {@inheritDoc} */ @Override public IAggregatedData<HttpTimerData> getClone(HttpTimerData httpData) { RegExAggregatedHttpTimerData clone = new RegExAggregatedHttpTimerData(); clone.setPlatformIdent(httpData.getPlatformIdent()); clone.setSensorTypeIdent(httpData.getSensorTypeIdent()); clone.setMethodIdent(httpData.getMethodIdent()); clone.setCharting(httpData.isCharting()); clone.getHttpInfo().setRequestMethod(httpData.getHttpInfo().getRequestMethod()); clone.setTransformedUri(RegExAggregatedHttpTimerData.getTransformedUri(httpData, httpSensorTypeIdent)); return clone; } /** * {@inheritDoc} */ @Override public Object getAggregationKey(HttpTimerData httpData) { final int prime = 31; int result = 0; String transformed = RegExAggregatedHttpTimerData.getTransformedUri(httpData, httpSensorTypeIdent); result = (prime * result) + ((transformed == null) ? 0 : transformed.hashCode()); if (includeRequestMethod) { result = (prime * result) + ((httpData.getHttpInfo().getRequestMethod() == null) ? 0 : httpData.getHttpInfo().getRequestMethod().hashCode()); } return result; } } /** * Comparator that is needed for the column where URI or regular expression transformation can * be displayed. * * @author Ivan Senic */ private static final class UriOrRegExComparator implements IDataComparator<HttpTimerData> { /** * The comparator that will be used if reg ex is not active. */ private final IDataComparator<? super HttpTimerData> comparator; /** * @param dataComparator * The comparator that will be used if reg ex is not active. */ public UriOrRegExComparator(IDataComparator<? super HttpTimerData> dataComparator) { this.comparator = dataComparator; } /** * {@inheritDoc} */ @Override public int compare(HttpTimerData o1, HttpTimerData o2, ICachedDataService cachedDataService) { if ((o1 instanceof RegExAggregatedHttpTimerData) && (o2 instanceof RegExAggregatedHttpTimerData)) { return ((RegExAggregatedHttpTimerData) o1).getTransformedUri().compareToIgnoreCase(((RegExAggregatedHttpTimerData) o2).getTransformedUri()); } else { return comparator.compare(o1, o2, cachedDataService); } } } }