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