using System;
using System.Collections.Generic;
using System.Diagnostics.Eventing.Reader;
using System.Globalization;
namespace D365MetadataBridge.Services.Trace
{
///
/// Reads a D365FO ETW capture (.etl) and feeds an .
///
/// Event ids and data names are the ones in the provider manifests shipped with Trace Parser
/// (DynamicsAXXppExecutionTraces.man, DynamicsAXExecutionTraces.man):
/// Microsoft-Dynamics-AX-XppExecutionTraces {C0D248CE-634D-426B-9E31-5A50A6D83024}
/// 24500 XppMethodEnter, 24501 XppMethodExit — template ExecutionTrace (methodName, sessionId, userGuid, …)
/// Microsoft-Dynamics-AX-ExecutionTraces {70560195-BECD-45D4-AC93-97290953AD02}
/// 4922 AosSqlStatementExecutionLatency (sqlStatement, preparationTimeSeconds, executionTimeSeconds)
/// 4923 AosSqlStatementInputBind — counted only; parameterValue is customer data and is never selected
/// 4924 AosSqlRowFetchLatency (executionTimeSeconds)
/// Rendering the named properties needs those manifests registered on the machine, which they
/// are on an AOS/dev VM. A property selector is used so that only these few fields are rendered.
/// Thread and process come from the ETW header, the timestamp from TimeCreated (100 ns).
///
public static class EtlTraceReader
{
public static readonly Guid XppProvider = new Guid("C0D248CE-634D-426B-9E31-5A50A6D83024");
public static readonly Guid ExecutionProvider = new Guid("70560195-BECD-45D4-AC93-97290953AD02");
private static readonly string[] Selected =
{
"Event/EventData/Data[@Name='methodName']",
"Event/EventData/Data[@Name='sqlStatement']",
"Event/EventData/Data[@Name='executionTimeSeconds']",
"Event/EventData/Data[@Name='preparationTimeSeconds']",
"Event/EventData/Data[@Name='sessionId']",
"Event/EventData/Data[@Name='userGuid']",
};
public sealed class ReadStats
{
public long EventsRead;
public long EventsUnreadable;
}
/// Called every events with the number read so far.
public static ReadStats Read(string etlPath, XppTraceAggregator aggregator, Action? progress = null, long progressEvery = 250_000)
{
var stats = new ReadStats();
var selector = new EventLogPropertySelector(Selected);
using (var reader = new EventLogReader(new EventLogQuery(etlPath, PathType.FilePath)))
{
EventRecord? record;
while ((record = reader.ReadEvent()) != null)
{
using (record)
{
stats.EventsRead++;
if (progress != null && stats.EventsRead % progressEvery == 0) progress(stats.EventsRead);
var kind = KindOf(record);
if (kind == null) continue;
if (kind == XppTraceEventKind.Bind)
{
// Counted only: nothing of a bind event, least of all its value, is rendered.
aggregator.Add(new XppTraceEvent
{
Kind = XppTraceEventKind.Bind, ProcessId = record.ProcessId ?? 0, ThreadId = record.ThreadId ?? 0,
Ticks = record.TimeCreated.HasValue ? record.TimeCreated.Value.ToUniversalTime().Ticks : 0,
});
continue;
}
IList