If you use NHibernate heavily then you have a lot to gain from NHProfiler. It is a great tool for identifying problems with how you use NHibernate and how you access and update data. But I think its greatest strength is as a learning tool, as it is makes it easy to understand how NHibernate works and what the consequences of each mapping setup are.

Ok, this post is not just going to be free advertising for Ayende. A nice feature in NHProfiler is that it will show you the context of the data access. For web apps it will show the URL that triggered the data access, for WCF apps it will show the WCF service and operation name.

Example:
image

Here you see that ContractService and operation GetContractStatuses was called and generated one database call. This context is very nice to have when profiling and tuning your application. However in the current project that I am working on we have moved from classic WCF with services and operations to a generic WCF command & query service with just two operations  (SendCommand & SendQuery). This makes the context not so useful anymore.

image

Sorry for the censored image, it hides the project name. Anyway here the WCF service and operation name is not helping much as we do not se what command or query was issued. Fortunately NHProfiler offers a way to programmatically set the context. In order to set the context for this generic command & query service I implemented a ParameterInspector (a WCF extensibility point) in which I set the NHProfiler context.

Here is the code:

public class NHProfMessageInspector : IParameterInspector
{
        public object BeforeCall(string operationName, object[] inputs)
        {
                
                var typeName = inputs[0].GetType().Name;
                var commandOrQuery = "";
                
                if (typeName.EndsWith("Query"))
                {
                        commandOrQuery = "Query";
                        typeName = typeName.Substring(0, typeName.Length - 5);
                }
                if (typeName.EndsWith("Command"))
                {
                        commandOrQuery = "Command";
                        typeName = typeName.Substring(0, typeName.Length - 7);
                }

                var contextString = string.Format("{0} : {1}", commandOrQuery, typeName.ToSentenceStyle());
                SetContext(contextString);

                return null;
        }

        public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
        {
                SetContext(null);
        }

        private void SetContext(string value)
        {
                Assembly profilerAsm = null;

                try
                {
                        profilerAsm = Assembly.Load("HibernatingRhinos.Profiler.Appender");
                }
                catch (Exception) { return; }

                if (profilerAsm == null)
                        return;

                var profilerIntegration = profilerAsm.GetType("HibernatingRhinos.Profiler.Appender.ProfilerIntegration");
                if (profilerIntegration == null)
                        return;

                var currentContext = profilerIntegration.GetProperty("CurrentSessionContext");

                currentContext.SetValue(null, value, null);
        }
}

The BeforeCall method checks the first input type and creates a nice context string based on the type name. The SetContext method does the integration with NHProfiler. This is done through some reflection (in order to not have a strong reference to HibernatingRhinos.Profiler.Appender.dll and be version independent).

The result:
image

Now the context is much more useful! The same approach should be interesting for other people who also use a generic WCF interface or for example NServiceBus. Another option is to set the NHProfiler context in an integration test base class. This could be useful for example if you have a suite of repository tests and you want the text fixture name to appear as the context in NHProfiler.

1 comments:

Anders Jönsson said...

Coolt, det där måste jag testa :)