Click here to Skip to main content
15,888,320 members
Articles / Desktop Programming / WPF

Reflection Studio - Part 1 - Introduction: Architecture and Design

Rate me:
Please Sign up or sign in to vote.
4.83/5 (23 votes)
22 Sep 2010GPL36 min read 60K   6.9K   111  
Reflection Studio is a "developer" application for assembly, database, performance, and code generation, written in C# under WPF 4.0.
using System;
using System.ComponentModel;
using System.IO;
using System.Threading;
using Mono.Cecil;
using Mono.Cecil.Cil;
using ReflectionStudio.Core.Reflection.Types;
using ReflectionStudio.Core.Helpers;
using ReflectionStudio.Core.Project.Settings;
using ReflectionStudio.Core.Events;
using ReflectionStudio.Core.Properties;

namespace ReflectionStudio.Core.Project
{
	internal class ProfilingStatus
	{
		public int IgnoredMethods;
		public int ProfiledMethods;
		//public int MethodHandleCounter;
	}

	/// <summary>
	/// Class to inject profiling trace 
	/// </summary>
	internal class InjectionProfiler : ProjectWorker
	{
		#region ----------------------INTERNALS----------------------

		internal const string ProfilingAssembly = "ReflectionStudio.Spy.dll";
		internal const string ProfilingClass = "ReflectionStudio.Spy.Performance";

		internal const string ProfilingStartMethod = "StartEvent";
		internal const string ProfilingEndMethod = "EndEvent";
		internal const string ProfilingStartEndMethod = "StartEndEvent";

		private ProfilingStatus _Status = null;
		internal ProfilingStatus Status
		{
			get { return _Status; }
			set { _Status = value; }
		}

		private ProfilingContext _Context = null;

		#endregion

		#region ----------------------CONSTRUCTORS----------------------

		public InjectionProfiler(BackgroundWorker worker, DoWorkEventArgs e)
			: base(worker, e)
		{
			Project = (ProjectEntity)e.Argument;
		}

		#endregion

		#region ----------------------PUBLIC FUNCTION----------------------
		/// <summary>
		/// Start the profiling of a given project
		/// </summary>
		/// <param name="project">the project to profile</param>
		/// <param name="worker">the background worker, can be null</param>
		/// <param name="e">the event associated to the background worker, can be null</param>
		/// <returns></returns>
		public void Build()
		{
			Tracer.Verbose("InjectionProfiler.Build", "START");
			
			_Status = new ProfilingStatus();
			ProfileAssemblies();

			Tracer.Verbose("InjectionProfiler.Build", "END");
		}
		#endregion

