Skip to content

Commit

Permalink
Merge pull request #42 from karolz-ms/dev/karolz/activitypath
Browse files Browse the repository at this point in the history
Improve activity path decoding
  • Loading branch information
karolz-ms authored Feb 6, 2017
2 parents 15be2f3 + b576feb commit d90094f
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 126 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------

using System;
using System.Diagnostics;
using System.Text;

namespace Microsoft.Diagnostics.EventFlow.Inputs
{
/// <summary>
/// A class to decode ETW Activity ID GUIDs into activity paths.
/// </summary>
internal static class ActivityPathDecoder
{
/// <summary>
/// The encoding for a list of numbers used to make Activity Guids. Basically
/// we operate on nibbles (which are nice because they show up as hex digits). The
/// list is ended with a end nibble (0) and depending on the nibble value (Below)
/// the value is either encoded into nibble itself or it can spill over into the
/// bytes that follow.
/// </summary>
private enum NumberListCodes : byte
{
End = 0x0, // ends the list. No valid value has this prefix.
LastImmediateValue = 0xA,
PrefixCode = 0xB,
MultiByte1 = 0xC, // 1 byte follows. If this Nibble is in the high bits, it the high bits of the number are stored in the low nibble.
// commented out because the code does not explicitly reference the names (but they are logically defined).
// MultiByte2 = 0xD, // 2 bytes follow (we don't bother with the nibble optimzation
// MultiByte3 = 0xE, // 3 bytes follow (we don't bother with the nibble optimzation
// MultiByte4 = 0xF, // 4 bytes follow (we don't bother with the nibble optimzation
}

/// <summary>
/// Returns true if 'guid' follow the EventSource style activity ID for the process with ID processID.
/// You can pass a process ID of 0 to this routine and it will do the best it can, but the possibility
/// of error is significantly higher (but still under .1%).
/// </summary>
public static unsafe bool IsActivityPath(Guid guid, int processID)
{
uint* uintPtr = (uint*)&guid;

uint sum = uintPtr[0] + uintPtr[1] + uintPtr[2] + 0x599D99AD;
if (processID == 0)
{
// We guess that the process ID is < 16 bits and because it was xored
// with the lower bits, the upper 16 bits should be independent of the
// particular process, so we can at least confirm that the upper bits
// match.
return (sum & 0xFFFF0000) == (uintPtr[3] & 0xFFFF0000);
}

if ((sum ^ (uint)processID) == uintPtr[3])
{
// This is the new style
return true;
}
return sum == uintPtr[3]; // THis is old style where we don't make the ID unique machine wide.
}

/// <summary>
/// Returns a string representation for the activity path. If the GUID is not an activity path then it returns
/// the normal string representation for a GUID.
/// </summary>
public static unsafe string GetActivityPathString(Guid guid)
{
if (!IsActivityPath(guid, Process.GetCurrentProcess().Id))
{
return guid.ToString();
}

var processID = ActivityPathProcessID(guid);
StringBuilder sb = StringBuilderCache.Acquire();
if (processID != 0)
{
sb.Append("/#"); // Use /# to mark the fact that the first number is a process ID.
sb.Append(processID);
}
else
{
sb.Append('/'); // Use // to start to make it easy to anchor
}
byte* bytePtr = (byte*)&guid;
byte* endPtr = bytePtr + 12;
char separator = '/';
while (bytePtr < endPtr)
{
uint nibble = (uint)(*bytePtr >> 4);
bool secondNibble = false; // are we reading the second nibble (low order bits) of the byte.
NextNibble:
if (nibble == (uint)NumberListCodes.End)
{
break;
}

if (nibble <= (uint)NumberListCodes.LastImmediateValue)
{
sb.Append('/').Append(nibble);
if (!secondNibble)
{
nibble = (uint)(*bytePtr & 0xF);
secondNibble = true;
goto NextNibble;
}

// We read the second nibble so we move on to the next byte.
bytePtr++;
continue;
}
else if (nibble == (uint)NumberListCodes.PrefixCode)
{
// This are the prefix codes. If the next nibble is MultiByte, then this is an overflow ID.
// we we denote with a $ instead of a / separator.

// Read the next nibble.
if (!secondNibble)
{
nibble = (uint)(*bytePtr & 0xF);
}
else
{
bytePtr++;
if (endPtr <= bytePtr)
{
break;
}
nibble = (uint)(*bytePtr >> 4);
}

if (nibble < (uint)NumberListCodes.MultiByte1)
{
// If the nibble is less than MultiByte we have not defined what that means
// For now we simply give up, and stop parsing. We could add more cases here...
return guid.ToString();
}

// If we get here we have a overflow ID, which is just like a normal ID but the separator is $
separator = '$';

// Fall into the Multi-byte decode case.
}

Debug.Assert((uint)NumberListCodes.MultiByte1 <= nibble, "Single-byte number should be fully handled by the code above");

// At this point we are decoding a multi-byte number.
// We are fetching the number as a stream of bytes.
uint numBytes = nibble - (uint)NumberListCodes.MultiByte1;

uint value = 0;
if (!secondNibble)
{
value = (uint)(*bytePtr & 0xF);
}
bytePtr++; // Adance to the value bytes

numBytes++; // Now numBytes is 1-4 and reprsents the number of bytes to read.
if (endPtr < bytePtr + numBytes)
{
break;
}

// Compute the number (little endian) (thus backwards).
for (int i = (int)numBytes - 1; i >= 0; --i)
{
value = (value << 8) + bytePtr[i];
}

// Print the value
sb.Append(separator).Append(value);

bytePtr += numBytes; // Advance past the bytes.
}

sb.Append('/');
return StringBuilderCache.GetStringAndRelease(sb);
}

/// <summary>
/// Assuming guid is an Activity Path, extract the process ID from it.
/// </summary>
private static unsafe int ActivityPathProcessID(Guid guid)
{
uint* uintPtr = (uint*)&guid;
uint sum = uintPtr[0] + uintPtr[1] + uintPtr[2] + 0x599D99AD;
return (int)(sum ^ uintPtr[3]);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,23 @@ public static EventData ToEventData(this EventWrittenEventArgs eventSourceEvent,
};

IDictionary<string, object> payloadData = eventData.Payload;
payloadData.Add("EventId", eventSourceEvent.EventId);
payloadData.Add("EventName", eventSourceEvent.EventName);
payloadData.Add("ActivityID", ActivityPathString(eventSourceEvent.ActivityId));
payloadData.Add(nameof(eventSourceEvent.EventId), eventSourceEvent.EventId);
payloadData.Add(nameof(eventSourceEvent.EventName), eventSourceEvent.EventName);
if (eventSourceEvent.ActivityId != default(Guid))
{
payloadData.Add(nameof(EventWrittenEventArgs.ActivityId), ActivityPathDecoder.GetActivityPathString(eventSourceEvent.ActivityId));
}
if (eventSourceEvent.RelatedActivityId != default(Guid))
{
payloadData.Add(nameof(EventWrittenEventArgs.RelatedActivityId), eventSourceEvent.RelatedActivityId.ToString());
}

try
{
if (eventSourceEvent.Message != null)
{
// If the event has a badly formatted manifest, the FormattedMessage property getter might throw
payloadData.Add("Message", string.Format(CultureInfo.InvariantCulture, eventSourceEvent.Message, eventSourceEvent.Payload.ToArray()));
payloadData.Add(nameof(eventSourceEvent.Message), string.Format(CultureInfo.InvariantCulture, eventSourceEvent.Message, eventSourceEvent.Payload.ToArray()));
}
}
catch { }
Expand All @@ -63,125 +71,5 @@ private static void ExtractPayloadData(this EventWrittenEventArgs eventSourceEve
eventData.AddPayloadProperty(payloadNamesEnunmerator.Current, payloadEnumerator.Current, healthReporter, context);
}
}

/// <summary>
/// Returns true if 'guid' follow the EventSouce style activity IDs.
/// </summary>
private static unsafe bool IsActivityPath(Guid guid)
{
// We compute a very simple checksum which by adding the first 96 bits as 32 bit numbers.
uint* uintPtr = (uint*)&guid;
return (uintPtr[0] + uintPtr[1] + uintPtr[2] + 0x599D99AD == uintPtr[3]);
}

/// <summary>
/// The encoding for a list of numbers used to make Activity Guids. Basically
/// we operate on nibbles (which are nice because they show up as hex digits). The
/// list is ended with a end nibble (0) and depending on the nibble value (Below)
/// the value is either encoded into nibble itself or it can spill over into the
/// bytes that follow.
/// </summary>
private enum NumberListCodes : byte
{
End = 0x0, // ends the list. No valid value has this prefix.
LastImmediateValue = 0xA,
PrefixCode = 0xB,
MultiByte1 = 0xC, // 1 byte follows. If this Nibble is in the high bits, it the high bits of the number are stored in the low nibble.
// commented out because the code does not explicitly reference the names (but they are logically defined).
// MultiByte2 = 0xD, // 2 bytes follow (we don't bother with the nibble optimzation
// MultiByte3 = 0xE, // 3 bytes follow (we don't bother with the nibble optimzation
// MultiByte4 = 0xF, // 4 bytes follow (we don't bother with the nibble optimzation
}

/// <summary>
/// returns a string representation for the activity path. If the GUID is not an activity path then it returns
/// the normal string representation for a GUID.
/// </summary>
private static unsafe string ActivityPathString(Guid guid)
{
if (!IsActivityPath(guid))
return guid.ToString();

StringBuilder sb = new StringBuilder();
sb.Append('/'); // Use // to start to make it easy to anchor
byte* bytePtr = (byte*)&guid;
byte* endPtr = bytePtr + 12;
char separator = '/';
while (bytePtr < endPtr)
{
uint nibble = (uint)(*bytePtr >> 4);
bool secondNibble = false; // are we reading the second nibble (low order bits) of the byte.
NextNibble:
if (nibble == (uint)NumberListCodes.End)
break;
if (nibble <= (uint)NumberListCodes.LastImmediateValue)
{
sb.Append('/').Append(nibble);
if (!secondNibble)
{
nibble = (uint)(*bytePtr & 0xF);
secondNibble = true;
goto NextNibble;
}
// We read the second nibble so we move on to the next byte.
bytePtr++;
continue;
}
else if (nibble == (uint)NumberListCodes.PrefixCode)
{
// This are the prefix codes. If the next nibble is MultiByte, then this is an overflow ID.
// we we denote with a $ instead of a / separator.

// Read the next nibble.
if (!secondNibble)
nibble = (uint)(*bytePtr & 0xF);
else
{
bytePtr++;
if (endPtr <= bytePtr)
break;
nibble = (uint)(*bytePtr >> 4);
}

if (nibble < (uint)NumberListCodes.MultiByte1)
{
// If the nibble is less than MultiByte we have not defined what that means
// For now we simply give up, and stop parsing. We could add more cases here...
return guid.ToString();
}
// If we get here we have a overflow ID, which is just like a normal ID but the separator is $
separator = '$';
// Fall into the Multi-byte decode case.
}

Debug.Assert((uint)NumberListCodes.MultiByte1 <= nibble);
// At this point we are decoding a multi-byte number, either a normal number or a
// At this point we are byte oriented, we are fetching the number as a stream of bytes.
uint numBytes = nibble - (uint)NumberListCodes.MultiByte1;

uint value = 0;
if (!secondNibble)
value = (uint)(*bytePtr & 0xF);
bytePtr++; // Adance to the value bytes

numBytes++; // Now numBytes is 1-4 and reprsents the number of bytes to read.
if (endPtr < bytePtr + numBytes)
break;

// Compute the number (little endian) (thus backwards).
for (int i = (int)numBytes - 1; 0 <= i; --i)
value = (value << 8) + bytePtr[i];

// Print the value
sb.Append(separator).Append(value);

bytePtr += numBytes; // Advance past the bytes.
}

if (sb.Length == 0)
sb.Append('/');
return sb.ToString();
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("705eb0a8-8fb0-4f80-8f6a-7bb71a202a2a")]

[assembly: InternalsVisibleTo("Microsoft.Diagnostics.EventFlow.Inputs.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]

Loading

0 comments on commit d90094f

Please sign in to comment.