3

In order to get early-warning of a slow or potentially slow areas, I'd like to have an Interceptor for NHibernate that can act as a performance monitor, so that any database operation that takes more than a given time raises an event and (importantly) a full stacktrace into the application's logs.

Interceptors seemed to be a good window into this. However, having experimented, there doesn't seem to be anyway to catch a "just-back-from-SQL" event:

  • OnPreFlush and OnPostFlush work on full batches where writes are involved, but aren't invoked on read events.
  • OnPrepareStatement() seems to be the best to put start measuring, but to stop?
  • For read events, OnLoad might be the place to stop the clock, but it's called once-per-entity returned - how do I know when I've got to the end of all entities?
  • For write events, I can't see any post-SQL event (other than those that work on the entire batch - OnPostFlush and OnAfterTransaction; I get the impression OnSave, OnFlushDirty, etc are called before the actual database call occurs - happy to be corrected though).

From what I can tell, documentation is heavily lacking on exactly what the pipeline order is with NHibernate's interaction with the database, and thus when in that pipeline different events and interceptor calls are called in relation to the actual SQL execution itself.

This is something that needs to be permanently available, sitting in the background, pretty much that requires no human intervention except when slow queries are detected. It also needs to run headless on a large farm of servers, so interactive tools such as NHibernate Profiler are out: it's literally something that we can enable and forget about, letting it log as and when appropriate.

Is there anything I have missed or misunderstood?

Chris J
  • 30,688
  • 6
  • 69
  • 111
  • I think interceptors are not that difficult to implement, however sqlserver is able to monitor this for you. – Peter Mar 11 '14 at 09:43
  • Implementing an interceptor isn't the issue -- what I want to know is the event pipeline so I put my timers on the *right* events, without falling back on heristics to say "well this was a read, and therefore I need to stop the clock at this point; but I musn't reset the start time if this happened [etc]". And whilst SQL Server can monitor this, what it can't do is link back to the NHibernate query. Remember: NHibernate issues dynamic SQL. I need to be able to trace a random piece of dynamic SQL back into the code from where it was called: SQL Server cannot give me that. – Chris J Mar 11 '14 at 09:46

1 Answers1

3

I had a similar problem. I wanted measure and log all queries that goes through NHibernate. What I did is I wrote a custom batching factory (in this case I work with oracle) but you can apply the same technique to any db:

1-) Implement batcher factory, (in this case I am extending existing factory)

public class OracleLoggingBatchingBatcherFactory : OracleDataClientBatchingBatcherFactory
{
    public override IBatcher CreateBatcher(ConnectionManager connectionManager, IInterceptor interceptor)
    {
        return new OracleLoggingBatchingBatcher(connectionManager, interceptor);
    }
}

2-) Implement the Batcher itself (in this case I am extending existing batcher). Make sure you inherit IBatcher again since we want our new methods to be called

public class OracleLoggingBatchingBatcher : OracleDataClientBatchingBatcher, IBatcher
{
      .... // here override ExecuteNonQuery, DoExecuteBatch and ExecuteReader. 
           //You can do all kind of intercepting, logging or measuring here
           //If they are not overrideable just implement them and use "new" keyword if necessary
           //since we inherit IBatcher explicitly it will work polymorphically.
           //Make sure you call base implementation too or re-implement the method from scratch
}

3-) Register the factory via NHibernate config:

    <property name="adonet.factory_class">OracleLoggingBatchingBatcherFactory, MyAssembly</property>
Onur Gumus
  • 1,389
  • 11
  • 27
  • Unless I implement from scratch, it appears I can't override `ExecuteNonQuery` or `ExecuteReader`. As a result I can't catch single statements, only batches. `DoExecuteBatch` gives me stuff, but I'm not sure it's completely valid as a batch of five Updates is only listed as a single update in `IDbCommand.CommandText`. However I'll continue poking to see if I've missed anything (I'm overriding SqlClientBatchingBatcher). – Chris J Mar 11 '14 at 11:40
  • You don't have to override them, you can also "new" them and hide old implementation. It will still work polymorphicly since you inherit IBatcher explicitly! – Onur Gumus Mar 11 '14 at 11:57
  • Ah - a feature of C# I've never had reason to use before now. This appears to be working. Ta for the pointer :-) – Chris J Mar 11 '14 at 14:32