package nl.topicus.onderwijs.dashboard.modules.plots; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import nl.topicus.onderwijs.dashboard.config.ISettings; import nl.topicus.onderwijs.dashboard.datasources.AverageRequestTime; import nl.topicus.onderwijs.dashboard.datasources.NumberOfUsers; import nl.topicus.onderwijs.dashboard.datasources.RequestsPerMinute; import nl.topicus.onderwijs.dashboard.keys.Key; import nl.topicus.onderwijs.dashboard.keys.Project; import nl.topicus.onderwijs.dashboard.modules.AbstractService; import nl.topicus.onderwijs.dashboard.modules.DashboardRepository; import nl.topicus.onderwijs.dashboard.modules.DataSource; import nl.topicus.onderwijs.dashboard.modules.ServiceConfiguration; import nl.topicus.onderwijs.dashboard.web.WicketApplication; import org.apache.commons.math.ArgumentOutsideDomainException; import org.apache.commons.math.MathException; import org.apache.commons.math.analysis.interpolation.LoessInterpolator; import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service @ServiceConfiguration(interval = 1, unit = TimeUnit.MINUTES, runInRandomMode = true) public class PlotService extends AbstractService { private Map<PlotKey, DataSourceSeries<?, ?>> data = new HashMap<PlotKey, DataSourceSeries<?, ?>>(); private Map<PlotKey, DataSourcePlotSeries<?, ?>> series = new HashMap<PlotKey, DataSourcePlotSeries<?, ?>>(); private WicketApplication application; private LoessInterpolator loessInterpolator; @Autowired public PlotService(ISettings settings) { super(settings); } @Autowired public void setApplication(WicketApplication application) { this.application = application; } @Override public void onConfigure(DashboardRepository repository) { loessInterpolator = new LoessInterpolator(); for (Project curProject : repository.getKeys(Project.class)) { if (repository.getData(AverageRequestTime.class).containsKey( curProject)) addSeries(curProject, AverageRequestTime.class); if (repository.getData(NumberOfUsers.class).containsKey(curProject)) addSeries(curProject, NumberOfUsers.class); if (repository.getData(RequestsPerMinute.class).containsKey( curProject)) addSeries(curProject, RequestsPerMinute.class); } } private <T extends Number, D extends DataSource<T>> void addSeries( Project project, Class<D> dataSource) { PlotKey key = new PlotKey(project, dataSource); data.put(key, new DataSourceSeries<T, D>(project, dataSource)); series.put(key, new DataSourcePlotSeries<T, D>(project)); } @SuppressWarnings("unchecked") public <T extends Number, D extends DataSource<T>> List<DataSourcePlotSeries<T, D>> getSeries( Class<D> dataSource) { List<DataSourcePlotSeries<T, D>> ret = new ArrayList<DataSourcePlotSeries<T, D>>(); for (Project curProject : application.getRepository().getKeys( Project.class)) { ret.add((DataSourcePlotSeries<T, D>) series.get(new PlotKey( curProject, dataSource))); } return ret; } @Override public void refreshData() { cleanupDataEntries(); updateDataEntries(); updateAllPlotSeries(); } private void cleanupDataEntries() { Map<Key, Map<String, ?>> serviceSettings = getSettings() .getServiceSettings(PlotService.class); for (DataSourceSeries<?, ?> curData : data.values()) { if (serviceSettings.containsKey(curData.getKey()) && serviceSettings.get(curData.getKey()).containsKey( "timeToLive")) { int dataTTL = Integer.parseInt(serviceSettings .get(curData.getKey()).get("timeToLive").toString()); Calendar ttlDate = Calendar.getInstance(); ttlDate.add(Calendar.SECOND, 0 - dataTTL); curData.cleanupEntries(ttlDate.getTime()); } } } private void updateDataEntries() { for (DataSourceSeries<?, ?> curData : data.values()) { curData.addEntry(application.getRepository()); } } private void updateAllPlotSeries() { for (DataSourceSeries<?, ?> curData : data.values()) { DataSourcePlotSeries<Integer, ?> curSeries = (DataSourcePlotSeries<Integer, ?>) series .get(new PlotKey(curData.getKey(), curData.getDataSource())); updatePlotSeries(curSeries, curData); } } private void updatePlotSeries(DataSourcePlotSeries<Integer, ?> curSeries, DataSourceSeries<?, ?> curData) { if (!updatePlotSeriesWithLoessInterpolatorData(curSeries, curData)) { updatePlotSeriesWithOriginalData(curSeries, curData); } } private void updatePlotSeriesWithOriginalData( DataSourcePlotSeries<Integer, ?> curSeries, DataSourceSeries<?, ?> curData) { curSeries.clear(); for (DataSourceSeriesEntry<?> entry : curData.getData()) { curSeries.addEntry(entry.getKey(), (Integer) entry.getValue()); } } private boolean updatePlotSeriesWithLoessInterpolatorData( DataSourcePlotSeries<Integer, ?> curSeries, DataSourceSeries<?, ?> curData) { if (curData.getData().size() < 10) { return false; } Date last = curData.getLastEntry().getKey(); double[] xvals = new double[curData.getData().size()]; double[] yvals = new double[curData.getData().size()]; for (int i = 0; i < curData.getData().size(); i++) { xvals[i] = new Long(curData.getData().get(i).getKey().getTime()); if (curData.getData().get(i).getValue() != null) { yvals[i] = curData.getData().get(i).getValue().doubleValue(); } } PolynomialSplineFunction psf = null; try { psf = loessInterpolator.interpolate(xvals, yvals); } catch (MathException e) { e.printStackTrace(); return false; } if (psf != null) { curSeries.clear(); Date time = curData.getFirstEntry().getKey(); do { try { double v = psf.value(time.getTime()); curSeries.addEntry(time, Double.valueOf(v).intValue()); } catch (ArgumentOutsideDomainException e) { e.printStackTrace(); return false; } Calendar c = Calendar.getInstance(); c.setTime(time); c.add(Calendar.SECOND, 10); time = c.getTime(); } while (time.before(last)); return true; } return false; } }