View Javadoc

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