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