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