View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2007 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.loadtest.data;
14  
15  import java.beans.PropertyChangeEvent;
16  import java.beans.PropertyChangeListener;
17  import java.util.HashMap;
18  import java.util.List;
19  import java.util.Map;
20  import java.util.Stack;
21  
22  import javax.swing.table.AbstractTableModel;
23  
24  import org.apache.log4j.Logger;
25  
26  import com.eviware.soapui.SoapUI;
27  import com.eviware.soapui.impl.wsdl.loadtest.ColorPalette;
28  import com.eviware.soapui.impl.wsdl.loadtest.WsdlLoadTest;
29  import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
30  import com.eviware.soapui.model.support.LoadTestRunListenerAdapter;
31  import com.eviware.soapui.model.support.TestSuiteListenerAdapter;
32  import com.eviware.soapui.model.testsuite.LoadTestRunContext;
33  import com.eviware.soapui.model.testsuite.LoadTestRunner;
34  import com.eviware.soapui.model.testsuite.TestCase;
35  import com.eviware.soapui.model.testsuite.TestRunContext;
36  import com.eviware.soapui.model.testsuite.TestRunner;
37  import com.eviware.soapui.model.testsuite.TestStep;
38  import com.eviware.soapui.model.testsuite.TestStepResult;
39  
40  /***
41   * Model holding statistics.. should be refactored into interface for different statistic models
42   * 
43   * @author Ole.Matzura
44   */
45  
46  public final class LoadTestStatistics extends AbstractTableModel implements Runnable
47  {
48  	private final static Logger log = Logger.getLogger(LoadTestStatistics.class);
49  	
50  	private final WsdlLoadTest loadTest;
51  	private long [][] data;
52  	
53  	private final static int MIN_COLUMN = 0;
54  	private final static int MAX_COLUMN = 1;
55  	private final static int AVG_COLUMN = 2;
56  	private final static int LAST_COLUMN = 3;
57  	private final static int CNT_COLUMN = 4;
58  	private final static int TPS_COLUMN = 5;
59  	private final static int BYTES_COLUMN = 6;
60  	private final static int BPS_COLUMN = 7;
61  	private final static int ERR_COLUMN = 8;
62  	private final static int SUM_COLUMN = 9;
63  	private final static int CURRENT_CNT_COLUMN = 10;
64  	
65  	public static final int TOTAL = -1;
66  	
67  	public static final int DEFAULT_SAMPLE_INTERVAL = 250;
68  	
69  	private InternalTestRunListener testRunListener;
70  	private InternalTestSuiteListener testSuiteListener;
71  	private InternalPropertyChangeListener propertyChangeListener;
72  	
73  	private StatisticsHistory history;
74  	
75  	private boolean changed;
76  	private long updateFrequency = DEFAULT_SAMPLE_INTERVAL;
77  	private Stack<SamplesHolder> samplesStack = new Stack<SamplesHolder>();
78  	private long currentThreadCountStartTime;
79  	private long totalAverageSum;
80  	private boolean resetStatistics;
81  	private boolean running;
82  	private boolean adding;
83  
84  	public LoadTestStatistics( WsdlLoadTest loadTest )
85  	{
86  		this.loadTest = loadTest;
87  		
88  		testRunListener = new InternalTestRunListener();
89  		testSuiteListener = new InternalTestSuiteListener();
90  		propertyChangeListener = new InternalPropertyChangeListener();
91  		
92  		WsdlTestCase testCase = loadTest.getTestCase();
93  		loadTest.addPropertyChangeListener( propertyChangeListener );
94  		loadTest.addLoadTestRunListener( testRunListener );
95  		testCase.getTestSuite().addTestSuiteListener( testSuiteListener );
96  
97  		for( TestStep testStep : testCase.getTestStepList() )
98  		{
99  			testStep.addPropertyChangeListener( propertyChangeListener );
100 		}
101 
102 		history = new StatisticsHistory( this );
103 		
104 		init();
105 	}
106 
107 	private void init()
108 	{
109 		data = new long[getRowCount()][11];
110 	}
111 	
112 	public StatisticsHistory getHistory()
113 	{
114 		return history;
115 	}
116 
117 	public int getRowCount()
118 	{
119 		return loadTest.getTestCase().getTestStepCount() + 1;
120 	}
121 
122 	public WsdlLoadTest getLoadTest()
123 	{
124 		return loadTest;
125 	}
126 	
127 	public int getColumnCount()
128 	{
129 		return 11;
130 	}
131 
132 	public String getColumnName(int columnIndex)
133 	{
134 		switch( columnIndex )
135 		{
136 			case 0 : return " ";
137 			case 1 : return "Test Step";
138 			case 2 : return Statistic.MININMUM.getName();
139 			case 3 : return Statistic.MAXIMUM.getName();
140 			case 4 : return Statistic.AVERAGE.getName();
141 			case 5 : return Statistic.LAST.getName();
142 			case 6 : return Statistic.COUNT.getName();
143 			case 7 : return Statistic.TPS.getName();
144 			case 8 : return Statistic.BYTES.getName();
145 			case 9 : return Statistic.BPS.getName();
146 			case 10 : return Statistic.ERRORS.getName();
147 		}
148 		return null;
149 	}
150 
151 	public Class<?> getColumnClass(int columnIndex)
152 	{
153 		return String.class;
154 	}
155 
156 	public boolean isCellEditable(int rowIndex, int columnIndex)
157 	{
158 		return false;
159 	}
160 
161 	public long getStatistic( int stepIndex, Statistic statistic )
162 	{
163 		if( stepIndex == TOTAL )
164 			stepIndex = data.length-1;
165 				
166 		if( statistic == Statistic.TPS || statistic == Statistic.AVERAGE )
167 			return data[stepIndex][statistic.getIndex()]/100;
168 		else
169 			return data[stepIndex][statistic.getIndex()];
170 	}
171 	
172 	public Object getValueAt(int rowIndex, int columnIndex)
173 	{
174 		WsdlTestCase testCase = loadTest.getTestCase();
175 		
176 		switch( columnIndex )
177 		{
178 			case 0 : return rowIndex == testCase.getTestStepCount() ? "" : 
179 				 ColorPalette.getColor( testCase.getTestStepAt( rowIndex ) );
180 			case 1 :
181 			{
182 				if( rowIndex == testCase.getTestStepCount() )
183 				{
184 					return "TestCase:";
185 				}
186 				else
187 				{
188 					return testCase.getTestStepAt( rowIndex ).getLabel();
189 				}
190 			}
191 			case 4 :
192 			case 7 : return Float.toString( (float)data[rowIndex][columnIndex-2]/100 );
193 			default :
194 			{
195 				return data == null || rowIndex >= data.length ? "0" : data[rowIndex][columnIndex-2];
196 			}
197 		}
198 	}
199 
200 	public void pushSamples( long[] samples, long[] sizes, long[] sampleCounts, long startTime, long timeTaken )
201 	{
202 		if( samples.length == 0 || sizes.length == 0 )
203 			return;
204 		
205 		samplesStack.push( new SamplesHolder( samples, sizes, sampleCounts, startTime, timeTaken ));
206 	}
207 	
208 	public void run()
209 	{
210 		while( running || !samplesStack.isEmpty() )
211 		{
212 			try
213 			{
214 				while (!samplesStack.isEmpty())
215 				{
216 					SamplesHolder holder = samplesStack.pop();
217 					if (holder != null)
218 						addSamples(holder);
219 				}
220 				
221 				Thread.sleep( 200 );
222 			}
223 			catch (Exception e)
224 			{
225 				SoapUI.logError( e );
226 			}			
227 		}
228 	}
229 	
230 	private void addSamples( SamplesHolder holder )
231 	{
232 		if( adding )
233 			throw new RuntimeException( "Already adding!" );
234 		
235 		adding = true;
236 		
237 		int totalIndex = data.length-1;
238 		if( holder.samples.length != totalIndex || holder.sizes.length != totalIndex )
239 		{
240 			adding = false;
241 			throw new RuntimeException( "Unexpected number of samples: " + holder.samples.length + 
242 					", exptected " + (totalIndex) );
243 		}
244 
245 		
246 		// discard "old" results
247 		if( holder.startTime < currentThreadCountStartTime )
248 		{
249 			adding = false;
250 			return;
251 		}
252 		
253 		//	 first check that this is not a
254 		long timePassed = (holder.startTime+holder.timeTaken)-currentThreadCountStartTime;
255 		
256 		if( resetStatistics )
257 		{
258 			for( int c = 0; c < data.length; c++ )
259 			{
260 				data[c][CURRENT_CNT_COLUMN] = 0;
261 				data[c][AVG_COLUMN] = 0;
262 				data[c][SUM_COLUMN] = 0;
263 				data[c][TPS_COLUMN] = 0;
264 				data[c][BYTES_COLUMN] = 0;
265 			}
266 
267 			totalAverageSum = 0;
268 			resetStatistics = false;
269 		}
270 		
271 		long totalMin = 0;
272 		long totalMax = 0;
273 		long totalBytes = 0;
274 		long totalAvg = 0;
275 		long totalSum = 0;
276 		long totalLast = 0;
277 
278 		long threadCount = loadTest.getThreadCount();
279 		
280 		for( int c = 0; c < holder.samples.length; c++ )
281 		{
282 			if( holder.sampleCounts[c] > 0 )
283 			{
284 				long sampleAvg = holder.samples[c] / holder.sampleCounts[c];
285 				
286 				data[c][LAST_COLUMN] = sampleAvg; 
287 				data[c][CNT_COLUMN] += holder.sampleCounts[c];
288 				data[c][CURRENT_CNT_COLUMN] += holder.sampleCounts[c];
289 				data[c][SUM_COLUMN] += holder.samples[c];
290 				
291 				if( sampleAvg > 0 && (sampleAvg < data[c][MIN_COLUMN] || data[c][CNT_COLUMN] == 1) ) 
292 					data[c][MIN_COLUMN] = sampleAvg;
293 				
294 				if( sampleAvg > data[c][MAX_COLUMN] ) 
295 					data[c][MAX_COLUMN] = sampleAvg;
296 				
297 				float average = (float)data[c][SUM_COLUMN]/(float)data[c][CURRENT_CNT_COLUMN];
298 				
299 				data[c][AVG_COLUMN] = (long) (average*100);
300 				data[c][BYTES_COLUMN] += holder.sizes[c];
301 	
302 				if( timePassed > 0 )
303 				{
304 //					data[c][BPS_COLUMN] = (data[c][BYTES_COLUMN]*1000) / timePassed; 
305 					
306 					if( loadTest.getCalculateTPSOnTimePassed() )
307 					{
308 						data[c][TPS_COLUMN] = (data[c][CURRENT_CNT_COLUMN]*100000) / timePassed;
309 						data[c][BPS_COLUMN] = (data[c][BYTES_COLUMN]*1000) / timePassed; 
310 					}
311 					else
312 					{
313 						data[c][TPS_COLUMN] = (long) (data[c][AVG_COLUMN] > 0 ? 
314 								(100000F/average)*threadCount : 0);
315 						
316 //						long v = (data[c][TPS_COLUMN] * timePassed)/10000000;
317 //						data[c][BPS_COLUMN] = v == 0 ? 0 : data[c][BYTES_COLUMN] / v; 
318 						
319 						long avgBytes = data[c][CNT_COLUMN] == 0 ? 0 : data[c][BYTES_COLUMN] / data[c][CNT_COLUMN];
320 						data[c][BPS_COLUMN] = (avgBytes * data[c][TPS_COLUMN]) / 100;
321 					}
322 				}
323 				
324 				totalMin += data[c][MIN_COLUMN] * holder.sampleCounts[c];
325 				totalMax += data[c][MAX_COLUMN] * holder.sampleCounts[c];
326 				totalBytes += data[c][BYTES_COLUMN] * holder.sampleCounts[c];
327 				totalAvg += data[c][AVG_COLUMN] * holder.sampleCounts[c];
328 				totalSum += data[c][SUM_COLUMN] * holder.sampleCounts[c];
329 				totalLast += data[c][LAST_COLUMN] * holder.sampleCounts[c];
330 			}
331 			else
332 			{
333 				totalMin += data[c][MIN_COLUMN];
334 				totalMax += data[c][MAX_COLUMN];
335 				totalBytes += data[c][BYTES_COLUMN];
336 			}
337 		}
338 		
339 		data[totalIndex][CNT_COLUMN]++;
340 		data[totalIndex][CURRENT_CNT_COLUMN]++;
341 		
342 		totalAverageSum += totalLast*100;
343 		data[totalIndex][AVG_COLUMN] = (long)((float)totalAverageSum / (float)data[totalIndex][CURRENT_CNT_COLUMN]);
344 		data[totalIndex][BYTES_COLUMN] = totalBytes;
345 		
346 		if( timePassed > 0 )
347 		{
348 			if( loadTest.getCalculateTPSOnTimePassed() )
349 			{
350 				data[totalIndex][TPS_COLUMN] = (data[totalIndex][CURRENT_CNT_COLUMN]*100000) / timePassed;
351 				data[totalIndex][BPS_COLUMN] = (data[totalIndex][BYTES_COLUMN]*1000) / timePassed; 
352 			}
353 			else
354 			{
355 				data[totalIndex][TPS_COLUMN] = (long) (data[totalIndex][AVG_COLUMN] > 0 ? 
356 						(10000000F/data[totalIndex][AVG_COLUMN])*threadCount : 0);
357 				
358 				long avgBytes = data[totalIndex][CNT_COLUMN] == 0 ? 0 : 
359 					data[totalIndex][BYTES_COLUMN] / data[totalIndex][CNT_COLUMN];
360 				
361 				data[totalIndex][BPS_COLUMN] = (avgBytes * data[totalIndex][TPS_COLUMN]) / 100;
362 			}
363 		}
364 		
365 		data[totalIndex][MIN_COLUMN] = totalMin;
366 		data[totalIndex][MAX_COLUMN] = totalMax;
367 		data[totalIndex][SUM_COLUMN] = totalSum;
368 		data[totalIndex][LAST_COLUMN] = totalLast;
369 		
370 		if( updateFrequency == 0 )
371 			fireTableDataChanged();
372 		else
373 			changed = true;
374 		
375 		adding = false;
376 	}
377 
378 	private final class Updater implements Runnable
379 	{
380 		public void run()
381 		{
382 			// check all these for catching threading issues
383 			while( running || changed || !samplesStack.isEmpty())
384 			{
385 				if( changed )
386 				{
387 					fireTableDataChanged();
388 					changed = false;
389 				}
390 				
391 				if( !running && samplesStack.isEmpty() )
392 					break;
393 
394 				try
395 				{
396 					Thread.sleep( updateFrequency < 1 ? 1000 : updateFrequency );
397 				}
398 				catch (InterruptedException e)
399 				{
400 					SoapUI.logError( e );
401 				}
402 			}
403 		}
404 	}
405 	
406 	private void stop()
407 	{
408 		running = false;
409 	}
410 	
411 	/***
412 	 * Collect testresult samples
413 	 * 
414 	 * @author Ole.Matzura
415 	 */
416 	
417 	private class InternalTestRunListener extends LoadTestRunListenerAdapter
418 	{
419 		public void beforeLoadTest(LoadTestRunner loadTestRunner, LoadTestRunContext context)
420 		{
421 			running = true;
422 			new Thread( updater, loadTestRunner.getLoadTest().getName() + " LoadTestStatistics Updater" ).start();
423 			new Thread( LoadTestStatistics.this ).start();
424 			
425 			currentThreadCountStartTime = System.currentTimeMillis();
426 			totalAverageSum = 0;
427 		}
428 
429 		@Override
430 		public void afterTestStep( LoadTestRunner loadTestRunner, LoadTestRunContext context, TestRunner testRunner, TestRunContext runContext, TestStepResult testStepResult )
431 		{
432 			super.afterTestStep( loadTestRunner, context, testRunner, runContext, testStepResult );
433 		}
434 
435 		public void afterTestCase(LoadTestRunner loadTestRunner, LoadTestRunContext context, TestRunner testRunner, TestRunContext runContext)
436 		{
437 			List<TestStepResult> results = testRunner.getResults();
438 			TestCase testCase = testRunner.getTestCase();
439 			
440 			long[] samples = new long[testCase.getTestStepCount()];
441 			long[] sizes = new long[samples.length];
442 			long[] sampleCounts = new long[samples.length];
443 			
444 			for( int c = 0; c < results.size(); c++ )
445 			{
446 				TestStepResult testStepResult = results.get( c );
447 				if( testStepResult == null )
448 				{
449 					log.warn( "Result [" + c + "] is null in TestCase [" + testCase.getName() + "]" );
450 					continue;
451 				}
452 				
453 				int index = testCase.getIndexOfTestStep( testStepResult.getTestStep() );
454 				sampleCounts[index]++;
455 				
456 				samples[index] += testStepResult.getTimeTaken();
457 				sizes[index] += testStepResult.getSize();
458 			}
459 			
460 			pushSamples( samples, sizes, sampleCounts, testRunner.getStartTime(), testRunner.getTimeTaken() );
461 		}
462 
463 		@Override
464 		public void afterLoadTest( LoadTestRunner loadTestRunner, LoadTestRunContext context )
465 		{
466 			stop();
467 		}
468 	}
469 
470 	public int getStepCount()
471 	{
472 		return loadTest.getTestCase().getTestStepCount();
473 	}
474 
475 	public void reset()
476 	{
477 		init();
478 		fireTableDataChanged();
479 	}
480 
481 	public void release()
482 	{
483 		reset();
484 		
485 		loadTest.removeLoadTestRunListener( testRunListener );
486 		loadTest.getTestCase().getTestSuite().removeTestSuiteListener( testSuiteListener );
487 		
488 		for( TestStep testStep : loadTest.getTestCase().getTestStepList() )
489 		{
490 			testStep.removePropertyChangeListener( propertyChangeListener );
491 		}
492 	}
493 	
494 	private class InternalTestSuiteListener extends TestSuiteListenerAdapter
495 	{
496 		public void testStepAdded(TestStep testStep, int index)
497 		{
498 			if( testStep.getTestCase() == loadTest.getTestCase() )
499 			{
500 				init();
501 				testStep.addPropertyChangeListener( TestStep.NAME_PROPERTY, propertyChangeListener );
502 				fireTableDataChanged();
503 				
504 				history.reset();
505 			}
506 		}
507 
508 		public void testStepRemoved(TestStep testStep, int index)
509 		{
510 			if( testStep.getTestCase() == loadTest.getTestCase() )
511 			{
512 				init();
513 				testStep.removePropertyChangeListener( propertyChangeListener );
514 				fireTableDataChanged();
515 
516 				history.reset();
517 			}
518 		}
519 	}
520 
521 	private class InternalPropertyChangeListener implements PropertyChangeListener
522 	{
523 		public void propertyChange(PropertyChangeEvent evt)
524 		{
525 			if( evt.getSource() == loadTest && evt.getPropertyName().equals( WsdlLoadTest.THREADCOUNT_PROPERTY ))
526 			{
527 				if( loadTest.getResetStatisticsOnThreadCountChange() )
528 				{
529 					resetStatistics = true;
530 					currentThreadCountStartTime = System.currentTimeMillis();
531 				}
532 			}
533 			else if( evt.getPropertyName().equals( TestStep.NAME_PROPERTY ) ||
534 						evt.getPropertyName().equals( TestStep.DISABLED_PROPERTY ))
535 			{
536 				if( evt.getSource() instanceof TestStep )
537 					fireTableCellUpdated( loadTest.getTestCase().getIndexOfTestStep( (TestStep)evt.getSource() ), 1 );
538 			}
539 			else if( evt.getPropertyName().equals( WsdlLoadTest.HISTORYLIMIT_PROPERTY ))
540 			{
541 				if( loadTest.getHistoryLimit() == 0 )
542 					history.reset();
543 			}
544 		}}
545 	
546 	public TestStep getTestStepAtRow(int selectedRow)
547 	{
548 		if( selectedRow < getRowCount()-1 )
549 			return loadTest.getTestCase().getTestStepAt( selectedRow );
550 		else
551 			return null;
552 	}
553 
554 	public long getUpdateFrequency()
555 	{
556 		return updateFrequency;
557 	}
558 
559 	public void setUpdateFrequency(long updateFrequency)
560 	{
561 		this.updateFrequency = updateFrequency;
562 	}
563 	
564 	public void addError( int stepIndex )
565 	{
566 		if( stepIndex != -1 )
567 		{
568 			data[stepIndex][ERR_COLUMN]++;
569 		}
570 		
571 		data[data.length-1][ERR_COLUMN]++;
572 		changed = true;
573 	}
574 
575 	public long[][] getSnapshot()
576 	{
577 		return data.clone();
578 	}
579 	
580 	private final static Map<Integer,Statistic> statisticIndexMap = new HashMap<Integer,Statistic>();
581 
582 	private Updater updater = new Updater();
583 
584 	public enum Statistic 
585 	{ 
586 		MININMUM( MIN_COLUMN, "min", "the minimum measured teststep time" ), 
587 		MAXIMUM( MAX_COLUMN, "max", "the maximum measured testste time" ), 
588 		AVERAGE( AVG_COLUMN, "avg", "the average measured teststep time"), 
589 		LAST( LAST_COLUMN, "last", "the last measured teststep time" ), 
590 		COUNT( CNT_COLUMN, "cnt", "the number of teststep samples measured" ), 
591 		TPS( TPS_COLUMN, "tps", "the number of transactions per second for this teststep" ), 
592 		BYTES( BYTES_COLUMN, "bytes", "the total number of bytes returned by this teststep" ), 
593 		BPS( BPS_COLUMN, "bps", "the number of bytes per second returned by this teststep"), 
594 		ERRORS( ERR_COLUMN, "err", "the total number of assertion errors for this teststep" );
595 		
596 		private final String description;
597 		private final String name;
598 		private final int index;
599 
600 		Statistic( int index, String name, String description )
601 		{
602 			this.index = index;
603 			this.name = name;
604 			this.description = description;
605 			
606 			statisticIndexMap.put( index, this );
607 		}
608 
609 		public String getDescription()
610 		{
611 			return description;
612 		}
613 
614 		public int getIndex()
615 		{
616 			return index;
617 		}
618 
619 		public String getName()
620 		{
621 			return name;
622 		}
623 
624 		public static Statistic forIndex(int column)
625 		{
626 			return statisticIndexMap.get( column );
627 		}
628 	}
629 	
630 	/***
631 	 * Holds all sample values for a testcase run
632 	 * @author ole.matzura
633 	 */
634 	
635 	private static final class SamplesHolder
636 	{
637 		private final long[] samples;
638 		private final long[] sizes;
639 		private final long[] sampleCounts;
640 		
641 		private long startTime;
642 		private long timeTaken;
643 
644 		public SamplesHolder(long[] samples, long[] sizes, long[] sampleCounts, long startTime, long timeTaken )
645 		{
646 			this.samples = samples;
647 			this.sizes = sizes;
648 			this.startTime = startTime;
649 			this.timeTaken = timeTaken;
650 			this.sampleCounts = sampleCounts;
651 		}
652 	}
653 
654 	public synchronized void finish()
655 	{
656 		int sz = samplesStack.size();
657 		while( sz > 0 )
658 		{
659 			log.info( "Waiting for " + sz + " samples.." );
660 			try
661 			{
662 				Thread.sleep( 500 );
663 			}
664 			catch (InterruptedException e)
665 			{
666 				SoapUI.logError( e );
667 			}
668 			 sz = samplesStack.size();
669 		}
670 	}
671 }