/*
* soapUI, copyright (C) 2004-2011 eviware.com
*
* soapUI is free software; you can redistribute it and/or modify it under the
* terms of version 2.1 of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details at gnu.org.
*/
package com.eviware.soapui.impl.wsdl.panels.loadtest;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.Scrollable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import com.eviware.soapui.impl.wsdl.loadtest.WsdlLoadTest;
import com.eviware.soapui.impl.wsdl.loadtest.data.LoadTestStatistics;
import com.eviware.soapui.impl.wsdl.loadtest.data.LoadTestStatistics.Statistic;
import com.eviware.soapui.impl.wsdl.loadtest.data.StatisticsHistory.StatisticsHistoryModel;
import com.eviware.soapui.model.testsuite.TestStep;
import com.jgoodies.forms.builder.ButtonBarBuilder;
/**
* Graphical representation of testschedule statistics
*
* @author Ole.Matzura
*/
public class JStatisticsGraph extends JComponent implements Scrollable
{
private static final Color THREADCOUNT_COLOR = Color.GREEN.darker();
private static final Color AVERAGE_COLOR = Color.BLUE;
private static final Color ERRORS_COLOR = Color.RED.darker();
private static final Color TPS_COLOR = Color.BLACK;
private static final Color BPS_COLOR = Color.ORANGE;
private static final Color LAST_COLOR = Color.MAGENTA.brighter();
private static final int SCROLL_AHEAD = 50;
@SuppressWarnings( "unused" )
private final WsdlLoadTest loadTest;
private final LoadTestStatistics statisticsModel;
private StatisticsHistoryModel data;
private JComponent legend;
private InternalTableModelListener tableModelListener = new InternalTableModelListener();
private long[] maxValues;
private float[] scales;
public JStatisticsGraph( WsdlLoadTest loadTest )
{
this.loadTest = loadTest;
this.statisticsModel = loadTest.getStatisticsModel();
this.data = statisticsModel.getHistory().getTestStepHistory( LoadTestStatistics.TOTAL );
setAutoscrolls( true );
addMouseMotionListener( new InternalMouseMotionListener() );
data.addTableModelListener( tableModelListener );
initMaxValues();
initScales();
setBackground( Color.WHITE );
setOpaque( true );
addComponentListener( new ComponentAdapter()
{
public void componentResized( ComponentEvent e )
{
initScales();
}
} );
}
public TableModel getModel()
{
return data;
}
public void release()
{
data.removeTableModelListener( tableModelListener );
}
public void setTestStep( TestStep testStep )
{
if( data != null )
{
data.removeTableModelListener( tableModelListener );
data.release();
}
if( testStep == null )
{
data = statisticsModel.getHistory().getTestStepHistory( LoadTestStatistics.TOTAL );
}
else
{
data = statisticsModel.getHistory().getTestStepHistory( testStep.getTestCase().getIndexOfTestStep( testStep ) );
}
initMaxValues();
initScales();
data.addTableModelListener( tableModelListener );
getParent().invalidate();
revalidate();
repaint();
}
public long getResolution()
{
return statisticsModel.getHistory().getResolution();
}
public void setResolution( long resolution )
{
statisticsModel.getHistory().setResolution( resolution );
}
private void initMaxValues()
{
maxValues = new long[data.getColumnCount()];
for( int c = 0; c < data.getRowCount(); c++ )
{
for( int i = 0; i < data.getColumnCount(); i++ )
{
long value = ( Long )data.getValueAt( c, i );
if( value > maxValues[i] )
maxValues[i] = value;
}
}
}
private void initScales()
{
scales = new float[maxValues.length];
for( int c = 0; c < maxValues.length; c++ )
{
recalcScale( c );
}
}
private boolean recalcScale( int index )
{
float scale = ( index == 0 || maxValues[index] == 0 ) ? 1 : ( float )( getHeight() )
/ ( float )( maxValues[index] + 10 );
if( scale > 1 )
scale = 1;
if( Float.compare( scale, scales[index] ) == 0 )
{
return false;
}
scales[index] = scale;
return true;
}
public void paintComponent( Graphics g )
{
g.setColor( getBackground() );
Rectangle clip = g.getClipBounds();
g.fillRect( ( int )clip.getX(), ( int )clip.getY(), ( int )clip.getWidth(), ( int )clip.getHeight() );
double right = clip.getX() + clip.getWidth();
int rowCount = data.getRowCount();
int height = getHeight();
for( int c = ( int )clip.getX(); c < rowCount && c < right; c++ )
{
for( int i = 0; i < data.getColumnCount(); i++ )
{
if( i == 0 )
g.setColor( THREADCOUNT_COLOR );
else if( i == Statistic.AVERAGE.getIndex() + 1 )
g.setColor( AVERAGE_COLOR );
else if( i == Statistic.ERRORS.getIndex() + 1 )
g.setColor( ERRORS_COLOR );
else if( i == Statistic.TPS.getIndex() + 1 )
g.setColor( TPS_COLOR );
else if( i == Statistic.BPS.getIndex() + 1 )
g.setColor( BPS_COLOR );
else
continue;
int yOffset = ( int )( ( float )( ( Long )data.getValueAt( c, i ) ) * scales[i] );
if( clip.contains( c, height - yOffset - 1 ) )
{
g.drawLine( c, height - yOffset - 1, c, height - yOffset - 1 );
}
}
}
}
public JComponent getLegend()
{
if( legend == null )
buildLegend();
return legend;
}
private void buildLegend()
{
ButtonBarBuilder builder = new ButtonBarBuilder();
builder.addFixed( new JLabel( "ThreadCount", createLegendIcon( THREADCOUNT_COLOR ), JLabel.LEFT ) );
builder.addUnrelatedGap();
builder.addFixed( new JLabel( "Average (ms)", createLegendIcon( AVERAGE_COLOR ), JLabel.LEFT ) );
builder.addUnrelatedGap();
builder.addFixed( new JLabel( "ErrorCount", createLegendIcon( ERRORS_COLOR ), JLabel.LEFT ) );
builder.addUnrelatedGap();
builder.addFixed( new JLabel( "Transaction/Sec", createLegendIcon( TPS_COLOR ), JLabel.LEFT ) );
builder.addUnrelatedGap();
builder.addFixed( new JLabel( "Bytes/Sec", createLegendIcon( BPS_COLOR ), JLabel.LEFT ) );
builder.setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ) );
legend = builder.getPanel();
}
private Icon createLegendIcon( Color color )
{
BufferedImage image = new BufferedImage( 10, 10, BufferedImage.TYPE_3BYTE_BGR );
Graphics g = image.getGraphics();
g.setColor( color );
g.fillRect( 1, 1, 8, 8 );
g.setColor( Color.DARK_GRAY );
g.drawRect( 0, 0, 10, 10 );
return new ImageIcon( image );
}
public Dimension getPreferredScrollableViewportSize()
{
return getPreferredSize();
}
public Dimension getPreferredSize()
{
int height = getHeight();
int width = data.getRowCount() + SCROLL_AHEAD;
return new Dimension( width, height );
}
public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction )
{
return 1;
}
public int getScrollableBlockIncrement( Rectangle visibleRect, int orientation, int direction )
{
return 10;
}
public boolean getScrollableTracksViewportWidth()
{
return false;
}
public boolean getScrollableTracksViewportHeight()
{
return true;
}
private final class InternalTableModelListener implements TableModelListener
{
public synchronized void tableChanged( TableModelEvent e )
{
boolean repaint = false;
if( e.getType() == TableModelEvent.INSERT )
{
int firstRow = e.getFirstRow();
int lastRow = e.getLastRow();
int height = getHeight();
for( int c = firstRow; c <= lastRow; c++ )
{
for( int i = 0; i < data.getColumnCount(); i++ )
{
long value = ( Long )data.getValueAt( c, i );
if( value > maxValues[i] )
{
maxValues[i] = value;
repaint = recalcScale( i );
}
}
}
if( !repaint )
{
Rectangle rect = new Rectangle( firstRow, 0, ( lastRow - firstRow ) + 1, height );
repaint( rect );
}
Dimension size = getSize();
Rectangle r = getVisibleRect();
double x2 = r.getX() + r.getWidth();
if( x2 >= data.getRowCount() && x2 < data.getRowCount() + SCROLL_AHEAD )
{
scrollRectToVisible( new Rectangle( firstRow + SCROLL_AHEAD / 2, 0, ( lastRow - firstRow ) + 1, height ) );
}
if( !repaint && size.getWidth() < data.getRowCount() + SCROLL_AHEAD )
{
revalidate();
}
}
else if( e.getType() == TableModelEvent.UPDATE )
{
initMaxValues();
initScales();
repaint = true;
}
if( repaint )
{
getParent().invalidate();
revalidate();
repaint();
}
}
}
private class InternalMouseMotionListener implements MouseMotionListener
{
public void mouseDragged( MouseEvent e )
{
Rectangle r = new Rectangle( e.getX(), e.getY(), 1, 1 );
scrollRectToVisible( r );
}
public void mouseMoved( MouseEvent e )
{
}
}
}