View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2007 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
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 		// discard "old" results
250 		if( holder.startTime < currentThreadCountStartTime )
251 		{
252 			adding = false;
253 			return;
254 		}
255 		
256 		//	 first check that this is not a
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][MIN_COLUMN] == 0) ) 
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 //					data[c][BPS_COLUMN] = (data[c][BYTES_COLUMN]*1000) / timePassed; 
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 //						long v = (data[c][TPS_COLUMN] * timePassed)/10000000;
320 //						data[c][BPS_COLUMN] = v == 0 ? 0 : data[c][BYTES_COLUMN] / v; 
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 			// check all these for catching threading issues
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 }