using System.IO.MemoryMappedFiles; using System.Text; namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor; /// /// Proxy-side reader for the Host's post-mortem MMF. After a Host crash the supervisor /// opens the file (which persists beyond the process lifetime) and enumerates the last /// few thousand IPC operations that were in flight. Format matches /// Driver.FOCAS.Host.Stability.PostMortemMmf — magic 'OFPC' / 256-byte entries. /// public sealed class PostMortemReader { private const int Magic = 0x4F465043; // 'OFPC' private const int HeaderBytes = 16; private const int EntryBytes = 256; private const int MessageOffset = 16; private const int MessageCapacity = EntryBytes - MessageOffset; public string Path { get; } public PostMortemReader(string path) => Path = path; public PostMortemEntry[] ReadAll() { if (!File.Exists(Path)) return []; using var mmf = MemoryMappedFile.CreateFromFile(Path, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); using var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); if (accessor.ReadInt32(0) != Magic) return []; var capacity = accessor.ReadInt32(8); var writeIndex = accessor.ReadInt32(12); var entries = new PostMortemEntry[capacity]; var count = 0; for (var i = 0; i < capacity; i++) { var slot = (writeIndex + i) % capacity; var offset = HeaderBytes + slot * EntryBytes; var ts = accessor.ReadInt64(offset + 0); if (ts == 0) continue; var op = accessor.ReadInt64(offset + 8); var msgBuf = new byte[MessageCapacity]; accessor.ReadArray(offset + MessageOffset, msgBuf, 0, MessageCapacity); var nulTerm = Array.IndexOf(msgBuf, 0); var msg = Encoding.UTF8.GetString(msgBuf, 0, nulTerm < 0 ? MessageCapacity : nulTerm); entries[count++] = new PostMortemEntry(ts, op, msg); } Array.Resize(ref entries, count); return entries; } } public readonly record struct PostMortemEntry(long UtcUnixMs, long OpKind, string Message);