context: #region LGPL License
  /*
  Axiom Graphics Engine Library
  Copyright (C) 2003-2006  Axiom Project Team
  
  The overall design, and a majority of the core engine and rendering code 
  contained within this library is a derivative of the open source Object Oriented 
  Graphics Engine OGRE, which can be found at http://ogre.sourceforge.net.  
  Many thanks to the OGRE team for maintaining such a high quality project.
  
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.
  
  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  */
  #endregion
  
  #region SVN Version Information
  // <file>
  //     <copyright see="prj:///doc/copyright.txt"/>
  //     <license see="prj:///doc/license.txt"/>
  //     <id value="$Id$"/>
  // </file>
  #endregion SVN Version Information
  
  #region Namespace Declarations
  
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.IO;
  using System.Text;
  
  #endregion Namespace Declarations
  			
  namespace Axiom.Core
  {
  
  	/// <summary>
  	///   The MeterManager creates and hands out TimingMeter
  	///   instances.  Those instances are looked up by meter "title", a
  	///   string name for the meter.  Meter instances also have a string
  	///   "category", so you can turn metering on and off by category.
  	///   All public methods of MeterManager are static, so the user
  	///   doesn't have to worry about managing the instance of
  	///   MeterManager.  
  	///
  	///   The workflow is that the user program creates several meters by
  	///   calling the static MakeMeter method, passing the title and
  	///   category of the meter.  That method looks up the meter by title,
  	///   creating it if it doesn't already exists, and returns the meter.
  	///   Thereafter, the user invokes the TimingMeter.Enter() and
  	///   TimingMeter.Exit() methods, each of which causes the
  	///   MeterManager to add a record to a collection of entries and
  	///   exits.  The record has the identity of the meter; whether it's
  	///   an entry or exit, and the time in processor ticks, captured
  	///   using the assembler primitive RDTSC.  At any point, the program
  	///   can call the method MeterManager.Report, which produces a report
  	///   based on the trace.  
  	///
  	/// </summary>
  	public class MeterManager
  	{
  
  		#region Protected MeterManager members
  
  		internal const int ekEnter = 1;
  		internal const int ekExit = 2;
  		internal const int ekInfo = 3;
  
  		protected static MeterManager instance = null;
  		// Are we collecting now?
  		protected bool collecting;
  		// An id counter for timers
  		protected short timerIdCounter;
  		// The time when the meter manager was started
  		protected long startTime;
  		// The number of microseconds per tick; obviously a fraction
  		float microsecondsPerTick;
  		// The list of timing meter events
  		internal List<MeterEvent> eventTrace;
  		// Look up meters by title&category
  		internal Dictionary<string, TimingMeter> metersByName;
  		// Look up meters by id
  		protected Dictionary<int, TimingMeter> metersById;
  
  		// DEBUG
  		private static List<MeterStackEntry> debugMeterStack = new List<MeterStackEntry>();
  
  		public static string MeterLogFilename = "MeterLog.txt";
  		public static string MeterEventsFilename = "MeterEvents.txt";
  
  		private static void DebugAddEvent( TimingMeter meter, MeterEvent evt )
  		{
  			if ( evt.eventKind == ekEnter )
  			{
  				debugMeterStack.Add( new MeterStackEntry( meter, 0 ) );
  			}
  			else if ( evt.eventKind == ekExit )
  			{
  				Debug.Assert( debugMeterStack.Count > 0, "Meter stack is empty during ekExit" );
  				MeterStackEntry s = debugMeterStack[ debugMeterStack.Count - 1 ];
  				Debug.Assert( s.meter == meter, "Entered " + s.meter.title + "; Exiting " + meter.title );
  				debugMeterStack.RemoveAt( debugMeterStack.Count - 1 );
  			}
  			else if ( evt.eventKind == ekInfo )
  			{
  				// just ignore these
  			}
  			else
  			{
  				Debug.Assert( false );
  			}
  		}
  
  		protected static long CaptureCurrentTime()
  		{
  			return Stopwatch.GetTimestamp();
  		}
  
  		protected string OptionValue( string name, Dictionary<string, string> options )
  		{
  			string value;
  			if ( options.TryGetValue( name, out value ) )
  				return value;
  			else
  				return "";
  		}
  
  		protected bool BoolOption( string name, Dictionary<string, string> options )
  		{
  			string value = OptionValue( name, options );
  			return ( value != "" && value != "false" );
  		}
  
  		protected int IntOption( string name, Dictionary<string, string> options )
  		{
  			string value = OptionValue( name, options );
  			return ( value == "" ? 0 : int.Parse( value ) );
  		}
  
  		protected static void BarfOnBadChars( string name, string nameDescription )
  		{
  			if ( name.IndexOf( "\n" ) >= 0 )
  				throw new Exception( string.Format( "Carriage returns are not allowed in {0}", nameDescription ) );
  			else if ( name.IndexOf( "," ) >= 0 )
  				throw new Exception( string.Format( "Commas are not allowed in {0}", nameDescription ) );
  		}
  
  		protected MeterManager()
  		{
  			timerIdCounter = 1;
  			eventTrace = new List<MeterEvent>();
  			metersByName = new Dictionary<string, TimingMeter>();
  			metersById = new Dictionary<int, TimingMeter>();
  			startTime = CaptureCurrentTime();
  			microsecondsPerTick = 1000000.0f / (float)Stopwatch.Frequency;
  			instance = this;
  		}
  
  		protected TimingMeter GetMeterById( int id )
  		{
  			TimingMeter meter;
  			metersById.TryGetValue( id, out meter );
  			Debug.Assert( meter != null, string.Format( "Meter for id {0} is not in the index", id ) );
  			return meter;
  		}
  
  		protected void SaveToFileInternal( string pathname )
  		{
  			FileStream f = new FileStream( pathname, FileMode.Create, FileAccess.Write );
  			StreamWriter writer = new StreamWriter( f );
  			writer.Write( string.Format( "MeterCount={0}\n", metersById.Count ) );
  			foreach ( KeyValuePair<int, TimingMeter> pair in instance.metersById )
  			{
  				TimingMeter meter = pair.Value;
  				writer.Write( string.Format( "{0},{1},{2}\n", meter.title, meter.category, meter.meterId ) );
  			}
  		}
  
  		protected string IndentCount( int count )
  		{
  			if ( count > 20 )
  				count = 20;
  			string s = "|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-";
  			return s.Substring( 0, 2 * count );
  		}
  
  		protected long ToMicroseconds( long ticks )
  		{
  			return (long)( ( (float)ticks ) * microsecondsPerTick );
  		}
  
  		protected void DumpEventLog()
  		{
  			if ( File.Exists( MeterEventsFilename ) )
  				File.Delete( MeterEventsFilename );
  			FileStream f = new FileStream( MeterEventsFilename, FileMode.Create, FileAccess.Write );
  			StreamWriter writer = new StreamWriter( f );
  			writer.Write( string.Format( "Dumping meter event log on {0} at {1}; units are usecs\r\n",
  									   DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString() ) );
  			int indent = 0;
  			List<MeterStackEntry> meterStack = new List<MeterStackEntry>();
  			long firstEventTime = 0;
  			for ( int i = 0; i < eventTrace.Count; i++ )
  			{
  				short kind = eventTrace[ i ].eventKind;
  				long t = eventTrace[ i ].eventTime;
  				if ( i == 0 )
  					firstEventTime = t;
  				if ( kind == ekInfo )
  				{
  					writer.WriteLine( string.Format( "{0,12:D} {1}{2} {3}{4}",
  												   ToMicroseconds( t - firstEventTime ),
  												   IndentCount( indent ), "Info ", " ",
  												   eventTrace[ i ].info ) );
  					continue;
  				}
  				TimingMeter meter = GetMeterById( eventTrace[ i ].meterId );
  				if ( kind == ekEnter )
  				{
  					indent++;
  					writer.WriteLine( string.Format( "{0,12:D} {1}{2} {3}.{4}",
  												   ToMicroseconds( t - firstEventTime ),
  												   IndentCount( indent ),
  												   "Enter",
  												   meter.category, meter.title ) );
  					meterStack.Add( new MeterStackEntry( meter, t ) );
  				}
  				else if ( kind == ekExit )
  				{
  					Debug.Assert( meterStack.Count > 0, "Meter stack is empty during ekExit" );
  					MeterStackEntry s = meterStack[ meterStack.Count - 1 ];
  					Debug.Assert( s.meter == meter, "Entered " + s.meter.title + "; Exiting " + meter.title );
  					writer.WriteLine( string.Format( "{0,12:D} {1}{2} {3}.{4}",
  												   ToMicroseconds( t - s.eventTime ),
  												   IndentCount( indent ),
  												   "Exit ",
  												   meter.category, meter.title ) );
  					indent--;
  					meterStack.RemoveAt( meterStack.Count - 1 );
  				}
  			}
  			writer.Close();
  		}
  
  		protected static bool dumpEventLog = true;
  
  		protected void GenerateReport( StreamWriter writer, int start, Dictionary<string, string> options )
  		{
  			// For now, ignore options and just print the event trace
  			if ( dumpEventLog )
  				DumpEventLog();
  
  			// Zero the stack depth and added time
  			foreach ( KeyValuePair<int, TimingMeter> pair in instance.metersById )
  			{
  				TimingMeter meter = pair.Value;
  				meter.stackDepth = 0;
  				meter.addedTime = 0;
  			}
  			List<MeterStackEntry> meterStack = new List<MeterStackEntry>();
  			int indent = 0;
  			long firstEventTime = 0;
  			for ( int i = 0; i < eventTrace.Count; i++ )
  			{
  				short kind = eventTrace[ i ].eventKind;
  				long t = eventTrace[ i ].eventTime;
  				if ( i == 0 )
  					firstEventTime = t;
  				if ( kind == ekInfo )
  				{
  					writer.WriteLine( string.Format( "{0,12:D} {1}{2} {3}{4}",
  												   ToMicroseconds( t - firstEventTime ),
  												   IndentCount( indent ), "Info ", " ",
  												   eventTrace[ i ].info ) );
  					continue;
  				}
  				TimingMeter meter = GetMeterById( eventTrace[ i ].meterId );
  				if ( kind == ekEnter )
  				{
  					if ( meter.accumulate && meter.stackDepth == 0 )
  						meter.addedTime = 0;
  					if ( i >= start && ( !meter.accumulate || meter.stackDepth == 0 ) )
  					{
  						// Don't display the enter and exit if the
  						// exit is the very next record, and the
  						// elapsed usecs is less than DontDisplayUsecs
  						if ( eventTrace.Count > i + 1 && eventTrace[ i + 1 ].meterId == eventTrace[ i ].meterId &&
  							eventTrace[ i + 1 ].eventKind == ekExit &&
  							ToMicroseconds( eventTrace[ i + 1 ].eventTime - t ) < DontDisplayUsecs )
  						{
  							i++;
  							continue;
  						}
  						writer.WriteLine( string.Format( "{0,12:D} {1}{2} {3}{4}.{5}",
  													   ToMicroseconds( t - firstEventTime ),
  													   IndentCount( indent ), "Enter",
  													   ( meter.accumulate ? "*" : " " ),
  													   meter.category, meter.title ) );
  						if ( !meter.accumulate )
  							indent++;
  					}
  					meter.stackDepth++;
  					meterStack.Add( new MeterStackEntry( meter, t ) );
  				}
  				else if ( kind == ekExit )
  				{
  					Debug.Assert( meterStack.Count > 0, "Meter stack is empty during ekExit" );
  					MeterStackEntry s = meterStack[ meterStack.Count - 1 ];
  					meter.stackDepth--;
  					Debug.Assert( s.meter == meter );
  					if ( meter.stackDepth > 0 && meter.accumulate )
  						meter.addedTime += t - s.eventTime;
  					else if ( i >= start )
  					{
  						if ( !meter.accumulate )
  							indent--;
  						writer.WriteLine( string.Format( "{0,12:D} {1}{2} {3}{4}.{5}",
  													   ToMicroseconds( meter.accumulate ? meter.addedTime : t - s.eventTime ),
  													   IndentCount( indent ), "Exit ",
  													   ( meter.accumulate ? "*" : " " ),
  													   meter.category, meter.title ) );
  					}
  					meterStack.RemoveAt( meterStack.Count - 1 );
  				}
  			}
  		}
  
  		#endregion Protected MeterManager members
  
  		#region Public MeterManager methods - - all static
  
  		public static void Init()
  		{
  			if ( instance == null )
  				instance = new MeterManager();
  		}
  
  		public static int DontDisplayUsecs = 3;
  
  		public static bool Collecting
  		{
  			get
  			{
  				return instance.collecting;
  			}
  			set
  			{
  				instance.collecting = value;
  			}
  		}
  
  		// Enable or disable meters by category
  		public static void EnableCategory( string categoryName, bool enable )
  		{
  			Init();
  			foreach ( KeyValuePair<int, TimingMeter> pair in instance.metersById )
  			{
  				TimingMeter meter = pair.Value;
  				if ( meter.category == categoryName )
  					meter.enabled = enable;
  			}
  		}
  
  		// Enable or disable only a single category
  		public static void EnableOnlyCategory( string categoryName, bool enable )
  		{
  			Init();
  			foreach ( KeyValuePair<int, TimingMeter> pair in instance.metersById )
  			{
  				TimingMeter meter = pair.Value;
  				meter.enabled = ( meter.category == categoryName ? enable : !enable );
  			}
  		}
  
  		// Look up the timing meter by title; if it doesn't exist
  		// create one with the title and category
  		public static TimingMeter GetMeter( string title, string category )
  		{
  			string name = title + "&" + category;
  			TimingMeter meter;
  			Init();
  			if ( instance.metersByName.TryGetValue( name, out meter ) )
  				return meter;
  			else
  			{
  				BarfOnBadChars( title, "TimingMeter title" );
  				BarfOnBadChars( category, "TimingMeter category" );
  				short id = instance.timerIdCounter++;
  				meter = new TimingMeter( title, category, id );
  				instance.metersByName.Add( name, meter );
  				instance.metersById.Add( id, meter );
  				return meter;
  			}
  		}
  
  		public static TimingMeter GetMeter( string title, string category, bool accumulate )
  		{
  			TimingMeter meter = GetMeter( title, category );
  			meter.accumulate = true;
  			return meter;
  		}
  
  
  		public static int AddEvent( TimingMeter meter, short eventKind, string info )
  		{
  			long time = CaptureCurrentTime();
  
gt: 			short meterId = ( meter == null ) ? (short)0 : meter.meterId;