Java tutorial
/*! ****************************************************************************** * * 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.custom.CTabItem; 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.Button; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; 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.chart.title.TextTitle; 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.gui.GUIResource; import org.pentaho.di.ui.spoon.Spoon; import org.pentaho.di.ui.spoon.delegates.SpoonDelegate; import org.pentaho.di.ui.trans.dialog.TransDialog; import org.pentaho.di.ui.util.ImageUtil; public class TransPerfDelegate extends SpoonDelegate { private static Class<?> PKG = Spoon.class; // for i18n purposes, needed by Translator2!! // private static final LogWriter log = LogWriter.getInstance(); 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 TransGraph transGraph; private CTabItem transPerfTab; private Map<String, List<StepPerformanceSnapShot>> stepPerformanceSnapShots; private String[] steps; 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; private Composite perfComposite; private boolean emptyGraph; /** * @param spoon * @param transGraph */ public TransPerfDelegate(Spoon spoon, TransGraph transGraph) { super(spoon); this.transGraph = transGraph; } public void addTransPerf() { // First, see if we need to add the extra view... // if (transGraph.extraViewComposite == null || transGraph.extraViewComposite.isDisposed()) { transGraph.addExtraView(); } else { if (transPerfTab != null && !transPerfTab.isDisposed()) { // just set this one active and get out... // transGraph.extraViewTabFolder.setSelection(transPerfTab); return; } } // Add a transLogTab : display the logging... // transPerfTab = new CTabItem(transGraph.extraViewTabFolder, SWT.NONE); transPerfTab.setImage(GUIResource.getInstance().getImageShowPerf()); transPerfTab.setText(BaseMessages.getString(PKG, "Spoon.TransGraph.PerfTab.Name")); // Create a composite, slam everything on there like it was in the history tab. // perfComposite = new Composite(transGraph.extraViewTabFolder, SWT.NONE); perfComposite.setBackground(GUIResource.getInstance().getColorBackground()); perfComposite.setLayout(new FormLayout()); spoon.props.setLook(perfComposite); transGraph.getDisplay().asyncExec(new Runnable() { public void run() { setupContent(); } }); transPerfTab.setControl(perfComposite); transGraph.extraViewTabFolder.setSelection(transPerfTab); transGraph.extraViewTabFolder.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent arg0) { layoutPerfComposite(); updateGraph(); } }); } public void setupContent() { // there is a potential infinite loop below if this method // is called when the transgraph is not running, so we check // early to make sure it won't happen (see PDI-5009) if (!transGraph.isRunning() || transGraph.trans == null || !transGraph.trans.getTransMeta().isCapturingStepPerformanceSnapShots()) { showEmptyGraph(); return; // TODO: display help text and rerty button } if (perfComposite.isDisposed()) { return; } // Remove anything on the perf composite, like an empty page message // for (Control control : perfComposite.getChildren()) { if (!control.isDisposed()) { control.dispose(); } } emptyGraph = false; this.title = transGraph.trans.getTransMeta().getName(); this.timeDifference = transGraph.trans.getTransMeta().getStepPerformanceCapturingDelay(); this.stepPerformanceSnapShots = transGraph.trans.getStepPerformanceSnapShots(); // Wait a second for the first data to pour in... // TODO: make this wait more elegant... // while (this.stepPerformanceSnapShots == null || stepPerformanceSnapShots.isEmpty()) { this.stepPerformanceSnapShots = transGraph.trans.getStepPerformanceSnapShots(); try { Thread.sleep(100L); } catch (InterruptedException e) { // Ignore errors } } Set<String> stepsSet = stepPerformanceSnapShots.keySet(); steps = stepsSet.toArray(new String[stepsSet.size()]); Arrays.sort(steps); // 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 // Label dataListLabel = new Label(perfComposite, SWT.NONE); dataListLabel.setText(BaseMessages.getString(PKG, "StepPerformanceSnapShotDialog.Metrics.Label")); spoon.props.setLook(dataListLabel); FormData fdDataListLabel = new FormData(); fdDataListLabel.left = new FormAttachment(0, 0); fdDataListLabel.right = new FormAttachment(spoon.props.getMiddlePct() / 2, Const.MARGIN); fdDataListLabel.top = new FormAttachment(0, Const.MARGIN + 5); dataListLabel.setLayoutData(fdDataListLabel); dataList = new org.eclipse.swt.widgets.List(perfComposite, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.LEFT | SWT.BORDER); spoon.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(spoon.props.getMiddlePct() / 2, Const.MARGIN); fdDataList.top = new FormAttachment(dataListLabel, Const.MARGIN); fdDataList.bottom = new FormAttachment(40, Const.MARGIN); dataList.setLayoutData(fdDataList); Label stepsListLabel = new Label(perfComposite, SWT.NONE); stepsListLabel.setText(BaseMessages.getString(PKG, "StepPerformanceSnapShotDialog.Steps.Label")); spoon.props.setLook(stepsListLabel); FormData fdStepsListLabel = new FormData(); fdStepsListLabel.left = new FormAttachment(0, 0); fdStepsListLabel.right = new FormAttachment(spoon.props.getMiddlePct() / 2, Const.MARGIN); fdStepsListLabel.top = new FormAttachment(dataList, Const.MARGIN); stepsListLabel.setLayoutData(fdStepsListLabel); stepsList = new org.eclipse.swt.widgets.List(perfComposite, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.LEFT | SWT.BORDER); spoon.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(spoon.props.getMiddlePct() / 2, Const.MARGIN); fdStepsList.top = new FormAttachment(stepsListLabel, Const.MARGIN); fdStepsList.bottom = new FormAttachment(100, Const.MARGIN); stepsList.setLayoutData(fdStepsList); canvas = new Canvas(perfComposite, SWT.NONE); spoon.props.setLook(canvas); FormData fdCanvas = new FormData(); fdCanvas.left = new FormAttachment(spoon.props.getMiddlePct() / 2, Const.MARGIN); fdCanvas.right = new FormAttachment(100, 0); fdCanvas.top = new FormAttachment(0, Const.MARGIN); fdCanvas.bottom = new FormAttachment(100, 0); canvas.setLayoutData(fdCanvas); perfComposite.addControlListener(new ControlAdapter() { public void controlResized(ControlEvent event) { updateGraph(); } }); perfComposite.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 && !image.isDisposed()) { event.gc.drawImage(image, 0, 0); } } }); // Refresh automatically every 5 seconds as well. // final Timer timer = new Timer("TransPerfDelegate Timer"); timer.schedule(new TimerTask() { public void run() { updateGraph(); } }, 0, 5000); // When the tab is closed, we remove the update timer // transPerfTab.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent arg0) { timer.cancel(); } }); } /** * Tell the user that the transformation is not running or that there is no monitoring configured. */ private void showEmptyGraph() { if (perfComposite.isDisposed()) { return; } emptyGraph = true; Label label = new Label(perfComposite, SWT.CENTER); label.setText(BaseMessages.getString(PKG, "TransLog.Dialog.PerformanceMonitoringNotEnabled.Message")); label.setBackground(perfComposite.getBackground()); label.setFont(GUIResource.getInstance().getFontMedium()); FormData fdLabel = new FormData(); fdLabel.left = new FormAttachment(5, 0); fdLabel.right = new FormAttachment(95, 0); fdLabel.top = new FormAttachment(5, 0); label.setLayoutData(fdLabel); Button button = new Button(perfComposite, SWT.CENTER); button.setText(BaseMessages.getString(PKG, "TransLog.Dialog.PerformanceMonitoring.Button")); button.setBackground(perfComposite.getBackground()); button.setFont(GUIResource.getInstance().getFontMedium()); button.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { TransGraph.editProperties(spoon.getActiveTransformation(), spoon, spoon.rep, true, TransDialog.Tabs.MONITOR_TAB); } }); FormData fdButton = new FormData(); fdButton.left = new FormAttachment(40, 0); fdButton.right = new FormAttachment(60, 0); fdButton.top = new FormAttachment(label, 5); button.setLayoutData(fdButton); perfComposite.layout(true, true); } public void showPerfView() { // What button? // // XulToolbarButton showLogXulButton = toolbar.getButtonById("trans-show-log"); // ToolItem toolBarButton = (ToolItem) showLogXulButton.getNativeObject(); if (transPerfTab == null || transPerfTab.isDisposed()) { addTransPerf(); } else { transPerfTab.dispose(); transGraph.checkEmptyExtraView(); } } private void updateGraph() { transGraph.getDisplay().asyncExec(new Runnable() { public void run() { if (perfComposite != null && !perfComposite.isDisposed() && canvas != null && !canvas.isDisposed() && transPerfTab != null && !transPerfTab.isDisposed()) { if (transPerfTab.isShowing()) { 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); TextTitle title = new TextTitle(chartTitle); // title.setExpandToFitSpace(true); // org.eclipse.swt.graphics.Color pentahoColor = GUIResource.getInstance().getColorPentaho(); // java.awt.Color color = new java.awt.Color(pentahoColor.getRed(), pentahoColor.getGreen(),pentahoColor.getBlue()); // title.setBackgroundPaint(color); title.setFont(new java.awt.Font("SansSerif", java.awt.Font.BOLD, 12)); chart.setTitle(title); 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, BufferedImage.TYPE_INT_RGB, null); ImageData imageData = ImageUtil.convertToSWT(bufferedImage); // dispose previous image... // if (image != null) { image.dispose(); } image = new Image(transGraph.getDisplay(), imageData); // Draw the image on the canvas... // canvas.redraw(); } /** * @return the transHistoryTab */ public CTabItem getTransPerfTab() { return transPerfTab; } /** * @return the emptyGraph */ public boolean isEmptyGraph() { return emptyGraph; } public void layoutPerfComposite() { if (!perfComposite.isDisposed()) { perfComposite.layout(true, true); } } }