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.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
247 if( holder.startTime < currentThreadCountStartTime )
248 {
249 adding = false;
250 return;
251 }
252
253
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
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
317
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
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 }