Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

DOTA 2 gifts

I have 2 DOTA 2 gifts. Probably you can ask me by email, probably someone will definitely get it. Go, gentlemen.

Realtime ETW consumer howto

Short howto for real time Event Tracing for Windows.

#pragma once

struct ITraceConsumer;

class TraceSession
{

public:
   TraceSession(LPCTSTR szSessionName);
   ~TraceSession();

public:
   bool Start();
   bool EnableProvider(const GUID& providerId, UCHAR level, ULONGLONG anyKeyword = 0, ULONGLONG allKeyword = 0);
   bool OpenTrace(ITraceConsumer *pConsumer);
   bool Process();
   bool CloseTrace();
   bool DisableProvider(const GUID& providerId);
   bool Stop();

   ULONG Status() const;
   LONGLONG PerfFreq() const;

private:
   LPTSTR _szSessionName;
   ULONG _status;
   EVENT_TRACE_PROPERTIES* _pSessionProperties;
   TRACEHANDLE hSession;
   EVENT_TRACE_LOGFILE _logFile;
   TRACEHANDLE _hTrace;

};

Initialization and cleanup:

TraceSession::TraceSession(LPCTSTR szSessionName) : _szSessionName(_tcsdup(szSessionName))
{
}

TraceSession::~TraceSession(void)
{
   delete []_szSessionName;
   delete _pSessionProperties;
}

First step - lets start trace session:

bool TraceSession::Start()
{
   if (!_pSessionProperties) {
      const size_t buffSize = sizeof(EVENT_TRACE_PROPERTIES) + (_tcslen(_szSessionName) + 1) * sizeof(TCHAR);
      _pSessionProperties = reinterpret_cast<EVENT_TRACE_PROPERTIES *>(malloc(buffSize));
      ZeroMemory(_pSessionProperties, buffSize);
      _pSessionProperties->Wnode.BufferSize = buffSize;
      _pSessionProperties->Wnode.ClientContext = 1;
      _pSessionProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
      _pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
   }

   // Create the trace session.
   _status = StartTrace(&hSession, _szSessionName, _pSessionProperties);

   return (_status == ERROR_SUCCESS);
}

Then lets enable a provider with some filters:

bool TraceSession::EnableProvider(const GUID& providerId, UCHAR level, ULONGLONG anyKeyword, ULONGLONG allKeyword)
{
   _status = EnableTraceEx2(hSession, &providerId, EVENT_CONTROL_CODE_ENABLE_PROVIDER, level, anyKeyword, allKeyword, 0, NULL);
   return (_status == ERROR_SUCCESS);
}

BTW, you can have useful ETW metadata generated by mc.exe utility, just find your specific man manifest file for your ETW provider.

Now we open the trace:

bool TraceSession::OpenTrace(ITraceConsumer *pConsumer)
{   
   if (!pConsumer)
      return false;

   ZeroMemory(&_logFile, sizeof(EVENT_TRACE_LOGFILE));
   _logFile.LoggerName = _szSessionName;
   _logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
   _logFile.EventRecordCallback = &EventRecordCallback;
   _logFile.Context = pConsumer;

   _hTrace = ::OpenTrace(&_logFile);
   return (_hTrace != 0);
}

EventRecordCallback looks like this:

namespace
{

VOID WINAPI EventRecordCallback(_In_ PEVENT_RECORD pEventRecord)
{
   reinterpret_cast<ITraceConsumer *>(pEventRecord->UserContext)->OnEventRecord(pEventRecord);
}

}

But this is just for convenience and OOP, you can have just static function. Up to you.

Finally, call the following method:

bool TraceSession::Process()
{
   _status = ProcessTrace(&_hTrace, 1, NULL, NULL);
   return (_status == ERROR_SUCCESS);
}

Ensure you are running under elevated privileges.

When you decide its over - close the trace, disable any providers and stop the session:

bool TraceSession::CloseTrace()
{
   _status = ::CloseTrace(_hTrace);
   return (_status == ERROR_SUCCESS);
}

bool TraceSession::DisableProvider(const GUID& providerId)
{
   _status = EnableTraceEx2(hSession, &providerId, EVENT_CONTROL_CODE_DISABLE_PROVIDER, 0, 0, 0, 0, NULL);
   return (_status == ERROR_SUCCESS);
}

bool TraceSession::Stop()
{
   _status = ControlTrace(hSession, _szSessionName, _pSessionProperties, EVENT_TRACE_CONTROL_STOP);
   delete _pSessionProperties;
   _pSessionProperties = NULL;
   
   return (_status == ERROR_SUCCESS);
}

Some useful stuff:

ULONG TraceSession::Status() const
{
   return _status;
}

LONGLONG TraceSession::PerfFreq() const
{
   return _logFile.LogfileHeader.PerfFreq.QuadPart;
}

Remember: sessions are global, not tied to your executable. So be sure to write something like this:

if (!traceSession.Start()) {
      if (traceSession.Status() == ERROR_ALREADY_EXISTS) {
         if (!traceSession.Stop() || !traceSession.Start()) {
            // Handle an error
         }
      }
   }

Well, thats it.

Copyright 2007-2011 Chabster