0
votes

I would like to know what are yours experiences regarding using EventSource for components that are common and that can be utilized few times within the same process.

One simple example. My shared component is TestQueue and I would like to utilize it few times with my process and again in PerfView to see what event belongs to which queue.

public class TestQueue<T>
{
    private readonly IEtwQueue _etwQueue;
    private readonly Queue<T> _instance = new Queue<T>();

    public TestQueue(IEtwQueue etwQueue)
    {
        _etwQueue = etwQueue;
    }

    public void Enqueue(T item)
    {
        _instance.Enqueue(item);
        _etwQueue.CommandEnqueued(_instance.Count);
    }

    public T Dequeue()
    {
        _etwQueue.CommandDequed(_instance.Count);
        return _instance.Dequeue();
    }
}

public interface IEtwQueue
{
    void CommandEnqueued(int items);
    void CommandDequed(int items);
}

[EventSource(Name = "Test-ETW-Queue")]
public class EtwQueue : EventSource, IEtwQueue
{
    public static EtwQueue Log = new EtwQueue();

    private EtwQueue() { }

    [Event(1)]
    public void CommandEnqueued(int items) { if (IsEnabled()) WriteEvent(1, items); }

    [Event(2)]
    public void CommandDequed(int items) { if (IsEnabled()) WriteEvent(2, items); }
}

And I would like to use it like this:

TestQueue<string> testStringQueue = new TestQueue<string>(EtwQueue.Log);
TestQueue<int> testIntQueue = new TestQueue<int>(EtwQueue.Log);
testIntQueue.Enqueue(15);
testStringQueue.Enqueue("X");

Here is what I have in PerfView:

enter image description here

There is no difference between these two events. I would like to know how could I identify them so that some name (string) or ID figures out as part of event name? I know that I could use Tasks for logical grouping of events, but it is not what I would expect here, especially since they must be predefined in the event source. Activity ID is also the same in the use case.

Cheers!


One better way then implementing two EventSources is passing event source name through constructor of custom implementation. Maybe this is the way to do it :)

So there is no EventSource attribute on custom event source class and constructor has event source name parameter:

public class EtwQueueEventSource : EventSource, IEtwQueue
{
    public EtwQueueEventSource(string sourceName) : base(sourceName) { }

    [Event(1)]
    public void CommandEnqueued(int items) { if (IsEnabled()) WriteEvent(1, items); }

    [Event(2)]
    public void CommandDequed(int items) { if (IsEnabled()) WriteEvent(2, items); }
}

So previous usage example becomes something like:

TestQueue<string> testStringQueue = new TestQueue<string>(new EtwQueueEventSource("Test-ETW-Queue-String"));
TestQueue<int> testIntQueue = new TestQueue<int>(new EtwQueueEventSource("Test-ETW-Queue-Integer"));
testIntQueue.Enqueue(15);
testStringQueue.Enqueue("X");

PerfView

1
I forgot to mention... As you can see, in my implementation I have left a space to create new instance of IEtwQueue and pass it in every new use, but it does not seem as nice solution since every usage requires implementation of new EventSource. - Zeljko
It sounds like using the additional parameters of the EventAttribute will do what you want, esp. "Message". See aroundtuitblog.wordpress.com/2014/10/24/a-look-at-etw-part-2 for more info. - Kim Johnson
I could format content with message parameter. I could pass queue name or ID as part of the message, but is it how MS imagined to use it? Filtering based on message content is something that I would need to do in order to see events related to only queue instance. - Zeljko
I don't know how MS imagined it, and agree it's difficult to use. It can be either tightly bound to event-generating code or too generic for really effective filtering. - Kim Johnson
Hey Kim, can you please take a look on update of my question. Maybe something like that? - Zeljko

1 Answers

1
votes

Instead of using two sources I would add a parameters where you can pass the name of the queue in and the ToString representation of the object.

public class TestQueue<T>
{
    private readonly IEtwQueue _etwQueue;
    private readonly string _queueName;
    private readonly Queue<T> _instance = new Queue<T>();

    public TestQueue(IEtwQueue etwQueue, string queueName)
    {
        _etwQueue = etwQueue;
        _queueName = queueName;
    }

    public void Enqueue(T item)
    {
        _instance.Enqueue(item);

        if(_etwQueue.IsEnabled()) //So we only call item.ToString() if the queue is enabled.
        {
            _etwQueue.CommandEnqueued(_instance.Count, item.ToString(), queueName);
        }
    }

    public T Dequeue()
    {
        T item = _instance.Dequeue();
        if(_etwQueue.IsEnabled()) //So we only call item.ToString() if the queue is enabled.
        {
            _etwQueue.CommandDequed(_instance.Count, item.ToString(), queueName);
        }
        return 
    }
}

public interface IEtwQueue
{
    void CommandEnqueued(int items, string itemDescription, string queueName);
    void CommandDequed(int items, string itemDescription, string queueName);
}

[EventSource(Name = "Test-ETW-Queue")]
public class EtwQueue : EventSource, IEtwQueue
{
    public static EtwQueue Log = new EtwQueue();

    private EtwQueue() { }

    [Event(1)]
    public void CommandEnqueued(int items, string itemDescription, string queueName) 
    { 
        if (IsEnabled()) WriteEvent(1, items, itemDescription, queueName); 
    }

    [Event(2)]
    public void CommandDequed(int items, string itemDescription, string queueName)
    { 
        if (IsEnabled()) WriteEvent(2, items, itemDescription, queueName); 
    }

However, if you expect > 1000 / sec events to be raised you may want to increase performance by the unsafe code to call WriteEventCore to make a overload of WriteEvent that takes a int, int, string, string as the 4 arguments instead of using the slower int, params object[]` overload.

[EventSource(Name = "Test-ETW-Queue")]
public class EtwQueue : EventSource
{
    public static EtwQueue Log = new EtwQueue();

    private EtwQueue() { }

    [Event(1)]
    public void CommandEnqueued(int items, string itemDescription, string queueName)
    {
        if (IsEnabled()) WriteEvent(1, items, itemDescription, queueName);
    }

    [Event(2)]
    public void CommandDequed(int items, string itemDescription, string queueName)
    {
        if (IsEnabled()) WriteEvent(2, items, itemDescription, queueName);
    }

    [NonEvent]
    public unsafe void WriteEvent(int eventId, int arg1, string arg2, string arg3)
    {
        if (arg2 == null) arg2 = "";
        if (arg3 == null) arg3 = "";

        fixed (char* string2Bytes = arg2)
        fixed (char* string3Bytes = arg3)
        {
            EventSource.EventData* descrs = stackalloc EventSource.EventData[3];
            descrs[0].DataPointer = (IntPtr)(&arg1);
            descrs[0].Size = 4;
            descrs[1].DataPointer = (IntPtr)string2Bytes;
            descrs[1].Size = ((arg2.Length + 1) * 2);
            descrs[2].DataPointer = (IntPtr)string3Bytes;
            descrs[2].Size = ((arg3.Length + 1) * 2);
            WriteEventCore(eventId, 3, descrs);
        }
    }
}