Microsoft-Performance-Tools.../PerfDataExtension/PerfDataSourceParser.cs

385 строки
17 KiB
C#

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.PerfDataExtension
{
using Microsoft.LinuxTracepoints.Decode;
using Microsoft.Performance.SDK.Extensibility.SourceParsing;
using Microsoft.Performance.SDK.Processing;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using CancellationToken = System.Threading.CancellationToken;
using Debug = System.Diagnostics.Debug;
using EventHeaderFieldEncoding = Microsoft.LinuxTracepoints.EventHeaderFieldEncoding;
public sealed class PerfDataSourceParser : SourceParser<PerfDataEvent, PerfDataFileInfo, PerfEventHeaderType>
{
private const uint Billion = 1000000000;
private readonly HashSet<PerfEventHeaderType> requestedDataKeys = new HashSet<PerfEventHeaderType>();
private readonly string[] filenames;
private readonly List<PerfDataFileInfo> fileInfos;
private readonly ReadOnlyCollection<PerfDataFileInfo> fileInfosReadOnly;
private DataSourceInfo? dataSourceInfo;
private bool requestedAllEvents;
public PerfDataSourceParser(string[] filenames)
{
this.filenames = filenames;
this.fileInfos = new List<PerfDataFileInfo>(filenames.Length);
this.fileInfosReadOnly = this.fileInfos.AsReadOnly();
}
public const string SourceParserId = nameof(PerfDataSourceParser);
public override string Id => SourceParserId;
public ReadOnlyCollection<PerfDataFileInfo> FileInfos => this.fileInfosReadOnly;
public override DataSourceInfo DataSourceInfo => this.dataSourceInfo!;
public override void PrepareForProcessing(bool allEventsConsumed, IReadOnlyCollection<PerfEventHeaderType> requestedDataKeys)
{
this.requestedAllEvents = allEventsConsumed;
this.requestedDataKeys.Clear();
this.requestedDataKeys.UnionWith(requestedDataKeys);
}
public override void ProcessSource(
ISourceDataProcessor<PerfDataEvent, PerfDataFileInfo, PerfEventHeaderType> dataProcessor,
ILogger logger,
IProgress<int> progress,
CancellationToken cancellationToken)
{
this.fileInfos.Clear();
var sessionFirstTimeSpec = PerfTimeSpec.MaxValue;
using (var reader = new PerfDataFileReader())
{
var enumerator = new EventHeaderEnumerator();
var sb = new StringBuilder();
PerfEventBytes eventBytes;
PerfSampleEventInfo sampleEventInfo;
for (var filesProcessed = 0; filesProcessed < this.filenames.Length; filesProcessed += 1)
{
// For each file:
progress.Report((filesProcessed * 100) / this.filenames.Length);
if (cancellationToken.IsCancellationRequested)
{
logger.Warn("Processing cancelled with {0}/{1} files loaded.",
filesProcessed,
this.filenames.Length);
break;
}
var filename = this.filenames[filesProcessed];
if (!reader.OpenFile(filename, PerfDataFileEventOrder.Time))
{
logger.Error("Invalid data, cannot open file: {0}",
filename);
continue;
}
logger.Info("Opened file: {0}",
filename);
var byteReader = reader.ByteReader;
var fileInfo = new FileInfo(filename, byteReader);
var setHeaderAttributes = false;
var firstEventTime = ulong.MaxValue;
var lastEventTime = ulong.MinValue;
var commonFieldCount = ushort.MaxValue;
var previousEventTime = ulong.MinValue;
var eventCount = 0u;
while (true)
{
// For each event in the file:
if (cancellationToken.IsCancellationRequested)
{
logger.Warn("Processing cancelled with {0}/{1} files loaded; partially loaded file: {2}",
filesProcessed,
this.filenames.Length,
filename);
break;
}
var result = reader.ReadEvent(out eventBytes);
if (result != PerfDataFileResult.Ok)
{
if (result != PerfDataFileResult.EndOfFile)
{
logger.Error("Error: {0} reading from file: {1}",
result.AsString(),
filename);
}
break; // No more events in this file
}
if (!setHeaderAttributes)
{
if (eventBytes.Header.Type == PerfEventHeaderType.FinishedInit ||
eventBytes.Header.Type == PerfEventHeaderType.Sample)
{
setHeaderAttributes = true;
fileInfo.SetHeaderAttributes(reader);
}
}
eventCount += 1; // Include ignored events in the count.
if (!this.requestedAllEvents && !this.requestedDataKeys.Contains(eventBytes.Header.Type))
{
continue;
}
PerfDataEvent eventInfo;
if (eventBytes.Header.Type == PerfEventHeaderType.Sample)
{
result = reader.GetSampleEventInfo(eventBytes, out sampleEventInfo);
if (result != PerfDataFileResult.Ok)
{
logger.Warn("Skipped event: {0} resolving sample event from file: {1}",
result.AsString(),
filename);
continue;
}
if (sampleEventInfo.SampleType.HasFlag(PerfEventAttrSampleType.Time))
{
previousEventTime = sampleEventInfo.Time;
}
else
{
sampleEventInfo.Time = previousEventTime;
}
var format = sampleEventInfo.Format;
if (format.IsEmpty)
{
logger.Warn("Skipped event: no format information for sample event in file: {0}",
filename);
continue;
}
if (format.Fields.Count >= ushort.MaxValue)
{
logger.Warn("Skipped event: bad field count for sample event in file: {0}",
filename);
continue;
}
if (format.CommonFieldCount > format.Fields.Count)
{
logger.Warn("Skipped event: bad CommonFieldCount for sample event in file: {0}",
filename);
continue;
}
if (format.CommonFieldCount != commonFieldCount)
{
if (commonFieldCount != ushort.MaxValue)
{
logger.Warn("Skipped event: inconsistent CommonFieldCount for sample event in file: {0}",
filename);
continue;
}
commonFieldCount = format.CommonFieldCount;
}
if (sampleEventInfo.Time < firstEventTime)
{
firstEventTime = sampleEventInfo.Time;
}
if (sampleEventInfo.Time > lastEventTime)
{
lastEventTime = sampleEventInfo.Time;
}
if (format.DecodingStyle != PerfEventDecodingStyle.EventHeader ||
!enumerator.StartEvent(sampleEventInfo))
{
eventInfo = new PerfDataEvent(byteReader, eventBytes.Header, sampleEventInfo);
}
else
{
var userData = sampleEventInfo.UserDataSpan;
var info = enumerator.GetEventInfo(userData);
uint topLevelFieldCount = 0;
uint structFields = 0;
while (enumerator.MoveNextMetadata(userData))
{
if (structFields == 0)
{
topLevelFieldCount += 1;
}
else
{
structFields -= 1;
}
var itemMetadata = enumerator.GetItemMetadata();
if (itemMetadata.Encoding == EventHeaderFieldEncoding.Struct)
{
structFields += itemMetadata.StructFieldCount;
}
}
eventInfo = new PerfDataEvent(byteReader, eventBytes.Header, sampleEventInfo, info, (ushort)topLevelFieldCount);
}
}
else
{
result = reader.GetNonSampleEventInfo(eventBytes, out var nonSampleEventInfo);
if (result == PerfDataFileResult.Ok)
{
if (nonSampleEventInfo.SampleType.HasFlag(PerfEventAttrSampleType.Time))
{
previousEventTime = nonSampleEventInfo.Time;
}
else
{
nonSampleEventInfo.Time = previousEventTime;
}
if (nonSampleEventInfo.Time < firstEventTime)
{
firstEventTime = nonSampleEventInfo.Time;
}
if (nonSampleEventInfo.Time > lastEventTime)
{
lastEventTime = nonSampleEventInfo.Time;
}
eventInfo = new PerfDataEvent(byteReader, eventBytes.Header, nonSampleEventInfo);
}
else if (result == PerfDataFileResult.IdNotFound)
{
// Event info not available for this event. Maybe ok.
eventInfo = new PerfDataEvent(byteReader, eventBytes, previousEventTime);
}
else
{
logger.Warn("Skipped event: {0} resolving nonsample event from file: {1}",
result.AsString(),
filename);
continue;
}
}
dataProcessor.ProcessDataElement(eventInfo, fileInfo, cancellationToken);
}
fileInfo.SetFileAttributes(firstEventTime, lastEventTime, eventCount);
// Track the wall-clock time of the first event in the session
// (but only if the file had one or more time-stamped events).
if (firstEventTime <= lastEventTime)
{
var fileFirstTimeSpec = fileInfo.FirstEventTimeSpec;
if (fileFirstTimeSpec < sessionFirstTimeSpec)
{
sessionFirstTimeSpec = fileFirstTimeSpec;
}
}
logger.Info("Finished file: {0}",
filename);
this.fileInfos.Add(fileInfo);
}
}
long sessionFirst;
long sessionLast;
if (sessionFirstTimeSpec == PerfTimeSpec.MaxValue)
{
// No events were found in any file.
sessionFirstTimeSpec = PerfTimeSpec.UnixEpoch;
sessionFirst = 0;
sessionLast = 0;
}
else
{
sessionFirst = long.MaxValue;
sessionLast = long.MinValue;
foreach (var fileInfo in this.fileInfos)
{
var fileOffsetSpec = fileInfo.ClockOffset;
// Compute the difference between session-relative and file-relative timestamps.
// sessionFirstTimeSpec + sessionTimestampOffset = fileOffsetSpec.
var sessionTimestampOffset =
(fileOffsetSpec.TvSec - sessionFirstTimeSpec.TvSec) * Billion
+ fileOffsetSpec.TvNsec - sessionFirstTimeSpec.TvNsec;
// File-relative timestamp + SessionTimestampOffset = session-relative timestamp.
((FileInfo)fileInfo).SetSessionAttributes(sessionTimestampOffset);
var fileFirstFileRelative = fileInfo.FirstEventTime;
var fileLastFileRelative = fileInfo.LastEventTime;
if (fileFirstFileRelative <= fileLastFileRelative)
{
var fileFirst = (long)fileFirstFileRelative + sessionTimestampOffset;
if (fileFirst < sessionFirst)
{
sessionFirst = fileFirst;
}
var fileLast = (long)fileLastFileRelative + sessionTimestampOffset;
if (fileLast > sessionLast)
{
sessionLast = fileLast;
}
}
}
}
Debug.Assert(sessionFirst == 0);
Debug.Assert(sessionFirst <= sessionLast);
this.dataSourceInfo = new DataSourceInfo(
0,
sessionLast,
sessionFirstTimeSpec.DateTime ?? DateTime.UnixEpoch);
progress.Report(100);
}
private sealed class FileInfo : PerfDataFileInfo
{
public FileInfo(string filename, PerfByteReader byteReader)
: base(filename, byteReader)
{
return;
}
public new void SetHeaderAttributes(PerfDataFileReader reader)
{
base.SetHeaderAttributes(reader);
}
public new void SetFileAttributes(ulong firstEventTime, ulong lastEventTime, uint eventCount)
{
base.SetFileAttributes(firstEventTime, lastEventTime, eventCount);
}
public new void SetSessionAttributes(long sessionTimestampOffset)
{
base.SetSessionAttributes(sessionTimestampOffset);
}
}
}
}