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