package rocks.inspectit.ui.rcp.editor.graph.plot;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.geom.Ellipse2D;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.swt.widgets.Display;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.DeviationRenderer;
import org.jfree.data.RangeType;
import org.jfree.data.xy.YIntervalSeriesCollection;
import org.jfree.ui.RectangleInsets;
import rocks.inspectit.shared.all.communication.data.ThreadInformationData;
import rocks.inspectit.shared.cs.cmr.service.IGlobalDataAccessService;
import rocks.inspectit.shared.cs.indexing.aggregation.IAggregator;
import rocks.inspectit.shared.cs.indexing.aggregation.impl.ThreadInformationDataAggregator;
import rocks.inspectit.ui.rcp.editor.inputdefinition.InputDefinition;
import rocks.inspectit.ui.rcp.editor.preferences.PreferenceId;
import rocks.inspectit.ui.rcp.repository.CmrRepositoryDefinition;
/**
* This class creates a {@link XYPlot} containing the {@link ThreadInformationData} informations.
*
* @author Eduard Tudenhoefner
* @author Patrice Bouillet
*
*/
public class DefaultThreadsPlotController extends AbstractPlotController {
/**
* The ID of this subview / controller.
*/
public static final String ID = "inspectit.subview.graph.threads";
/**
* The template of the {@link ThreadInformationData} object.
*/
private ThreadInformationData template;
/**
* Indicates the weight of the upper {@link XYPlot}.
*/
private static final int WEIGHT_UPPER_PLOT = 1;
/**
* Indicates the weight of the lower {@link XYPlot}.
*/
private static final int WEIGHT_LOWER_PLOT = 1;
/**
* The upper {@link XYPlot}.
*/
private XYPlot upperPlot;
/**
* The lower {@link XYPlot}.
*/
private XYPlot lowerPlot;
/**
* The map containing the weight of the {@link XYPlot}s.
*/
private Map<XYPlot, Integer> weights = new HashMap<>();
/**
* The {@link YIntervalSeriesImproved} for live threads.
*/
private YIntervalSeriesImproved liveThreads;
/**
* The {@link YIntervalSeriesImproved} for peak threads.
*/
private YIntervalSeriesImproved peakThreads;
/**
* The {@link YIntervalSeriesImproved} for daemon threads.
*/
private YIntervalSeriesImproved daemonThreads;
/**
* The data access service to access the data on the CMR.
*/
private IGlobalDataAccessService dataAccessService;
/**
* Old list containing some data objects which could be reused.
*/
private List<ThreadInformationData> oldData = Collections.emptyList();
/**
* The old from date.
*/
private Date oldFromDate = new Date(Long.MAX_VALUE);
/**
* The old to date.
*/
private Date oldToDate = new Date(0);
/**
* This represents the date of one of the objects which was received at some time in the past
* but was the one with the newest date. This is needed for not requesting some data of the CMR
* sometimes.
*/
private Date newestDate = new Date(0);
/**
* {@link IAggregator}.
*/
IAggregator<ThreadInformationData> aggregator = new ThreadInformationDataAggregator();
/**
* {@inheritDoc}
*/
@Override
public void setInputDefinition(InputDefinition inputDefinition) {
super.setInputDefinition(inputDefinition);
template = new ThreadInformationData();
template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId());
template.setSensorTypeIdent(inputDefinition.getIdDefinition().getSensorTypeId());
template.setId(-1L);
dataAccessService = inputDefinition.getRepositoryDefinition().getGlobalDataAccessService();
}
/**
* {@inheritDoc}
*/
@Override
public List<XYPlot> getPlots() {
upperPlot = initializeUpperPlot();
lowerPlot = initializeLowerPlot();
List<XYPlot> plots = new ArrayList<>(2);
plots.add(upperPlot);
plots.add(lowerPlot);
weights.put(upperPlot, WEIGHT_UPPER_PLOT);
weights.put(lowerPlot, WEIGHT_LOWER_PLOT);
return plots;
}
/**
* Initializes the upper plot with the given input data.
*
* @return An instance of {@link XYPlot}
*/
private XYPlot initializeUpperPlot() {
liveThreads = new YIntervalSeriesImproved("live");
peakThreads = new YIntervalSeriesImproved("peak");
YIntervalSeriesCollection yIntervalSeriesCollection = new YIntervalSeriesCollection();
yIntervalSeriesCollection.addSeries(liveThreads);
yIntervalSeriesCollection.addSeries(peakThreads);
DeviationRenderer renderer = new DeviationRenderer(true, false);
renderer.setBaseShapesVisible(true);
renderer.setSeriesStroke(0, new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
renderer.setSeriesFillPaint(0, new Color(255, 200, 200));
renderer.setSeriesOutlineStroke(0, new BasicStroke(2.0f));
renderer.setSeriesShape(0, new Ellipse2D.Double(-2.5, -2.5, 5.0, 5.0));
renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT, DateFormat.getDateTimeInstance(), NumberFormat.getNumberInstance()));
final NumberAxis rangeAxis = new NumberAxis("Threads");
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
rangeAxis.setAutoRangeMinimumSize(10.0d, false);
rangeAxis.setRangeType(RangeType.POSITIVE);
final XYPlot subplot = new XYPlot(yIntervalSeriesCollection, null, rangeAxis, renderer);
subplot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
subplot.setRangeAxisLocation(AxisLocation.TOP_OR_LEFT);
subplot.setRangeCrosshairVisible(true);
return subplot;
}
/**
* Updates the upper plot with the given input data.
*
* @param threadData
* the input data.
*/
private void addUpperPlotData(List<ThreadInformationData> threadData) {
for (ThreadInformationData data : threadData) {
float liveThreadAverage = ((float) data.getTotalThreadCount()) / data.getCount();
float peakThreadAverage = ((float) data.getTotalPeakThreadCount()) / data.getCount();
liveThreads.add(data.getTimeStamp().getTime(), liveThreadAverage, data.getMinThreadCount(), data.getMaxThreadCount(), false);
peakThreads.add(data.getTimeStamp().getTime(), peakThreadAverage, data.getMinPeakThreadCount(), data.getMaxPeakThreadCount(), false);
}
liveThreads.fireSeriesChanged();
peakThreads.fireSeriesChanged();
}
/**
* Removes all data from the upper plot and sets the {@link ThreadInformationData} objects on
* the plot.
*
* @param threadData
* The data to set on the plot.
*/
private void setUpperPlotData(List<ThreadInformationData> threadData) {
liveThreads.clear();
peakThreads.clear();
addUpperPlotData(threadData);
}
/**
* Initializes the lower plot with the given input data.
*
* @return An instance of {@link XYPlot}.
*/
private XYPlot initializeLowerPlot() {
daemonThreads = new YIntervalSeriesImproved("daemon");
YIntervalSeriesCollection yIntervalSeriesCollection = new YIntervalSeriesCollection();
yIntervalSeriesCollection.addSeries(daemonThreads);
DeviationRenderer renderer = new DeviationRenderer(true, false);
renderer.setBaseShapesVisible(true);
renderer.setSeriesStroke(0, new BasicStroke(3.0f));
renderer.setSeriesOutlineStroke(0, new BasicStroke(2.0f));
renderer.setSeriesShape(0, new Ellipse2D.Double(-2.5, -2.5, 5.0, 5.0));
renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT, DateFormat.getDateTimeInstance(), NumberFormat.getNumberInstance()));
final NumberAxis rangeAxis = new NumberAxis("Daemon threads");
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
rangeAxis.setAutoRangeMinimumSize(10.0d, false);
rangeAxis.setRangeType(RangeType.POSITIVE);
final XYPlot subplot = new XYPlot(yIntervalSeriesCollection, null, rangeAxis, renderer);
subplot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
subplot.setRangeAxisLocation(AxisLocation.TOP_OR_LEFT);
subplot.setRangeCrosshairVisible(true);
return subplot;
}
/**
* Updates the lower plot with the given input data.
*
* @param threadData
* the input data.
*/
private void addLowerPlotData(List<ThreadInformationData> threadData) {
for (ThreadInformationData data : threadData) {
float daemonThreadAverage = ((float) data.getTotalDaemonThreadCount()) / data.getCount();
daemonThreads.add(data.getTimeStamp().getTime(), daemonThreadAverage, data.getMinDaemonThreadCount(), data.getMaxDaemonThreadCount(), false);
}
daemonThreads.fireSeriesChanged();
}
/**
* Removes all data from the lower plot and sets the {@link ThreadInformationData} objects on
* the plot.
*
* @param threadData
* The data to set on the plot.
*/
private void setLowerPlotData(List<ThreadInformationData> threadData) {
daemonThreads.clear();
addLowerPlotData(threadData);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public void update(Date from, Date to) {
Date dataNewestDate = new Date(0);
if (!oldData.isEmpty()) {
dataNewestDate = oldData.get(oldData.size() - 1).getTimeStamp();
}
boolean leftAppend = from.before(oldFromDate);
// boolean rightAppend = to.after(dataNewestDate) &&
// (to.equals(newestDate) || to.after(newestDate));
boolean rightAppend = to.after(newestDate) || oldToDate.before(to);
List<ThreadInformationData> adjustedThreadData = Collections.emptyList();
if (oldData.isEmpty() || to.before(oldFromDate) || from.after(dataNewestDate)) {
// the old data is empty or the range does not fit, thus we need
// to access the whole range
List<ThreadInformationData> data = (List<ThreadInformationData>) dataAccessService.getDataObjectsFromToDate(template, from, to);
if (!data.isEmpty()) {
adjustedThreadData = adjustSamplingRate(data, from, to, aggregator);
// we got some data, thus we can set the date
oldFromDate = (Date) from.clone();
oldToDate = (Date) to.clone();
if (newestDate.before(data.get(data.size() - 1).getTimeStamp())) {
newestDate = new Date(data.get(data.size() - 1).getTimeStamp().getTime());
}
}
oldData = data;
} else if (leftAppend && rightAppend) {
// we have some data in between, but we need to append something
// to the start and to the end
Date rightDate = new Date(newestDate.getTime() + 1);
Date leftDate = new Date(oldFromDate.getTime() - 1);
List<ThreadInformationData> rightData = (List<ThreadInformationData>) dataAccessService.getDataObjectsFromToDate(template, rightDate, to);
List<ThreadInformationData> leftData = (List<ThreadInformationData>) dataAccessService.getDataObjectsFromToDate(template, from, leftDate);
if (!leftData.isEmpty()) {
oldData.addAll(0, leftData);
oldFromDate = (Date) from.clone();
}
if (!rightData.isEmpty()) {
oldData.addAll(rightData);
oldToDate = (Date) to.clone();
if (newestDate.before(rightData.get(rightData.size() - 1).getTimeStamp())) {
newestDate = new Date(rightData.get(rightData.size() - 1).getTimeStamp().getTime());
}
}
adjustedThreadData = adjustSamplingRate(oldData, from, to, aggregator);
} else if (rightAppend) {
// just append something on the right
Date rightDate = new Date(newestDate.getTime() + 1);
List<ThreadInformationData> timerData = (List<ThreadInformationData>) dataAccessService.getDataObjectsFromToDate(template, rightDate, to);
if (!timerData.isEmpty()) {
oldData.addAll(timerData);
oldToDate = (Date) to.clone();
if (newestDate.before(timerData.get(timerData.size() - 1).getTimeStamp())) {
newestDate = new Date(timerData.get(timerData.size() - 1).getTimeStamp().getTime());
}
}
adjustedThreadData = adjustSamplingRate(oldData, from, to, aggregator);
} else if (leftAppend) {
// just append something on the left
Date leftDate = new Date(oldFromDate.getTime() - 1);
List<ThreadInformationData> timerData = (List<ThreadInformationData>) dataAccessService.getDataObjectsFromToDate(template, from, leftDate);
if (!timerData.isEmpty()) {
oldData.addAll(timerData);
oldFromDate = (Date) from.clone();
}
adjustedThreadData = adjustSamplingRate(oldData, from, to, aggregator);
} else {
// No update is needed here because we already have all the
// needed data
adjustedThreadData = adjustSamplingRate(oldData, from, to, aggregator);
}
final List<ThreadInformationData> finalAdjustedThreadData = adjustedThreadData;
// updating the plots in the UI thread
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
setUpperPlotData(finalAdjustedThreadData);
setLowerPlotData(finalAdjustedThreadData);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public Set<PreferenceId> getPreferenceIds() {
Set<PreferenceId> preferenceList = EnumSet.noneOf(PreferenceId.class);
if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) {
preferenceList.add(PreferenceId.LIVEMODE);
}
preferenceList.add(PreferenceId.TIMELINE);
preferenceList.add(PreferenceId.SAMPLINGRATE);
preferenceList.add(PreferenceId.UPDATE);
return preferenceList;
}
/**
* {@inheritDoc}
*/
@Override
public int getWeight(XYPlot subPlot) {
return weights.get(subPlot);
}
/**
* {@inheritDoc}
*/
@Override
public void dispose() {
}
}