1 // 2 // Copyright (c) 2010-2024 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Threading; 9 using System.Collections.Generic; 10 using System.IO; 11 using Antmicro.Renode.Time; 12 using Antmicro.Renode.Utilities; 13 14 namespace Antmicro.Renode.Utilities.RESD 15 { 16 public static class DataBlockExtensions 17 { GetEndTime(this IDataBlock @this)18 public static ulong GetEndTime(this IDataBlock @this) 19 { 20 return @this.StartTime + @this.Duration; 21 } 22 } 23 24 public interface IDataBlock 25 { 26 ulong StartTime { get; } 27 SampleType SampleType { get; } 28 ushort ChannelId { get; } 29 ulong SamplesCount { get; } 30 ulong Duration { get; } 31 IDictionary<String, MetadataValue> Metadata { get; } 32 IDictionary<String, String> ExtraInformation { get; } 33 IEnumerable<KeyValuePair<TimeInterval, RESDSample>> Samples { get; } 34 } 35 36 public abstract class DataBlock<T> : IDataBlock where T: RESDSample, new() 37 { DataBlock(DataBlockHeader header)38 public DataBlock(DataBlockHeader header) 39 { 40 Header = header; 41 } 42 TryGetSample(ulong timestamp, out T sample)43 public abstract RESDStreamStatus TryGetSample(ulong timestamp, out T sample); 44 45 public abstract ulong StartTime { get; } 46 public abstract T CurrentSample { get; } 47 public abstract ulong CurrentTimestamp { get; } 48 public abstract ulong SamplesCount { get; } 49 public abstract ulong Duration { get; } 50 public abstract IDictionary<String, String> ExtraInformation { get; } 51 public abstract IEnumerable<KeyValuePair<TimeInterval, RESDSample>> Samples { get; } 52 53 public virtual BlockType BlockType => Header.BlockType; 54 public virtual SampleType SampleType => Header.SampleType; 55 public virtual ushort ChannelId => Header.ChannelId; 56 public virtual ulong DataSize => Header.Size; 57 public virtual IDictionary<String, MetadataValue> Metadata => CurrentSample.Metadata; 58 59 protected virtual DataBlockHeader Header { get; } 60 } 61 62 public class DataBlockHeader 63 { ReadFromStream(SafeBinaryReader reader)64 public static DataBlockHeader ReadFromStream(SafeBinaryReader reader) 65 { 66 var startPosition = reader.BaseStream.Position; 67 var blockType = (BlockType)reader.ReadByte(); 68 var sampleType = (SampleType)reader.ReadUInt16(); 69 var channelId = reader.ReadUInt16(); 70 var dataSize = reader.ReadUInt64(); 71 72 return new DataBlockHeader(blockType, sampleType, channelId, dataSize, startPosition); 73 } 74 75 public SampleType SampleType { get; } 76 public BlockType BlockType { get; } 77 public ushort ChannelId { get; } 78 public ulong Size { get; } 79 public long StartPosition { get; } 80 DataBlockHeader(BlockType blockType, SampleType sampleType, ushort channel, ulong dataSize, long startPosition)81 private DataBlockHeader(BlockType blockType, SampleType sampleType, ushort channel, ulong dataSize, long startPosition) 82 { 83 SampleType = sampleType; 84 BlockType = blockType; 85 ChannelId = channel; 86 Size = dataSize; 87 StartPosition = startPosition; 88 } 89 } 90 91 public class ConstantFrequencySamplesDataBlock<T> : DataBlock<T> where T : RESDSample, new() 92 { ReadFromStream(DataBlockHeader header, SafeBinaryReader reader)93 public static ConstantFrequencySamplesDataBlock<T> ReadFromStream(DataBlockHeader header, SafeBinaryReader reader) 94 { 95 var startTime = reader.ReadUInt64(); 96 var period = reader.ReadUInt64(); 97 98 return new ConstantFrequencySamplesDataBlock<T>(header, startTime, period, reader); 99 } 100 TryGetSample(ulong timestamp, out T sample)101 public override RESDStreamStatus TryGetSample(ulong timestamp, out T sample) 102 { 103 if(Interlocked.Exchange(ref usingReader, 1) != 0) 104 { 105 throw new RESDException("trying to call TryGetSample when using Samples iterator"); 106 } 107 108 using(DisposableWrapper.New(() => Interlocked.Exchange(ref usingReader, 0))) 109 { 110 if(timestamp < currentSampleTimestamp) 111 { 112 // we don't support moving back in time 113 sample = null; 114 return RESDStreamStatus.BeforeStream; 115 } 116 117 var samplesDiff = (timestamp - currentSampleTimestamp) / Period; 118 if(!samplesData.Move((int)samplesDiff)) 119 { 120 // past the current block 121 sample = null; 122 return RESDStreamStatus.AfterStream; 123 } 124 125 currentSampleTimestamp += samplesDiff * Period; 126 sample = samplesData.GetCurrentSample(); 127 } 128 129 return RESDStreamStatus.OK; 130 } 131 132 public ulong Period { get; } 133 public decimal Frequency => NanosecondsInSecond / Period; 134 135 public override ulong StartTime { get; } 136 public override T CurrentSample => samplesData.GetCurrentSample(); 137 public override ulong CurrentTimestamp => currentSampleTimestamp; 138 public override ulong SamplesCount => samplesCount.Value; 139 public override ulong Duration => SamplesCount * Period; 140 public override IDictionary<String, String> ExtraInformation => new Dictionary<String, String>() { 141 {"Period", TimeInterval.FromMicroseconds(Period / 1000).ToString()}, 142 {"Frequency", $"{Frequency}Hz"} 143 }; 144 145 public override IEnumerable<KeyValuePair<TimeInterval, RESDSample>> Samples 146 { 147 get 148 { 149 if(Interlocked.Exchange(ref usingReader, 1) != 0) 150 { 151 throw new RESDException("Trying to use Samples iterator during TryGetSample"); 152 } 153 using(reader.Checkpoint) 154 { 155 reader.BaseStream.Seek(samplesData.SampleDataOffset, SeekOrigin.Begin); 156 157 var currentTime = StartTime; 158 var currentSample = new T(); 159 160 while(!reader.EOF && currentSample.TryReadFromStream(reader)) 161 { 162 yield return new KeyValuePair<TimeInterval, RESDSample>(TimeInterval.FromMicroseconds(currentTime / 1000), currentSample); 163 currentTime += Period; 164 } 165 } 166 Interlocked.Exchange(ref usingReader, 0); 167 } 168 } 169 ConstantFrequencySamplesDataBlock(DataBlockHeader header, ulong startTime, ulong period, SafeBinaryReader reader)170 private ConstantFrequencySamplesDataBlock(DataBlockHeader header, ulong startTime, ulong period, SafeBinaryReader reader) : base(header) 171 { 172 this.reader = reader; 173 this.samplesData = new SamplesData<T>(reader); 174 175 currentSampleTimestamp = startTime; 176 177 Period = period; 178 StartTime = startTime; 179 180 samplesCount = new Lazy<ulong>(() => 181 { 182 using(reader.Checkpoint) 183 { 184 reader.BaseStream.Seek(samplesData.SampleDataOffset, SeekOrigin.Begin); 185 if(reader.EOF) 186 { 187 return 0; 188 } 189 190 var sample = new T(); 191 var packets = 0UL; 192 while(sample.Skip(reader, 1)) 193 { 194 packets += 1; 195 } 196 return packets; 197 } 198 }); 199 } 200 201 private ulong currentSampleTimestamp; 202 private int usingReader; 203 204 private readonly SafeBinaryReader reader; 205 private readonly SamplesData<T> samplesData; 206 private readonly Lazy<ulong> samplesCount; 207 208 private const decimal NanosecondsInSecond = 1e9m; 209 } 210 211 public enum BlockType 212 { 213 ArbitraryTimestampSamples = 1, 214 ConstantFrequencySamples = 2, 215 } 216 } 217