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.Reflection;
9 using System.Collections.Generic;
10 using System.Linq;
11 using System.IO;
12 using Antmicro.Renode.Core;
13 using Antmicro.Renode.Peripherals;
14 using Antmicro.Renode.Time;
15 using Antmicro.Renode.Logging;
16 using Antmicro.Migrant;
17 using Antmicro.Migrant.Hooks;
18 
19 namespace Antmicro.Renode.Utilities.RESD
20 {
21     public enum RESDStreamStatus
22     {
23         // Provided sample is from requested timestamp
24         OK = 0,
25         // There are no samples for given samples in current block yet
26         BeforeStream = -1,
27         // There are no more samples of given type in the block/file
28         AfterStream = -2,
29     }
30 
31     public enum RESDStreamSampleOffset
32     {
33         // Use specified sample offset
34         Specified,
35         // User current virtual-time timestamp as offset
36         CurrentVirtualTime,
37     }
38 
39     public static class RESDStreamExtension
40     {
41         public static RESDStream<T> CreateRESDStream<T>(this IPeripheral @this, ReadFilePath path, uint channel,
42             RESDStreamSampleOffset offsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0,
43             Predicate<DataBlock<T>> extraFilter = null) where T: RESDSample, new()
44         {
45             if(offsetType == RESDStreamSampleOffset.CurrentVirtualTime)
46             {
47                 var machine = @this.GetMachine();
48                 if(machine.SystemBus.TryGetCurrentCPU(out var cpu))
49                 {
50                     cpu.SyncTime();
51                 }
52                 sampleOffsetTime += (long)machine.ClockSource.CurrentValue.TotalMicroseconds * -1000L;
53             }
54 
55             var stream = new RESDStream<T>(path, channel, sampleOffsetTime, extraFilter);
56             stream.Owner = @this;
57             return stream;
58         }
59 
60         public static IManagedThread StartSampleFeedThread<T>(this RESDStream<T> @this, IUnderstandRESD owner, uint frequency,
61             ulong startTime = 0, string domain = null, bool shouldStop = true) where T: RESDSample, new()
62         {
63             Action<T, TimeInterval> beforeCallback = FindCallback<T>(owner, @this.SampleType, RESDStreamStatus.BeforeStream, @this.Channel, domain);
64             Action<T, TimeInterval> currentCallback = FindCallback<T>(owner, @this.SampleType, RESDStreamStatus.OK, @this.Channel, domain);
65             Action<T, TimeInterval> afterCallback = FindCallback<T>(owner, @this.SampleType, RESDStreamStatus.AfterStream, @this.Channel, domain);
66             Action<T, TimeInterval, RESDStreamStatus> sampleCallback = (sample, ts, status) =>
67             {
68                 switch(status)
69                 {
70                     case RESDStreamStatus.BeforeStream:
71                         beforeCallback(sample, ts);
72                         break;
73                     case RESDStreamStatus.OK:
74                         currentCallback(sample, ts);
75                         break;
76                     case RESDStreamStatus.AfterStream:
77                         afterCallback(sample, ts);
78                         break;
79                 }
80             };
81             return @this.StartSampleFeedThread(owner, frequency, sampleCallback, startTime, shouldStop);
82         }
83 
84         public static IManagedThread StartSampleFeedThread<T, Out>(this RESDStream<T, Out> @this, IUnderstandRESD owner, uint frequency,
85             ulong startTime = 0, string domain = null, bool shouldStop = true) where T: RESDSample, new()
86         {
87             Action<Out, TimeInterval> beforeCallback = FindCallback<Out>(owner, @this.SampleType, RESDStreamStatus.BeforeStream, @this.Channel, domain);
88             Action<Out, TimeInterval> currentCallback = FindCallback<Out>(owner, @this.SampleType, RESDStreamStatus.OK, @this.Channel, domain);
89             Action<Out, TimeInterval> afterCallback = FindCallback<Out>(owner, @this.SampleType, RESDStreamStatus.AfterStream, @this.Channel, domain);
90             Action<Out, TimeInterval, RESDStreamStatus> sampleCallback = (sample, ts, status) =>
91             {
92                 switch(status)
93                 {
94                     case RESDStreamStatus.BeforeStream:
95                         beforeCallback(sample, ts);
96                         break;
97                     case RESDStreamStatus.OK:
98                         currentCallback(sample, ts);
99                         break;
100                     case RESDStreamStatus.AfterStream:
101                         afterCallback(sample, ts);
102                         break;
103                 }
104             };
105             return @this.StartSampleFeedThread(owner, frequency, sampleCallback, startTime, shouldStop);
106         }
107 
108         public static RESDStreamStatus TryGetCurrentSample<T, Out>(this RESDStream<T> @this, IPeripheral peripheral, Func<T, Out> transformer,
109             out Out sample, out TimeInterval timestamp) where T: RESDSample, new()
110         {
111             var result = @this.TryGetCurrentSample(peripheral, out var originalSample, out timestamp);
112             sample = transformer.TransformSample(originalSample);
113             return result;
114         }
115 
116         public static Out TransformSample<T, Out>(this Func<T, Out> @this, T sample)
117             where T: RESDSample, new()
118         {
119             if(sample == null)
120             {
121                 return default(Out);
122             }
123             return @this(sample);
124         }
125 
FindCallback(IUnderstandRESD instance, SampleType sampleType, RESDStreamStatus status, uint channel, string domain)126         private static Action<Out, TimeInterval> FindCallback<Out>(IUnderstandRESD instance, SampleType sampleType, RESDStreamStatus status, uint channel, string domain)
127         {
128             Func<ParameterInfo[], bool> checkCorrectPrototype = parameters =>
129                 parameters.Length == 2 &&
130                 parameters[0].ParameterType == typeof(Out) &&
131                 parameters[1].ParameterType == typeof(TimeInterval);
132 
133             var methodInfo = instance.GetType()
134                 .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
135                 .Where(info => checkCorrectPrototype(info.GetParameters()))
136                 .FirstOrDefault(info =>
137                     info.GetCustomAttributes(typeof(RESDSampleCallbackAttribute))
138                         .OfType<RESDSampleCallbackAttribute>()
139                         .Any(attribute =>
140                             attribute.SampleType == sampleType &&
141                             attribute.Status == status &&
142                             attribute.ChannelId == channel &&
143                             attribute.Domain == domain));
144 
145             if(methodInfo == null)
146             {
147                 return delegate {};
148             }
149 
150             return (sample, ts) => methodInfo.Invoke(instance, new object[] { sample, ts });
151         }
152     }
153 
154     public class RESDStream<T, Out> : RESDStream<T> where T: RESDSample, new()
155     {
RESDStream(ReadFilePath path, uint channel, Func<T, Out> transformer, long sampleOffsetTime = 0)156         public RESDStream(ReadFilePath path, uint channel, Func<T, Out> transformer, long sampleOffsetTime = 0) : base(path, channel, sampleOffsetTime)
157         {
158             this.transformer = transformer;
159         }
160 
TryGetSample(ulong timestamp, out Out sample, long? overrideSampleOffsetTime = null)161         public RESDStreamStatus TryGetSample(ulong timestamp, out Out sample, long? overrideSampleOffsetTime = null)
162         {
163             var result = TryGetSample(timestamp, out T originalSample, overrideSampleOffsetTime);
164             sample = transformer.TransformSample(originalSample);
165             return result;
166         }
167 
TryGetCurrentSample(IPeripheral peripheral, out Out sample, out TimeInterval timestamp)168         public RESDStreamStatus TryGetCurrentSample(IPeripheral peripheral, out Out sample, out TimeInterval timestamp)
169         {
170             var result = TryGetCurrentSample(peripheral, out T originalSample, out timestamp);
171             sample = transformer.TransformSample(originalSample);
172             return result;
173         }
174 
StartSampleFeedThread(IPeripheral owner, uint frequency, Action<Out, TimeInterval, RESDStreamStatus> newSampleCallback, ulong startTime = 0, bool shouldStop = true)175         public IManagedThread StartSampleFeedThread(IPeripheral owner, uint frequency, Action<Out, TimeInterval, RESDStreamStatus> newSampleCallback, ulong startTime = 0, bool shouldStop = true)
176         {
177             Action<T, TimeInterval, RESDStreamStatus> transformedCallback =
178                 (sample, timestamp, status) => newSampleCallback(transformer.TransformSample(sample), timestamp, status);
179             return StartSampleFeedThread(owner, frequency, transformedCallback, startTime, shouldStop);
180         }
181 
182         private readonly Func<T, Out> transformer;
183     }
184 
185     public class RESDStream<T> : IDisposable where T : RESDSample, new()
186     {
RESDStream(ReadFilePath path, uint channel, long sampleOffsetTime = 0, Predicate<DataBlock<T>> extraFilter = null)187         public RESDStream(ReadFilePath path, uint channel, long sampleOffsetTime = 0, Predicate<DataBlock<T>> extraFilter = null)
188         {
189             var sampleTypeAttribute = typeof(T).GetCustomAttributes(typeof(SampleTypeAttribute), true).FirstOrDefault() as SampleTypeAttribute;
190             if(sampleTypeAttribute == null)
191             {
192                 throw new RESDException($"Unsupported RESD sample type: {typeof(T).Name}");
193             }
194 
195             SampleType = sampleTypeAttribute.SampleType;
196             Channel = channel;
197 
198             this.sampleOffsetTime = sampleOffsetTime;
199             this.managedThreads = new List<IManagedThread>();
200             this.parser = new LowLevelRESDParser(path);
201             this.parser.LogCallback += (logLevel, message) => Owner?.Log(logLevel, message);
202             this.blockEnumerator = parser.GetDataBlockEnumerator<T>().GetEnumerator();
203             this.extraFilter = extraFilter;
204 
205             PrereadFirstBlock();
206         }
207 
TryGetCurrentSample(IPeripheral peripheral, out T sample, out TimeInterval timestamp)208         public RESDStreamStatus TryGetCurrentSample(IPeripheral peripheral, out T sample, out TimeInterval timestamp)
209         {
210             var machine = peripheral.GetMachine();
211             timestamp = machine.ClockSource.CurrentValue;
212             var timestampInNanoseconds = timestamp.TotalNanoseconds;
213             return TryGetSample(timestampInNanoseconds, out sample);
214         }
215 
TryGetSample(ulong timestamp, out T sample, long? overrideSampleOffsetTime = null)216         public RESDStreamStatus TryGetSample(ulong timestamp, out T sample, long? overrideSampleOffsetTime = null)
217         {
218             currentTimestampInNanoseconds = timestamp;
219             var currentSampleOffsetTime = overrideSampleOffsetTime ?? sampleOffsetTime;
220             if(currentSampleOffsetTime < 0)
221             {
222                 if(timestamp >= (ulong)(-currentSampleOffsetTime))
223                 {
224                     timestamp = timestamp - (ulong)(-currentSampleOffsetTime);
225                 }
226                 else
227                 {
228                     Owner?.Log(LogLevel.Debug, "RESD: Tried getting sample at timestamp {0}ns, before the start time of the current block"
229                         + " after applying the {1}ns offset", timestamp, currentSampleOffsetTime);
230                     sample = null;
231                     return RESDStreamStatus.BeforeStream;
232                 }
233             }
234             else
235             {
236                 timestamp = timestamp + (ulong)currentSampleOffsetTime;
237             }
238 
239             while(blockEnumerator != null)
240             {
241                 if(currentBlock == null)
242                 {
243                     if(!TryGetNextBlock(out currentBlock))
244                     {
245                         break;
246                     }
247                     MetadataChanged?.Invoke();
248                 }
249 
250                 switch(currentBlock.TryGetSample(timestamp, out sample))
251                 {
252                     case RESDStreamStatus.BeforeStream:
253                         Owner?.Log(LogLevel.Debug, "RESD: Tried getting sample at timestamp {0}ns, before the first sample in the block", timestamp);
254                         sample = null;
255                         return RESDStreamStatus.BeforeStream;
256                     case RESDStreamStatus.OK:
257                         // Just return sample
258                         Owner?.Log(LogLevel.Debug, "RESD: Getting sample at timestamp {0}ns: {1}", timestamp, sample);
259                         return RESDStreamStatus.OK;
260                     case RESDStreamStatus.AfterStream:
261                         // Find next block
262                         Owner?.Log(LogLevel.Debug, "RESD: Tried getting sample at timestamp {0}ns after the last sample of the current block", timestamp);
263                         currentBlock = null;
264                         continue;
265                 }
266 
267                 return RESDStreamStatus.OK;
268             }
269 
270             Owner?.Log(LogLevel.Debug, "RESD: That was the last block of the file");
271             sample = null;
272             return RESDStreamStatus.AfterStream;
273         }
274 
275         // If shouldStop is false, the thread will continue running after the end of the stream
StartSampleFeedThread(IPeripheral owner, uint frequency, Action<T, TimeInterval, RESDStreamStatus> newSampleCallback, ulong startTime = 0, bool shouldStop = true)276         public IManagedThread StartSampleFeedThread(IPeripheral owner, uint frequency, Action<T, TimeInterval, RESDStreamStatus> newSampleCallback, ulong startTime = 0, bool shouldStop = true)
277         {
278             var machine = owner.GetMachine();
279             Action feedSample = () =>
280             {
281                 var status = TryGetCurrentSample(owner, out var sample, out var timestamp);
282                 newSampleCallback(sample, timestamp, status);
283             };
284 
285             Func<bool> stopCondition = () =>
286             {
287                 if(blockEnumerator == null)
288                 {
289                     if(shouldStop)
290                     {
291                         feedSample(); // invoke action to update timestamp and status before stopping thread
292                         Owner?.Log(LogLevel.Debug, "RESD: End of sample feeding thread detected");
293                     }
294                     return shouldStop;
295                 }
296                 return false;
297             };
298 
299             var thread = machine.ObtainManagedThread(feedSample, frequency, "RESD stream thread", owner, stopCondition);
300             var delayInterval = TimeInterval.FromMicroseconds(startTime / 1000);
301             Owner?.Log(LogLevel.Debug, "RESD: Starting samples feeding thread at frequency {0}Hz delayed by {1}us", frequency, delayInterval);
302             thread.StartDelayed(delayInterval);
303             managedThreads.Add(thread);
304             return thread;
305         }
306 
Dispose()307         public void Dispose()
308         {
309             foreach(var thread in managedThreads)
310             {
311                 thread.Dispose();
312             }
313             managedThreads.Clear();
314             blockEnumerator?.Dispose();
315         }
316 
317         // The `Owner` property is used to log detailed messages
318         // about the process of the RESD file parsing.
319         // If it's not set (set to `null`, by default) no log messages
320         // will be generated.
321         public IEmulationElement Owner { get; set; }
322 
323         public T CurrentSample => currentBlock?.CurrentSample;
324         public DataBlock<T> CurrentBlock => currentBlock;
325         public long CurrentBlockNumber => currentBlockNumber;
326         public SampleType SampleType { get; }
327         public uint Channel { get; }
328         public Action MetadataChanged;
329 
PrereadFirstBlock()330         private void PrereadFirstBlock()
331         {
332             if(!TryGetNextBlock(out currentBlock))
333             {
334                 throw new RESDException($"Provided RESD file doesn't contain data for {typeof(T)}");
335             }
336             Owner?.Log(LogLevel.Debug, "RESD: First sample of the file has timestamp {0}ns", currentBlock.StartTime);
337         }
338 
TryGetNextBlock(out DataBlock<T> block)339         private bool TryGetNextBlock(out DataBlock<T> block)
340         {
341             if(blockEnumerator == null)
342             {
343                 block = null;
344                 return false;
345             }
346 
347             while(blockEnumerator.TryGetNext(out var nextBlock))
348             {
349                 currentBlockNumber++;
350                 if(nextBlock.ChannelId != Channel || !(extraFilter?.Invoke(nextBlock) ?? true))
351                 {
352                     Owner?.Log(LogLevel.Debug, "RESD: Skipping block of type {0} and size {1} bytes", nextBlock.BlockType, nextBlock.DataSize);
353                     continue;
354                 }
355 
356                 block = nextBlock;
357                 return true;
358             }
359 
360             block = null;
361             blockEnumerator = null;
362             return false;
363         }
364 
365         [PreSerialization]
BeforeSerialization()366         private void BeforeSerialization()
367         {
368             // After stream ended, current block is null so serialize timestamp as the last registered time
369             // so after serialization it will also return RESDStreamStatus.AfterStream
370             serializedTimestamp = currentBlock != null ? currentBlock.CurrentTimestamp : currentTimestampInNanoseconds;
371         }
372 
373         [PostDeserialization]
AfterDeserialization()374         private void AfterDeserialization()
375         {
376             PrereadFirstBlock();
377             TryGetSample(serializedTimestamp, out _);
378         }
379 
380         [Transient]
381         private DataBlock<T> currentBlock;
382         [Transient]
383         private IEnumerator<DataBlock<T>> blockEnumerator;
384         private ulong serializedTimestamp;
385         private ulong currentTimestampInNanoseconds;
386         private long currentBlockNumber;
387         private long sampleOffsetTime;
388 
389         private readonly LowLevelRESDParser parser;
390         private readonly IList<IManagedThread> managedThreads;
391         private readonly Predicate<DataBlock<T>> extraFilter;
392     }
393 }
394