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.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
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.xy.YIntervalSeriesCollection;
import org.jfree.ui.RectangleInsets;
import rocks.inspectit.shared.all.cmr.model.JmxDefinitionDataIdent;
import rocks.inspectit.shared.all.cmr.service.ICachedDataService;
import rocks.inspectit.shared.all.communication.DefaultData;
import rocks.inspectit.shared.all.communication.data.JmxSensorValueData;
import rocks.inspectit.shared.cs.cmr.service.IGlobalDataAccessService;
import rocks.inspectit.shared.cs.cmr.service.cache.CachedDataService;
import rocks.inspectit.shared.cs.indexing.aggregation.IAggregator;
import rocks.inspectit.shared.cs.indexing.aggregation.impl.JmxSensorValueDataAggregator;
import rocks.inspectit.ui.rcp.editor.graph.plot.datasolver.AbstractPlotDataSolver;
import rocks.inspectit.ui.rcp.editor.graph.plot.datasolver.PlotDataSolver;
import rocks.inspectit.ui.rcp.editor.graph.plot.datasolver.impl.PlotDataSolverFactory;
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.preferences.PreferencesConstants;
import rocks.inspectit.ui.rcp.preferences.PreferencesUtils;
import rocks.inspectit.ui.rcp.repository.CmrRepositoryDefinition;
/**
* This input controller displays the JMX sensor data in a chart.
*
* @author Marius Oehler
*
*/
public class JmxPlotController extends AbstractPlotController {
/**
* The {@link YIntervalSeriesImproved}.
*/
private YIntervalSeriesImproved jmxChart;
/**
* The template of the {@link JmxSensorValueData} object.
*/
private JmxSensorValueData template;
/**
* The old from date.
*/
private Date loadedFromDate = new Date(Long.MAX_VALUE);
/**
* The old to date.
*/
private Date loadedToDate = new Date(0);
/**
* The date where the current plotted chart starts.
*/
private Date plottedFrom;
/**
* The date where the current plotted chart ends.
*/
private Date plottedTo;
/**
* This list contains the loaded JmxSensorValueData. It is like a cache of the
* {@link #update(Date, Date)} method.
*/
private List<JmxSensorValueData> loadedData;
/**
* {@link CachedDataService}.
*/
private ICachedDataService cachedDataService;
/**
* The {@link AbstractPlotDataSolver} that is used to plot the current chart.
*/
private AbstractPlotDataSolver plotDataSolver;
/**
* The plot itself.
*/
private XYPlot subplot;
/**
* The current {@link JmxDefinitionDataIdent} of this plot.
*/
private JmxDefinitionDataIdent currentJmxIdent;
/**
* The global data access service.
*/
private IGlobalDataAccessService globalDataAccessService;
/**
* Used aggregator.
*/
private IAggregator<JmxSensorValueData> aggregator = new JmxSensorValueDataAggregator();
/**
* {@inheritDoc}
*/
@Override
public void setInputDefinition(InputDefinition inputDefinition) {
super.setInputDefinition(inputDefinition);
template = new JmxSensorValueData();
template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId());
template.setSensorTypeIdent(inputDefinition.getIdDefinition().getSensorTypeId());
template.setJmxSensorDefinitionDataIdentId(inputDefinition.getIdDefinition().getJmxDefinitionId());
cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService();
globalDataAccessService = inputDefinition.getRepositoryDefinition().getGlobalDataAccessService();
currentJmxIdent = cachedDataService.getJmxDefinitionDataIdentForId(inputDefinition.getIdDefinition().getJmxDefinitionId());
plotDataSolver = getCurrentDataSolver();
}
/**
* Returns the selected {@link AbstractPlotDataSolver} of this view.
*
* @return the {@link AbstractPlotDataSolver}
*/
private AbstractPlotDataSolver getCurrentDataSolver() {
Map<String, String> dataSolverMap = PreferencesUtils.getObject(PreferencesConstants.JMX_PLOT_DATA_SOLVER);
if (dataSolverMap.containsKey(currentJmxIdent.getDerivedFullName())) {
String solver = dataSolverMap.get(currentJmxIdent.getDerivedFullName());
return PlotDataSolverFactory.getDataSolver(PlotDataSolver.valueOf(solver));
} else {
return PlotDataSolverFactory.getDefaultDataSolver();
}
}
/**
* Initializes the upper plot.
*
* @return An instance of {@link XYPlot}.
*/
private XYPlot initializePlot() {
jmxChart = new YIntervalSeriesImproved("jmx value");
YIntervalSeriesCollection yintervalseriescollection = new YIntervalSeriesCollection();
yintervalseriescollection.addSeries(jmxChart);
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()));
NumberAxis rangeAxis = plotDataSolver.getAxis();
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;
}
/**
* {@inheritDoc}
*/
@Override
public List<XYPlot> getPlots() {
List<XYPlot> plots = new ArrayList<>();
plots.add(initializePlot());
return plots;
}
/**
* {@inheritDoc}
*/
@Override
public int getWeight(XYPlot subPlot) {
return 2;
}
/**
* Loads the {@link JmxSensorValueData} objects which were created between the given
* {@link Date} objects.
*
* @param from
* the start date
* @param to
* the end date
* @return list of {@link JmxSensorValueData} objects
*/
@SuppressWarnings("unchecked")
private List<JmxSensorValueData> loadJmxData(Date from, Date to) {
return (List<JmxSensorValueData>) globalDataAccessService.getDataObjectsFromToDate(template, from, to);
}
/**
* Returns the date of the latest element in the {@link #loadedData} list.
*
* @return Date of the latest loaded {@link JmxSensorValueData}
*/
private Date getLatestLoadedElementDate() {
if (CollectionUtils.isEmpty(loadedData)) {
return null;
}
long max = Long.MIN_VALUE;
for (JmxSensorValueData data : loadedData) {
if (data.getTimeStamp().getTime() > max) {
max = data.getTimeStamp().getTime();
}
}
return new Date(max);
}
/**
* {@inheritDoc}
*/
@Override
public void update(Date from, Date toUnchecked) {
// Prevent loading into the future
Date to = new Date(Math.min(System.currentTimeMillis(), toUnchecked.getTime()));
if ((loadedData == null) || loadedData.isEmpty()) {
loadedData = loadJmxData(from, to);
loadedFromDate = from;
loadedToDate = getLatestLoadedElementDate();
} else {
// Only load the date which was not loaded before
boolean leftAppend = from.before(loadedFromDate);
boolean rightAppend = to.after(loadedToDate);
if (leftAppend && rightAppend) {
loadedData.addAll(loadJmxData(from, loadedFromDate));
loadedData.addAll(loadJmxData(new Date(loadedToDate.getTime() + 1), to));
loadedFromDate = from;
loadedToDate = getLatestLoadedElementDate();
} else if (leftAppend) {
if (to.before(loadedFromDate)) {
// Clear all loaded data to prevent a fragmentary loaded data list
loadedData.clear();
loadedData.addAll(loadJmxData(from, to));
} else {
loadedData.addAll(loadJmxData(from, loadedFromDate));
}
loadedFromDate = from;
} else if (rightAppend) {
if (from.after(loadedToDate)) {
// Clear all loaded data to prevent a fragmentary loaded data list
loadedData.clear();
loadedFromDate = from;
loadedData.addAll(loadJmxData(from, to));
} else {
loadedData.addAll(loadJmxData(new Date(loadedToDate.getTime() + 1), to));
}
loadedToDate = getLatestLoadedElementDate();
}
}
plottedFrom = from;
plottedTo = to;
final List<JmxSensorValueData> chartingDataList = selectChartingData(from, to);
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
jmxChart.clear();
for (JmxSensorValueData jmxData : chartingDataList) {
double avg = plotDataSolver.valueConvert(jmxData.getAverageValue());
double min = plotDataSolver.valueConvert(jmxData.getMinValue());
double max = plotDataSolver.valueConvert(jmxData.getMaxValue());
jmxChart.add(jmxData.getTimeStamp().getTime(), avg, min, max);
}
jmxChart.fireSeriesChanged();
if (chartingDataList.isEmpty()) {
getRootEditor().setDataInput(Collections.<DefaultData> emptyList());
} else {
getRootEditor().setDataInput(chartingDataList);
}
}
});
}
/**
* Returns a list containing {@link JmxSensorValueData} objects which were created between the
* {@code from} and {@code to} date.
*
* @param from
* Objects have to be created after this date
* @param to
* Objects have to be created before this date
* @return List containing {@link JmxSensorValueData} objects
*/
private List<JmxSensorValueData> selectChartingData(Date from, Date to) {
List<JmxSensorValueData> resultList = new ArrayList<>();
Date fromShifted = new Date(from.getTime() - 1);
Date toShifted = new Date(to.getTime() + 1);
for (JmxSensorValueData jmxData : loadedData) {
if (jmxData.getTimeStamp().after(fromShifted) && jmxData.getTimeStamp().before(toShifted)) {
resultList.add(jmxData);
}
}
return adjustSamplingRate(resultList, fromShifted, toShifted, aggregator);
}
/**
* {@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.UPDATE);
preferenceList.add(PreferenceId.TIMELINE);
preferenceList.add(PreferenceId.SAMPLINGRATE);
preferenceList.add(PreferenceId.JMX_PLOTDATASOLVER);
return preferenceList;
}
/**
* {@inheritDoc}
*/
@Override
public void preferenceEventFired(PreferenceEvent preferenceEvent) {
super.preferenceEventFired(preferenceEvent);
switch (preferenceEvent.getPreferenceId()) {
case JMX_PLOTDATASOLVER:
processJmxPlotDataSolverEvent(preferenceEvent);
break;
default:
break;
}
}
/**
* The logic which handles the {@link PreferenceId#JMX_PLOTDATASOLVER} events.
*
* @param preferenceEvent
* the event object
*/
private void processJmxPlotDataSolverEvent(PreferenceEvent preferenceEvent) {
PlotDataSolver eDataSolver = (PlotDataSolver) preferenceEvent.getPreferenceMap().get(PreferenceId.JmxPlotDataSolver.DATA_SOLVER);
// Update view
plotDataSolver = PlotDataSolverFactory.getDataSolver(eDataSolver);
subplot.setRangeAxis(plotDataSolver.getAxis());
// Update preferenceStore
Map<String, String> dataSolverMap = PreferencesUtils.getObject(PreferencesConstants.JMX_PLOT_DATA_SOLVER);
dataSolverMap.put(currentJmxIdent.getDerivedFullName(), eDataSolver.toString());
PreferencesUtils.saveObject(PreferencesConstants.JMX_PLOT_DATA_SOLVER, dataSolverMap, false);
}
/**
* Returns the {@link Date} where the plot starts.
*
* @return the {@link #plottedFrom} variable
*/
public Date getPlottedFrom() {
return plottedFrom;
}
/**
* Returns the {@link Date} where the plot ends.
*
* @return the {@link #plottedTo} variable
*/
public Date getPlottedTo() {
return plottedTo;
}
}