View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2008 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
11   */
12  
13  package com.eviware.soapui.impl.wsdl.panels.loadtest;
14  
15  import java.awt.Color;
16  import java.awt.Dimension;
17  import java.awt.Graphics;
18  import java.awt.Rectangle;
19  import java.awt.event.ComponentAdapter;
20  import java.awt.event.ComponentEvent;
21  import java.awt.event.MouseEvent;
22  import java.awt.event.MouseMotionListener;
23  import java.awt.image.BufferedImage;
24  
25  import javax.swing.BorderFactory;
26  import javax.swing.Icon;
27  import javax.swing.ImageIcon;
28  import javax.swing.JComponent;
29  import javax.swing.JLabel;
30  import javax.swing.Scrollable;
31  import javax.swing.event.TableModelEvent;
32  import javax.swing.event.TableModelListener;
33  import javax.swing.table.TableModel;
34  
35  import com.eviware.soapui.impl.wsdl.loadtest.WsdlLoadTest;
36  import com.eviware.soapui.impl.wsdl.loadtest.data.LoadTestStatistics;
37  import com.eviware.soapui.impl.wsdl.loadtest.data.LoadTestStatistics.Statistic;
38  import com.eviware.soapui.impl.wsdl.loadtest.data.StatisticsHistory.StatisticsHistoryModel;
39  import com.eviware.soapui.model.testsuite.TestStep;
40  import com.jgoodies.forms.builder.ButtonBarBuilder;
41  
42  /***
43   * Graphical representation of testschedule statistics
44   * 
45   * @author Ole.Matzura
46   */
47  
48  public class JStatisticsGraph extends JComponent implements Scrollable
49  {
50  	private static final Color THREADCOUNT_COLOR = Color.GREEN.darker();
51  	private static final Color AVERAGE_COLOR = Color.BLUE;
52  	private static final Color ERRORS_COLOR = Color.RED.darker();
53  	private static final Color TPS_COLOR = Color.BLACK;
54  	private static final Color BPS_COLOR = Color.ORANGE;
55  	private static final Color LAST_COLOR = Color.MAGENTA.brighter();
56  
57  	private static final int SCROLL_AHEAD = 50;
58  
59  	@SuppressWarnings("unused")
60  	private final WsdlLoadTest loadTest;
61  	private final LoadTestStatistics statisticsModel;
62  	private StatisticsHistoryModel data;
63  	private JComponent legend;
64  	private InternalTableModelListener tableModelListener = new InternalTableModelListener();
65  	private long [] maxValues;
66  	private float [] scales;
67  
68  	public JStatisticsGraph(WsdlLoadTest loadTest)
69  	{
70  		this.loadTest = loadTest;
71  		this.statisticsModel = loadTest.getStatisticsModel();
72  		this.data = statisticsModel.getHistory().getTestStepHistory(LoadTestStatistics.TOTAL);
73  
74  		setAutoscrolls(true);
75  		addMouseMotionListener(new InternalMouseMotionListener());
76  
77  		data.addTableModelListener(tableModelListener);
78  		
79  		initMaxValues();
80  		initScales();
81  		
82  		setBackground( Color.WHITE );
83  		setOpaque( true );
84  		
85  		addComponentListener( new ComponentAdapter() {
86  
87  			public void componentResized(ComponentEvent e)
88  			{
89  				initScales();
90  			}
91  		} );
92  	}
93  
94  	public TableModel getModel()
95  	{
96  		return data;
97  	}
98  
99  	public void release()
100 	{
101 		data.removeTableModelListener( tableModelListener );
102 	}
103 	
104 	public void setTestStep(TestStep testStep)
105 	{
106 		if (data != null)
107 		{
108 			data.removeTableModelListener(tableModelListener);
109 			data.release();
110 		}
111 		
112 		if (testStep == null)
113 		{
114 			data = statisticsModel.getHistory().getTestStepHistory(LoadTestStatistics.TOTAL);
115 		}
116 		else
117 		{
118 			data = statisticsModel.getHistory().getTestStepHistory(testStep.getTestCase().getIndexOfTestStep(testStep));
119 		}
120 		
121 		
122 		initMaxValues();
123 		initScales();
124 		
125 		data.addTableModelListener(tableModelListener);
126 		
127 		getParent().invalidate();
128 		revalidate();
129 		repaint();
130 	}
131 	
132 	public long getResolution()
133 	{
134 		return statisticsModel.getHistory().getResolution();
135 	}
136 	
137 	public void setResolution( long resolution )
138 	{
139 		statisticsModel.getHistory().setResolution( resolution );
140 	}
141 
142 	private void initMaxValues()
143 	{
144 		maxValues = new long[data.getColumnCount()];
145 		
146 		for( int c = 0; c < data.getRowCount(); c++ )
147 		{
148 			for( int i = 0; i < data.getColumnCount(); i++ )
149 			{
150 				long value = (Long)data.getValueAt( c, i );
151 				if( value > maxValues[i] ) 
152 					maxValues[i] = value;
153 			}
154 		}
155 	}
156 
157 	private void initScales()
158 	{
159 		scales = new float[maxValues.length];
160 		
161 		for( int c = 0; c < maxValues.length; c++ )
162 		{
163 			recalcScale( c );
164 		}
165 	}
166 
167 	private boolean recalcScale( int index)
168 	{
169 		float scale = (index == 0 || maxValues[index] == 0) ? 1 : (float)(getHeight())/(float)(maxValues[index]+10);
170 		if( scale > 1 ) scale = 1;
171 		
172 		if( Float.compare( scale, scales[index] ) == 0 )
173 		{
174 			return false;
175 		}
176 		
177 		scales[index] = scale;
178 		return true;
179 	}
180 	
181 	public void paintComponent(Graphics g)
182 	{
183 		g.setColor( getBackground() );
184 		
185 		Rectangle clip = g.getClipBounds();
186 		g.fillRect( (int)clip.getX(), (int)clip.getY(), (int)clip.getWidth(), (int)clip.getHeight() );
187 		
188 		double right = clip.getX() + clip.getWidth();
189 		int rowCount = data.getRowCount();
190 		int height = getHeight();
191 		
192 		for( int c = (int) clip.getX(); c < rowCount && c < right; c++ )
193 		{
194 			for (int i = 0; i < data.getColumnCount(); i++)
195 			{
196 				if( i == 0 )
197 					g.setColor( THREADCOUNT_COLOR ); 
198 				else if( i == Statistic.AVERAGE.getIndex()+1 )
199 					g.setColor( AVERAGE_COLOR ); 
200 				else if( i == Statistic.ERRORS.getIndex()+1 )
201 					g.setColor( ERRORS_COLOR ); 
202 				else if( i == Statistic.TPS.getIndex()+1 )
203 					g.setColor( TPS_COLOR ); 
204 				else if( i == Statistic.LAST.getIndex()+1 )
205 					g.setColor( LAST_COLOR ); 
206 				else if( i == Statistic.BPS.getIndex()+1 )
207 					g.setColor( BPS_COLOR ); 
208 				else continue;
209 
210 				int yOffset = (int) ((float) ((Long) data.getValueAt(c, i)) * scales[i]);
211 
212 				if( clip.contains( c, height - yOffset - 1 ))
213 				{
214 					g.drawLine(c, height - yOffset - 1, c, height - yOffset -1);
215 				}
216 			}
217 		}
218 	}
219 
220 	public JComponent getLegend()
221 	{
222 		if (legend == null)
223 			buildLegend();
224 
225 		return legend;
226 	}
227 
228 	private void buildLegend()
229 	{
230 		ButtonBarBuilder builder = new ButtonBarBuilder();
231 
232 		builder.addFixed( new JLabel( "ThreadCount", createLegendIcon( THREADCOUNT_COLOR ), JLabel.LEFT ));
233 		builder.addUnrelatedGap();
234 		builder.addFixed( new JLabel( "Average (ms)", createLegendIcon( AVERAGE_COLOR ), JLabel.LEFT ));
235 		builder.addUnrelatedGap();
236 		builder.addFixed( new JLabel( "ErrorCount", createLegendIcon( ERRORS_COLOR ), JLabel.LEFT ));
237 		builder.addUnrelatedGap();
238 		builder.addFixed( new JLabel( "Transaction/Sec", createLegendIcon( TPS_COLOR ), JLabel.LEFT ));
239 		builder.addUnrelatedGap();
240 		builder.addFixed( new JLabel( "Bytes/Sec", createLegendIcon( BPS_COLOR ), JLabel.LEFT ));
241 		builder.addUnrelatedGap();
242 		builder.addFixed( new JLabel( "Last (ms)", createLegendIcon( LAST_COLOR ), JLabel.LEFT ));
243 
244 		builder.setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ));
245 		
246 		legend = builder.getPanel();
247 	}
248 
249 	private Icon createLegendIcon(Color color)
250 	{
251 		BufferedImage image = new BufferedImage( 10, 10, BufferedImage.TYPE_3BYTE_BGR );
252 		Graphics g = image.getGraphics();
253 		g.setColor( color );
254 		g.fillRect( 1, 1, 8, 8 );
255 		g.setColor( Color.DARK_GRAY );
256 		g.drawRect( 0, 0, 10, 10 );
257 		return new ImageIcon( image );
258 	}
259 
260 	public Dimension getPreferredScrollableViewportSize()
261 	{
262 		return getPreferredSize();
263 	}
264 	
265 	public Dimension getPreferredSize()
266 	{
267 		int height = getHeight();
268 		int width = data.getRowCount() + SCROLL_AHEAD;
269 		return new Dimension( width, height);
270 	}
271 
272 	public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)
273 	{
274 		return 1;
275 	}
276 
277 	public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction)
278 	{
279 		return 10;
280 	}
281 
282 	public boolean getScrollableTracksViewportWidth()
283 	{
284 		return false;
285 	}
286 
287 	public boolean getScrollableTracksViewportHeight()
288 	{
289 		return true;
290 	}
291 
292 	private final class InternalTableModelListener implements TableModelListener
293 	{
294 		public synchronized void tableChanged(TableModelEvent e)
295 		{
296 			boolean repaint = false;
297 			
298 			if( e.getType() == TableModelEvent.INSERT )
299 			{
300 				int firstRow = e.getFirstRow();
301 				int lastRow = e.getLastRow();
302 				int height = getHeight();
303 				
304 				for( int c = firstRow; c <= lastRow; c++ )
305 				{
306 					for( int i = 0; i < data.getColumnCount(); i++ )
307 					{
308 						long value = (Long)data.getValueAt( c, i );
309 						
310 						if( value > maxValues[i] ) 
311 						{
312 							maxValues[i] = value;
313 							repaint = recalcScale( i );
314 						}
315 					}
316 				}
317 				
318 				if( !repaint )
319 				{
320 					Rectangle rect = new Rectangle(firstRow, 0, (lastRow-firstRow)+1, height );
321 					repaint( rect );
322 				}
323 				
324 				Dimension size = getSize();
325 				Rectangle r = getVisibleRect();
326 				
327 				double x2 = r.getX() + r.getWidth();
328 				if( x2 >= data.getRowCount() && x2 < data.getRowCount()+SCROLL_AHEAD)
329 				{
330 					scrollRectToVisible( new Rectangle(firstRow + SCROLL_AHEAD/2, 0, (lastRow-firstRow)+1, height ) );
331 				}
332 				
333 				if( !repaint && size.getWidth() < data.getRowCount() + SCROLL_AHEAD )
334 				{
335 					revalidate();
336 				}
337 			}
338 			else if( e.getType() == TableModelEvent.UPDATE )
339 			{
340 				initMaxValues();
341 				initScales();
342 				
343 				repaint = true;
344 			}
345 			
346 			if( repaint )
347 			{
348 				getParent().invalidate();
349 				revalidate();
350 				repaint();
351 			}
352 		}
353 	}
354 
355 	private class InternalMouseMotionListener implements MouseMotionListener
356 	{
357 		public void mouseDragged(MouseEvent e)
358 		{
359 			Rectangle r = new Rectangle(e.getX(), e.getY(), 1, 1);
360 			scrollRectToVisible(r);
361 		}
362 
363 		public void mouseMoved(MouseEvent e)
364 		{
365 		}
366 	}
367 }