using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; using Time = System.Double; using ObjectID = System.UInt64; namespace ProfileEventViewer { static class LoadingLogsToTrace { public enum LoadingLogType { LoadingProfiler, FastloadManager, BigFileAsynchStream, SoundStream, Unknown } public static bool IsLoadingLog(string fileName) { return GetLoadingLogType(fileName) != LoadingLogType.Unknown; } private static LoadingLogType GetLoadingLogType(string fileName) { // Verify (very coarsely) of which loading log type the file is try { if (Path.GetExtension(fileName).ToLower() != ".csv") return LoadingLogType.Unknown; using (StreamReader reader = File.OpenText(fileName)) { string headerLine = reader.ReadLine(); // Not really rock-solid, but should do the trick for now... if (headerLine.Contains("Cancelled")) return LoadingLogType.LoadingProfiler; else if (headerLine.Contains("Recycle")) return LoadingLogType.BigFileAsynchStream; else if (headerLine.Contains("Container")) return LoadingLogType.FastloadManager; else if (headerLine.Contains("Sound Type")) return LoadingLogType.SoundStream; } } catch { } return LoadingLogType.Unknown; } public static string CreateTraceFromLoadingLogs(IEnumerable fileNames) { List logFileNames = new List(); foreach (string fileName in fileNames) { if (!IsLoadingLog(fileName)) { MessageBox.Show(string.Format("File {0} does not appear to be a loading log.\n\nPlease see the help for more information.", fileName), "Loading logs", MessageBoxButtons.OK, MessageBoxIcon.Warning); continue; } logFileNames.Add(fileName); } SimplifiedTrace trace = new SimplifiedTrace(); foreach (string fileName in logFileNames) { LoadingLogType type = GetLoadingLogType(fileName); using (StreamReader reader = File.OpenText(fileName)) { reader.ReadLine(); // Skip header if (type == LoadingLogType.LoadingProfiler) { ReadLoadingProfilerLog(reader, trace); } else if (type == LoadingLogType.BigFileAsynchStream) { ReadBigFileAsynchStreamLog(reader, trace, GetPriorityFromFilename(fileName)); } else if (type == LoadingLogType.FastloadManager) { ReadFastloadManagerLog(reader, trace); } else if (type == LoadingLogType.SoundStream) { ReadSoundStreamLog(reader, trace); } } } if (logFileNames.Count > 0) { SaveFileDialog saveDialog = new SaveFileDialog(); saveDialog.DefaultExt = "pel2"; saveDialog.Filter = "Profile event log (*.pel2)|*.pel2"; saveDialog.Title = "Save Loading Logs Trace"; saveDialog.FileName = "loadingstat"; DialogResult result = saveDialog.ShowDialog(); if (result == DialogResult.OK) { trace.Write(saveDialog.FileName); return saveDialog.FileName; } } return null; } #region FastloadManager logs private static int ReadFastloadManagerLog(StreamReader reader, SimplifiedTrace trace) { List stats = new List(); Dictionary containerProfiles = new Dictionary(); containerProfiles.Add(0, trace.NextProfileID); trace.Profiles.Add(new EventFileProfileDesc() { m_Name = "No Container", m_ProfileID = trace.NextProfileID }); Dictionary groupProfiles = new Dictionary(); groupProfiles.Add(0, trace.NextProfileID); trace.Profiles.Add(new EventFileProfileDesc() { m_Name = "No Group", m_ProfileID = trace.NextProfileID }); Dictionary postLoadProfiles = new Dictionary(); postLoadProfiles.Add(unchecked((ObjectID)PostLoadStep.Link), trace.NextProfileID); trace.Profiles.Add(new EventFileProfileDesc() { m_Name = "Link Requests", m_ProfileID = trace.NextProfileID }); postLoadProfiles.Add(unchecked((ObjectID)PostLoadStep.InitBeforeHandleActivation), trace.NextProfileID); trace.Profiles.Add(new EventFileProfileDesc() { m_Name = "Init Before Handle Activation", m_ProfileID = trace.NextProfileID }); postLoadProfiles.Add(unchecked((ObjectID)PostLoadStep.InitAfterHandleActivation), trace.NextProfileID); trace.Profiles.Add(new EventFileProfileDesc() { m_Name = "Init After Handle Activation", m_ProfileID = trace.NextProfileID }); postLoadProfiles.Add(unchecked((ObjectID)PostLoadStep.DelayedResources), trace.NextProfileID); trace.Profiles.Add(new EventFileProfileDesc() { m_Name = "Delayed Resources", m_ProfileID = trace.NextProfileID }); // Read the data string line = reader.ReadLine(); while (line != null) { string[] tokens = line.Split(','); if (tokens.Length != 9) { Console.WriteLine("Malformed line does not contain 9 tokens: \"{0}\"", line); return 2; } ObjectLoadingStat stat = new ObjectLoadingStat(); stat.Key = ObjectID.Parse(tokens[0].Substring(2, tokens[0].Length - 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); stat.Container = ObjectID.Parse(tokens[1].Substring(2, tokens[1].Length - 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); stat.Group = ObjectID.Parse(tokens[2].Substring(2, tokens[2].Length - 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); stat.Size = UInt32.Parse(tokens[3]); stat.Source = UInt32.Parse(tokens[4]); stat.StartTime = UInt64.Parse(tokens[5]); stat.ReadHeaderTime = UInt64.Parse(tokens[6]); stat.ReadTime = UInt64.Parse(tokens[7]); stat.SkipTime = UInt64.Parse(tokens[8]); UInt32 postLoadProfileIndex; if (!postLoadProfiles.TryGetValue(stat.Key, out postLoadProfileIndex)) { if (stat.SkipTime == 0) { if (stat.ReadHeaderTime < stat.StartTime) throw new InvalidDataException(string.Format("FastloadManager log file corrupt when reading load event: stat.ReadHeaderTime ({0}) < stat.StartTime ({1}).", stat.ReadHeaderTime, stat.StartTime)); if (stat.ReadTime < stat.ReadHeaderTime) throw new InvalidDataException(string.Format("FastloadManager log file corrupt when reading load event: stat.ReadTime ({0}) < stat.ReadHeaderTime ({1}).", stat.ReadTime, stat.ReadHeaderTime)); } else { if (stat.SkipTime < stat.StartTime) throw new InvalidDataException(string.Format("FastloadManager log file corrupt when reading load event: stat.SkipTime ({0}) < stat.StartTime ({1}).", stat.SkipTime, stat.StartTime)); } stat.IsPostLoadEvent = false; stat.HeaderProfile = new EventFileProfileDesc() { m_Name = string.Format("FileHeader 0x{0:x08} 0x{1:x08} {2} [{3}]", stat.Key, stat.Group, stat.Size, stat.Source), m_ProfileID = trace.NextProfileID }; trace.Profiles.Add(stat.HeaderProfile); stat.Profile = new EventFileProfileDesc() { m_Name = string.Format("File{4} 0x{0:x08} 0x{1:x08} {2} [{3}]", stat.Key, stat.Group, stat.Size, stat.Source, stat.ReadTime != 0 ? "Read" : "Skip"), m_ProfileID = trace.NextProfileID }; trace.Profiles.Add(stat.Profile); UInt32 containerProfileIndex; if (!containerProfiles.TryGetValue(stat.Container, out containerProfileIndex)) { containerProfileIndex = trace.NextProfileID; trace.Profiles.Add(new EventFileProfileDesc() { m_Name = string.Format("Container 0x{0:x08} [{1}]", stat.Container, stat.Source), m_ProfileID = trace.NextProfileID }); containerProfiles.Add(stat.Container, containerProfileIndex); } stat.ContainerProfile = trace.Profiles[(int)containerProfileIndex]; UInt32 groupProfileIndex; if (!groupProfiles.TryGetValue(stat.Group, out groupProfileIndex)) { groupProfileIndex = trace.NextProfileID; trace.Profiles.Add(new EventFileProfileDesc() { m_Name = string.Format("Group 0x{0:x08}", stat.Group), m_ProfileID = trace.NextProfileID }); groupProfiles.Add(stat.Group, groupProfileIndex); } stat.GroupProfile = trace.Profiles[(int)groupProfileIndex]; } else { // Special case: post load! if (stat.ReadTime < stat.StartTime) throw new InvalidDataException(string.Format("FastloadManager log file corrupt when reading postload event: stat.ReadTime ({0}) < stat.StartTime ({1}).", stat.ReadTime, stat.StartTime)); stat.IsPostLoadEvent = true; stat.PostLoadProfile = trace.Profiles[(int)postLoadProfileIndex]; } stats.Add(stat); line = reader.ReadLine(); } // Create contexts. EventFileThreadDesc context = new EventFileThreadDesc(); context.m_Name = "FastloadManager"; context.m_ThreadID = trace.NextContextID; context.m_OSThreadID = trace.NextContextID; trace.Contexts.Add(context); // Create events if (stats.Count > 0) { { WriteEventStart(trace.ProfileEvents, context.m_ThreadID, stats[0].GroupProfile.m_ProfileID, stats[0].StartTime); WriteEventStart(trace.ProfileEvents, context.m_ThreadID, stats[0].ContainerProfile.m_ProfileID, stats[0].StartTime); if (stats[0].ReadTime != 0) { WriteEvent(trace.ProfileEvents, context.m_ThreadID, stats[0].HeaderProfile.m_ProfileID, stats[0].StartTime, stats[0].ReadHeaderTime); WriteEvent(trace.ProfileEvents, context.m_ThreadID, stats[0].Profile.m_ProfileID, stats[0].ReadHeaderTime, stats[0].ReadTime); } else { WriteEvent(trace.ProfileEvents, context.m_ThreadID, stats[0].Profile.m_ProfileID, stats[0].StartTime, stats[0].SkipTime); } UInt64 endTime = (stats[0].ReadTime != 0 ? stats[0].ReadTime : stats[0].SkipTime); WriteEvent(trace.ProfileEvents, context.m_ThreadID, stats[0].Profile.m_ProfileID, stats[0].ReadHeaderTime, endTime); if (stats.Count == 1 || stats[0].Container == 0 || stats[0].Container != stats[1].Container) WriteEventEnd(trace.ProfileEvents, context.m_ThreadID, stats[0].ContainerProfile.m_ProfileID, endTime); if (stats.Count == 1 || stats[0].Group == 0 || stats[0].Group != stats[1].Group) WriteEventEnd(trace.ProfileEvents, context.m_ThreadID, stats[0].GroupProfile.m_ProfileID, endTime); } for (int i = 1; i < stats.Count; ++i) { if (!stats[i].IsPostLoadEvent) { if (stats[i].Group != stats[i - 1].Group || stats[i].Group == 0) WriteEventStart(trace.ProfileEvents, context.m_ThreadID, stats[i].GroupProfile.m_ProfileID, stats[i].StartTime); if (stats[i].Container != stats[i - 1].Container || stats[i].Container == 0) WriteEventStart(trace.ProfileEvents, context.m_ThreadID, stats[i].ContainerProfile.m_ProfileID, stats[i].StartTime); if (stats[i].ReadTime != 0) { WriteEvent(trace.ProfileEvents, context.m_ThreadID, stats[i].HeaderProfile.m_ProfileID, stats[i].StartTime, stats[i].ReadHeaderTime); WriteEvent(trace.ProfileEvents, context.m_ThreadID, stats[i].Profile.m_ProfileID, stats[i].ReadHeaderTime, stats[i].ReadTime); } else { WriteEvent(trace.ProfileEvents, context.m_ThreadID, stats[i].Profile.m_ProfileID, stats[i].StartTime, stats[i].SkipTime); } UInt64 endTime = (stats[i].ReadTime != 0 ? stats[i].ReadTime : stats[i].SkipTime); if (i + 1 >= stats.Count || stats[i].Container == 0 || stats[i].Container != stats[i + 1].Container) WriteEventEnd(trace.ProfileEvents, context.m_ThreadID, stats[i].ContainerProfile.m_ProfileID, endTime); if (i + 1 >= stats.Count || stats[i].Group == 0 || stats[i].Group != stats[i + 1].Group) WriteEventEnd(trace.ProfileEvents, context.m_ThreadID, stats[i].GroupProfile.m_ProfileID, endTime); } else { WriteEvent(trace.ProfileEvents, context.m_ThreadID, stats[i].PostLoadProfile.m_ProfileID, stats[i].StartTime, stats[i].ReadTime); } } } return 0; } private class ObjectLoadingStat { public ObjectID Key; public ObjectID Group; public ObjectID Container; public UInt32 Size; public UInt32 Source; public UInt64 StartTime; public UInt64 ReadHeaderTime; // 0 if SkipTime is set public UInt64 ReadTime; // 0 if SkipTime is set public UInt64 SkipTime; public bool IsPostLoadEvent; public EventFileProfileDesc HeaderProfile; public EventFileProfileDesc Profile; public EventFileProfileDesc ContainerProfile; public EventFileProfileDesc GroupProfile; public EventFileProfileDesc PostLoadProfile; // If this is used, the other profiles are not } enum PostLoadStep { Link = -1, InitBeforeHandleActivation = -2, InitAfterHandleActivation = -3, DelayedResources = -4, }; #endregion #region BigFileAsynchStream logs private static int ReadBigFileAsynchStreamLog(StreamReader reader, SimplifiedTrace trace, StreamingPriority priority) { List files = new List(); List chunks = new List(); HashSet chunkIndices = new HashSet(); EventFileProfileDesc recycleProfile = new EventFileProfileDesc() { m_Name = "No Group", m_ProfileID = trace.NextProfileID }; trace.Profiles.Add(recycleProfile); Dictionary groupProfiles = new Dictionary(); groupProfiles.Add(0, trace.NextProfileID); EventFileProfileDesc noGroupProfile = new EventFileProfileDesc() { m_Name = "No Group", m_ProfileID = trace.NextProfileID }; trace.Profiles.Add(noGroupProfile); // Read the data string line = reader.ReadLine(); while (line != null) { string[] tokens = line.Split(','); if (tokens.Length != 11) { Console.WriteLine("Malformed line does not contain 11 tokens: \"{0}\"", line); return 2; } if (tokens[0] == "File") { PrefetchFileStat file = new PrefetchFileStat(); file.Key = ObjectID.Parse(tokens[1].Substring(2, tokens[1].Length - 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); file.Group = ObjectID.Parse(tokens[2].Substring(2, tokens[2].Length - 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); file.Offset = UInt64.Parse(tokens[3]); file.Size = UInt32.Parse(tokens[4]); file.RequestStartTime = UInt64.Parse(tokens[5]); file.StreamStartTime = UInt64.Parse(tokens[6]); file.EndTime = UInt64.Parse(tokens[7]); if (file.RequestStartTime > file.StreamStartTime) throw new InvalidDataException(string.Format("BigFileAsynchStream log file corrupt: file.RequestStartTime ({0}) > file.StreamStartTime ({1}).", file.RequestStartTime, file.StreamStartTime)); if (file.StreamStartTime > file.EndTime) throw new InvalidDataException(string.Format("BigFileAsynchStream log file corrupt: file.StreamStartTime ({0}) > file.EndTime ({1}).", file.StreamStartTime, file.EndTime)); file.Profile = new EventFileProfileDesc() { m_Name = string.Format("File 0x{0:x08} 0x{1:x08} {2} {3}", file.Key, file.Group, file.Offset, file.Size), m_ProfileID = trace.NextProfileID }; trace.Profiles.Add(file.Profile); UInt32 groupProfileIndex; if (!groupProfiles.TryGetValue(file.Group, out groupProfileIndex)) { groupProfileIndex = trace.NextProfileID; trace.Profiles.Add(new EventFileProfileDesc() { m_Name = string.Format("Group 0x{0:x08}", file.Group), m_ProfileID = trace.NextProfileID }); groupProfiles.Add(file.Group, groupProfileIndex); } file.GroupProfile = trace.Profiles[(int)groupProfileIndex]; files.Add(file); } else // Chunk { PrefetchChunkStat chunk = new PrefetchChunkStat(); chunk.Index = UInt32.Parse(tokens[1]); chunk.Offset = UInt64.Parse(tokens[3]); chunk.Size = UInt32.Parse(tokens[4]); chunk.StartTime = UInt64.Parse(tokens[6]); chunk.EndTime = UInt64.Parse(tokens[7]); chunk.RecycleTime = UInt64.Parse(tokens[8]); if (chunk.EndTime < chunk.StartTime) throw new InvalidDataException(string.Format("BigFileAsynchStream log file corrupt: chunk.EndTime ({0}) < chunk.StartTime ({1}).", chunk.EndTime, chunk.StartTime)); if (chunk.RecycleTime != 0 && chunk.RecycleTime < chunk.EndTime) throw new InvalidDataException(string.Format("BigFileAsynchStream log file corrupt: chunk.RecycleTime ({0}) < chunk.EndTime ({1}).", chunk.RecycleTime, chunk.EndTime)); chunk.Profile = new EventFileProfileDesc() { m_Name = string.Format("Chunk {0} {1}", chunk.Offset, chunk.Size), m_ProfileID = trace.NextProfileID }; trace.Profiles.Add(chunk.Profile); chunkIndices.Add(chunk.Index); chunks.Add(chunk); } line = reader.ReadLine(); } // Create contexts. EventFileThreadDesc fileContext = new EventFileThreadDesc(); fileContext.m_Name = string.Format("File Requests{0}", priority == StreamingPriority.High ? " - High" : ""); fileContext.m_ThreadID = trace.NextContextID; fileContext.m_OSThreadID = trace.NextContextID; trace.Contexts.Add(fileContext); UInt32 baseChunkContextID = trace.NextContextID; for (int i = 0; i < chunkIndices.Count; ++i) { EventFileThreadDesc context = new EventFileThreadDesc(); context.m_Name = string.Format("Prefetch Chunk {0}{1}", i, priority == StreamingPriority.High ? " - High" : ""); context.m_ThreadID = trace.NextContextID; context.m_OSThreadID = trace.NextContextID; trace.Contexts.Add(context); } // Create events if (files.Count > 0) { WriteEventStart(trace.ProfileEvents, fileContext.m_ThreadID, files[0].GroupProfile.m_ProfileID, files[0].StreamStartTime); WriteEvent(trace.ProfileEvents, fileContext.m_ThreadID, files[0].Profile.m_ProfileID, files[0].StreamStartTime, files[0].EndTime); if (files.Count == 1 || files[0].Group == 0 || files[0].Group != files[1].Group) WriteEventEnd(trace.ProfileEvents, fileContext.m_ThreadID, files[0].GroupProfile.m_ProfileID, files[0].EndTime); trace.DataEvents.Add(new DataEvent { m_ContextID = fileContext.m_ThreadID, m_Data = string.Format("Request 0x{0:x08}", files[0].Key), m_Time = files[0].RequestStartTime }); for (int i = 1; i < files.Count; ++i) { if (files[i].Group != files[i - 1].Group || files[i].Group == 0) WriteEventStart(trace.ProfileEvents, fileContext.m_ThreadID, files[i].GroupProfile.m_ProfileID, files[i].StreamStartTime); WriteEvent(trace.ProfileEvents, fileContext.m_ThreadID, files[i].Profile.m_ProfileID, files[i].StreamStartTime, files[i].EndTime); trace.DataEvents.Add(new DataEvent { m_ContextID = fileContext.m_ThreadID, m_Data = string.Format("Request 0x{0:x08}", files[i].Key), m_Time = files[i].RequestStartTime }); if (i + 1 >= files.Count || files[i].Group == 0 || files[i].Group != files[i + 1].Group) WriteEventEnd(trace.ProfileEvents, fileContext.m_ThreadID, files[i].GroupProfile.m_ProfileID, files[i].EndTime); } //foreach (PrefetchFileStat file in files) //{ // WriteEvent(profileEvents, fileContext.m_ThreadID, file.Profile.m_ProfileID, file.StreamStartTime, file.EndTime); // dataEvents.Add(new DataEvent { m_ContextID = fileContext.m_ThreadID, m_Data = string.Format("Request 0x{0:x08}", file.Key), m_Time = file.RequestStartTime }); //} } foreach (PrefetchChunkStat chunk in chunks) { WriteEvent(trace.ProfileEvents, baseChunkContextID + chunk.Index, chunk.Profile.m_ProfileID, chunk.StartTime, chunk.EndTime); if (chunk.RecycleTime != 0) WriteEvent(trace.ProfileEvents, baseChunkContextID + chunk.Index, recycleProfile.m_ProfileID, chunk.EndTime, chunk.RecycleTime); } return 0; } private static StreamingPriority GetPriorityFromFilename(string fileName) { if (fileName.Contains("_H")) return StreamingPriority.High; return StreamingPriority.Normal; } private enum StreamingPriority { Normal, High, } private struct PrefetchFileStat { public ObjectID Key; public ObjectID Group; public UInt64 Offset; public UInt32 Size; public UInt64 RequestStartTime; public UInt64 StreamStartTime; public UInt64 EndTime; public EventFileProfileDesc Profile; public EventFileProfileDesc GroupProfile; } private struct PrefetchChunkStat { public UInt32 Index; public UInt64 Offset; public UInt32 Size; public UInt64 StartTime; public UInt64 EndTime; public UInt64 RecycleTime; public EventFileProfileDesc Profile; } #endregion #region Loading Profiler logs private static int ReadLoadingProfilerLog(StreamReader reader, SimplifiedTrace trace) { List stats = new List(); EventFileProfileDesc whiteRoomProfile = new EventFileProfileDesc() { m_Name = "WhiteRoom", m_ProfileID = trace.NextProfileID }; trace.Profiles.Add(whiteRoomProfile); EventFileProfileDesc waitingProfile = new EventFileProfileDesc() { m_Name = "Waiting For Loading", m_ProfileID = trace.NextProfileID }; trace.Profiles.Add(waitingProfile); EventFileProfileDesc ghostProfile = new EventFileProfileDesc() { m_Name = "Ghosting", m_ProfileID = trace.NextProfileID }; trace.Profiles.Add(ghostProfile); // Read the data string line = reader.ReadLine(); while (line != null) { string[] tokens = line.Split(','); if (tokens.Length != 6) { Console.WriteLine("Malformed line does not contain 6 tokens: \"{0}\"", line); return 2; } LoadingProfilerStat stat = new LoadingProfilerStat(); stat.ObjectID = ObjectID.Parse(tokens[0].Substring(2, tokens[0].Length - 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); stat.AdditionalID = ObjectID.Parse(tokens[1].Substring(2, tokens[1].Length - 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); stat.Type = (LoadingType)Enum.Parse(typeof(LoadingType), tokens[2]); stat.StartTime = UInt64.Parse(tokens[3]); stat.EndTime = UInt64.Parse(tokens[4]); if (stat.EndTime < stat.StartTime && stat.Type != LoadingType.Custom) throw new InvalidDataException(string.Format("LoadingProfiler log file corrupt: stat.EndTime ({0}) < stat.StartTime ({1}).", stat.EndTime, stat.StartTime)); stat.Cancelled = (UInt32.Parse(tokens[5]) != 0); if (stat.Type == LoadingType.WhiteRoom) { stat.Profile = whiteRoomProfile; } else if (stat.Type == LoadingType.WaitingForLoading && stat.ObjectID == 0ul) { stat.Profile = waitingProfile; } else if (stat.Type == LoadingType.Custom) { CustomType customType = (CustomType)stat.ObjectID; if (customType == CustomType.Ghost) { stat.Profile = ghostProfile; } else if (customType == CustomType.Walkthrough) { stat.Profile = new EventFileProfileDesc() { m_Name = string.Format("Walkthrough {0}", stat.AdditionalID), m_ProfileID = trace.NextProfileID }; trace.Profiles.Add(stat.Profile); } else { throw new NotImplementedException(string.Format("An unrecognized CustomType was used ({0}). Please update the executable with this new CustomType.", customType)); } } else { stat.Profile = new EventFileProfileDesc() { m_Name = string.Format("{0} 0x{1:x08}{2}", stat.Type, stat.ObjectID, stat.Cancelled ? " (cancelled)" : ""), m_ProfileID = trace.NextProfileID }; trace.Profiles.Add(stat.Profile); } stats.Add(stat); line = reader.ReadLine(); } // Create contexts. EventFileThreadDesc loadingContext = new EventFileThreadDesc(); loadingContext.m_Name = "Loading"; loadingContext.m_ThreadID = trace.NextContextID; loadingContext.m_OSThreadID = trace.NextContextID; trace.Contexts.Add(loadingContext); EventFileThreadDesc missionContext = new EventFileThreadDesc(); missionContext.m_Name = "Mission"; missionContext.m_ThreadID = trace.NextContextID; missionContext.m_OSThreadID = trace.NextContextID; trace.Contexts.Add(missionContext); EventFileThreadDesc datalayerContext = new EventFileThreadDesc(); datalayerContext.m_Name = "Data Layers"; datalayerContext.m_ThreadID = trace.NextContextID; datalayerContext.m_OSThreadID = trace.NextContextID; trace.Contexts.Add(datalayerContext); //EventFileThreadDesc worldContext = new EventFileThreadDesc(); //worldContext.m_Name = "World"; //worldContext.m_ThreadID = trace.NextContextID;; //worldContext.m_OSThreadID = trace.NextContextID;; //trace.Contexts.Add(worldContext); EventFileThreadDesc whiteRoomContext = new EventFileThreadDesc(); whiteRoomContext.m_Name = "WhiteRoom"; whiteRoomContext.m_ThreadID = trace.NextContextID; whiteRoomContext.m_OSThreadID = trace.NextContextID; trace.Contexts.Add(whiteRoomContext); EventFileThreadDesc waitingContext = new EventFileThreadDesc(); waitingContext.m_Name = "Waiting For Loading"; waitingContext.m_ThreadID = trace.NextContextID; waitingContext.m_OSThreadID = trace.NextContextID; trace.Contexts.Add(waitingContext); // Custom contexts EventFileThreadDesc ghostContext = new EventFileThreadDesc(); ghostContext.m_Name = "Ghost"; ghostContext.m_ThreadID = trace.NextContextID; ghostContext.m_OSThreadID = trace.NextContextID; trace.Contexts.Add(ghostContext); EventFileThreadDesc walkthroughContext = new EventFileThreadDesc(); walkthroughContext.m_Name = "Walkthroughs"; walkthroughContext.m_ThreadID = trace.NextContextID; walkthroughContext.m_OSThreadID = trace.NextContextID; trace.Contexts.Add(walkthroughContext); // Create events UInt64 lastLoadingEndTime = UInt64.MaxValue; UInt64 lastDataLayerEndTime = UInt64.MaxValue; UInt64 lastWaitingEndTime = UInt64.MaxValue; bool ghosting = false; uint lastWalkthroughProfileId = uint.MaxValue; foreach (LoadingProfilerStat stat in stats) { //if (stat.Type == LoadingType.World) //{ // WriteEvent(trace.ProfileEvents, worldContext.m_ThreadID, stat.Profile.m_ProfileID, stat.StartTime, stat.EndTime); //} if (stat.Type == LoadingType.NonSpatialDataLayer) { UInt64 startTime = stat.StartTime; if (stat.StartTime <= lastDataLayerEndTime && stat.EndTime >= lastDataLayerEndTime) startTime = lastDataLayerEndTime; WriteEvent(trace.ProfileEvents, datalayerContext.m_ThreadID, stat.Profile.m_ProfileID, startTime, stat.EndTime); lastDataLayerEndTime = stat.EndTime; } if (stat.Type == LoadingType.WaitingForLoading) { UInt64 startTime = stat.StartTime; if (stat.StartTime <= lastWaitingEndTime && stat.EndTime >= lastWaitingEndTime) startTime = lastWaitingEndTime; WriteEvent(trace.ProfileEvents, waitingContext.m_ThreadID, stat.Profile.m_ProfileID, startTime, stat.EndTime); lastWaitingEndTime = stat.EndTime; } else if (stat.Type == LoadingType.HighRes || stat.Type == LoadingType.DataBlock || stat.Type == LoadingType.World) { if (!(stat.Type == LoadingType.DataBlock && stat.Cancelled)) { UInt64 startTime = stat.StartTime; if (stat.StartTime <= lastLoadingEndTime && stat.EndTime >= lastLoadingEndTime) startTime = lastLoadingEndTime; WriteEvent(trace.ProfileEvents, loadingContext.m_ThreadID, stat.Profile.m_ProfileID, startTime, stat.EndTime); lastLoadingEndTime = stat.EndTime; } else { // Data events, for now... trace.DataEvents.Add(new DataEvent() { m_Time = stat.StartTime, m_ContextID = loadingContext.m_ThreadID, m_Data = string.Format("Start Datablock 0x{0:x8}", stat.ObjectID) }); trace.DataEvents.Add(new DataEvent() { m_Time = stat.EndTime, m_ContextID = loadingContext.m_ThreadID, m_Data = string.Format("Cancel Datablock 0x{0:x8}", stat.ObjectID) }); } } else if (stat.Type == LoadingType.Mission) { // Data event, for now... trace.DataEvents.Add(new DataEvent() { m_Time = stat.StartTime, m_ContextID = loadingContext.m_ThreadID, m_Data = string.Format("Start Mission 0x{0:x8}", stat.ObjectID) }); trace.DataEvents.Add(new DataEvent() { m_Time = stat.EndTime, m_ContextID = loadingContext.m_ThreadID, m_Data = string.Format("{1} Mission 0x{0:x8}", stat.ObjectID, stat.Cancelled ? "Cancel" : "End") }); } else if (stat.Type == LoadingType.WhiteRoom) { WriteEvent(trace.ProfileEvents, whiteRoomContext.m_ThreadID, stat.Profile.m_ProfileID, stat.StartTime, stat.EndTime); } else if (stat.Type == LoadingType.Custom) { CustomType customType = (CustomType)stat.ObjectID; bool startEvent = (stat.StartTime != 0); if (customType == CustomType.Ghost) { if (startEvent) WriteEventStart(trace.ProfileEvents, ghostContext.m_ThreadID, stat.Profile.m_ProfileID, stat.StartTime); else WriteEventEnd(trace.ProfileEvents, ghostContext.m_ThreadID, stat.Profile.m_ProfileID, stat.EndTime); ghosting = !ghosting; } else if (customType == CustomType.Walkthrough) { // If the last walkthrough was not closed, close it. if (lastWalkthroughProfileId != uint.MaxValue) WriteEventEnd(trace.ProfileEvents, walkthroughContext.m_ThreadID, lastWalkthroughProfileId, stat.StartTime); if (startEvent) { WriteEventStart(trace.ProfileEvents, walkthroughContext.m_ThreadID, stat.Profile.m_ProfileID, stat.StartTime); lastWalkthroughProfileId = stat.Profile.m_ProfileID; } else { WriteEventEnd(trace.ProfileEvents, walkthroughContext.m_ThreadID, stat.Profile.m_ProfileID, stat.EndTime); lastWalkthroughProfileId = uint.MaxValue; } } } } if (ghosting) // Close ghosting if we were ghosting at the end of the log WriteEventEnd(trace.ProfileEvents, ghostContext.m_ThreadID, ghostProfile.m_ProfileID, trace.ProfileEvents[trace.ProfileEvents.Count - 1].m_Time); if (lastWalkthroughProfileId != uint.MaxValue) // Close walkthrough if it was not closed properly WriteEventEnd(trace.ProfileEvents, walkthroughContext.m_ThreadID, lastWalkthroughProfileId, trace.ProfileEvents[trace.ProfileEvents.Count - 1].m_Time); return 0; } private enum LoadingType { World, Mission, //SpatialDataLayer, // Unused, as there is no way for now to know when this is done. NonSpatialDataLayer, DataBlock, HighRes, WhiteRoom, WaitingForLoading, Custom, } private enum CustomType { Ghost, Walkthrough, // Can have additional data. COUNT } private struct LoadingProfilerStat { public ObjectID ObjectID; public ObjectID AdditionalID; public LoadingType Type; public UInt64 StartTime; public UInt64 EndTime; public bool Cancelled; public EventFileProfileDesc Profile; } #endregion #region SoundStream logs private static int ReadSoundStreamLog(StreamReader reader, SimplifiedTrace trace) { List sounds = new List(); // Read the data string line = reader.ReadLine(); while (line != null) { string[] tokens = line.Split(','); if (tokens.Length != 7) throw new InvalidDataException(string.Format("SoundStream log file corrupt: Malformed line does not contain 7 tokens: \"{0}\"", line)); SoundStreamStat sound = new SoundStreamStat(); sound.SoundType = tokens[0] == "Bank" ? LoadSoundType.Bank : LoadSoundType.Stream; sound.ReadOffset = UInt64.Parse(tokens[1]); sound.ReadSize = UInt32.Parse(tokens[2]); sound.BufferSize = UInt32.Parse(tokens[3]); sound.FileSize = Int64.Parse(tokens[4]); sound.StartTime = UInt64.Parse(tokens[5]); sound.EndTime = UInt64.Parse(tokens[6]); if (sound.StartTime > sound.EndTime) throw new InvalidDataException(string.Format("SoundStream log file corrupt: sound.StartTime ({0}) > sound.EndTime ({1}).", sound.StartTime, sound.EndTime)); sound.Profile = new EventFileProfileDesc() { m_Name = string.Format("Sound {0} {1} {2} ({3})", sound.SoundType.ToString(), sound.ReadOffset, sound.ReadSize, sound.BufferSize), m_ProfileID = trace.NextProfileID }; trace.Profiles.Add(sound.Profile); sounds.Add(sound); line = reader.ReadLine(); } // Create contexts. EventFileThreadDesc soundContext = new EventFileThreadDesc(); soundContext.m_Name = "Sound Requests"; soundContext.m_ThreadID = trace.NextContextID; soundContext.m_OSThreadID = trace.NextContextID; trace.Contexts.Add(soundContext); // Create events if (sounds.Count > 0) { UInt64 currentTime = sounds[0].StartTime; for (int i = 0; i < sounds.Count; ++i) { WriteEvent(trace.ProfileEvents, soundContext.m_ThreadID, sounds[i].Profile.m_ProfileID, Math.Max(currentTime, sounds[i].StartTime), sounds[i].EndTime); currentTime = sounds[i].EndTime; trace.DataEvents.Add(new DataEvent { m_ContextID = soundContext.m_ThreadID, m_Data = string.Format("Sound {0} {1}", sounds[i].SoundType.ToString(), sounds[i].ReadOffset), m_Time = sounds[i].StartTime }); } } return 0; } private enum LoadSoundType { Bank, Stream } private struct SoundStreamStat { public LoadSoundType SoundType; public Int64 FileSize; public UInt64 ReadOffset; public UInt32 ReadSize; public UInt32 BufferSize; public UInt64 StartTime; public UInt64 EndTime; public EventFileProfileDesc Profile; } #endregion #region Utilities private static void WriteEvent(List events, UInt32 contextID, UInt32 profileId, UInt64 startTime, UInt64 endTime) { WriteEventStart(events, contextID, profileId, startTime); WriteEventEnd(events, contextID, profileId, endTime); } private static void WriteEventStart(List events, UInt32 contextID, UInt32 profileId, UInt64 time) { WriteEvent(events, contextID, profileId, time, 0 /*StartEvent*/); } private static void WriteEventEnd(List events, UInt32 contextID, UInt32 profileId, UInt64 time) { WriteEvent(events, contextID, profileId, time, 1 /*StopEvent*/); } private static void WriteEvent(List events, UInt32 contextID, UInt32 profileId, UInt64 time, uint type) { ProfileEvent eventStart = new ProfileEvent(); eventStart.m_ContextID = contextID; eventStart.m_ProfileID = profileId; eventStart.m_Type = type; eventStart.m_Time = time; events.Add(eventStart); } private static void StableSort(IList list, Comparison comparison) { MergeSort.Sort(list, comparison); } // TODO: Use ProfileEventData and add a method to save it. private class SimplifiedTrace { public List Contexts = new List(); public List Profiles = new List(); public List ProfileEvents = new List(); public List DataEvents = new List(); public UInt32 NextContextID { get { return (UInt32)Contexts.Count; } } public UInt32 NextProfileID { get { return (UInt32)Profiles.Count; } } public void Write(string fileName) { // Stable sort needed... StableSort(ProfileEvents, (ProfileEvent a, ProfileEvent b) => a.m_Time.CompareTo(b.m_Time)); StableSort(DataEvents, (DataEvent a, DataEvent b) => a.m_Time.CompareTo(b.m_Time)); Stream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write); BinaryWriter writer = new BinaryWriter(stream); writer.Write((UInt32)3); // Version writer.Write((UInt32)1000000); // Frequency byte[] descriptor = new byte[256]; descriptor.Initialize(); Encoding.ASCII.GetBytes("Loading stats").CopyTo(descriptor, 0); writer.Write(descriptor); // Descriptor writer.Write((UInt32)Contexts.Count); // Nb context descs writer.Write((UInt32)Profiles.Count); // Nb profile descs writer.Write((UInt32)0); // Nb task descs writer.Write((UInt32)ProfileEvents.Count); // Nb profile events writer.Write((UInt32)DataEvents.Count); // Nb data events writer.Write((UInt32)0); // Nb frame events writer.Write((UInt32)0); // Nb task events writer.Write((UInt32)0); // Nb thread switch events // Contexts foreach (EventFileThreadDesc context in Contexts) { byte[] contextName = new byte[60]; contextName.Initialize(); Encoding.ASCII.GetBytes(context.m_Name, 0, Math.Min(60, context.m_Name.Length), contextName, 0); writer.Write(contextName); writer.Write((UInt32)context.m_ThreadID); // Context ID writer.Write((UInt32)context.m_OSThreadID); // Thread ID } // Profiles foreach (EventFileProfileDesc profile in Profiles) { byte[] profileName = new byte[60]; profileName.Initialize(); Encoding.ASCII.GetBytes(profile.m_Name, 0, Math.Min(60, profile.m_Name.Length), profileName, 0); writer.Write(profileName); writer.Write((UInt32)profile.m_ProfileID); // Profile ID } // Profile Events foreach (ProfileEvent profileEvent in ProfileEvents) { writer.Write((UInt64)profileEvent.m_Time); // Time writer.Write((UInt32)profileEvent.m_ProfileID); // Profile ID writer.Write((UInt16)profileEvent.m_Type); // Type (StartEvent) writer.Write((UInt16)profileEvent.m_ContextID); // Context ID } // Event data foreach (DataEvent data in DataEvents) { writer.Write((UInt64)data.m_Time); writer.Write((UInt16)data.m_ContextID); byte[] dataStr = new byte[118]; dataStr.Initialize(); Encoding.ASCII.GetBytes(data.m_Data, 0, Math.Min(118, data.m_Data.Length), dataStr, 0); writer.Write(dataStr); } writer.Close(); } } #endregion } }