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