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.DecimalFormat;
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.axis.NumberTickUnit;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.DeviationRenderer;
import org.jfree.data.Range;
import org.jfree.data.RangeType;
import org.jfree.data.xy.YIntervalSeriesCollection;
import org.jfree.ui.RectangleInsets;
import rocks.inspectit.shared.all.communication.data.CpuInformationData;
import rocks.inspectit.shared.cs.cmr.service.IGlobalDataAccessService;
import rocks.inspectit.shared.cs.indexing.aggregation.IAggregator;
import rocks.inspectit.shared.cs.indexing.aggregation.impl.CpuInformationDataAggregator;
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 CpuInformationData} informations.
*
* @author Eduard Tudenhoefner
* @author Patrice Bouillet
*
*/
public class DefaultCpuPlotController extends AbstractPlotController {
/**
* The ID of this subview / controller.
*/
public static final String ID = "inspectit.subview.graph.cpu";
/**
* The template of the {@link CpuInformationData} object.
*/
private CpuInformationData template;
/**
* Indicates the weight of the upper {@link XYPlot}.
*/
private static final int WEIGHT_UPPER_PLOT = 2;
/**
* The upper {@link XYPlot} containing the graphical view.
*/
private XYPlot upperPlot;
/**
* The map containing the weights of the {@link XYPlot}s.
*/
private Map<XYPlot, Integer> weights = new HashMap<>();
/**
* The {@link YIntervalSeriesImproved}.
*/
private YIntervalSeriesImproved cpuUsage;
/**
* 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<CpuInformationData> 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}.
*/
private IAggregator<CpuInformationData> aggregator = new CpuInformationDataAggregator();
/**
* {@inheritDoc}
*/
@Override
public void setInputDefinition(InputDefinition inputDefinition) {
super.setInputDefinition(inputDefinition);
template = new CpuInformationData();
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();
List<XYPlot> plots = new ArrayList<>(1);
plots.add(upperPlot);
weights.put(upperPlot, WEIGHT_UPPER_PLOT);
return plots;
}
/**
* Initializes the upper plot.
*
* @return An instance of {@link XYPlot}.
*/
private XYPlot initializeUpperPlot() {
cpuUsage = new YIntervalSeriesImproved("cpu usage");
YIntervalSeriesCollection yintervalseriescollection = new YIntervalSeriesCollection();
yintervalseriescollection.addSeries(cpuUsage);
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("CPU usage of the VM");
rangeAxis.setRange(new Range(0, 100), true, false);
rangeAxis.setAutoRangeMinimumSize(100.0d, false);
rangeAxis.setTickUnit(new NumberTickUnit(10.0d, new DecimalFormat("0")));
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 cpuData
* The input data.
*/
private void addUpperPlotData(List<CpuInformationData> cpuData) {
for (CpuInformationData data : cpuData) {
float cpuAverage = data.getTotalCpuUsage() / data.getCount();
cpuUsage.add(data.getTimeStamp().getTime(), cpuAverage, data.getMinCpuUsage(), data.getMaxCpuUsage(), false);
}
cpuUsage.fireSeriesChanged();
}
/**
* Removes all data from the upper plot and sets the {@link CpuInformationData} objects on the
* plot.
*
* @param cpuInformationData
* The data to set on the plot.
*/
private void setUpperPlotData(List<CpuInformationData> cpuInformationData) {
cpuUsage.clear();
addUpperPlotData(cpuInformationData);
}
/**
* {@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<CpuInformationData> adjustedCpuData = 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<CpuInformationData> data = (List<CpuInformationData>) dataAccessService.getDataObjectsFromToDate(template, from, to);
if (!data.isEmpty()) {
adjustedCpuData = 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<CpuInformationData> rightData = (List<CpuInformationData>) dataAccessService.getDataObjectsFromToDate(template, rightDate, to);
List<CpuInformationData> leftData = (List<CpuInformationData>) 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());
}
}
adjustedCpuData = adjustSamplingRate(oldData, from, to, aggregator);
} else if (rightAppend) {
// just append something on the right
Date rightDate = new Date(newestDate.getTime() + 1);
List<CpuInformationData> timerData = (List<CpuInformationData>) 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());
}
}
adjustedCpuData = adjustSamplingRate(oldData, from, to, aggregator);
} else if (leftAppend) {
// just append something on the left
Date leftDate = new Date(oldFromDate.getTime() - 1);
List<CpuInformationData> timerData = (List<CpuInformationData>) dataAccessService.getDataObjectsFromToDate(template, from, leftDate);
if (!timerData.isEmpty()) {
oldData.addAll(timerData);
oldFromDate = (Date) from.clone();
}
adjustedCpuData = adjustSamplingRate(oldData, from, to, aggregator);
} else {
// No update is needed here because we already have all the
// needed data
adjustedCpuData = adjustSamplingRate(oldData, from, to, aggregator);
}
final List<CpuInformationData> finalAdjustedCpuData = adjustedCpuData;
// updating the plots in the UI thread
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
setUpperPlotData(finalAdjustedCpuData);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public int getWeight(XYPlot subPlot) {
return weights.get(subPlot);
}
/**
* {@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 void dispose() {
}
}