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 						int threadIndex = 0;
415 						
416 						try
417 						{
418 							threadIndex = Integer.parseInt( runContext.getProperty( "ThreadIndex" ).toString());
419 						}
420 						catch( Throwable t )
421 						{}
422 						
423 						loadTestLog.addEntry( new LoadTestLogErrorEntry( assertion.getName(), error, assertion.getIcon(), threadIndex ));
424 						statisticsModel.addError( LoadTestStatistics.TOTAL );
425 					}	
426 				}
427 			}				
428 		}
429 
430 		@Override
431       public void afterTestStep(LoadTestRunner loadTestRunner, LoadTestRunContext context, TestRunner testRunner, TestRunContext runContext, TestStepResult result)
432 		{
433 			if( !assertions.isEmpty() )
434 			{
435 				boolean added = false;
436 				
437 				for( LoadTestAssertion assertion : assertions )
438 				{
439 					String error = assertion.assertResult( loadTestRunner, context, result, testRunner, runContext );
440 					if( error != null )
441 					{
442 						int indexOfTestStep = testRunner.getTestCase().getIndexOfTestStep( result.getTestStep() );
443 						int threadIndex = 0;
444 						
445 						try
446 						{
447 							threadIndex = Integer.parseInt( runContext.getProperty( "ThreadIndex" ).toString());
448 						}
449 						catch( Throwable t )
450 						{}
451 						
452 						LoadTestLogErrorEntry errorEntry = new LoadTestLogErrorEntry( assertion.getName(), error, result, 
453 									assertion.getIcon(), threadIndex );
454 						
455 						loadTestLog.addEntry( errorEntry);
456 						statisticsModel.addError( indexOfTestStep );
457 						
458 						long maxAssertionErrors = getMaxAssertionErrors();
459 						if( maxAssertionErrors > 0 )
460 						{
461 							synchronized( assertionErrors )
462 							{
463 								assertionErrors.add( errorEntry );
464 								while( assertionErrors.size() > maxAssertionErrors )
465 								{
466 									assertionErrors.remove( 0 ).discard();
467 								}
468 							}
469 						}
470 						
471 						added = true;
472 					}	
473 				}
474 
475 				// always discard result if there were no errors
476 				if( !added )
477 				{
478 					result.discard();
479 				}
480 			}				
481 			else result.discard();
482 		}
483 	}
484 
485 	public LoadStrategy getLoadStrategy()
486 	{
487 		return loadStrategy;
488 	}
489 	
490 	public void setLoadStrategy( LoadStrategy loadStrategy )
491 	{
492 		this.loadStrategy.removeConfigurationChangeListener( loadStrategyListener );
493 		removeLoadTestRunListener( this.loadStrategy );
494 		
495 		this.loadStrategy = loadStrategy;
496 		this.loadStrategy.addConfigurationChangeListener( loadStrategyListener );
497 		addLoadTestRunListener( this.loadStrategy );
498 		
499 		getConfig().getLoadStrategy().setType( loadStrategy.getType() );
500 		getConfig().getLoadStrategy().setConfig( loadStrategy.getConfig() );
501 	}
502 
503 	private class LoadStrategyConfigurationChangeListener implements PropertyChangeListener
504 	{
505 		public void propertyChange(PropertyChangeEvent evt)
506 		{
507 			getConfig().getLoadStrategy().setConfig( loadStrategy.getConfig() );
508 		}
509 	}
510 
511 	public LoadTestAssertion addAssertion( String type, String targetStep, boolean showConfig )
512 	{
513 		LoadTestAssertion assertion = LoadTestAssertionRegistry.createAssertion( type, this );
514 		assertion.setTargetStep( targetStep );
515 		
516 		if( assertion instanceof Configurable && showConfig )
517 		{
518 			if( !((Configurable)assertion).configure() )
519 				return null;
520 		}
521 		
522 		assertions.add( assertion );
523 		
524 		getConfig().addNewAssertion().set( assertion.getConfiguration() );
525 		assertion.addPropertyChangeListener( LoadTestAssertion.CONFIGURATION_PROPERTY, configurationChangeListener );
526 		fireAssertionAdded( assertion );
527 		
528 		return assertion;
529 	}
530 	
531 	public void removeAssertion( LoadTestAssertion assertion)
532 	{
533 		int ix = assertions.indexOf( assertion );
534 		if( ix >= 0 )
535 		{
536 			try
537 			{
538 				assertions.remove( ix );
539 				fireAssertionRemoved(assertion);
540 			}
541 			finally
542 			{
543 				assertion.removePropertyChangeListener( configurationChangeListener );
544 				assertion.release();
545 				getConfig().removeAssertion( ix );
546 			}
547 		}
548 	}
549 
550 	private void fireAssertionRemoved(LoadTestAssertion assertion)
551 	{
552 		if( !loadTestListeners.isEmpty() )
553 		{
554 			LoadTestListener[] l = loadTestListeners.toArray( new LoadTestListener[loadTestListeners.size()] );
555 			for( LoadTestListener listener : l )
556 			{
557 				listener.assertionRemoved( assertion );
558 			}
559 		}
560 	}
561 	
562 	private void fireAssertionAdded(LoadTestAssertion assertion)
563 	{
564 		if( !loadTestListeners.isEmpty() )
565 		{
566 			LoadTestListener[] l = loadTestListeners.toArray( new LoadTestListener[loadTestListeners.size()] );
567 			for( LoadTestListener listener : l )
568 			{
569 				listener.assertionAdded( assertion );
570 			}
571 		}
572 	}
573 
574 	public int getAssertionCount()
575 	{
576 		return assertions.size();
577 	}
578 	
579 	public LoadTestAssertion getAssertionAt( int index )
580 	{
581 		return index < 0 || index >= assertions.size() ? null : assertions.get( index );
582 	}
583 	
584 	private class ConfigurationChangePropertyListener implements PropertyChangeListener
585 	{
586 		public void propertyChange(PropertyChangeEvent evt)
587 		{
588 			 int ix = assertions.indexOf( evt.getSource() );
589 			 if( ix >= 0 )
590 			 {
591 				 getConfig().getAssertionArray( ix ).set( assertions.get( ix ).getConfiguration() );
592 			 }
593 		}
594 	}
595 
596 	public LoadTestLog getLoadTestLog()
597 	{
598 		return loadTestLog;
599 	}
600 
601 	public List<LoadTestAssertion> getAssertionList()
602 	{
603 		return assertions;
604 	}
605 	
606 	public void addLoadTestListener( LoadTestListener listener )
607 	{
608 		loadTestListeners.add( listener );
609 	}
610 
611 	public void removeLoadTestListener( LoadTestListener listener )
612 	{
613 		loadTestListeners.remove( listener );
614 	}
615 
616 	public void addLoadTestRunListener(LoadTestRunListener listener)
617 	{
618 		loadTestRunListeners.add( listener );
619 	}
620 
621 	public void removeLoadTestRunListener(LoadTestRunListener listener)
622 	{
623 		loadTestRunListeners.remove( listener );
624 	}
625 	
626 	public LoadTestRunListener [] getLoadTestRunListeners()
627 	{
628 		return loadTestRunListeners.toArray( new LoadTestRunListener[loadTestRunListeners.size()] );
629 	}
630 
631 	/***
632 	 * Release internal objects so they can remove listeners
633 	 */
634 	
635 	@Override
636    public void release()
637 	{
638 		super.release();
639 		
640 		statisticsModel.release();
641 		loadTestLog.release();
642 		
643 		for( LoadTestAssertion assertion : assertions )
644 			assertion.release();
645 		
646 		loadTestRunListeners.clear();
647 		loadTestListeners.clear();
648 	}
649 
650 	public boolean isRunning()
651 	{
652 		return runner != null && runner.getStatus() == LoadTestRunner.Status.RUNNING;
653 	}
654 
655 	public WsdlLoadTestRunner getRunner()
656 	{
657 		return runner;
658 	}
659 
660 	public void resetConfigOnMove( LoadTestConfig config )
661 	{
662 		setConfig( config );
663 		
664 		loadStrategy.updateConfig( config.getLoadStrategy().getConfig() );
665 		
666 		List<LoadTestAssertionConfig> assertionList = config.getAssertionList();
667 		for( int c = 0; c < assertionList.size(); c++ )
668 		{
669 			assertions.get( c ).updateConfiguration( assertionList.get( c ) );
670 		}
671 	}
672 
673    @SuppressWarnings("unchecked")
674    public List<? extends ModelItem> getChildren()
675    {
676       return Collections.EMPTY_LIST;
677    }
678    
679    public class StatisticsLogger implements Runnable
680    {
681 		private boolean stopped;
682 		private List<PrintWriter> writers = new ArrayList<PrintWriter>();
683 		private long startTime;
684 
685 		public void run()
686 		{
687 			stopped = false;
688 			
689 			while( !stopped && getStatisticsLogInterval() > 0 )
690 			{
691 				try
692 				{
693 					long statisticsInterval = getStatisticsLogInterval();
694 					Thread.sleep( statisticsInterval );
695 					if( !stopped )
696 					{
697 						logStatistics( "Interval" );
698 					}
699 				}
700 				catch( InterruptedException e )
701 				{
702 					e.printStackTrace();
703 				}
704 			}
705 		}
706 		
707 		public void start()
708 		{
709 			new Thread( this, "Statistics Logger for LoadTest [" + getName() + "]" ).start();
710 		}
711 
712 		public void init(LoadTestRunContext context)
713 		{
714 			writers.clear();
715 			
716 			String statisticsLogFolder = context.expand( getStatisticsLogFolder() );
717 			if( StringUtils.isNullOrEmpty( statisticsLogFolder ))
718 				return;
719 			
720 			File folder = new File( statisticsLogFolder );
721 			if( !folder.exists() )
722 			{
723 				if( !folder.mkdirs())
724 				{
725 					SoapUI.logError( new Exception( "Failed to create statistics log folder [" + statisticsLogFolder + "]") );
726 					return;
727 				}
728 			}
729 			
730 			for( int c = 0; c < testCase.getTestStepCount(); c++ )
731 			{
732 				try
733 				{
734 					WsdlTestStep testStep = testCase.getTestStepAt( c );
735 					String fileName = StringUtils.createFileName( testStep.getName(), '_' ) + ".log";
736 					PrintWriter writer = new PrintWriter( new File( folder, fileName ));
737 					writers.add( writer );
738 					addHeaders( writer );
739 				}
740 				catch( FileNotFoundException e )
741 				{
742 					e.printStackTrace();
743 					writers.add( null );
744 				}
745 			}
746 			
747 			// and one writer for the testcase..
748 			try
749 			{
750 				String fileName = StringUtils.createFileName( testCase.getName(), '_' ) + ".log";
751 				writers.add( new PrintWriter( new File( folder, fileName )) );
752 			}
753 			catch( FileNotFoundException e )
754 			{
755 				e.printStackTrace();
756 			}
757 			
758 			startTime = System.nanoTime();
759 		}
760 
761 		private void addHeaders( PrintWriter writer )
762 		{
763 			writer.print( "date,threads,elapsed,min,max,avg,last,cnt,tps,bytes,bps,err,reason\n" );
764 		}
765 
766 		public void finish()
767 		{
768 			stopped = true;
769 			
770 			logStatistics( "Finished" );
771 			for( PrintWriter writer : writers )
772 			{
773 				if( writer != null )
774 					writer.close();
775 			}
776 		}
777 		
778 		private synchronized void logStatistics( String trigger )
779 		{
780 			if( writers.isEmpty() )
781 				return;
782 			
783 			long timestamp = System.nanoTime();
784 			String elapsedString = String.valueOf( (timestamp-startTime)/100000 );
785 			String dateString = new Date().toString();
786 			String threadCountString = String.valueOf( getThreadCount());
787 			
788 			StringList[] snapshot = statisticsModel.getSnapshot();
789 			for( int c = 0; c < snapshot.length; c++ )
790 			{
791 				PrintWriter writer = writers.get( c );
792 				if( writer == null )
793 					continue;
794 				
795 				StringList values = snapshot[c];
796 				writer.append( dateString ).append( ',' );
797 				writer.append( threadCountString).append( ',' );
798 				writer.append( elapsedString );
799 				
800 				for( String value : values )
801 				{
802 					writer.append( ',' ).append( value );
803 				}
804 				
805 				writer.append( ',' ).append( trigger ).append( '\n' );
806 				writer.flush();
807 			}
808 		}
809 	}
810 }