/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ package org.pentaho.di.ui.spoon.trans; import java.awt.BasicStroke; import java.awt.Color; import java.awt.geom.Ellipse2D; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Dialog; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.renderer.category.LineAndShapeRenderer; import org.jfree.data.category.DefaultCategoryDataset; import org.pentaho.di.core.Const; import org.pentaho.di.i18n.BaseMessages; import org.pentaho.di.trans.performance.StepPerformanceSnapShot; import org.pentaho.di.ui.core.PropsUI; import org.pentaho.di.ui.core.gui.GUIResource; import org.pentaho.di.ui.spoon.Spoon; import org.pentaho.di.ui.util.ImageUtil; public class StepPerformanceSnapShotDialog extends Dialog { private static Class<?> PKG = Spoon.class; // for i18n purposes, needed by Translator2!! private static final int DATA_CHOICE_WRITTEN = 0; private static final int DATA_CHOICE_READ = 1; private static final int DATA_CHOICE_INPUT = 2; private static final int DATA_CHOICE_OUTPUT = 3; private static final int DATA_CHOICE_UPDATED = 4; private static final int DATA_CHOICE_REJECTED = 5; private static final int DATA_CHOICE_INPUT_BUFFER_SIZE = 6; private static final int DATA_CHOICE_OUTPUT_BUFFER_SIZE = 7; private static String[] dataChoices = new String[] { BaseMessages.getString( PKG, "StepPerformanceSnapShotDialog.Written" ), BaseMessages.getString( PKG, "StepPerformanceSnapShotDialog.Read" ), BaseMessages.getString( PKG, "StepPerformanceSnapShotDialog.Input" ), BaseMessages.getString( PKG, "StepPerformanceSnapShotDialog.Output" ), BaseMessages.getString( PKG, "StepPerformanceSnapShotDialog.Updated" ), BaseMessages.getString( PKG, "StepPerformanceSnapShotDialog.Rejected" ), BaseMessages.getString( PKG, "StepPerformanceSnapShotDialog.InputBufferSize" ), BaseMessages.getString( PKG, "StepPerformanceSnapShotDialog.OutputBufferSize" ), }; private Shell parent, shell; private Map<String, List<StepPerformanceSnapShot>> stepPerformanceSnapShots; private Display display; private String[] steps; private PropsUI props; private org.eclipse.swt.widgets.List stepsList; private Canvas canvas; private Image image; private long timeDifference; private String title; private org.eclipse.swt.widgets.List dataList; public StepPerformanceSnapShotDialog( Shell parent, String title, Map<String, List<StepPerformanceSnapShot>> stepPerformanceSnapShots, long timeDifference ) { super( parent ); this.parent = parent; this.display = parent.getDisplay(); this.props = PropsUI.getInstance(); this.timeDifference = timeDifference; this.title = title; this.stepPerformanceSnapShots = stepPerformanceSnapShots; Set<String> stepsSet = stepPerformanceSnapShots.keySet(); steps = stepsSet.toArray( new String[stepsSet.size()] ); Arrays.sort( steps ); } public void open() { shell = new Shell( parent, SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MAX | SWT.MIN ); props.setLook( shell ); shell.setText( BaseMessages.getString( PKG, "StepPerformanceSnapShotDialog.Title" ) ); shell.setImage( GUIResource.getInstance().getImageLogoSmall() ); FormLayout formLayout = new FormLayout(); formLayout.marginWidth = Const.FORM_MARGIN; formLayout.marginHeight = Const.FORM_MARGIN; shell.setLayout( formLayout ); // Display 2 lists with the data types and the steps on the left side. // Then put a canvas with the graph on the right side // dataList = new org.eclipse.swt.widgets.List( shell, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.LEFT | SWT.BORDER ); props.setLook( dataList ); dataList.setItems( dataChoices ); dataList.addSelectionListener( new SelectionAdapter() { public void widgetSelected( SelectionEvent event ) { // If there are multiple selections here AND there are multiple selections in the steps list, we only take the // first step in the selection... // if ( dataList.getSelectionCount() > 1 && stepsList.getSelectionCount() > 1 ) { stepsList.setSelection( stepsList.getSelectionIndices()[0] ); } updateGraph(); } } ); FormData fdDataList = new FormData(); fdDataList.left = new FormAttachment( 0, 0 ); fdDataList.right = new FormAttachment( props.getMiddlePct() / 2, Const.MARGIN ); fdDataList.top = new FormAttachment( 0, 0 ); fdDataList.bottom = new FormAttachment( 30, 0 ); dataList.setLayoutData( fdDataList ); stepsList = new org.eclipse.swt.widgets.List( shell, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.LEFT | SWT.BORDER ); props.setLook( stepsList ); stepsList.setItems( steps ); stepsList.addSelectionListener( new SelectionAdapter() { public void widgetSelected( SelectionEvent event ) { // If there are multiple selections here AND there are multiple selections in the data list, we only take the // first data item in the selection... // if ( dataList.getSelectionCount() > 1 && stepsList.getSelectionCount() > 1 ) { dataList.setSelection( dataList.getSelectionIndices()[0] ); } updateGraph(); } } ); FormData fdStepsList = new FormData(); fdStepsList.left = new FormAttachment( 0, 0 ); fdStepsList.right = new FormAttachment( props.getMiddlePct() / 2, Const.MARGIN ); fdStepsList.top = new FormAttachment( dataList, Const.MARGIN ); fdStepsList.bottom = new FormAttachment( 100, Const.MARGIN ); stepsList.setLayoutData( fdStepsList ); canvas = new Canvas( shell, SWT.NONE ); props.setLook( canvas ); FormData fdCanvas = new FormData(); fdCanvas.left = new FormAttachment( props.getMiddlePct() / 2, 0 ); fdCanvas.right = new FormAttachment( 100, 0 ); fdCanvas.top = new FormAttachment( 0, 0 ); fdCanvas.bottom = new FormAttachment( 100, 0 ); canvas.setLayoutData( fdCanvas ); shell.addControlListener( new ControlAdapter() { public void controlResized( ControlEvent event ) { updateGraph(); } } ); shell.addDisposeListener( new DisposeListener() { public void widgetDisposed( DisposeEvent event ) { if ( image != null ) { image.dispose(); } } } ); canvas.addPaintListener( new PaintListener() { public void paintControl( PaintEvent event ) { if ( image != null ) { event.gc.drawImage( image, 0, 0 ); } } } ); // Refresh automatically every 5 seconds as well. // Timer timer = new Timer( "step performance snapshot dialog Timer" ); timer.schedule( new TimerTask() { public void run() { updateGraph(); } }, 0, 5000 ); shell.open(); while ( !shell.isDisposed() ) { if ( !display.readAndDispatch() ) { display.sleep(); } } } private void updateGraph() { display.asyncExec( new Runnable() { public void run() { if ( !shell.isDisposed() && !canvas.isDisposed() ) { updateCanvas(); } } } ); } private void updateCanvas() { Rectangle bounds = canvas.getBounds(); if ( bounds.width <= 0 || bounds.height <= 0 ) { return; } // The list of snapshots : convert to JFreeChart dataset // DefaultCategoryDataset dataset = new DefaultCategoryDataset(); String[] selectedSteps = stepsList.getSelection(); if ( selectedSteps == null || selectedSteps.length == 0 ) { selectedSteps = new String[] { steps[0], }; // first step stepsList.select( 0 ); } int[] dataIndices = dataList.getSelectionIndices(); if ( dataIndices == null || dataIndices.length == 0 ) { dataIndices = new int[] { DATA_CHOICE_WRITTEN, }; dataList.select( 0 ); } boolean multiStep = stepsList.getSelectionCount() > 1; boolean multiData = dataList.getSelectionCount() > 1; boolean calcMoving = !multiStep && !multiData; // A single metric shown for a single step List<Double> movingList = new ArrayList<Double>(); int movingSize = 10; double movingTotal = 0; int totalTimeInSeconds = 0; for ( int t = 0; t < selectedSteps.length; t++ ) { String stepNameCopy = selectedSteps[t]; List<StepPerformanceSnapShot> snapShotList = stepPerformanceSnapShots.get( stepNameCopy ); if ( snapShotList != null && snapShotList.size() > 1 ) { totalTimeInSeconds = (int) Math .round( ( (double) ( snapShotList.get( snapShotList.size() - 1 ).getDate().getTime() - snapShotList .get( 0 ).getDate().getTime() ) ) / 1000 ); for ( int i = 0; i < snapShotList.size(); i++ ) { StepPerformanceSnapShot snapShot = snapShotList.get( i ); if ( snapShot.getTimeDifference() != 0 ) { double factor = (double) 1000 / (double) snapShot.getTimeDifference(); for ( int d = 0; d < dataIndices.length; d++ ) { String dataType; if ( multiStep ) { dataType = stepNameCopy; } else { dataType = dataChoices[dataIndices[d]]; } String xLabel = Integer.toString( Math.round( i * timeDifference / 1000 ) ); Double metric = null; switch ( dataIndices[d] ) { case DATA_CHOICE_INPUT: metric = snapShot.getLinesInput() * factor; break; case DATA_CHOICE_OUTPUT: metric = snapShot.getLinesOutput() * factor; break; case DATA_CHOICE_READ: metric = snapShot.getLinesRead() * factor; break; case DATA_CHOICE_WRITTEN: metric = snapShot.getLinesWritten() * factor; break; case DATA_CHOICE_UPDATED: metric = snapShot.getLinesUpdated() * factor; break; case DATA_CHOICE_REJECTED: metric = snapShot.getLinesRejected() * factor; break; case DATA_CHOICE_INPUT_BUFFER_SIZE: metric = (double) snapShot.getInputBufferSize(); break; case DATA_CHOICE_OUTPUT_BUFFER_SIZE: metric = (double) snapShot.getOutputBufferSize(); break; default: break; } if ( metric != null ) { dataset.addValue( metric, dataType, xLabel ); if ( calcMoving ) { movingTotal += metric; movingList.add( metric ); if ( movingList.size() > movingSize ) { movingTotal -= movingList.get( 0 ); movingList.remove( 0 ); } double movingAverage = movingTotal / movingList.size(); dataset.addValue( movingAverage, dataType + "(Avg)", xLabel ); // System.out.println("moving average = "+movingAverage+", movingTotal="+movingTotal+", m"); } } } } } } } String chartTitle = title; if ( multiStep ) { chartTitle += " (" + dataChoices[dataIndices[0]] + ")"; } else { chartTitle += " (" + selectedSteps[0] + ")"; } final JFreeChart chart = ChartFactory.createLineChart( chartTitle, // chart title BaseMessages.getString( PKG, "StepPerformanceSnapShotDialog.TimeInSeconds.Label", Integer .toString( totalTimeInSeconds ), Long.toString( timeDifference ) ), // domain axis label BaseMessages.getString( PKG, "StepPerformanceSnapShotDialog.RowsPerSecond.Label" ), // range axis label dataset, // data PlotOrientation.VERTICAL, // orientation true, // include legend true, // tooltips false ); // urls chart.setBackgroundPaint( Color.white ); CategoryPlot plot = (CategoryPlot) chart.getPlot(); plot.setBackgroundPaint( Color.white ); plot.setForegroundAlpha( 0.5f ); plot.setRangeGridlinesVisible( true ); NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); rangeAxis.setStandardTickUnits( NumberAxis.createIntegerTickUnits() ); CategoryAxis domainAxis = plot.getDomainAxis(); domainAxis.setTickLabelsVisible( false ); // Customize the renderer... // LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer(); renderer.setBaseShapesVisible( true ); renderer.setDrawOutlines( true ); renderer.setUseFillPaint( true ); renderer.setBaseFillPaint( Color.white ); renderer.setSeriesStroke( 0, new BasicStroke( 1.5f ) ); renderer.setSeriesOutlineStroke( 0, new BasicStroke( 1.5f ) ); renderer.setSeriesStroke( 1, new BasicStroke( 2.5f ) ); renderer.setSeriesOutlineStroke( 1, new BasicStroke( 2.5f ) ); renderer.setSeriesShape( 0, new Ellipse2D.Double( -3.0, -3.0, 6.0, 6.0 ) ); BufferedImage bufferedImage = chart.createBufferedImage( bounds.width, bounds.height ); ImageData imageData = ImageUtil.convertToSWT( bufferedImage ); // dispose previous image... // if ( image != null ) { image.dispose(); } image = new Image( display, imageData ); // Draw the image on the canvas... // canvas.redraw(); } /** * @return the shell */ public Shell getShell() { return parent; } /** * @param shell * the shell to set */ public void setShell( Shell shell ) { this.parent = shell; } /** * @return the stepPerformanceSnapShots */ public Map<String, List<StepPerformanceSnapShot>> getStepPerformanceSnapShots() { return stepPerformanceSnapShots; } /** * @param stepPerformanceSnapShots * the stepPerformanceSnapShots to set */ public void setStepPerformanceSnapShots( Map<String, List<StepPerformanceSnapShot>> stepPerformanceSnapShots ) { this.stepPerformanceSnapShots = stepPerformanceSnapShots; } }