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;
14  
15  import java.beans.PropertyChangeEvent;
16  import java.beans.PropertyChangeListener;
17  import java.io.File;
18  import java.io.FileNotFoundException;
19  import java.io.PrintWriter;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.Date;
23  import java.util.HashSet;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Set;
27  
28  import org.apache.log4j.Logger;
29  
30  import com.eviware.soapui.SoapUI;
31  import com.eviware.soapui.config.LoadStrategyConfig;
32  import com.eviware.soapui.config.LoadTestAssertionConfig;
33  import com.eviware.soapui.config.LoadTestConfig;
34  import com.eviware.soapui.config.LoadTestLimitTypesConfig;
35  import com.eviware.soapui.config.LoadTestLimitTypesConfig.Enum;
36  import com.eviware.soapui.impl.wsdl.AbstractWsdlModelItem;
37  import com.eviware.soapui.impl.wsdl.loadtest.assertions.AbstractLoadTestAssertion;
38  import com.eviware.soapui.impl.wsdl.loadtest.assertions.LoadTestAssertionRegistry;
39  import com.eviware.soapui.impl.wsdl.loadtest.data.LoadTestStatistics;
40  import com.eviware.soapui.impl.wsdl.loadtest.log.LoadTestLog;
41  import com.eviware.soapui.impl.wsdl.loadtest.log.LoadTestLogErrorEntry;
42  import com.eviware.soapui.impl.wsdl.loadtest.strategy.BurstLoadStrategy;
43  import com.eviware.soapui.impl.wsdl.loadtest.strategy.LoadStrategy;
44  import com.eviware.soapui.impl.wsdl.loadtest.strategy.LoadStrategyFactory;
45  import com.eviware.soapui.impl.wsdl.loadtest.strategy.LoadStrategyRegistry;
46  import com.eviware.soapui.impl.wsdl.loadtest.strategy.SimpleLoadStrategy;
47  import com.eviware.soapui.impl.wsdl.support.Configurable;
48  import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
49  import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStep;
50  import com.eviware.soapui.model.ModelItem;
51  import com.eviware.soapui.model.support.LoadTestRunListenerAdapter;
52  import com.eviware.soapui.model.testsuite.LoadTest;
53  import com.eviware.soapui.model.testsuite.LoadTestRunContext;
54  import com.eviware.soapui.model.testsuite.LoadTestRunListener;
55  import com.eviware.soapui.model.testsuite.LoadTestRunner;
56  import com.eviware.soapui.model.testsuite.TestRunContext;
57  import com.eviware.soapui.model.testsuite.TestRunner;
58  import com.eviware.soapui.model.testsuite.TestStepResult;
59  import com.eviware.soapui.model.testsuite.LoadTestRunner.Status;
60  import com.eviware.soapui.support.StringUtils;
61  import com.eviware.soapui.support.types.StringList;
62  
63  /***
64   * TestCase implementation for LoadTests
65   *  
66   * @todo add assertionFailed event to LoadTestListener
67   * @todo create and return LoadTestAssertionResult from load-test assertions 
68   *  
69   * @author Ole.Matzura
70   */
71  
72  public class WsdlLoadTest extends AbstractWsdlModelItem<LoadTestConfig> implements LoadTest
73  {
74  	public final static String THREADCOUNT_PROPERTY = WsdlLoadTest.class.getName() + "@threadcount";
75  	public final static String STARTDELAY_PROPERTY = WsdlLoadTest.class.getName() + "@startdelay";
76  	public final static String TESTLIMIT_PROPERTY = WsdlLoadTest.class.getName() + "@testlimit";
77  	public final static String HISTORYLIMIT_PROPERTY = WsdlLoadTest.class.getName() + "@historylimit";
78  	public final static String LIMITTYPE_PROPERRY = WsdlLoadTest.class.getName() + "@limittype";
79  	public final static String SAMPLEINTERVAL_PROPERRY = WsdlLoadTest.class.getName() + "@sample-interval";
80  	public static final String MAXASSERTIONERRORS_PROPERTY = WsdlLoadTest.class.getName() + "@max-assertion-errors";
81  
82     private final static Logger logger = Logger.getLogger( WsdlLoadTest.class );
83  	
84     private InternalTestRunListener internalTestRunListener = new InternalTestRunListener();
85     
86  	private WsdlTestCase testCase;
87  	private LoadTestStatistics statisticsModel;
88  	private LoadStrategy loadStrategy = new BurstLoadStrategy();
89  	private LoadTestLog loadTestLog;
90  	
91  	private LoadStrategyConfigurationChangeListener loadStrategyListener = new LoadStrategyConfigurationChangeListener();
92  	private List<LoadTestAssertion> assertions = new ArrayList<LoadTestAssertion>();
93  	private ConfigurationChangePropertyListener configurationChangeListener = new ConfigurationChangePropertyListener();
94  	private Set<LoadTestListener> loadTestListeners = new HashSet<LoadTestListener>();
95  	private Set<LoadTestRunListener> loadTestRunListeners = new HashSet<LoadTestRunListener>();
96  	private List<LoadTestLogErrorEntry> assertionErrors = new LinkedList<LoadTestLogErrorEntry>();
97  	private WsdlLoadTestRunner runner;
98  	private StatisticsLogger statisticsLogger = new StatisticsLogger();
99     
100    public WsdlLoadTest(WsdlTestCase testCase, LoadTestConfig config )
101    {
102    	super( config, testCase, "/loadTest.gif" );
103    	
104       this.testCase = testCase;
105 		
106 		if( getConfig().getThreadCount() < 1 )
107 			getConfig().setThreadCount( 5 );
108 		
109 		if( getConfig().getLimitType() == null )
110 		{
111 			getConfig().setLimitType( LoadTestLimitTypesConfig.TIME );
112 			getConfig().setTestLimit( 60 );
113 		}	
114 
115 		if( !getConfig().isSetHistoryLimit() )
116 		{
117 			getConfig().setHistoryLimit( -1 );
118 		}
119 		
120       addLoadTestRunListener( internalTestRunListener );
121       
122       LoadStrategyConfig ls = getConfig().getLoadStrategy();
123       if( ls == null )
124       {
125       	ls = getConfig().addNewLoadStrategy();
126       	ls.setType( SimpleLoadStrategy.STRATEGY_TYPE );
127       }
128       
129       LoadStrategyFactory factory = LoadStrategyRegistry.getInstance().getFactory( ls.getType() );
130       if( factory == null )
131       {
132       	ls.setType( SimpleLoadStrategy.STRATEGY_TYPE );
133       	factory = LoadStrategyRegistry.getInstance().getFactory( ls.getType() );
134       }
135       
136       loadStrategy = factory.build( ls.getConfig() );
137       loadStrategy.addConfigurationChangeListener( loadStrategyListener );
138       
139       addLoadTestRunListener( loadStrategy );
140       
141       statisticsModel = new LoadTestStatistics( this );
142       
143       if( getConfig().xgetSampleInterval() == null )
144       	setSampleInterval( LoadTestStatistics.DEFAULT_SAMPLE_INTERVAL );
145       
146       statisticsModel.setUpdateFrequency( getSampleInterval() );
147       
148       List<LoadTestAssertionConfig> assertionList = getConfig().getAssertionList();
149       for( LoadTestAssertionConfig assertionConfig : assertionList )
150       {
151       	AbstractLoadTestAssertion assertion = LoadTestAssertionRegistry.buildAssertion( assertionConfig, this );
152       	if( assertion != null )
153       	{
154       		assertions.add( assertion);
155       		assertion.addPropertyChangeListener( LoadTestAssertion.CONFIGURATION_PROPERTY, configurationChangeListener );
156       	}
157       	else
158       	{
159       		logger.warn( "Failed to build LoadTestAssertion from getConfig() [" + assertionConfig + "]" );
160       	}
161       }
162       
163       if( getConfig().xgetResetStatisticsOnThreadCountChange() == null )
164       	getConfig().setResetStatisticsOnThreadCountChange( true );
165 
166       if( getConfig().xgetCalculateTPSOnTimePassed() == null )
167       	getConfig().setCalculateTPSOnTimePassed( false );
168       
169       if( !getConfig().isSetMaxAssertionErrors())
170       	getConfig().setMaxAssertionErrors( 100 );
171       
172       loadTestLog = new LoadTestLog( this );
173       
174       for (LoadTestRunListener listener : SoapUI.getListenerRegistry().getListeners( LoadTestRunListener.class ))
175       {
176           addLoadTestRunListener(listener);
177       }
178    }
179 
180    public LoadTestStatistics getStatisticsModel()
181    {
182    	return statisticsModel;
183    }
184    
185    
186    public StatisticsLogger getStatisticsLogger()
187 	{
188 		return statisticsLogger;
189 	}
190 
191 	public long getThreadCount()
192    {
193    	return getConfig().getThreadCount();
194    }
195    
196    public void setThreadCount( long threadCount )
197    {
198    	long oldCount = getThreadCount();
199    	if( threadCount < 1 || threadCount == oldCount )
200    		return;
201    	
202    	if( getLogStatisticsOnThreadChange() && isRunning() )
203    		statisticsLogger.logStatistics( "ThreadCount change from " + oldCount + " to " + threadCount );
204    	
205    	getConfig().setThreadCount( (int) threadCount );
206    	notifyPropertyChanged( THREADCOUNT_PROPERTY, oldCount, threadCount );
207    }
208    
209    public boolean getResetStatisticsOnThreadCountChange()
210    {
211    	return getConfig().getResetStatisticsOnThreadCountChange();
212    }
213    
214    public void setResetStatisticsOnThreadCountChange( boolean value )
215    {
216    	getConfig().setResetStatisticsOnThreadCountChange( value );
217    }   
218    
219    public boolean getCancelOnReachedLimit()
220    {
221    	return getConfig().getCancelOnReachedLimit();
222    }
223    
224    public void setCancelOnReachedLimit( boolean value )
225    {
226    	getConfig().setCancelOnReachedLimit( value );
227    }   
228    
229    public boolean getLogStatisticsOnThreadChange()
230    {
231    	return getConfig().getLogStatisticsOnThreadChange();
232    }
233    
234    public void setLogStatisticsOnThreadChange( boolean value )
235    {
236    	getConfig().setLogStatisticsOnThreadChange( value );
237    }   
238    
239    public String getStatisticsLogFolder()
240    {
241    	return getConfig().getStatisticsLogFolder();
242    }
243    
244    public void setStatisticsLogFolder( String value )
245    {
246    	getConfig().setStatisticsLogFolder( value );
247    }   
248 
249    public boolean getCalculateTPSOnTimePassed()
250    {
251    	return getConfig().getCalculateTPSOnTimePassed();
252    }
253    
254    public void setCalculateTPSOnTimePassed( boolean value )
255    {
256    	getConfig().setCalculateTPSOnTimePassed( value );
257    }   
258 
259    public int getStartDelay()
260    {
261    	return getConfig().getStartDelay();
262    }
263    
264    public void setStartDelay( int startDelay )
265    {
266    	if( startDelay < 0 )
267    		return;
268    	
269    	int oldDelay = getStartDelay();
270    	getConfig().setStartDelay( startDelay );
271    	notifyPropertyChanged( STARTDELAY_PROPERTY, oldDelay, startDelay );
272    }
273 
274    public long getHistoryLimit()
275    {
276    	return getConfig().getHistoryLimit();
277    }
278    
279    public void setHistoryLimit( long historyLimit )
280    {
281    	long oldLimit = getHistoryLimit();
282    	getConfig().setHistoryLimit( historyLimit );
283    	if( historyLimit == 0 )
284    	
285    	
286    	notifyPropertyChanged( HISTORYLIMIT_PROPERTY, oldLimit, historyLimit );
287    }
288    
289    public long getTestLimit()
290    {
291    	return getConfig().getTestLimit();
292    }
293    
294    public void setTestLimit( long testLimit )
295    {
296    	if( testLimit < 0 )
297    		return;
298    	
299    	long oldLimit = getTestLimit();
300    	getConfig().setTestLimit( testLimit );
301    	notifyPropertyChanged( TESTLIMIT_PROPERTY, oldLimit, testLimit );
302    }
303    
304    public long getMaxAssertionErrors()
305    {
306    	return getConfig().getMaxAssertionErrors();
307    }
308    
309    public void setMaxAssertionErrors( long testLimit )
310    {
311    	if( testLimit < 0 )
312    		return;
313    	
314    	long oldLimit = getMaxAssertionErrors();
315    	getConfig().setMaxAssertionErrors( testLimit );
316    	notifyPropertyChanged( MAXASSERTIONERRORS_PROPERTY, oldLimit, testLimit );
317    }
318    
319    public long getStatisticsLogInterval()
320    {
321    	return getConfig().getStatisticsLogInterval();
322    }
323    
324    public void setStatisticsLogInterval( int sampleInterval )
325    {
326    	if( sampleInterval < 0 )
327    		return;
328    	
329    	long oldInterval = getStatisticsLogInterval();
330    	getConfig().setStatisticsLogInterval( sampleInterval );
331    	
332    	notifyPropertyChanged( SAMPLEINTERVAL_PROPERRY, oldInterval, sampleInterval );
333    	
334    	if( oldInterval == 0 && sampleInterval > 0 && isRunning() )
335    		statisticsLogger.start();
336    }
337    
338    public long getSampleInterval()
339    {
340    	return getConfig().getSampleInterval();
341    }
342    
343    public void setSampleInterval( int sampleInterval )
344    {
345    	if( sampleInterval < 0 )
346    		return;
347    	
348    	long oldInterval = getSampleInterval();
349    	getConfig().setSampleInterval( sampleInterval );
350    	
351    	statisticsModel.setUpdateFrequency( sampleInterval );
352    	notifyPropertyChanged( SAMPLEINTERVAL_PROPERRY, oldInterval, sampleInterval );
353    }
354 
355    public Enum getLimitType()
356    {
357    	return getConfig().getLimitType();
358    }
359    
360    public void setLimitType( Enum limitType )
361    {
362    	if( limitType == null )
363    		return;
364    	
365    	Enum oldType = getLimitType();
366    	getConfig().setLimitType( limitType );
367    	notifyPropertyChanged( LIMITTYPE_PROPERRY, oldType, limitType );
368    }
369    
370    public WsdlTestCase getTestCase()
371 	{
372 		return testCase;
373 	}
374 
375    public synchronized WsdlLoadTestRunner run() 
376    {
377    	getStatisticsModel().reset();
378    	if( runner != null && runner.getStatus() == Status.RUNNING )
379    		return null;
380    	
381    	assertionErrors.clear();
382    	runner = new WsdlLoadTestRunner( this );
383    	runner.start();
384    	return runner;
385 	}
386 
387 	private class InternalTestRunListener extends LoadTestRunListenerAdapter
388 	{
389 		@Override
390 		public void afterLoadTest( LoadTestRunner loadTestRunner, LoadTestRunContext context )
391 		{
392 			statisticsLogger.finish();
393 		}
394 
395 		@Override
396 		public void beforeLoadTest( LoadTestRunner loadTestRunner, LoadTestRunContext context )
397 		{
398 			statisticsLogger.init( context );
399 			
400 			if( getStatisticsLogInterval() > 0 )
401 				statisticsLogger.start();
402 		}
403 
404 		@Override
405       public void afterTestCase(LoadTestRunner loadTestRunner, LoadTestRunContext context, TestRunner testRunner, TestRunContext runContext)
406 		{
407 			if( !assertions.isEmpty() )
408 			{
409 				for( LoadTestAssertion assertion : assertions )
410 				{
411 					String error = assertion.assertResults( loadTestRunner, context, testRunner, runContext );
412 					if( error != null )
413 					{
414 						loadTestLog.addEntry( new LoadTestLogErrorEntry( assertion.getName(), error, assertion.getIcon() ));
415 						statisticsModel.addError( LoadTestStatistics.TOTAL );
416 					}	
417 				}
418 			}				
419 		}
420 
421 		@Override
422       public void afterTestStep(LoadTestRunner loadTestRunner, LoadTestRunContext context, TestRunner testRunner, TestRunContext runContext, TestStepResult result)
423 		{
424 			if( !assertions.isEmpty() )
425 			{
426 				boolean added = false;
427 				
428 				for( LoadTestAssertion assertion : assertions )
429 				{
430 					String error = assertion.assertResult( loadTestRunner, context, result, testRunner, runContext );
431 					if( error != null )
432 					{
433 						int indexOfTestStep = testRunner.getTestCase().getIndexOfTestStep( result.getTestStep() );
434 						
435 						LoadTestLogErrorEntry errorEntry = new LoadTestLogErrorEntry( assertion.getName(), error, result, assertion.getIcon() );
436 						loadTestLog.addEntry( errorEntry);
437 						statisticsModel.addError( indexOfTestStep );
438 						
439 						long maxAssertionErrors = getMaxAssertionErrors();
440 						if( maxAssertionErrors > 0 )
441 						{
442 							synchronized( assertionErrors )
443 							{
444 								assertionErrors.add( errorEntry );
445 								while( assertionErrors.size() > maxAssertionErrors )
446 								{
447 									assertionErrors.remove( 0 ).discard();
448 								}
449 							}
450 						}
451 						
452 						added = true;
453 					}	
454 				}
455 
456 				// always discard result if there were no errors
457 				if( !added )
458 				{
459 					result.discard();
460 				}
461 			}				
462 			else result.discard();
463 		}
464 	}
465 
466 	public LoadStrategy getLoadStrategy()
467 	{
468 		return loadStrategy;
469 	}
470 	
471 	public void setLoadStrategy( LoadStrategy loadStrategy )
472 	{
473 		this.loadStrategy.removeConfigurationChangeListener( loadStrategyListener );
474 		removeLoadTestRunListener( this.loadStrategy );
475 		
476 		this.loadStrategy = loadStrategy;
477 		this.loadStrategy.addConfigurationChangeListener( loadStrategyListener );
478 		addLoadTestRunListener( this.loadStrategy );
479 		
480 		getConfig().getLoadStrategy().setType( loadStrategy.getType() );
481 		getConfig().getLoadStrategy().setConfig( loadStrategy.getConfig() );
482 	}
483 
484 	private class LoadStrategyConfigurationChangeListener implements PropertyChangeListener
485 	{
486 		public void propertyChange(PropertyChangeEvent evt)
487 		{
488 			getConfig().getLoadStrategy().setConfig( loadStrategy.getConfig() );
489 		}
490 	}
491 
492 	public LoadTestAssertion addAssertion( String type, String targetStep, boolean showConfig )
493 	{
494 		LoadTestAssertion assertion = LoadTestAssertionRegistry.createAssertion( type, this );
495 		assertion.setTargetStep( targetStep );
496 		
497 		if( assertion instanceof Configurable && showConfig )
498 		{
499 			if( !((Configurable)assertion).configure() )
500 				return null;
501 		}
502 		
503 		assertions.add( assertion );
504 		
505 		getConfig().addNewAssertion().set( assertion.getConfiguration() );
506 		assertion.addPropertyChangeListener( LoadTestAssertion.CONFIGURATION_PROPERTY, configurationChangeListener );
507 		fireAssertionAdded( assertion );
508 		
509 		return assertion;
510 	}
511 	
512 	public void removeAssertion( LoadTestAssertion assertion)
513 	{
514 		int ix = assertions.indexOf( assertion );
515 		if( ix >= 0 )
516 		{
517 			try
518 			{
519 				assertions.remove( ix );
520 				fireAssertionRemoved(assertion);
521 			}
522 			finally
523 			{
524 				assertion.removePropertyChangeListener( configurationChangeListener );
525 				assertion.release();
526 				getConfig().removeAssertion( ix );
527 			}
528 		}
529 	}
530 
531 	private void fireAssertionRemoved(LoadTestAssertion assertion)
532 	{
533 		if( !loadTestListeners.isEmpty() )
534 		{
535 			LoadTestListener[] l = loadTestListeners.toArray( new LoadTestListener[loadTestListeners.size()] );
536 			for( LoadTestListener listener : l )
537 			{
538 				listener.assertionRemoved( assertion );
539 			}
540 		}
541 	}
542 	
543 	private void fireAssertionAdded(LoadTestAssertion assertion)
544 	{
545 		if( !loadTestListeners.isEmpty() )
546 		{
547 			LoadTestListener[] l = loadTestListeners.toArray( new LoadTestListener[loadTestListeners.size()] );
548 			for( LoadTestListener listener : l )
549 			{
550 				listener.assertionAdded( assertion );
551 			}
552 		}
553 	}
554 
555 	public int getAssertionCount()
556 	{
557 		return assertions.size();
558 	}
559 	
560 	public LoadTestAssertion getAssertionAt( int index )
561 	{
562 		return index < 0 || index >= assertions.size() ? null : assertions.get( index );
563 	}
564 	
565 	private class ConfigurationChangePropertyListener implements PropertyChangeListener
566 	{
567 		public void propertyChange(PropertyChangeEvent evt)
568 		{
569 			 int ix = assertions.indexOf( evt.getSource() );
570 			 if( ix >= 0 )
571 			 {
572 				 getConfig().getAssertionArray( ix ).set( assertions.get( ix ).getConfiguration() );
573 			 }
574 		}
575 	}
576 
577 	public LoadTestLog getLoadTestLog()
578 	{
579 		return loadTestLog;
580 	}
581 
582 	public List<LoadTestAssertion> getAssertionList()
583 	{
584 		return assertions;
585 	}
586 	
587 	public void addLoadTestListener( LoadTestListener listener )
588 	{
589 		loadTestListeners.add( listener );
590 	}
591 
592 	public void removeLoadTestListener( LoadTestListener listener )
593 	{
594 		loadTestListeners.remove( listener );
595 	}
596 
597 	public void addLoadTestRunListener(LoadTestRunListener listener)
598 	{
599 		loadTestRunListeners.add( listener );
600 	}
601 
602 	public void removeLoadTestRunListener(LoadTestRunListener listener)
603 	{
604 		loadTestRunListeners.remove( listener );
605 	}
606 	
607 	public LoadTestRunListener [] getLoadTestRunListeners()
608 	{
609 		return loadTestRunListeners.toArray( new LoadTestRunListener[loadTestRunListeners.size()] );
610 	}
611 
612 	/***
613 	 * Release internal objects so they can remove listeners
614 	 */
615 	
616 	@Override
617    public void release()
618 	{
619 		super.release();
620 		
621 		statisticsModel.release();
622 		loadTestLog.release();
623 		
624 		for( LoadTestAssertion assertion : assertions )
625 			assertion.release();
626 		
627 		loadTestRunListeners.clear();
628 		loadTestListeners.clear();
629 	}
630 
631 	public boolean isRunning()
632 	{
633 		return runner != null && runner.getStatus() == LoadTestRunner.Status.RUNNING;
634 	}
635 
636 	public WsdlLoadTestRunner getRunner()
637 	{
638 		return runner;
639 	}
640 
641 	public void resetConfigOnMove( LoadTestConfig config )
642 	{
643 		setConfig( config );
644 		
645 		loadStrategy.updateConfig( config.getLoadStrategy().getConfig() );
646 		
647 		List<LoadTestAssertionConfig> assertionList = config.getAssertionList();
648 		for( int c = 0; c < assertionList.size(); c++ )
649 		{
650 			assertions.get( c ).updateConfiguration( assertionList.get( c ) );
651 		}
652 	}
653 
654    @SuppressWarnings("unchecked")
655    public List<? extends ModelItem> getChildren()
656    {
657       return Collections.EMPTY_LIST;
658    }
659    
660    public class StatisticsLogger implements Runnable
661    {
662 		private boolean stopped;
663 		private List<PrintWriter> writers = new ArrayList<PrintWriter>();
664 		private long startTime;
665 
666 		public void run()
667 		{
668 			stopped = false;
669 			
670 			while( !stopped && getStatisticsLogInterval() > 0 )
671 			{
672 				try
673 				{
674 					long statisticsInterval = getStatisticsLogInterval();
675 					Thread.sleep( statisticsInterval );
676 					if( !stopped )
677 					{
678 						logStatistics( "Interval" );
679 					}
680 				}
681 				catch( InterruptedException e )
682 				{
683 					e.printStackTrace();
684 				}
685 			}
686 		}
687 		
688 		public void start()
689 		{
690 			new Thread( this, "Statistics Logger for LoadTest [" + getName() + "]" ).start();
691 		}
692 
693 		public void init(LoadTestRunContext context)
694 		{
695 			writers.clear();
696 			
697 			String statisticsLogFolder = context.expand( getStatisticsLogFolder() );
698 			if( StringUtils.isNullOrEmpty( statisticsLogFolder ))
699 				return;
700 			
701 			File folder = new File( statisticsLogFolder );
702 			if( !folder.exists() )
703 			{
704 				if( !folder.mkdirs())
705 				{
706 					SoapUI.logError( new Exception( "Failed to create statistics log folder [" + statisticsLogFolder + "]") );
707 					return;
708 				}
709 			}
710 			
711 			for( int c = 0; c < testCase.getTestStepCount(); c++ )
712 			{
713 				try
714 				{
715 					WsdlTestStep testStep = testCase.getTestStepAt( c );
716 					String fileName = StringUtils.createFileName( testStep.getName(), '_' ) + ".log";
717 					PrintWriter writer = new PrintWriter( new File( folder, fileName ));
718 					writers.add( writer );
719 					addHeaders( writer );
720 				}
721 				catch( FileNotFoundException e )
722 				{
723 					e.printStackTrace();
724 					writers.add( null );
725 				}
726 			}
727 			
728 			// and one writer for the testcase..
729 			try
730 			{
731 				String fileName = StringUtils.createFileName( testCase.getName(), '_' ) + ".log";
732 				writers.add( new PrintWriter( new File( folder, fileName )) );
733 			}
734 			catch( FileNotFoundException e )
735 			{
736 				e.printStackTrace();
737 			}
738 			
739 			startTime = System.nanoTime();
740 		}
741 
742 		private void addHeaders( PrintWriter writer )
743 		{
744 			writer.print( "date,threads,elapsed,min,max,avg,last,cnt,tps,bytes,bps,err,reason\n" );
745 		}
746 
747 		public void finish()
748 		{
749 			stopped = true;
750 			
751 			logStatistics( "Finished" );
752 			for( PrintWriter writer : writers )
753 			{
754 				if( writer != null )
755 					writer.close();
756 			}
757 		}
758 		
759 		private synchronized void logStatistics( String trigger )
760 		{
761 			if( writers.isEmpty() )
762 				return;
763 			
764 			long timestamp = System.nanoTime();
765 			String elapsedString = String.valueOf( (timestamp-startTime)/100000 );
766 			String dateString = new Date().toString();
767 			String threadCountString = String.valueOf( getThreadCount());
768 			
769 			StringList[] snapshot = statisticsModel.getSnapshot();
770 			for( int c = 0; c < snapshot.length; c++ )
771 			{
772 				PrintWriter writer = writers.get( c );
773 				if( writer == null )
774 					continue;
775 				
776 				StringList values = snapshot[c];
777 				writer.append( dateString ).append( ',' );
778 				writer.append( threadCountString).append( ',' );
779 				writer.append( elapsedString );
780 				
781 				for( String value : values )
782 				{
783 					writer.append( ',' ).append( value );
784 				}
785 				
786 				writer.append( ',' ).append( trigger ).append( '\n' );
787 				writer.flush();
788 			}
789 		}
790 	}
791 }