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