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