package rocks.inspectit.ui.rcp.editor.graph.plot;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import org.apache.commons.collections.CollectionUtils;
import org.eclipse.swt.widgets.Display;
import rocks.inspectit.shared.all.communication.IAggregatedData;
import rocks.inspectit.shared.all.communication.data.AggregatedHttpTimerData;
import rocks.inspectit.shared.all.communication.data.HttpTimerData;
import rocks.inspectit.shared.cs.cmr.service.IHttpTimerDataAccessService;
import rocks.inspectit.shared.cs.indexing.aggregation.IAggregator;
import rocks.inspectit.ui.rcp.editor.inputdefinition.InputDefinition;
import rocks.inspectit.ui.rcp.editor.inputdefinition.extra.HttpChartingInputDefinitionExtra;
import rocks.inspectit.ui.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory;
import rocks.inspectit.ui.rcp.util.data.RegExAggregatedHttpTimerData;
/**
* {@link PlotController} for displaying many Http requests in the graph.
*
* @author Ivan Senic
*
*/
public class HttpTimerPlotController extends AbstractTimerDataPlotController<HttpTimerData> {
/**
* {@link IAggregator}.
*/
private static final IAggregator<HttpTimerData> AGGREGATOR = new SimpleHttpAggregator();
/**
* Templates that will be used for data display. Every template is one line in line chart.
*/
private List<HttpTimerData> templates;
/**
* List of {@link RegExAggregatedHttpTimerData} if regular expression is defined.
*/
private List<RegExAggregatedHttpTimerData> regExTemplates;
/**
* If true tag values from templates will be used in plotting. Otherwise URI is used.
*/
private boolean plotByTagValue = false;
/**
* If true than regular expression transformation will be performed on the template URIs.
*/
private boolean regExTransformation = false;
/**
* {@link IHttpTimerDataAccessService}.
*/
private IHttpTimerDataAccessService dataAccessService;
/**
* List of displayed data.
*/
List<HttpTimerData> displayedData = Collections.emptyList();
/**
* Date to display data to.
*/
Date toDate = new Date(0);
/**
* Date to display data from.
*/
Date fromDate = new Date(Long.MAX_VALUE);
/**
* Date that mark the last displayed data on the graph.
*/
Date latestDataDate = new Date(0);
/**
* {@inheritDoc}
*/
@Override
public void setInputDefinition(InputDefinition inputDefinition) {
super.setInputDefinition(inputDefinition);
if (inputDefinition.hasInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.HTTP_CHARTING_EXTRAS_MARKER)) {
HttpChartingInputDefinitionExtra inputDefinitionExtra = inputDefinition.getInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.HTTP_CHARTING_EXTRAS_MARKER);
templates = inputDefinitionExtra.getTemplates();
plotByTagValue = inputDefinitionExtra.isPlotByTagValue();
regExTransformation = inputDefinitionExtra.isRegExTransformation();
if (regExTransformation) {
regExTemplates = inputDefinitionExtra.getRegExTemplates();
}
}
dataAccessService = inputDefinition.getRepositoryDefinition().getHttpTimerDataAccessService();
}
/**
* {@inheritDoc}
*/
@Override
public boolean showLegend() {
return templates.size() > 1;
}
/**
* {@inheritDoc}
*/
@Override
public void update(Date from, Date to) {
// complete load if we have no data, or wanted time range is completely outside the current
boolean completeLoad = CollectionUtils.isEmpty(displayedData) || fromDate.after(to) || toDate.before(from);
// left append if currently displayed from date is after the new from date
boolean leftAppend = fromDate.after(from);
// right append if the currently displayed to date is before new to date or the date of the
// last data is before new date
boolean rightAppend = toDate.before(to) || latestDataDate.before(to);
if (completeLoad) {
List<HttpTimerData> httpTimerDatas = dataAccessService.getChartingHttpTimerDataFromDateToDate(templates, from, to, plotByTagValue);
if (CollectionUtils.isNotEmpty(httpTimerDatas)) {
fromDate = (Date) from.clone();
toDate = (Date) to.clone();
}
displayedData = httpTimerDatas;
} else {
if (rightAppend) {
Date startingFrom = new Date(latestDataDate.getTime() + 1);
List<HttpTimerData> httpTimerDatas = dataAccessService.getChartingHttpTimerDataFromDateToDate(templates, startingFrom, to, plotByTagValue);
if (CollectionUtils.isNotEmpty(httpTimerDatas)) {
displayedData.addAll(httpTimerDatas);
toDate = (Date) to.clone();
}
}
if (leftAppend) {
Date endingTo = new Date(fromDate.getTime() - 1);
List<HttpTimerData> httpTimerDatas = dataAccessService.getChartingHttpTimerDataFromDateToDate(templates, from, endingTo, plotByTagValue);
if (CollectionUtils.isNotEmpty(httpTimerDatas)) {
displayedData.addAll(0, httpTimerDatas);
fromDate = (Date) from.clone();
}
}
}
// update the last displayed data
if (CollectionUtils.isNotEmpty(displayedData)) {
latestDataDate = new Date(displayedData.get(displayedData.size() - 1).getTimeStamp().getTime());
}
Map<Object, List<HttpTimerData>> map = new HashMap<>();
for (HttpTimerData data : displayedData) {
HttpTimerData template = findTemplateForData(data);
if (null == template) {
continue;
}
Object seriesKey = getSeriesKey(template);
List<HttpTimerData> list = map.get(seriesKey);
if (null == list) {
list = new ArrayList<>();
map.put(seriesKey, list);
}
list.add(data);
}
for (Entry<Object, List<HttpTimerData>> entry : map.entrySet()) {
entry.setValue(adjustSamplingRate(entry.getValue(), from, to, AGGREGATOR));
}
final Map<Object, List<HttpTimerData>> finalMap = map;
// update plots in UI thread
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
setDurationPlotData(finalMap);
setCountPlotData(finalMap);
}
});
}
/**
* Finds matching template for the given {@link HttpTimerData} based on if regular expression
* transformation is active or not.
*
* @param httpTimerData
* Data to find matching template.
* @return Matching template of <code>null</code> if one can not be found.
*/
private HttpTimerData findTemplateForData(HttpTimerData httpTimerData) {
if (regExTransformation) {
for (RegExAggregatedHttpTimerData regExTemplate : regExTemplates) {
if (HttpTimerData.REQUEST_METHOD_MULTIPLE.equals(regExTemplate.getHttpInfo().getRequestMethod())
|| Objects.equals(regExTemplate.getHttpInfo().getRequestMethod(), httpTimerData.getHttpInfo().getRequestMethod())) {
if (null != findTemplateForUriData(httpTimerData, regExTemplate.getAggregatedDataList(), true)) {
return regExTemplate;
}
}
}
} else if (plotByTagValue) {
return findTemplateForTagData(httpTimerData, templates);
} else {
return findTemplateForUriData(httpTimerData, templates, false);
}
return null;
}
/**
* Finds matching template for the given {@link HttpTimerData} by uri.
*
* @param httpTimerData
* Data to find matching template.
* @param templates
* List of templates to search.
* @param checkOnlyUri
* If matching should be done only by uri.
* @return Matching template of <code>null</code> if one can not be found.
*/
private HttpTimerData findTemplateForUriData(HttpTimerData httpTimerData, List<HttpTimerData> templates, boolean checkOnlyUri) {
for (HttpTimerData template : templates) {
if (Objects.equals(template.getHttpInfo().getUri(), httpTimerData.getHttpInfo().getUri())) {
if (!checkOnlyUri && HttpTimerData.REQUEST_METHOD_MULTIPLE.equals(template.getHttpInfo().getRequestMethod())) {
return template;
} else if (Objects.equals(template.getHttpInfo().getRequestMethod(), httpTimerData.getHttpInfo().getRequestMethod())) {
return template;
}
}
}
return null;
}
/**
* Finds matching template for the given {@link HttpTimerData} by tag value.
*
* @param httpTimerData
* Data to find matching template.
* @param templates
* List of templates to search.
* @return Matching template of <code>null</code> if one can not be found.
*/
private HttpTimerData findTemplateForTagData(HttpTimerData httpTimerData, List<HttpTimerData> templates) {
for (HttpTimerData template : templates) {
if (Objects.equals(template.getHttpInfo().getInspectItTaggingHeaderValue(), httpTimerData.getHttpInfo().getInspectItTaggingHeaderValue())) {
if (HttpTimerData.REQUEST_METHOD_MULTIPLE.equals(template.getHttpInfo().getRequestMethod())
|| Objects.equals(template.getHttpInfo().getRequestMethod(), httpTimerData.getHttpInfo().getRequestMethod())) {
return template;
}
}
}
return null;
}
/**
* Returns the series key for the {@link HttpTimerData} object.
*
* @param httpTimerData
* {@link HttpTimerData}.
* @return Key used to initialize the series and later on compare which series data should be
* added to.
*/
@Override
protected Comparable<?> getSeriesKey(HttpTimerData httpTimerData) {
if (regExTransformation && (httpTimerData instanceof RegExAggregatedHttpTimerData)) {
return "Transformed URI: " + ((RegExAggregatedHttpTimerData) httpTimerData).getTransformedUri() + " [" + httpTimerData.getHttpInfo().getRequestMethod() + "]";
} else {
if (plotByTagValue) {
return "Tag: " + httpTimerData.getHttpInfo().getInspectItTaggingHeaderValue() + " [" + httpTimerData.getHttpInfo().getRequestMethod() + "]";
} else {
return "URI: " + httpTimerData.getHttpInfo().getUri() + " [" + httpTimerData.getHttpInfo().getRequestMethod() + "]";
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected List<HttpTimerData> getTemplates() {
if (regExTransformation) {
return new ArrayList<HttpTimerData>(regExTemplates);
} else {
return templates;
}
}
/**
* Simple {@link IAggregator} to use for {@link HttpTimerData} aggregation, since we separate
* the data correctly before aggregation.
*
* @author Ivan Senic
*
*/
private static class SimpleHttpAggregator implements IAggregator<HttpTimerData> {
/**
* {@inheritDoc}
*/
@Override
public void aggregate(IAggregatedData<HttpTimerData> aggregatedObject, HttpTimerData objectToAdd) {
aggregatedObject.aggregate(objectToAdd);
}
/**
* {@inheritDoc}
*/
@Override
public IAggregatedData<HttpTimerData> getClone(HttpTimerData object) {
return new AggregatedHttpTimerData();
}
/**
* {@inheritDoc}
*/
@Override
public Object getAggregationKey(HttpTimerData object) {
return 1;
}
}
}