org.pentaho.di.ui.spoon.trans.TransPerfDelegate.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.di.ui.spoon.trans.TransPerfDelegate.java

Source

/*! ******************************************************************************
 *
 * 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);
        }
    }

}