		#region ----------------------INTERNAL FUNCTIONS----------------------

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		private void PrepareContext()
		{
			Tracer.Verbose("InjectionProfiler.PrepareContext", "START");

			//create a new context
			_Context = new ProfilingContext();

			try
			{
				// TODO chemin !
				// get the references to the runtime tracer
				string prof = Path.Combine(PathHelper.ApplicationPath, ProfilingAssembly);

				_Context.SpyAsm = System.Reflection.Assembly.LoadFrom(prof);
				_Context.SpyAsmDefinition = AssemblyFactory.GetAssembly(prof);
				_Context.SpyAsmNameReference = new AssemblyNameReference(_Context.SpyAsmDefinition.Name.Name,
																		_Context.SpyAsmDefinition.Name.Culture,
																		_Context.SpyAsmDefinition.Name.Version);
				_Context.SpyAsmNameReference.PublicKeyToken = _Context.SpyAsmDefinition.Name.PublicKeyToken;

				//get a reference to the logger class
				Type type = _Context.SpyAsm.GetType(ProfilingClass);

				//and the timer methods
				_Context.StartMethodInfo = type.GetMethod(ProfilingStartMethod,
					System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
					Type.DefaultBinder, new Type[] { typeof(int), typeof(int) }, null);

				_Context.EndMethodInfo = type.GetMethod(ProfilingEndMethod,
					System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
					Type.DefaultBinder, new Type[] { }, null);

				//and the counter method
				_Context.StartEndMethodInfo = type.GetMethod(ProfilingStartEndMethod,
					System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
					Type.DefaultBinder, new Type[] { typeof(int), typeof(int) }, null);
			}
			catch (Exception error)
			{
				Tracer.Error("InjectionProfiler:PrepareContext", error);
			}
			finally
			{
				Tracer.Verbose("InjectionProfiler.PrepareContext", "END");
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		private void ProfileAssemblies()
		{
			try
			{
				foreach (NetAssembly asm in Project.Assemblies)
				{
					//if (asm.IsChecked == true)
					{
						EventDispatcher.Instance.RaiseStatus(string.Format(Resources.CORE_PROFILING_START, asm.DisplayName),
							StatusEventType.StartProgress);

						ProfileAssembly(asm);
					}

					if (CancelPending())
					{
						EventDispatcher.Instance.RaiseStatus(Resources.CORE_MAP_INTERUPT, StatusEventType.StopProgress);
						return;
					}
				}
			}
			catch (Exception error)
			{
				Tracer.Error("InjectionProfiler:ProfileAssemblies", error);
				Result = false;
			}
			finally
			{
				EventDispatcher.Instance.RaiseStatus(
					string.Format(Resources.CORE_PROFILING_RESUME,
									_Status.ProfiledMethods, _Status.IgnoredMethods), StatusEventType.StopProgress);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="AsmToProfile"></param>
		/// <param name="context"></param>
		/// <returns></returns>
		private void ProfileAssembly(NetAssembly AsmToProfile)
		{
			Tracer.Verbose("InjectionProfiler.ProfileAssembly START ", AsmToProfile.Name);

			PrepareContext();

			try
			{
				AssemblyDefinition CecilAssDefinition = (AssemblyDefinition)AsmToProfile.Tag;

				// add a build tag class so we know later if already built
				CecilAssDefinition.MainModule.Types.Add(_Context.TagType);

				//add the runtime spy reference
				CecilAssDefinition.MainModule.AssemblyReferences.Add(_Context.SpyAsmNameReference);

				//get back the reference of enter method
				_Context.StartMethodRef = CecilAssDefinition.MainModule.Import(_Context.StartMethodInfo);
				_Context.EndMethodRef = CecilAssDefinition.MainModule.Import(_Context.EndMethodInfo);
				_Context.StartEndMethodRef = CecilAssDefinition.MainModule.Import(_Context.StartEndMethodInfo);

				//for each type we get in our tree
				foreach (NetClass classTyp in AsmToProfile.Children)
				{
					//if need to be profiled
					if (classTyp.IsChecked == true)
					{
						Tracer.Info( "InjectionProfiler.ProfileAssembly", string.Format(Resources.CORE_PROFILING_CLASS, classTyp.Name));

						foreach (NetMethod methodType in classTyp.Children)
						{
							if (methodType.IsChecked == true)
								this.PrepareMethod(CecilAssDefinition, methodType);
						}

						CecilAssDefinition.MainModule.Import((TypeDefinition)classTyp.Tag);
					}

					if (CancelPending()) return;
				}

				// TODO
				// gestion chemin
				string ProfiledFilePath = Path.Combine(Project.SubFolderProfiled, new FileInfo(AsmToProfile.FilePath).Name);

				//call cecil to save it
				AssemblyFactory.SaveAssembly(CecilAssDefinition, ProfiledFilePath);

				Tracer.Info("InjectionProfiler.ProfileAssembly", string.Format(Resources.CORE_PROFILING_SAVE, AsmToProfile.Name));
			}
			catch (Exception error)
			{
				Tracer.Error("InjectionProfiler:ProfileAssembly", error);
				Result = false;
			}
			finally
			{
				Tracer.Verbose("InjectionProfiler.ProfileAssembly END", AsmToProfile.Name);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="assembly"></param>
		/// <param name="method"></param>
		/// <param name="rtmodule"></param>
		private bool MustSkipMethod(MethodDefinition method)
		{
			Tracer.Verbose("InjectionProfiler.MustSkipMethod START ", method.Name);

			try
			{
				if (!method.HasBody || (method.Body == null))		//empty body
				{
					this._Status.IgnoredMethods++;
					return true;
				}
				if (method.Body.Instructions.Count == 1)			//empty method
				{
					this._Status.IgnoredMethods++;
					return true;
				}

				//skip event listenner
				if ((method.IsSpecialName && (method.Name.StartsWith("add_") || method.Name.StartsWith("remove_")))
					&& (method.Body.Instructions.Count <= 8))
				{
					this._Status.IgnoredMethods++;
					return true;
				}

				//skip small methods
				if (Project.Settings.SkipSmallMethods)
				{
					if (CecilHelper.IsSmallMethod(method.Body))
					{
						this._Status.IgnoredMethods++;
						return true;
					}
				}
			}
			catch (Exception error)
			{
				Tracer.Error("InjectionProfiler:ProfileAssembly", error);
				return false;
			}
			finally
			{
				Tracer.Verbose("InjectionProfiler.MustSkipMethod" , "END" );
			}

			this._Status.ProfiledMethods++;
			return false;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="assembly"></param>
		/// <param name="method"></param>
		/// <param name="rtmodule"></param>
		private void PrepareMethod(AssemblyDefinition assembly, NetMethod method)
		{
			Tracer.Verbose("InjectionProfiler.PrepareMethod START ", method.Name);

			// no profiling on this method
			if (MustSkipMethod((MethodDefinition)method.Tag))
				return;

			//trace the number of call in methods
			if (Project.Settings.Action == ProfilMode.CallCount)
				PrepareMethodForCallCount(method);

			//trace for performance
			if (Project.Settings.Action == ProfilMode.TimeSpent)
				PrepareMethodForTiming( assembly, method );

			Tracer.Verbose("InjectionProfiler.PrepareMethod END ", method.Name);
		}

		private void PrepareMethodForTiming(AssemblyDefinition assembly, NetMethod method)
		{
			MethodDefinition CecilType = (MethodDefinition)method.Tag;
			MethodBody body = CecilType.Body;

			BodyWorker bw = new BodyWorker(body);

			VariableDefinition definition = null;
			if (CecilType.ReturnType.ReturnType != assembly.MainModule.TypeReferences["System.Void"])
			{
				definition = new VariableDefinition(CecilType.ReturnType.ReturnType);
				body.Variables.Add(definition);
			}

			Instruction instruction6 = null;
			Instruction instruction7 = body.Instructions[0];
			Instruction instruction8 = bw.InsertBefore(instruction7, bw.Create(OpCodes.Ldc_I4, method.Handle));
			Instruction instruction9 = bw.AppendAfter(instruction8, bw.Create(OpCodes.Ldc_I4, Project.Settings.BuildKey));
			bw.AppendAfter(instruction9, bw.Create(OpCodes.Call, _Context.StartMethodRef));

			Instruction instruction10 = bw.OriginalInstructions[bw.OriginalInstructions.Count - 1];
			Instruction instruction11 = bw.AppendAfter(instruction10, bw.Create(OpCodes.Call, _Context.EndMethodRef));
			Instruction instruction12 = bw.AppendAfter(instruction11, bw.Create(OpCodes.Endfinally));
			Instruction outside = body.Instructions.Outside;

			//gestion des methodes de debut/fin ?
			// TODO

			foreach (Instruction original in bw.OriginalInstructions)
			{
				Instruction instruction15;

				if (!(original.OpCode == OpCodes.Ret) || (original == instruction6))
					continue;
				
				//return type exist
				if (definition != null)
				{
					//return the value
					instruction15 = bw.AppendAfter(instruction12, bw.Create(OpCodes.Ldloc, definition));
					bw.AppendAfter(instruction15, bw.Create(OpCodes.Ret));
				}
				else
				{
					//else quit
					instruction15 = bw.AppendAfter(instruction12, bw.Create(OpCodes.Ret));
				}

				foreach (Instruction instruction16 in bw.OriginalInstructions)
				{
					if ((instruction16.OpCode == OpCodes.Ret) && (instruction16 != instruction6))
					{
						if (definition != null)
						{
							Instruction instruction17 = bw.Replace(instruction16, bw.Create(OpCodes.Stloc, definition));
							bw.AppendAfter(instruction17, bw.Create(OpCodes.Leave, instruction15));
						}
						else
						{
							bw.Replace(instruction16, bw.Create(OpCodes.Leave, instruction15));
						}
					}
				}
				outside = instruction15;
				break;
			}
			bw.RemapBodyOutside(instruction11);

			bw.Done();

			//define the exception handling arround the existing code
			ExceptionHandler handler = new ExceptionHandler(ExceptionHandlerType.Finally)
			{
				TryStart = instruction7,
				TryEnd = instruction11,
				HandlerStart = instruction11,
				HandlerEnd = outside
			};

			body.ExceptionHandlers.Add(handler);

			//if return type, be sure that variables are initialized
			if (definition != null)
			{
				body.InitLocals = true;
			}
		}

		private void PrepareMethodForCallCount(NetMethod method)
		{
			MethodDefinition CecilType = (MethodDefinition)method.Tag;
			BodyWorker worker = new BodyWorker(CecilType.Body);

			Instruction beforeIns = CecilType.Body.Instructions[0];
			Instruction afterIns = worker.InsertBefore(beforeIns, worker.Create(OpCodes.Ldc_I4, method.Handle));
			Instruction instruction4 = worker.AppendAfter(afterIns, worker.Create(OpCodes.Ldc_I4, Project.Settings.BuildKey));
			
			worker.AppendAfter(instruction4, worker.Create(OpCodes.Call, _Context.StartEndMethodRef));
            worker.Done();
        }
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Architect
France France
WPF and MVVM fan, I practice C # in all its forms from the beginning of the NET Framework without mentioning C ++ / MFC and other software packages such as databases, ASP, WCF, Web & Windows services, Application, and now Core and UWP.
In my wasted hours, I am guilty of having fathered C.B.R. and its cousins C.B.R. for WinRT and UWP on the Windows store.
But apart from that, I am a great handyman ... the house, a rocket stove to heat the jacuzzi and the last one: a wood oven for pizza, bread, and everything that goes inside

https://guillaumewaser.wordpress.com/
https://fouretcompagnie.wordpress.com/

Comments and Discussions